首先我们在学习原理前,需要先了解下反射
Reflection(反射)
在有些场景中,需要在运行时动态的操作类的成员,比如在运行时根据数据库中提供具体的类名或方法名,或者根据字符串来动态实例化对象或者调用方法时,就不能通过new()对象,的方式 创建类或者 是使用类中的方法了
关键思路在于创建 class对象
我们写的每一个类如下被虚拟机解析
JVM是如何创建 Class 对象的
首先通过.java 编译成 .class 文件,然后通过 Class Loader 加载到内存, 随后JVM根据内存中的信息创建响应的Class 对象。
如何获取某个类的Class 对象
-
直接用类名.class, 就是某个类的class对象
javaClass<User> userClass = User.class;
-
通过对象的.getClass() 方法,我们在编译阶段无法准确地判断Class对象的确切类型
javaClass<?> clazz = user.getClass(); //使用通配符
-
通过Class.forName() 方法获取 类型
javaClass<?> class = Class.forName("org.blcu.taco.User")
- 参数为完全类的限定名
- 通过这种方法获取类的对象时,类的静态代码块会立即执行。
某个类的Class对象的常用操作
假设我们有一个类为:
java
public class Son {
private final String idNum;
public String school;
public String name;
private static final String staticField = "Son static field"
public Son (String idNum,String name) {
this.idNum = idNum;
this.name = name;
}
static {
System.out.println("Son static block");
}
public static void sayHello() {
System.out.println("Son say hello");
}
public void sayHi(String name) {
System.out.println("Son say hi to " + name);
}
private static void sayBye() {
System.out.println("Son sayBye");
}
private static void sayBye(String what) {
System.out.println("Son".concat(what));
}
private void sayBye1() {
System.out.println(this.name+"Son sayBye1");
}
}
那当我们获取了Son 的 Class 对象后,我们能做什么操作呢?
-
获取所有字段名
java// 获取Son类的Class对象 Class<?> aClass = Class.forName("studydemo.reflection.Son"); // 通过这种方法获取类的对象时,类的静态代码块会立即执行, 此时我们的静态代码块就会执行,输出:son static block // 获取反射类中所有的公有字段 Field[] fields = aClass.getFields(); for (Field field : fields) { System.out.println(field.getName()); } // 获取所有的字段名,包括共有和私有 Field[] declaredFields1 = aClass.getDeclaredFields(); for (Field field : declaredFields1) { System.out.println(field.getName()); }
-
获取本类中所有的方法名
java// 获取Son类的Class对象 Class<?> aClass = Class.forName("studydemo.reflection.Son"); // 获取所有的方法名 Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod.getName()); }
-
通过名称访问静态变量和静态方法(静态变量、静态方法、代码块在类加载时就已经被初始化了,所以才可以访问)
-
访问静态变量
java// 按照名称获取变量或方法 Field declaredField = aClass.getDeclaredField("staticField"); // 因为字段是私有的所以要给它访问的权限 declaredField.setAccessible(true); // 获取的是静态变量并要取值 Object o = declaredField.get(null); System.out.println(o); // 输出:Son static field
-
访问静态方法
java// invoke函数是执行通过一个类的class对象获取的方法的 Method sayBye = aClass.getDeclaredMethod("sayBye"); // 因为方法时私有的,所以要给它访问的权限 sayBye.setAccessible(true); sayBye.invoke(null); // 输出:"Son sayBye" // 访问带参的静态方法 Method sayBye1 = aClass.getDeclaredMethod("sayBye", String.class); sayBye1.setAccessible(true); sayBye1.invoke(null,"88"); // 输出 say88
-
-
使用反射类创建类的实例
主要就是Class对象有两个函数:getConstructor() 和 newInstance(), 第一个用来获取构造函数,第二个用来使用该构造器创建类的实例
-
使用反射来创建类的实例(无参构造方法)
javaConstructor<?> constructor = aClass.getConstructor(); Object o1 = constructor.newInstance();
-
使用反射来创建类的实例(有参构造方法)
javaConstructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, String.class); Object o2 = declaredConstructor.newInstance("01", "syl");
-
-
创建实例之后执行类的私有方法(不是静态私有方法,这里要和静态私有方法做区分,静态私有方法是不用先实例化对象的)
javaMethod sayBye11 = aClass.getDeclaredMethod("sayBye1"); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, String.class); Object o2 = declaredConstructor.newInstance("01", "syl"); // 私有方法,设置访问劝降为 true sayBye11.setAccessible(true); // 传入的对象就为实例化的对象 sayBye11.invoke(o2); // 输出:sylSon sayBye1
-
创建实例之后访问类的final型私有变量
javaField declaredField1 = aClass.getDeclaredField("idNum"); declaredField1.setAccessible(true); declaredField1.set(o2,"11111"); System.out.println(declaredField1.get(o2));
我们可以发现,在使用反射的情况下,final型变量也可以被修改,我们在平常使用中,要避免出现这种情况。
-
如何在运行时进行类型转化,在反射过程中如何保证类型安全
-
如何在运行时进行类型转换
javaMethod sayBye11 = aClass.getDeclaredMethod("sayBye1"); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, String.class); Object o2 = declaredConstructor.newInstance("01", "syl"); // 进行类型转换 Son son = (Son) o2;
-
转化时如何保证类型安全
javaif(o2 instanceof Son) { Son son = (Son) o2; }
-
通过上述如此多的例子,我们对反射的基本使用有了一些了解,那么我们用一个案例来模拟spring boot 自动注入的流程
-
创建一个Config类 (对标Spring Boot 的@Configure + @Bean), Config中的类,都会被装配到我们的Container类中(对标SpringBoot的容器)
javapackage studydemo.reflection.autowired; import org.springframework.context.annotation.Bean; public class Config { @Bean public Customer customer() { return new Customer("syl","2819719869@qq.com"); } @Bean public Address address() { return new Address("345","467100"); } public Message message() { return new Message("hi there!"); } }
-
创建一个Container类(对标Spring Boot的容器),其中的 init()方法,相当于Spring Boot 的run 方法, 这里面的 init()、 getServiceInstanceByClass()和createInstance() 时浓缩的精华,我们慢慢来看
javapackage studydemo.reflection.autowired; import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; // 相当于Spring Boot 容器 @NoArgsConstructor public class Container { // Method 存储的方法本身,而不是方法执行后返回的实例,具体的实例将在使用时执行这些方法来获取,这种方法可以节约资源并提高性能 private Map<Class<?>, Method> methods; private Object config; private Map<Class<?>,Object> services; // 相当于Spring Boot 的 run 方法 public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { this.methods = new HashMap<>(); this.services = new HashMap<>(); Class<?> clazz = Class.forName("studydemo.reflection.autowired.Config"); Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { if(ObjectUtils.isNotNull(declaredMethod.getAnnotation(Bean.class) != null)) { this.methods.put(declaredMethod.getReturnType(),declaredMethod); } System.out.println(declaredMethod.getName()); } this.config = clazz.getConstructor().newInstance(); } // 通过类的Class 对象,获取相应的的服务实例 // 执行该方法后,我们确实获取了该方法的对象,我们每次调用这个方法时,都会创造一个实例,这在性能上可能会有点问题,因为许多服务都是全局的,这里在一个容器中应该用单例模式 public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException { if(this.services.containsKey(clazz)) { return this.services.get(clazz); } if(this.methods.containsKey(clazz)) { Method method = this.methods.get(clazz); Object obj = method.invoke(this.config); this.services.put(clazz,obj); return obj; } return null; } // 用于通过Class对象创建普通实例,并且实现将服务自动注入到对象里面 public Object createInstance(Class<?> clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor<?> declaredConstructor : declaredConstructors) { if(ObjectUtils.isNotNull(declaredConstructor.getAnnotation(Autowired.class))) { // 首先获取构造器所有的参数类型 Class<?>[] parameterTypes = declaredConstructor.getParameterTypes(); Object[] arguments = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { arguments[i] = getServiceInstanceByClass(parameterTypes[i]); } return declaredConstructor.newInstance(arguments); } } return clazz.getDeclaredConstructor().newInstance(); } }
-
变量的解释
java// 存储Config 类中的方法 private Map<Class<?>, Method> methods; // Config类的实例化对象 private Object config; // private Map<Class<?>,Object> services;
Method 存储的方法本身,而不是方法执行后返回的实例,具体的实例将在使用时执行这些方法来获取,这种方法可以节约资源并提高性能
-
init() 函数,相当于Spring Boot 的run方法
java// 相当于Spring Boot 的 run 方法 public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { // 初始化变量 this.methods = new HashMap<>(); this.services = new HashMap<>(); // 利用反射获取Config类 的 Class对象 Class<?> clazz = Class.forName("studydemo.reflection.autowired.Config"); // 获取Config类中所有的方法,大家可以回去看一下Config类,Config的每个方法都是用来创建不同类的实例的 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { // 加了Bean注解的方法才会被放到methods(Spring Boot 中为什么加了 @Bean注解的方法会被放到容器中呢,本质也是这个原理) if(ObjectUtils.isNotNull(declaredMethod.getAnnotation(Bean.class) != null)) { this.methods.put(declaredMethod.getReturnType(),declaredMethod); } System.out.println(declaredMethod.getName()); } // 实例化Config类对象,并赋值给 成员变量 config this.config = clazz.getConstructor().newInstance(); }
-
getServiceInstanceByClass()
java// 通过类的Class 对象,获取相应的的服务实例 // 执行该方法后,我们确实获取了该方法的对象,我们每次调用这个方法时,都会创造一个实例,这在性能上可能会有点问题,因为许多服务都是全局的,这里在一个容器中应该用单例模式 public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException { // this.services 存储的是创建过的实例,如果已经创建过了,那就返回原来创建过的,否则再新创建 if(this.services.containsKey(clazz)) { return this.services.get(clazz); } // this.methods 是一个Map , key为Class<?> if(this.methods.containsKey(clazz)) { // this.methods 中存储的都是 Config类的方法,执行的时候必须要传入一个实例化后的 Config对象,this.config 正好是 Config 的实例化对象 Method method = this.methods.get(clazz); // 指向该方法,通过 类的 Class 对象,就能获取到这个类的对象 Object obj = method.invoke(this.config); this.services.put(clazz,obj); return obj; } return null; }
-
createInstance() --- 用于通过Class 对象创建普通实例,并且自动实现将所需的服务自动注入到对象里面
java// 用于通过Class对象创建普通实例,并且实现将服务自动注入到对象里面 public Object createInstance(Class<?> clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { // 获取到Class对象所指代的类所有的构造方法 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 遍历构造方法 for (Constructor<?> declaredConstructor : declaredConstructors) { // 找到加了 @Autowried 注解的构造器 if(ObjectUtils.isNotNull(declaredConstructor.getAnnotation(Autowired.class))) { // 首先获取构造器所有的参数类型,也就是需要注入什么(特别重要) Class<?>[] parameterTypes = declaredConstructor.getParameterTypes(); Object[] arguments = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { // arguments[i] 就是实例化的类 arguments[i] = getServiceInstanceByClass(parameterTypes[i]); } // 返回注入了所需服务的实例化对象 return declaredConstructor.newInstance(arguments); } } // 默认返回一个无参构造器所创建的对象 return clazz.getDeclaredConstructor().newInstance(); }
至此,我们调用 Container 的 createIntance() 函数,就能够按照我们的规则创建 已经注入了所需服务的对象。
那么你们能通过代码来看出要按照什么规则吗?
- 对象要有一个带有 @Autowired 注解的有参构造方法(参数就是这个类依赖的服务类),如果没有依赖其他服务,可以不写
- 需要注册服务的类,要在 Config类中加 @Bean 注解,这样才能被注册成为一个服务。