IoC容器通过注解进行注入的简单实现

控制反转和和依赖注入是Spring中的重要概念。控制反转是将当前bean所依赖的其他bean组件的实例化过程交由IoC容器来实现并由IoC容器注入到当前bean当中。之前对其实现有一个简单的认识,即根据类名通过反射的方式加载所需要的bean组件。现在对其做一个通过注解方式进行注入简单的实现。
首先定义Container接口,容器的主要功能即实现对bean的存储,即实现bean的增删查。所以定义其获取、注册、删除、初始化功能。

/**
 * IoC容器接口
 * @author OrangeXXL
 *
 */
public interface Container {
	//根据class获取Bean
    public <T> T getBean(Class<T> clazz);

    //使用name获取Bean
    public <T> T getBeanByName(String name);

    //将Bean注册到容器
    public Object registerBean(Object bean);

    //将class注册到容器
    public Object registerBean(Class<?> clazz);

    //注册带名称的bean到容器
    public Object registerBean(String name, Object bean);

    //删除一个Bean
    public void remove(Class<?> clazz);

    //根据名称删除Bean
    public void removeByName(String name);

    //返回所有bean对象名称
    public Set<String> getBeanNames();

    //初始化装配
    public void initWired();
}

接下来是对容器的具体实现。容器实现的是在加载bean的过程中,对其进行扫描,找到有注解的字段后对其所依赖的bean组件进行注入。首先我们需要存储已加载的bean。所以通过map以<class name,bean object>的形式存储bean。同时用户会对bean进行命名,因此再创建一个map以<custom name, class name>的形式存储bean名称和类名称的对应关系。再初始化当中加载当前bean。因此容器的实现如下。

public class SampleContainer implements Container {

	/**
	 * 创建map用来保存所有beans
	 * key是className value为Bean对象
	 */
	private Map<String, Object> beans;
	
	/**
	 * 存储Bean和className的关系
	 * 其中key是customName value是className
	 */
	private Map<String, String> beanKeys;

    public SampleContainer() {
    	this.beans = new ConcurrentHashMap<String, Object>();
    	this.beanKeys = new ConcurrentHashMap<String, String>();
    }

    @Override
    //根据class获取Bean
    public <T> T getBean(Class<T> clazz) {
		String name = clazz.getName();
		Object object = beans.get(name);
		if(null != object){
			return (T) object;
		}
		return null;
    }
    
    @Override
    //根据name获取bean
	public <T> T getBeanByName(String customName) {
		String className = beanKeys.get(customName);
		Object object = beans.get(className);
		if(null != object){
			return (T) object;
		}
		return null;
    }
    
    @Override
    //将Bean注册到容器
	public Object registerBean(Object bean) {
		String customName = bean.getClass().getName();
		beanKeys.put(customName, customName);
		beans.put(customName, bean);
		return bean;
	}
	
    @Override
    //将class注册到容器
	public Object registerBean(Class<?> clazz) {
		String className = clazz.getName();
		beanKeys.put(className, className);
		Object bean = ReflectUtil.newInstance(clazz);
		beans.put(className, bean);
		return bean;
	}

    @Override
    //注册带名称的bean到容器
	public Object registerBean(String customName, Object bean) {
		String className = bean.getClass().getName();
		beanKeys.put(customName, className);
		beans.put(className, bean);
		return bean;
    }

    @Override
    //删除一个Bean
	public void remove(Class<?> clazz) {
		String className = clazz.getName();
		if(null != className && !className.equals("")){
			beanKeys.remove(className);
			beans.remove(className);
		}
	}

    @Override
    //根据名称删除Bean
	public void removeByName(String customName) {
		String className = beanKeys.get(customName);
		if(null != className && !className.equals("")){
			beanKeys.remove(customName);
			beans.remove(className);
		}
    }
    
    @Override
    //返回所有bean对象名称
	public Set<String> getBeanNames() {
		return beanKeys.keySet();
	}

    @Override
    //初始化装配
	public void initWired() {
		Iterator<Entry<String, Object>> it = beans.entrySet().iterator();
        while (it.hasNext()) {
			Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
			System.out.println("[DEBUG] className=>"+entry.getKey()+"\n[DEBUG] beanName=>"+entry.getValue().getClass().getName());
			Object object = entry.getValue();
			injection(object);
		}
    }
    
    /**
     * 注入对象
     * @param obj
     * @return 
     */
    public void injection(Object obj){
    	try{
    		Field[] fields = obj.getClass().getDeclaredFields();
    		for(Field field : fields){
    			/**
    			 * 获取Autowired注解标注的字段
    			 * 即获取需要注入的字段
    			 */
    			
    			/**
    			 * getAnnotation方法是AccessibleObject类的方法。
    			 * 如果存在注解,则返回该元素的指定类型的注解,否则返回null
    			 */
    			Autowired autowired = field.getAnnotation(Autowired.class);
    			System.out.println("[DEBUG] print autowired field name"+autowired+"end");
    			if(null != autowired){
    				//进入判断条件的即为存在Autowired类型注解的字段
    				Object autowiredField = null;
    				String name = autowired.name();
    				System.out.println("[DEBUG] autowired field name"+"["+name+"]");
    				if(!name.equals("")){
    					//name不为空,则需要注入的就是bean,所以首先通过beanName拿到className
    					String className = beanKeys.get(name);
    					if(null != className && className.equals("")){
    						//className存在说明曾经注入过。则直接取出相应的实例即可
    						autowiredField = beans.get(className);
    					}
    					if(null == autowiredField){
    						throw new RuntimeException("Unable to load " + name);
    					}
    					    					
    				}else{
    					//name为空则需要注入的是class,
    					System.out.println("[DEBUG] name == null in this");
    					if(autowired.value() == Class.class){
    						autowiredField = recursiveAssembly(field.getType());
    					}else{
    						autowiredField = this.getBean(autowired.value());
    						if(autowiredField == null){
    							autowiredField = recursiveAssembly(autowired.value());
    						}
    					}
    				}
    				 if (null == autowiredField) {
 			            throw new RuntimeException("Unable to load " + field.getType().getCanonicalName());
 			        }
 			        //将获取到的实例注入到相应字段(autowiredField)中
 			        boolean accessible = field.isAccessible();
 			        field.setAccessible(true);
 			        field.set(obj, autowiredField);
 			        field.setAccessible(accessible);
    			}
    		}
    	}catch(SecurityException e) {
        	e.printStackTrace();
        } catch (IllegalArgumentException e) {
        	e.printStackTrace();
        }catch (IllegalAccessException e) {
        	e.printStackTrace();
        }
    }
    
    /**
     * 装配class
     * @param clazz
     * @return
     */
    private Object recursiveAssembly(Class<?> clazz){
    	if(null != clazz){
    		return this.registerBean(clazz);
    	}
    	return null;
    }

}

这里对initWired函数做一个说明。当我们需要对某个bean进行加载时,首先我们会先将该类注册到容器当中。之后初始化时,我们迭代容器中所有的bean对其进行检查看是否存在需要注入的字段。并进行注入操作。这里迭代容器中的bean有一个很有意思的地方。举个例子,我们需要加载的bean中有若干字段需要被注入。第一次,我们将该bean注册时候去检查其字段并加载它的依赖。当加载完成后,初始化操作还会继续迭代(此时map中有了新的元素)。此时倘若被依赖的bean中同样存在对别的bean的依赖则同样会被加载进来。
但这一处我也有个疑问,在加载当前bean的时候map通过迭代器读取当前Entry,之后我们对当前Value中存储的bean进行注入。而注入时又会向map中添加元素。虽然concurrentHashMap支持迭代器的边访问,便修改的操作。concurrentHashMap通过segements细化粒度来提高其并发度,但是也存在几率新添加的元素和正在访问的元素在一个segement当中。我认为同一个segement中是会阻塞的吧。但是看到有文章说支持边访问边修改。我目前也不太清楚怎么证实。如果有了解的老哥可以评论一下。
另外容器中注册bean中有一个ReflectUtil其中主要有一个newInstance的重载方法。可以根据类名(全限定类名)和类对象创建bean。

/**
	 * 根据类名创建对象
	 * @param className
	 * @return
	 */
	public static Object newInstance(String className){
		Object obj = null;
		try{
			Class<?> clazz = Class.forName(className);
			obj = clazz.newInstance();
		}catch(Exception e){
			e.printStackTrace();
		}
		return obj;
	}
	
	/**
	 * 创建类的实例
	 * @param clazz
	 * @return
	 */
	public static Object newInstance(Class<?> clazz){
		try{
			return clazz.newInstance();
		}catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
    	return null;
	}

以上就是容器的基本实现。另外我们需要通过注解对依赖的bean进行标注。因此需要实现一个注解。注解有两个默认参数,一个是类对象,另一个是类名。代码如下。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
	
	/**
	 * @return	要注入的类类型
	 */
    Class<?> value() default Class.class;
	
    /**
     * @return	bean的名称
     */
    String name() default "";
    
}

最后我们创建两个类Stu和OutputStu来进行验证。

  • Stu是一个被依赖的bean
public class Stu {
	private String name;
	private int age;
	
	public void setName(String name){
		this.name = name;
	}
	public void setAge(int age){
		this.age = age;
	}
	public String getName(){
		return name;
	}
	public int getAge(){
		return age;
	}
	
	public void show(){
		System.out.println(name+"的年龄为:"+age);
	}
}

OutputStu是我们希望加载的bean

public class OutputStu {

	@Autowired
	private Stu s;
	
	public void setValue(String name, int age){
		s.setName(name);
		s.setAge(age);
	}
	public void printAns(){
		s.show();
	}
}

他们之间的关系可以将OutoutStu对应为开发中的Controller将Stu对应为Service
以下是测试类:

public class IoCTest {

	public static Container container = new SampleContainer();
	public static void testAutowired(){
		container.registerBean(OutputStu.class);
		container.initWired();
		
		OutputStu op = container.getBeanByName("com.wen.ioc.test.OutputStu");
		op.setValue("hezhiheng", 23);
		op.printAns();
		
	}
	
	public static void main(String[] args){
		testAutowired();
	}
}

首先我们加载OutputStu类,之后通过初始化装配,加载依赖beanStu。之后我们获取OutputStu对象进行相应操作。
至此一个通过注解进行注入的简单IoC容器就实现了。
参考来自biezhi的github。如有纰漏欢迎指正交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值