本文原創,地址為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();
輸出結果: