编写一个自己的IOC容器

Posted PPG007

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写一个自己的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);
    }
}

以上是关于编写一个自己的IOC容器的主要内容,如果未能解决你的问题,请参考以下文章

Castle IOC容器与Spring.NET配置之比较

[spring学习1] IoC容器

Spring IOC容器的初体验

[PHP] 项目实践中使用的IOC容器思想

ioc、aop以及di

利用反射做一个简易 Spring IOC 容器,模仿其装配功能