1. 程式人生 > 其它 >編寫一個自己的IOC容器

編寫一個自己的IOC容器

寫在最前

這個工程旨在練習Java註解和反射,以及體會依賴注入的原理、過程,不以追求可靠、可用為目的,且閱讀此部落格前應當熟練掌握Java且有一定的Spring使用經驗

預期功能

  • 模擬Spring中的Bean註冊、自動裝配

編碼部分

自定義註解部分

模擬Spring中的部分註解

  • @Bean註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
	/**
	* 指定Bean的名字
	*/
    String name() default "";
}
  • @Configuration註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Configuration {
}
  • @Import註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Import {
	/**
	* 指定要引入的配置類
	*/
    Class<!--?-->[] classes();
}
  • @Qualifier註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
	/**
	* 自動裝配時指定需要的Bean的名字
	*/
    String value() default "";
}
  • @Autowired註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
	/**
	* 指定自動裝配的方式,通過型別還是名字
	*/
    AutoWiredType type() default AutoWiredType.BY_TYPE;
}
  • 裝配方式列舉類
public enum AutoWiredType {
    /**
     * ByType自動裝配
     */
    BY_TYPE,
    /**
     * ByName自動裝配
     */
    BY_NAME
}

自定義異常類

只要繼承Exception類即可,異常類如下

容器類

首先我們需要一個靜態HashMap變數充當單例Spring容器

    /**
     * Spring容器
     */
    private static HashMap<String, Object> container = null;

然後建立一個向容器中增加Bean的方法addBean,通過synchronized關鍵字確保執行緒安全

/**
 * 新增Bean到容器
 * @param name 名字
 * @param o    物件
 * @throws BeanExistException 異常
 */
private synchronized static void addBean(String name, Object o) throws BeanExistException {
	//不允許重複新增
	if (container.containsKey(name)) {
		throw new BeanExistException("already exist bean with name:'" + name + "'");
	}
	container.put(name, o);
}

編寫獲取Bean的方法getBean,並進行過載,分別使用字串、Class物件做引數

public Object getBean(String name) throws NoSuchNameBeanException {
	if (!container.containsKey(name)) {
		//如果容器中沒有這個名字的Bean就丟出異常
		throw new NoSuchNameBeanException("there is no bean with name:" + name);
	}
	return container.get(name);
}

@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> requiredType, String name) throws NoSuchTypeBeanException, NoQualifiedBeanException, MultipleQualifiedBeanException {
	Set<Map.Entry<String, Object>> entries = container.entrySet();
	boolean byType = false;// ByType自動裝配的結果標誌
	T bean=null;
	for (Map.Entry<String, Object> entry : entries) {
		if (requiredType.isAssignableFrom(entry.getValue().getClass())) {
			byType = true;// 只要有對應型別的Bean就認為ByType成功
			if (name == null || name.trim().isEmpty() || name.equals(entry.getKey())) {// ByType成功後進一步判斷有沒有指定Bean的名字
				if (bean != null) {
					//如果能找到多個滿足條件的Bean就丟擲異常
					throw new MultipleQualifiedBeanException("there is more than one qualified bean with type:"+requiredType.getName());
				}
				bean = ((T) entry.getValue());
			}
		}
	}
	if (bean!=null){// 如果找到了符合條件的Bean就返回
		return bean;
	}
	if (!byType) {// 如果ByType失敗,就丟擲ByType失敗的異常
		throw new NoSuchTypeBeanException("there is no bean with type:" + requiredType.getName());
	} else {// 如果ByType成功而根據指定name篩選失敗則丟擲沒有滿足條件Bean異常
		throw new NoQualifiedBeanException("there is no qualified bean with type:" + requiredType.getName() + ",and with name:" + name);
	}

}

編寫初始化容器的方法initContainer,使用synchronized關鍵字保證執行緒安全,另外由於我們有一個@Import註解,需要遞迴呼叫這個初始化方法,所以需要一個HashSet用於記錄已經被解析過的配置類,防止兩個配置類同時在@Import中引用對方導致無限遞迴和重複定義Bean導致丟擲異常

private synchronized void initContainer(String name) throws Exception {
	if (this.alreadyInitClassName==null){
		this.alreadyInitClassName=new HashSet<>();
	}
	if (this.alreadyInitClassName.contains(name)){
		return;
	}else {
		this.alreadyInitClassName.add(name);
	}
	//        反射載入
	Class<?> aClass = Class.forName(name);
	//        判斷所選類是否存在@Configuration註解
	if (aClass.isAnnotationPresent(Configuration.class)) {
		//            建立一個配置類物件
		Object config = aClass.newInstance();
		//            獲取配置類中所有的方法
		Method[] declaredMethods = aClass.getDeclaredMethods();
		if (container == null) {
			container = new HashMap<>(declaredMethods.length*4/3+1);
		}
		//            遍歷
		for (Method declaredMethod : declaredMethods) {
			//                判斷此方法是否存在@Bean註解
			if (declaredMethod.isAnnotationPresent(Bean.class)) {
				//                    如果沒有指定Bean的名字
				if ("".equals(declaredMethod.getAnnotation(Bean.class).name())) {
					//                        就使用方法名作為bean的名字並執行這個方法初始化bean並注入到容器
					addBean(declaredMethod.getName(), declaredMethod.invoke(config));
				} else {
					//                        否則使用指定的名字初始化bean並注入到容器
					addBean(declaredMethod.getAnnotation(Bean.class).name(), declaredMethod.invoke(config));
				}
			}
		}
	}
	if (aClass.isAnnotationPresent(Import.class)){
		Class<?>[] classes = aClass.getAnnotation(Import.class).classes();
		for (Class<?> aClass1 : classes) {
			initContainer(aClass1.getName());
		}
	}
}

然後定義自動裝配方法autowiredInit,並進行過載,一個接受字串引數,一個接受Class物件引數

public synchronized Object autowiredInit(String name) throws Exception {
	//        載入這個要裝配的類的class物件
	Class<?> aClass = Class.forName(name);
	return autowiredInit(aClass);
}

public synchronized <T> T autowiredInit(Class<T> clazz) throws Exception{
	//        構造這個類的例項物件
	T o = clazz.newInstance();
	//        獲取這個類所有的宣告的變數
	Field[] declaredFields = clazz.getDeclaredFields();
	//        遍歷這些變數
	for (Field declaredField : declaredFields) {
		//            判斷這個變數是否有@Autowired修飾
		if (declaredField.isAnnotationPresent(Autowired.class)) {
			//                修改訪問許可權,可以修改private的變數
			declaredField.setAccessible(true);
			AutoWiredType type = declaredField.getAnnotation(Autowired.class).type();
			if (type==AutoWiredType.BY_TYPE){// 判斷自動裝配的方式
				if (declaredField.isAnnotationPresent(Qualifier.class)){
					String qualifyName = declaredField.getAnnotation(Qualifier.class).value();
					declaredField.set(o,this.getBean(declaredField.getType(),qualifyName));
				}else{
					declaredField.set(o,this.getBean(declaredField.getType(),null));
				}
			}else {
				//                    獲取變數的名字
				String name1 = declaredField.getName();
				//                獲取對應的Bean
				Object bean = getBean(name1);
				//                設定值
				declaredField.set(o, bean);
			}
		}
	}
	//        返回這個被裝配完畢的例項物件
	return o;
}

最後編寫建構函式,同樣具有兩個過載

public Container(String name) throws Exception {
	initContainer(name);
}

public Container(Class<?> configClass) throws Exception {
	initContainer(configClass.getName());
}

進行測試

隨便編寫幾個JavaBean即可,只要具有一些屬性並且能夠正常輸出即可
示例:

public class DataSource {
    private String url;
    private String username;
    private String password;

    @Override
    public String toString() {
        return "DataSource{" +
                "url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public DataSource() {
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUrl() {
        return url;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

其他Bean如下:

編寫service層介面和實現類,驗證控制反轉和依賴倒轉

public interface IDemoService {

    /**
     * 輸出一段話
     */
    void demo();
}

實現類一:

public class DemoServiceImpl implements IDemoService {
    @Override
    public void demo() {
        System.out.println("這是第一種實現");
    }
}

實現類二:

    @Override
    public void demo() {
        System.out.println("這是第二種實現");
    }
}

編寫多個配置檔案

@Configuration
@Import(classes = {Config2.class,Config3.class})
public class Config {

    @Bean(name = "mysql")
    public DataSource dataSource(){
        DataSource dataSource = new DataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(){
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactory();
        sqlSessionFactory.setDataSource(dataSource());
        return sqlSessionFactory;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }

    @Bean
    public Encoding encoding(){
        Encoding encoding = new Encoding();
        encoding.setEncoding("UTF-8");
        return encoding;
    }


}
@Configuration
// 由於容器類做了判斷,此處不會出現無限遞迴及重複定義Bean的異常
@Import(classes = {Config.class,Config3.class})
public class Config2 {
    @Bean
    public IDemoService iDemoService2(){
//        註冊第二個實現類
        return new AnotherDemoServiceImpl();
    }
}
@Configuration
public class Config3 {
    @Bean
    public IDemoService iDemoService(){
//        註冊第一個實現類
        return new DemoServiceImpl();
    }
}

編寫啟動類

public class Client {

    @Autowired
    private DataSource mysql;

    @Autowired
    private DataSourceTransactionManager transactionManager;

    @Autowired
    @Qualifier("iDemoService")// 從多個IDemoService型別Bean中通過name指定
    private IDemoService service;

    public static void main(String[] args) throws Exception {
        //使用指定配置檔案初始化容器
        Container container = new Container(Config.class);
//        進行自動裝配
        Client client = container.autowiredInit(Client.class);
//        呼叫方法
        client.service.demo();
        System.out.println(client.mysql);
    }
}