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

