Spring boot實現(xiàn)一個簡單的ioc(2)
前言
跳過廢話,直接看正文
仿照spring-boot的項目結構以及部分注解,寫一個簡單的ioc容器。
測試代碼完成后,便正式開始這個ioc容器的開發(fā)工作。
正文
項目結構

實際上三四個類完全能搞定這個簡單的ioc容器,但是出于可擴展性的考慮,還是寫了不少的類。
因篇幅限制,接下來只將幾個最重要的類的代碼貼出來并加以說明,完整的代碼請直接參考https://github.com/clayandgithub/simple-ioc。
SimpleAutowired
代碼
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleAutowired {
boolean required() default true;
String value() default ""; // this field is moved from @Qualifier to here for simplicity
}
說明
@SimpleAutowired的作用是用于注解需要自動裝配的字段。
此類和spring的@Autowired的作用類似。但又有以下兩個區(qū)別:
- @SimpleAutowired只能作用于類字段,而不能作用于方法(這樣實現(xiàn)起來相對簡單些,不會用到aop)
- @SimpleAutowired中包括了required(是否一定需要裝配)和value(要裝配的bean的名字)兩個字段,實際上是將spring中的@Autowired以及Qualifier的功能簡單地融合到了一起
SimpleBean
代碼
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
String value() default "";
}
說明
@SimpleBean作用于方法,根據(jù)方法返回值來生成一個bean,對應spring中的@Bean
用value來設置要生成的bean的名字
SimpleComponent
代碼
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
String value() default "";
}
說明
@SimpleComponent作用于類,ioc容器會為每一個擁有@SimpleComponent的類生成一個bean,對應spring中的@Component。特殊說明,為了簡單起見,@SimpleComponent注解的類必須擁有一個無參構造函數(shù),否則無法生成該類的實例,這個在之后的SimpleAppliationContext中的processSingleClass方法中會有說明。
SimpleIocBootApplication
代碼
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleIocBootApplication {
String[] basePackages() default {};
}
說明
@SimpleIocBootApplication作用于應用的入口類。
這個啟動模式是照搬了spring-boot的啟動模式,將啟動任務委托給SimpleIocApplication來完成。ioc容器將根據(jù)注解@SimpleIocBootApplication的相關配置自動掃描相應的package,生成beans并完成自動裝配。(如果沒有配置,默認掃描入口類(測試程序中的SampleApplication)所在的package及其子package)
以上就是這個ioc容器所提供的所有注解,接下來講解ioc容器的掃描和裝配過程的實現(xiàn)。
SimpleIocApplication
代碼
import com.clayoverwind.simpleioc.context.*;
import com.clayoverwind.simpleioc.util.LogUtil;
import java.util.Arrays;
import java.util.Map;
import java.util.logging.Logger;
public class SimpleIocApplication {
private Class<?> applicationEntryClass;
private ApplicationContext applicationContext;
private final Logger LOGGER = LogUtil.getLogger(this.getClass());
public SimpleIocApplication(Class<?> applicationEntryClass) {
this.applicationEntryClass = applicationEntryClass;
}
public static void run(Class<?> applicationEntryClass, String[] args) {
new SimpleIocApplication(applicationEntryClass).run(args);
}
public void run(String[] args) {
LOGGER.info("start running......");
// create application context and application initializer
applicationContext = createSimpleApplicationContext();
ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass);
// initialize the application context (this is where we create beans)
initializer.initialize(applicationContext); // here maybe exist a hidden cast
// process those special beans
processSpecialBeans(args);
LOGGER.info("over!");
}
private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class<?> entryClass) {
// get base packages
SimpleIocBootApplication annotation = entryClass.getDeclaredAnnotation(SimpleIocBootApplication.class);
String[] basePackages = annotation.basePackages();
if (basePackages.length == 0) {
basePackages = new String[]{entryClass.getPackage().getName()};
}
// create context initializer with base packages
return new SimpleApplicationContextInitializer(Arrays.asList(basePackages));
}
private SimpleApplicationContext createSimpleApplicationContext() {
return new SimpleApplicationContext();
}
private void processSpecialBeans(String[] args) {
callRegisteredRunners(args);
}
private void callRegisteredRunners(String[] args) {
Map<String, SimpleIocApplicationRunner> applicationRunners = applicationContext.getBeansOfType(SimpleIocApplicationRunner.class);
try {
for (SimpleIocApplicationRunner applicationRunner : applicationRunners.values()) {
applicationRunner.run(args);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
說明
前面說到應用的啟動會委托SimpleIocApplication來完成,通過將應用入口類(測試程序中的SampleApplication)傳入SimpleIocApplication的構造函數(shù),構造出SimpleIocApplication的一個實例并運行run方法。在run方法中,會首先生成一個applicationContext,并調用SimpleApplicationContextInitializer來完成applicationContext的初始化(bean的掃描、裝配)。然后調用processSpecialBeans來處理一些特殊的bean,如實現(xiàn)了SimpleIocApplicationRunner接口的bean會調用run方法來完成一些應用程序的啟動任務。
這就是這個ioc容器的整個流程。
SimpleApplicationContextInitializer
代碼
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class SimpleApplicationContextInitializer implements ApplicationContextInitializer<SimpleApplicationContext> {
private Set<String> basePackages = new LinkedHashSet<>();
public SimpleApplicationContextInitializer(List<String> basePackages) {
this.basePackages.addAll(basePackages);
}
@Override
public void initialize(SimpleApplicationContext applicationContext) {
try {
applicationContext.scan(basePackages, true);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
applicationContext.setStartupDate(System.currentTimeMillis());
}
}
說明
在SimpleIocApplication的run中,會根據(jù)basePackages來構造一個SimpleApplicationContextInitializer 的實例,進而通過這個ApplicationContextInitializer來完成SimpleApplicationContext 的初始化。
在SimpleApplicationContextInitializer中, 簡單地調用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任務
SimpleApplicationContext
說明:
終于到了最重要的部分了,在SimpleApplicationContext中將真正完成掃描、生成bean以及自動裝配的任務。這里scan即為SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化時調用。
代碼的調用邏輯簡單易懂,就不多加說明了。
這里只簡單列一下各個字段的含義以及幾個比較關鍵的方法的作用。
字段
- startupDate:啟動時間記錄字段
- scannedPackages:已經(jīng)掃描的包的集合,保證不重復掃描
- registeredBeans:已經(jīng)完全裝配好并注冊好了的bean
- earlyBeans : 只是生成好了,還未裝配完成的bean,用于處理循環(huán)依賴的問題
- totalBeanCount : 所有bean的計數(shù)器,在生成bean的名字時會用到其唯一性
方法
- processEarlyBeans:用于最終裝配earlyBeans 中的bean,若裝配成功,則將bean移至registeredBeans,否則報錯
- scan : 掃描并處理傳入的package集合
- processSingleClass:處理單個類,嘗試生成該類的bean并進行裝配(前提是此類有@SimpleComponent注解)
- createBeansByMethodsOfClass : 顧名思義,根據(jù)那些被@Bean注解的方法來生成bean
- autowireFields:嘗試裝配某個bean,lastChance代表是否在裝配失敗是報錯(在第一次裝配時,此值為false,在裝配失敗后會將bean移至earlyBeans,在第二次裝配時,此值為true,實際上就是在裝配earlyBeans中的bean,因此若仍然裝配失敗,就會報錯)。在這個方法中,裝配相應的bean時會從registeredBeans以及earlyBeans中去尋找符合條件的bean,只要找到,不管是來自哪里,都算裝配成功。
代碼
import com.clayoverwind.simpleioc.context.annotation.SimpleAutowired;
import com.clayoverwind.simpleioc.context.annotation.SimpleBean;
import com.clayoverwind.simpleioc.context.annotation.SimpleComponent;
import com.clayoverwind.simpleioc.context.factory.Bean;
import com.clayoverwind.simpleioc.util.ClassUtil;
import com.clayoverwind.simpleioc.util.ConcurrentHashSet;
import com.clayoverwind.simpleioc.util.LogUtil;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
/**
* @author clayoverwind
* @E-mail clayanddev@163.com
* @version 2017/4/5
*/
public class SimpleApplicationContext implements ApplicationContext {
private long startupDate;
private Set<String> scannedPackages = new ConcurrentHashSet<>();
private Map<String, Bean> registeredBeans = new ConcurrentHashMap<>();
private Map<String, Bean> earlyBeans = new ConcurrentHashMap<>();
private final Logger LOGGER = LogUtil.getLogger(this.getClass());
AtomicLong totalBeanCount = new AtomicLong(0L);
AtomicLong nameConflictCount = new AtomicLong(0L);
@Override
public Object getBean(String name) {
return registeredBeans.get(name);
}
@Override
public <T> T getBean(String name, Class<T> type) {
Bean bean = (Bean)getBean(name);
return bean == null ? null : (type.isAssignableFrom(bean.getClazz()) ? type.cast(bean.getObject()) : null);
}
@Override
public <T> T getBean(Class<T> type) {
Map<String, T> map = getBeansOfType(type);
return map.isEmpty() ? null : type.cast(map.values().toArray()[0]);
}
@Override
public boolean containsBean(String name) {
return getBean(name) != null;
}
@Override
public <T> Map<String, T> getBeansOfType(Class<T> type) {
Map<String, T> res = new HashMap<>();
registeredBeans.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> res.put(entry.getKey(), type.cast(entry.getValue().getObject())));
return res;
}
@Override
public void setStartupDate(long startupDate) {
this.startupDate = startupDate;
}
@Override
public long getStartupDate() {
return startupDate;
}
/**
* try to autowire those beans in earlyBeans
* if succeed, remove it from earlyBeans and put it into registeredBeans
* otherwise ,throw a RuntimeException(in autowireFields)
*/
private synchronized void processEarlyBeans() {
for (Map.Entry<String, Bean> entry : earlyBeans.entrySet()) {
Bean myBean = entry.getValue();
try {
if (autowireFields(myBean.getObject(), myBean.getClazz(), true)) {
registeredBeans.put(entry.getKey(), myBean);
earlyBeans.remove(entry.getKey());
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
/**
* scan base packages and create beans
* @param basePackages
* @param recursively
* @throws ClassNotFoundException
*/
public void scan(Set<String> basePackages, boolean recursively) throws ClassNotFoundException, IOException {
LOGGER.info("start scanning......");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// get all classes who haven't been registered
Set<Class<?>> classes = new LinkedHashSet<>();
for (String packageName : basePackages) {
if (scannedPackages.add(packageName)) {
classes.addAll(ClassUtil.getClassesByPackageName(classLoader, packageName, recursively));
}
}
// autowire or create bean for each class
classes.forEach(this::processSingleClass);
processEarlyBeans();
LOGGER.info("scan over!");
}
/**
* try to create a bean for certain class, put it into registeredBeans if success, otherwise put it into earlyBeans
* @param clazz
*/
private void processSingleClass(Class<?> clazz) {
LOGGER.info(String.format("processSingleClass [%s] ...", clazz.getName()));
Annotation[] annotations = clazz.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof SimpleComponent) {
Object instance;
try {
instance = clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
long beanId = totalBeanCount.getAndIncrement();
SimpleComponent component = (SimpleComponent) annotation;
String beanName = component.value();
if (beanName.isEmpty()) {
beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
}
try {
if (autowireFields(instance, clazz, false)) {
registeredBeans.put(beanName, new Bean(instance, clazz));
} else {
earlyBeans.put(beanName, new Bean(instance, clazz));
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
try {
createBeansByMethodsOfClass(instance, clazz);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
private void createBeansByMethodsOfClass(Object instance, Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
List<Method> methods = getMethodsWithAnnotation(clazz, SimpleBean.class);
for (Method method : methods) {
method.setAccessible(true);
Object methodBean = method.invoke(instance);
long beanId = totalBeanCount.getAndIncrement();
Class<?> methodBeanClass = methodBean.getClass();
//bean name
SimpleBean simpleBean = method.getAnnotation(SimpleBean.class);
String beanName = simpleBean.value();
if (beanName.isEmpty()) {
beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
}
// register bean
registeredBeans.put(beanName, new Bean(methodBean, methodBeanClass));
}
}
private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<?> annotationClass) {
List<Method> res = new LinkedList<>();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == annotationClass) {
res.add(method);
break;
}
}
}
return res;
}
/**
* try autowire all fields of a certain instance
* @param instance
* @param clazz
* @param lastChance
* @return true if success, otherwise return false or throw a exception if this is the lastChance
* @throws IllegalAccessException
*/
private boolean autowireFields(Object instance, Class<?> clazz, boolean lastChance) throws IllegalAccessException {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof SimpleAutowired) {
SimpleAutowired autowired = (SimpleAutowired) annotation;
String beanName = autowired.value();
Bean bean = getSimpleBeanByNameOrType(beanName, field.getType(), true);
if (bean == null) {
if (lastChance) {
if (!autowired.required()) {
break;
}
throw new RuntimeException(String.format("Failed in autowireFields : [%s].[%s]", clazz.getName(), field.getName()));
} else {
return false;
}
}
field.setAccessible(true);
field.set(instance, bean.getObject());
}
}
}
return true;
}
/**
* only used in autowireFields
* @param beanName
* @param type
* @param allowEarlyBean
* @return
*/
private Bean getSimpleBeanByNameOrType(String beanName, Class<?> type, boolean allowEarlyBean) {
// 1. by name
Bean res = registeredBeans.get(beanName);
if (res == null && allowEarlyBean) {
res = earlyBeans.get(beanName);
}
// 2. by type
if (type != null) {
if (res == null) {
res = getSimpleBeanByType(type, registeredBeans);
}
if (res == null && allowEarlyBean) {
res = getSimpleBeanByType(type, earlyBeans);
}
}
return res;
}
/**
* search bean by type in certain beans map
* @param type
* @param beansMap
* @return
*/
private Bean getSimpleBeanByType(Class<?> type, Map<String, Bean> beansMap) {
List<Bean> beans = new LinkedList<>();
beansMap.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> beans.add(entry.getValue()));
if (beans.size() > 1) {
throw new RuntimeException(String.format("Autowire by type, but more than one instance of type [%s] is founded!", beans.get(0).getClazz().getName()));
}
return beans.isEmpty() ? null : beans.get(0);
}
private String getUniqueBeanNameByClassAndBeanId(Class<?> clazz, long beanId) {
String beanName = clazz.getName() + "_" + beanId;
while (registeredBeans.containsKey(beanName) || earlyBeans.containsKey(beanName)) {
beanName = clazz.getName() + "_" + beanId + "_" + nameConflictCount.getAndIncrement();
}
return beanName;
}
}
后記
至此,一個簡單的ioc容器就完成了,總結一下優(yōu)缺點。
優(yōu)點:
小而簡單。
可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 來完成一些簡單但常用的依賴注入任務.
缺點:
很明顯,實現(xiàn)過于簡單,提供的功能太少。
如果你想了解ioc的實現(xiàn)原理,或者你想要開發(fā)一個小型個人項目但又嫌spring過于龐大,這個簡單的ioc容器或許可以幫到你。
如果你想做的不僅如此,那么你應該將目光轉向spring-boot。
完整代碼參考:https://github.com/clayandgithub/simple-ioc。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
SpringBoot整合MongoDB的實現(xiàn)代碼
自己本科時候一直使用的是Mysql,目前的課題組使用的是MongoDB,因此就花了一部分時間整理了一下,實現(xiàn)springboot與MongoDB的整合,并且實現(xiàn)基本的增刪改查操作,從頭到尾給出一個完整的案例。2021-05-05
java Beanutils.copyProperties( )用法詳解
這篇文章主要介紹了java Beanutils.copyProperties( )用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-05-05

