依赖注入是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);
}
}
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();
}
}
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)
}
}
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类。