模拟实现Spring中的注解装配
本文原创,地址为http://www.cnblogs.com/fengzheng/p/5037359.html
在Spring中,XML文件中的bean配置是实现Spring IOC的核心配置文件,在早版本的Spring中,只能基于XML配置文件,配置各个对象之间的依赖关系。在Spring 2.5以后出现了注解,使用注解结合XML的方式,简化了XML配置的复杂度。
老版本中纯XML配置实现IOC
在配置文件中配置如下:
<bean id="userDao" class="com.springapp.mvc.dao.UserDao"> </bean> <bean id="userService" class="com.springapp.mvc.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean>
UserServiceImpl的实现如下:
public class UserServiceImpl implements UserService { public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } private UserDao userDao; public User getUserById(int id){ return userDao.getUserById(id); } public int getUserCount(){ return userDao.getUserCount(); } }
配置的意思是:<property name="userDao" ref="userDao"></property>这行配置是为UserServiceImpl类中的userDao指定userDao这个bean,这样在UserServiceImpl类中调用userDao的方法,其实就是调用com.springapp.mvc.dao.UserDao的方法。
结合注解的实现方式
配置文件简化如下:
<bean id="userDao" class="com.springapp.mvc.dao.UserDao"> </bean> <bean id="userService" class="com.springapp.mvc.service.impl.UserServiceImpl"> </bean>
UserServiceImpl的实现如下:
public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; public User getUserById(int id){ return userDao.getUserById(id); } public int getUserCount(){ return userDao.getUserCount(); } }
利用@Autowired注解,实现在xml中<property name="userDao" ref="userDao"></property>的配置,从而实现了自动注入。@Autowired自动注入的规则为byType,意思就是按照被注解字段的类型和xml中配置的bean的类型相匹配,即在UserServiceImpl 类中的userDao为UserDao类型,匹配的时候会在所有bean中查找类型同样为UserDao的bean。
那么既然是按照类型匹配,如果存在两个相同类型的bean呢,这时候,就会启用第二个匹配规则ByName,即根据字段的名字来匹配相同id的bean。如下XML配置:
<bean id="userDao1" class="com.springapp.mvc.dao.UserDao"> </bean> <bean id="userDao2" class="com.springapp.mvc.dao.UserDao"> </bean> <bean id="userService" class="com.springapp.mvc.service.impl.UserServiceImpl"> </bean>
那么在UserServiceImpl类中应该使用以下这种方式来自动注入:
@Autowired private UserDao userDao1; @Autowired private UserDao userDao2;
这样好像不是很灵活的样子,看起来有些不爽,有没有办法可以指定要匹配的bean呢?没错,是有的。可以使用这样的方式,通过@Autowired和@Qualifier相结合的方式,@Qualifier后跟的参数就是bean的名称:
@Autowired @Qualifier("userDao1") private UserDao userDao;
还有更常用的方式,@Resource:
@Resource(name = "userDao1") private UserDao userDao;
关于注解IOC的内容可以参看这篇文章,写的很详细。
注解在Spring中的用法讲完了,下面来自己实习一个简单的类,来模拟Spring利用注解实现IOC的原理。
Spring IOC实现原理
1.首先Spring根据bean配置文件,收集所有bean的实例;
2.Spring根据配置文件中的context:component-scan,扫描需要被注入的包(递归包中的所有待注入类);
3.扫描待注入类时,判断是否有特定的注解(例如@Autowired、@Resource),如果有,则进行第4步,注入;
4.注入:根据注解类型或参数,利用反射,为被注解的字段或属性等设置对应的bean实例。
以上是我个人理解,可能和Spring真正的实现有些出入。
模拟利用注解实现注入
这里要定义一个类似于@Resource的注解,命名为@MyAutowired,定义如下:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.FIELD}) @Documented public @interface MyAutowired { public String name() default ""; public String value() default ""; }
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:context="http://www.springframework.org/schema/context"> <context:component-scan id="test" class="fengzheng.Test"/> <bean id="tomoto" class="fengzheng.Tomoto"></bean> </beans>
其中bean和Spring中bean的定义是一样的,而context:component-scan在Spring是定义属性base-package,之后根据这个属性,扫描这个包下的所有类,这里为做演示,也定义为一个类,之后会根据这个class属性,对这个类进行注入。
配置文件中的tomoto bean的定义:
package fengzheng; public class Tomoto { public void SayHello(){ System.out.println("hello I'm tomoto"); } }
配置文件中fengzheng.Test类定义,这个即为要被注入的类:
package fengzheng; import fengzheng.fzAnnotation.MyAutowired; public class Test { @MyAutowired(name = "tomoto") private Tomoto tomoto; public void Say(){ tomoto.SayHello(); } }
核心注解分析并实现注入的类:
import java.lang.reflect.Field; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import fengzheng.fzAnnotation.MyAutowired; import java.util.stream.*; public class FzClassPathXMLApplication { //xml配置文件中 bean定义的集合 private List<BeanDefine> beanList = new ArrayList<BeanDefine>(); // 存储bean实例和bean的id的对应关系 以便可以根据注解名称找到对应的实例 Map<String, Object> beanInstanceList = new HashMap<String, Object>(); //xml配置文件中 被扫描类的定义的集合 在Spring框架中 直接扫描一个或多个包 List<ScanDefine> scanList = new ArrayList<ScanDefine>(); // 存储被扫描的待注入的实体集合 Map<String, Object> annotationInstanceList = new HashMap<String, Object>(); public FzClassPathXMLApplication(String xmlName) { ReadXml(xmlName); //实例化所有定义的bean BeanInstance(); //实例化所有的待注入类 ScanClassInstance(); //开始根据注解实现依赖注入 InjectAnnotation(); } /** * 读取配置文件 收集bean集合和待注入类的集合 * @param xmlFileName */ public void ReadXml(String xmlFileName) { URL xmlPath = this.getClass().getClassLoader().getResource(xmlFileName); System.out.println(xmlPath); SAXReader reader = new SAXReader(); try { Document dom = reader.read(xmlPath); Element root = dom.getRootElement(); List<Element> iters = root.elements(); Stream<Element> beans = iters.stream().filter(bean -> bean.getName().equals("bean")); Iterator<Element> iterBeans = beans.iterator(); while (iterBeans.hasNext()) { Element bean = iterBeans.next(); String id = bean.attributeValue("id"); String clsName = bean.attributeValue("class"); //System.out.println("id:" + id + "\nclass:" + clsName); BeanDefine bd = new BeanDefine(); bd.setId(id); bd.setClsName(clsName); beanList.add(bd); } Stream<Element> scanClasses = iters.stream().filter(scan -> scan.getName().equals("component-scan")); //iters.stream().forEach(scan -> System.out.println(scan.getName())); Iterator<Element> iterScans = scanClasses.iterator(); while (iterScans.hasNext()) { Element scan = iterScans.next(); String id = scan.attributeValue("id"); String clsName = scan.attributeValue("class"); ScanDefine sd = new ScanDefine(); sd.setId(id); sd.setClassName(clsName); scanList.add(sd); } System.out.println("scanList.size():"+scanList.size()); } catch (Exception e) { e.printStackTrace(); } } /** * 收集bean实例 */ private void BeanInstance() { for (BeanDefine bd : beanList) { try { Object beanInstance = Class.forName(bd.getClsName()).newInstance(); System.out.println(beanInstance.getClass().getName()); beanInstanceList.put(bd.getId(), beanInstance); } catch (Exception e) { e.printStackTrace(); } } } /** * 收集被扫描的待注入的类的实例 */ private void ScanClassInstance(){ for(ScanDefine sd:scanList){ try { Object scanInstance = Class.forName(sd.getClassName()).newInstance(); System.out.println(scanInstance.getClass().getName()); annotationInstanceList.put(sd.getId(), scanInstance); } catch (Exception e) { e.printStackTrace(); } } } /** * 循环遍历待注入的类 */ public void InjectAnnotation() { Iterator<Map.Entry<String, Object>> iters = annotationInstanceList.entrySet().iterator(); while (iters.hasNext()) { Map.Entry<String, Object> iter = iters.next(); Object scanInstance = iter.getValue(); InjectField(scanInstance); } } /** * 注入:把需要注入类中的注解为MyAutowired的字段 注入bean实例 * @param injectClass */ private void InjectField(Object injectClass) { try { Field[] fields = injectClass.getClass().getDeclaredFields(); for (Field field : fields) { if (field != null && field.isAnnotationPresent(MyAutowired.class)) { System.out.println(field.getName()); MyAutowired myAutowired = field.getAnnotation(MyAutowired.class); String beanName = myAutowired.name(); Object value = null; if (beanName != null && !beanName.equals("")) { value = beanInstanceList.get(beanName); } else { Class<?> fType = field.getType(); for (String key : beanInstanceList.keySet()) { if (fType.isAssignableFrom(beanInstanceList.get(key).getClass())) { value = beanInstanceList.get(key); break; } } } field.setAccessible(true); field.set(injectClass, value); } } } catch (Exception e) { e.printStackTrace(); } } public Object getScan(String scanName){ return this.annotationInstanceList.get(scanName); } }
注解处理类中用到的两个实体类:
package fengzheng.simpleSpring; public class BeanDefine { private String id; private String clsName; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getClsName() { return clsName; } public void setClsName(String clsName) { this.clsName = clsName; } } package fengzheng.simpleSpring; public class ScanDefine { public String id; public String className; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } }
对这段程序逻辑的解释:
1.首先通过ReadXml()方法读取配置文件beans.xml,找到其中的bean节点和context:component-scan节点,然后把bean节点实例化为BeanDefine,并加入beanList集合,把component-scan节点实例化为ScanDefine,并加入scanList集合;
2.通过BeanInstance()方法,把配置文件中的bean都实例化存储到Map集合beanInstanceList中;
3.通过ScanClassInstance()方法,把配置文件中的component-scan节点(即待注入的类)实例化并处处到Map集合annotationInstanceList中;
4.通过InjectAnnotation()方法,遍历annotationInstanceList集合,为其中的被@MyAutowired注解的字段赋值(即对应的bean的实例)
最后调用实现如下:
FzClassPathXMLApplication ctx = new FzClassPathXMLApplication("beans.xml"); Test test =(Test) ctx.getScan("test"); test.Say();
输出结果: