注意:
阅读本文前,请先具备Java反射相关知识。
引入
有一个Student
类,我们希望实现其成员变量College
、Major
的自动注入(依赖注入)。本文将展示如何通过反射实现这一功能,最终模拟一个简易的依赖注入框架。
1. 基本类
College类
/**
* 学院信息
*/
public class College {
private String name;
public College() {
}
public College(String name) {
this.name = name;
}
}
Major类
/**
* 专业信息
*/
public class Major {
private String name;
public Major(){
}
public Major(String name) {
this.name = name;
}
}
Student类
/**
* 学生信息
*/
public class Student {
private College college;
private Major major;
public Student() {
}
public Student(College college, Major major) {
this.college = college;
this.major = major;
}
public void print(){
System.out.println(college);
System.out.println(major);
}
}
这里仅仅提供了简单的三个类,主要是展示如何实现依赖注入。
2. 配置类
Config类
public class Config {
public College college(){
return new College("信息工程学院");
}
public Major major(){
return new Major("软件工程");
}
/**
* 普通方法,非返回对应实例的方法
*/
public String msg(){
return "hello MyFramework!";
}
}
本文将College
、Major
的实例定义为服务Service
。
在配置类中定义方法返回对应服务。
3. 容器类
使用容器类注册服务
和获取服务
Container类
public class Container {
/**
* 保存服务的Class和获取对应服务的方法
*/
private Map<Class<?>, Method> methods;
private Object config;
}
3.1 向Container类中添加init方法,用于初始化
public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
methods = new HashMap<>();
Class<?> clazz = Class.forName("com.gor.framework.Config");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
config = clazz.getConstructor().newInstance();
}
此时运行init方法,做一个测试,将会输出配置类的所有方法
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Container container = new Container();
container.init();
}
}
可以看到msg
非服务方法,但程序还是输出了此方法的信息,不是Student
实例所需要的服务。那么应该如何只获取Student
实例所需要的服务呢?(即只获取college、major方法)
解决方案
使用自定义注解,只有该方法含有此注解,此方法才会被放入Map<Class<?>, Method> methods
集合。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
}
@Retention(RetentionPolicy.RUNTIME)
的含义:
注解保留在源代码和编译后的class文件中,但是在运行时JVM 不保留。所以此时通过反射是获取不到注解信息的。需要显式指定注解的保留策略,即在注解头部加入这个注解。
RetentionPolicy.SOURCE
:注解只保留在源代码级别,编译后的class文件中不包含。RetentionPolicy.CLASS
:注解保留在源代码和编译后的class文件中,但是在运行时 VM 不保留。(默认)RetentionPolicy.RUNTIME
:注解保留在源代码、编译后的class文件中,且在运行时 VM 也会保留,因此可以通过反射机制读取注解信息。
修改Config
类
package com.gor.concurrent.reflection.framework;
public class Config {
@MyBean
public College college(){
return new College("信息工程学院");
}
@MyBean
public Major major(){
return new Major("软件工程");
}
public String msg(){
return "hello MyFramework!";
}
}
完善init方法
public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
methods = new HashMap<>();
Class<?> clazz = Class.forName("com.gor.framework.Config");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if(declaredMethod.getDeclaredAnnotation(MyBean.class) != null){
methods.put(declaredMethod.getReturnType(), declaredMethod);
}
}
config = clazz.getConstructor().newInstance();
}
3.2 注册服务
成员变量保存获取服务的方法后,就可以注册服务了。
public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
if(methods.containsKey(clazz)){
Method method = methods.get(clazz);
Object obj = method.invoke(config);
services.put(clazz, obj);
return obj;
}
return null;
}
困惑点:
这里的返回参数为什么是Object?
答:因为是动态生成的实例,编译器无法确切知道实例类型。
服务可以复用,也就是不必每次都创建新的实例。在这里我创建一个Map集合(Map<Class<?>, Object> services
)用于保存已创建的服务。
完善getServiceInstanceByClass方法
public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
if(services.containsKey(clazz)){
return services.get(clazz);
}
if(methods.containsKey(clazz)){
Method method = methods.get(clazz);
Object obj = method.invoke(config);
services.put(clazz, obj);
return obj;
}
return null;
}
注意:此方法存在线程安全问题,因本文重点是反射,读者可以自己试试改造这个方法为线程安全的方法。
3.3 获取实例
服务注册完毕,也就是Student
实例所需要的依赖(服务)已创建,可以创建Student
实例。
public Object createInstance(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
/**
* 类的构造器有很多,应该使用哪一个来创建对象?
*/
}
}
public Student() {
}
public Student(College college, Major major) {
this.college = college;
this.major = major;
}
在本例中,Student
有两个构造器,怎么指定构造器?
解决方案
这里使用了和上文一样的解决方案,自定义注解。在想要执行的构造器上添加注解,这样即可通过注解获取到这个构造器。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}
在Student类
的有参构造器中加入@MyAutowired
注解
完善createInstance方法
public Object createInstance(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
if(constructor.getDeclaredAnnotation(MyAutowired.class) != null){
Class<?>[] parameterTypes = constructor.getParameterTypes();
/**
* 存放参数类型对应的实例
*/
Object[] arguments = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
arguments[i] = getServiceInstanceByClass(parameterTypes[i]);
}
return constructor.newInstance(arguments);
}
}
/**
* 如果没有使用MyAutowired注解,默认返回通过无参构造器创建的对象实例
*/
return clazz.getConstructor().newInstance();
}
容器类完整代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Container {
/**
* 保存服务的Class和获取对应服务的方法
*/
private Map<Class<?>, Method> methods;
private Map<Class<?>, Object> services;
private Object config;
public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
methods = new HashMap<>();
services = new HashMap<>();
Class<?> clazz = Class.forName("com.gor.framework.Config");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if(declaredMethod.getDeclaredAnnotation(MyBean.class) != null){
methods.put(declaredMethod.getReturnType(), declaredMethod);
}
}
config = clazz.getConstructor().newInstance();
}
public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
if(services.containsKey(clazz)){
return services.get(clazz);
}
if(methods.containsKey(clazz)){
Method method = methods.get(clazz);
Object obj = method.invoke(config);
services.put(clazz, obj);
return obj;
}
return null;
}
public Object createInstance(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
if(constructor.getDeclaredAnnotation(MyAutowired.class) != null){
Class<?>[] parameterTypes = constructor.getParameterTypes();
/**
* 存放参数类型对应的实例
*/
Object[] arguments = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
arguments[i] = getServiceInstanceByClass(parameterTypes[i]);
}
return constructor.newInstance(arguments);
}
}
/**
* 如果没有使用MyAutowired注解,默认返回通过无参构造器创建的对象实例
*/
return clazz.getConstructor().newInstance();
}
}
4. 最终效果
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Container container = new Container();
container.init();
Class<?> clazz = Class.forName("com.gor.framework.Student");
Object student = container.createInstance(clazz);
Method method = clazz.getDeclaredMethod("print");
method.invoke(student);
}
}
控制台成功打印出College
、Major
实例,Student
实例的依赖成功注入。
至此,利用反射成功实现了一个简单的框架。