Spring 依賴注入實現原理--java反射和ASM框架
依賴注入是spring的一個特性,從配置層面解決了程式耦合、依賴問題,spring提供了建構函式依賴注入、Setter方法依賴注入、自動裝配依賴注入和@autowired註解依賴注入等多種實現方式。
那麼依賴注入是如何實現的?第一反應就是java反射唄,比如建構函式注入,我們可以通過反射讀取Bean類的建構函式,引數個數,引數型別,所以只要我們在xml配置檔案中指定了引數型別或引數順序就可以輕鬆通過反射創建出該Bean的例項。但是實踐過程中我們發現,xml配置檔案中指定name=引數名稱也可以實現依賴注入,這是java反射很難實現的至少jdk1.8以下實現不了,因為jdk1.8以下通過反射得不到具體的引數名稱。看一個案例。
Student.java
package com.marcus.spring.beans;
public class Student {
private Integer age;
private String name;
private String gender;
public Student() {
}
public Student(String name, String gender, Integer age) {
this.name = name;
this.age = age;
this.gender = gender;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String toString() {
return String.format("{name: %s, gender: %s, age: %d}" , this.name, this.gender, this.age);
}
}
xml配置檔案 ioc-di-asm.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Definition for student bean -->
<bean id="student1" class="com.marcus.spring.beans.Student">
<constructor-arg value="student1" />
<constructor-arg value="male" />
<constructor-arg value="11" />
</bean>
<bean id="student2" class="com.marcus.spring.beans.Student">
<constructor-arg type="java.lang.Integer" value="22" />
<constructor-arg value="student2" />
<constructor-arg value="male" />
</bean>
<bean id="student3" class="com.marcus.spring.beans.Student">
<constructor-arg type="java.lang.Integer" value="33" />
<constructor-arg name="gender" value="female" />
<constructor-arg value="student3" />
</bean>
</beans>
測試類 DIAsmApp.java
public class DIAsmApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("ioc-di-asm.xml");
Student student1 = context.getBean("student1", Student.class);
System.out.println("student1: " + student1.toString());
Student student2 = context.getBean("student2", Student.class);
System.out.println("student2: " + student2.toString());
Student student3 = context.getBean("student3", Student.class);
System.out.println("student3: " + student3.toString());
context.close();
}
}
輸出結果
student1: {name: student1, gender: male, age: 11}
student2: {name: student2, gender: male, age: 22}
student3: {name: student3, gender: female, age: 33}
案例說明
1、student1,可以用反射實現
xml配置了3個constructor-arg,沒有type、index、name等其它裝飾,這種情況我們通過java反射可以成功建立例項,即找3個引數的建構函式,Student(String name, String gender, Integer age),然後按照constructor-arg出現順序依次給建構函式引數賦值,並建立例項。
2、student2,可以用反射實現
xml配置了3個constructor-arg: <constructor-arg type=“java.lang.Integer” value=“22” /> <constructor-arg value=“student2” /> <constructor-arg value=“male” /> 第1個引數標記為Integer型別,其餘2個只有value值,沒其它標識,這種情況我們依然可以通過java反射成功建立例項。
- 找到3個引數的建構函式,Student(String name, String gender, Integer age);
- 讀取第1個constructor-arg type=“java.lang.Integer”,建構函式中找到型別為Integer的引數,發現是第3個引數,則設定arg2 = 22
- 讀取第2個constructor-arg,建構函式為arg0,arg1未設定,先後順序賦值,arg0 = student2
- 讀取第3個constructor-arg,arg1未設定,arg1 = male
3、student3,java反射無法實現
xml配置了3個constructor-arg: <constructor-arg type=“java.lang.Integer” value=“33” /> <constructor-arg name=“gender” value=“female” /> <constructor-arg value=“student3” /> 第1個引數標記為Integer型別,第2個引數設定了name,第3個引數沒特殊標識,假設可以通過java反射實現,則實現步驟應該是:
- 找到3個引數的建構函式,Student(String name, String gender, Integer age);
- 讀取第1個constructor-arg type=“java.lang.Integer”,建構函式中找到型別為Integer的引數,發現是第3個引數,則設定arg2 = 22
- 讀取第2個constructor-arg,建構函式為arg0,arg1未設定,先後順序賦值,arg0 = female
- 讀取第3個constructor-arg,arg1未設定,arg1 = student3
我們得到student bean為{name=female, gender=student3,age=33},姓名變成了性別,性別變成了姓名,不是我們想要的結果,java反射失敗!
那麼spring依賴注入為何可以正常工作呢?原因是spring使用了asm框架,可以讀取java類位元組碼,讀取到建構函式的引數名稱,如上文的student3配置,spring可以讀取到第二個constructor-arg name="gender"對應建構函式的arg1,所以就可以正常工作了。 ASM框架程式碼演示:
AsmDemo.java
package com.marcus.spring.aop;
import java.lang.reflect.Constructor;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import com.marcus.spring.beans.Student;
public class AsmDemo {
public static void main(String[] args) throws ClassNotFoundException {
LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer
= new LocalVariableTableParameterNameDiscoverer();
Class<?> forName = Class.forName(Student.class.getName());
Constructor<?>[] constructors = forName.getConstructors();
for (Constructor<?> constructor : constructors) {
String argNames = "";
String[] parameterNames = localVariableTableParameterNameDiscoverer
.getParameterNames(constructor);
for (String parameterName : parameterNames) {
argNames += argNames.length() < 1 ? parameterName : "," + parameterName;
}
System.out.println(argNames.length()<1 ? constructor.getName() + "()"
: constructor.getName() + "(" + argNames + ")");
}
//output:
//com.marcus.spring.beans.Student()
//com.marcus.spring.beans.Student(name,gender,age)
}
}
通過ASM框架,我們讀取到了com.marcus.spring.beans.Student(name,gender,age)建構函式引數名稱,繼續student3 bean注入的實現步驟:
student3在xml檔案中,配置了3個constructor-arg: <constructor-arg type=“java.lang.Integer” value=“33” /> <constructor-arg name=“gender” value=“female” /> <constructor-arg value=“student3” /> 第1個引數標記為Integer型別,第2個引數設定了name,第3個引數沒特殊標識,通過java反射+ASM框架,實現步驟應該是:
- 找到3個引數的建構函式,Student(String name, String gender, Integer age);
- 讀取第1個constructor-arg type=“java.lang.Integer”,建構函式中找到型別為Integer的引數,發現是第3個引數,則設定arg2 = 22
- 讀取第2個constructor-arg name=“gender”,建構函式中找到名稱為gender的引數,發現是第2個引數,則設定arg1=female
- 讀取第三個constructor-arg,arg0未設定,arg0 = student3
我們得到student bean為{name=student3, gender=female,age=33},成功!
小結
spring框架廣泛使用了ASM框架,我們可以從spring的jar包構成可以看出ASM對於spring的重要性。以3.2.5.RELEASE版本為例,ASM部分在spring-core-3.2.5.RELEASE.jar包,spring最核心的jar包中!AOP,cglib等都需要ASM讀取、操作java類。