Java-Reflection(JAVA反射)
链接:https://www.jianshu.com/p/8679b15fc8b1
定义
Java-Reflection(JAVA反射)是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够通过Java-Reflection来调用它的任意方法和属性(不管是公共的还是私有的)。
这种动态获取信息以及动态调用对象方法的行为被称为java的反射机制。
用途
与普通的类编译静态生成不一样的是,Java-Reflection能够动态的生成类,所以Java-Reflection是Java框架开发的重要的一部分,很多优秀的开源框架都是通过反射完成的。
Java-Reflection可以在在日常的第三方应用开发过程中,会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。
使用主要种类
与Java-Reflection相关种类主要分为:
| 类名 | 用途 |
|---|---|
| Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
| Field类 | 代表类的成员变量(成员变量也称为类的属性) |
| Method类 | 代表类的方法 |
| Constructor类 | 代表类的构造方法 |
| Annotation类 | 代表类的注解 |
基础技术
Class类
获得类相关的方法
| 方法 | 用途 |
|---|---|
| asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
| Cast | 把对象转换成代表类或是接口的对象 |
| getClassLoader() | 获得类的加载器 |
| getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
| getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
| forName(String className) | 根据类名返回类的对象 |
| getName() | 获得类的完整路径名字 |
| newInstance() | 创建类的实例 |
| getPackage() | 获得类的包 |
| getSimpleName() | 获得类的名字 |
| getSuperclass() | 获得当前类继承的父类的名字 |
| getInterfaces() | 获得当前类实现的类或是接口 |
| getAnnotation(Class |
获取类上面的注解 |
| isAnnotationPresent(Class |
判断是否对应的注解 |
例:使用forName来创建一个指定类的实例:
Field field = c4.getDeclaredField("name");
String name = field.get(o);
获得类中构造器相关的方法
| 方法 | 用途 |
|---|---|
| getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
| getConstructors() | 获得该类的所有公有构造方法 |
| getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
| getDeclaredConstructors() | 获得该类所有构造方法 |
例:获取一个类中的某个私有构造函数:
Class[] p4 = {String.class};
Method method = c4.getDeclaredMethod("welcome",p4);
Field类
Field类的成员变量(成员变量也称为类的属性)。
| 方法 | 用途 |
|---|---|
| equals(Object obj) | 属性与obj相等则返回true |
| get(Object obj) | 获得obj中对应的属性值 |
| set(Object obj, Object value) | 设置obj中对应属性值 |
| getType() | 返回属性声明类型的class对象 |
| getGenericType() | 返回属性声明类型的Type对象 |
| getAnnotation(Class |
获取变量\属性上面的注解 |
| isAnnotationPresent(Class |
判断是否对应的注解 |
例:设置一个实例中的某个对象/属性的值:
Class c2 = Class.forName("com.test.bean.Xxx");
Class[] p = {String.class, Integer.class, Integer.class, String.class};
Constructor constructor1 = c2.getConstructor(p);
Object o1 = constructor1.newInstance("码云", 1, 57, "资本家");
Method类
Method代表类的方法。
| 方法 | 用途 |
|---|---|
| invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
| getParameterTypes() | 获取方法所有的参数类型 |
| getReturnType() | 获取方法返回对象的类型 |
| getParameterTypes() | 返回方法中参数对象的Class类型 |
| getGenericParameterTypes() | 返回方法中参数对象的Type类型 |
| getAnnotation(Class |
获取方法上面的注解 |
| isAnnotationPresent(Class |
判断是否对应的注解 |
例:使用一个 类的方法类 来 调用某个类的实例中的方法:
method1.setAccessible(true);
其中Class、和Constructor都拥有一个newInstance方法,都可以用来实例化对象。但是:
Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数; 如果该类的无参构造已经被私有化则会抛出IllegalAccessException异常
Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。
大部分时间,虽然使用Class.newInstance方法创建反射实例非常方便,但是为了容错性考虑,推荐使用Constractor.newInstance方法。
详解技术
上面只是简单的了解和应用了下Java反射的能力,要进一步进行应用,就得学习一些详细的内容。
一.泛型
1、声明一个需要被参数化(parameterizable)的类/接口。
2、使用一个参数化类。
当你声明一个类或者接口的时候你可以指明这个类或接口可以被参数化,java.util.List接口就是典型的例子。你可以运用泛型机制创建一个标明存储的是String类型list,这样比你创建一个Object的list要更好,new ArrayList。
你不能在运行期获知一个被参数化的类型的具体参数类型是什么,但是你可以在用到这个被参数化类型的方法以及变量中找到他们,换句话说就是获知他们具体的参数化类型,所以看看Java-Reflection中泛型的使用问题。
方法返回的泛型类型
如果你获得了java.lang.reflect.Method对象,那么你就可以获取到这个方法的返回类型信息。如果方法返回一个带有泛型类那么你就可以获得这个泛型类的具体参数化类型,而不是仅仅只是一个类。
比如这儿定义了一个类这个类:
Method method = MyClass.class.getMethod("getStringList", null);
// 返回方法返回对象类型
Type returnType = method.getGenericReturnType();
// 比较是否为参数化类型
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
// 打印结果:"typeArgClass = java.lang.String"
}
}
其中的ParameterizedType 是Type的子类,为参数化类型,就是带有类型参数的类型,即常说的泛型,如:List
而getActualTypeArguments()方法为返回表示此类型【实际类型参数】的 Type 对象的数组,也就是其中的泛型对象。
方法参数中的泛型类型
当然同样可以通过反射来获取方法参数的泛型类型:
比如将其上面的方法修改一些:
method = Myclass.class.getMethod("setStringList", List.class);
// 返回方法参数类型
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
其实和上方返回泛型获取相似,只是方法变了。
类中变量泛型类型
上面都是说方法中的泛型,说说类上的变量、属性的泛型:
一个定义了一个名为stringList的成员变量的类。
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
二.注解
注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用Java反射机制进行处理。下面是一个类注解的例子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
public String name();
public String value();
}
其中简单解析一下这个注解上面内容:
@Retention(RetentionPolicy.RUNTIME)表示这个注解存在于JAVA虚拟机运行期,可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,那么这样反射就无法获取它的信息。
@Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。
获取类上的注解
通过反射可以获取类上的注解信息:
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
通过反射可以获取方法参数上的注解信息:
int``[] intArray = (``int``[]) Array.newInstance(``int``.``class``, ``3``);
Array.newInstance()方法的第一个参数表示了我们要创建一个什么类型的数组。第二个参数表示了这个数组的空间是多大。
通过Java反射机制同样可以访问数组中的元素。具体可以使用Array.get(…)和Array.set(…)方法来访问。
Class intArray = Class.forName(``"[I"``);
上面便是获取int数组的Class对象。在JVM中字母I代表int类型,左边的‘[’代表我想要的是一个int类型的数组,这个规则同样适用于其他的原生数据类型。
而对于普通对象类型的数组有一点细微的不同,比如说字符串数组:
// 直接获取的化,这样都会抛出ClassNotFoundException
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");
不通过反射的话你可以这样来获取数组的Class对象:
public Class getClass(String className){
if("int" .equals(className)) return int .class;
if("long".equals(className)) return long.class;
...
return Class.forName(className);
}
...
Class theClass = getClass(theClassName);
这是一个特别的方式来获取指定类型的指定数组的Class对象。无需使用类名或其他方式来获取这个Class对象。
高级技术
Java的反射不仅仅只是简单的进行类的操作和模拟,一些动态技术也是属于Java反射实现的。下面来进行讲解更高级、复杂的技术点。
一.动态类的加载和重载
Java允许你在JVM(JAVA虚拟机)中运行期动态加载和重载类,但是这个功能并没有像人们希望的那么简单直接。
需要一题的是:JAVA加载类ClassLoader不属于Java反射API,而Java动态类加载特性是Java反射机制的一部分而不是Java核心平台的一部分。
了解动态类的加载和重载,之前我们需要先了解一些相关内容:
前置内容
1.类加载器
所有Java应用中的类都是被java.lang.ClassLoader类的一系列子类加载的。
因此要想动态加载类的话也必须使用java.lang.ClassLoader的子类。
一个类一旦被加载时,这个类引用的所有类也同时会被加载。所以类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。
2.类加载顺序
在Java中类的加载是一个有序的顺序。当你新创建一个标准的Java类加载器时你必须提供它的父加载器。
当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果从父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。所以加载类的时候是优先从父加载器来加载,然后在考虑自己的加载器。
所以类加载器 加载类的顺序如下:
1、检查这个类是否已经被加载。
2、如果没有被加载,则首先调用父加载器加载。
3、如果父加载器不能加载这个类,则尝试加载这个类。
当然当你实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。
动态类的加载
动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的loadClass()方法。下面是个例子:
public class MyObject extends MyObjectSuperClass implements AnInterface2{
//... body of class ... override superclass methods
// or implement interface methods
}
设置一个自定义加载器,对loadClass方法进行重写,如果你想用来重载类的话你可能会设计很多加载器。并且你也不会像下面这样将需要加载的类的路径硬编码(hardcore)到你的代码中:
public static void main(String[] args) throws
ClassNotFoundException,
IllegalAccessException,
InstantiationException {
ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
Class myObjectClass = classLoader.loadClass("reflection.MyObject");
AnInterface2 object1 =
(AnInterface2) myObjectClass.newInstance();
MyObjectSuperClass object2 =
(MyObjectSuperClass) myObjectClass.newInstance();
//重载类需要创建一个新的类加载器
classLoader = new MyClassLoader(parentClassLoader);
myObjectClass = classLoader.loadClass("reflection.MyObject");
object1 = (AnInterface2) myObjectClass.newInstance();
object2 = (MyObjectSuperClass) myObjectClass.newInstance();
}
二.动态代理
静态代理简介
在讲述动态代理前,我们先聊聊“静态代理”。
静态代理其实是Java中的一种设计模式,利用接口和实现类来实现进行扩展使用。
使用场景,打个比方:
某个卖票场景有两种模式,一种为线下,一种为线上,它们都有实现了一个售票接口,里面一种功能,“售票”。
大家都知道线上的肯定会比线下的贵(ps:不然房租不要钱的吗?零元购?)
所以在其他思路下,我们或许会单独进行重写“售票"功能,让其价格更改。
但在静态代理模式下,我们只需要实现该接口后,将起线上对象注入,然后再通过重写中调用线上对象然后,再额外算入XXX税等。
描述所得代码:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
它接受三个参数:代理类,对象真实方法,传递参数。这个方法的效果和Method中的invoke类似。返回的Object即方法返回内容。
其中第一个参数,大部分时间动态代理不会使用。
所以要实现一个动态代理,就需要创建一个动态代理类的调用处理程序。而每一个动态代理类的调用处理程序都必须实现这个InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的 动态代理类调用处理程序 中。
当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,从而实现动态代理类。
创建一个自定义动态代理的调用处理程序类,例如:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1、loader:类加载器(ClassLoader),用来加载动态代理类。
2、interfaces:需要代理的类 的实现的接口。
3、h:一个InvocationHandler对象,将其代理方法分派到的调用处理程序。
如下例子:
public class RealSubject implements Subject{
@Override
public String SayHello(String name) {
return "hello " + name;
}
@Override
public String toString() {
return "RealSubject{}";
}
}
我们创建一个RealSubject的动态代理:
System.out.println(realSubjectProxy.SayHello("我是一个代理类"));
结果输出:
System.out.println(realSubjectProxy.getClass().getName());
结果输出:
/**
* @description: TODO
* @author: Zhaotianyi
* @time: 2021/10/19 14:44
*/
public class WorkHandler implements InvocationHandler {
// 需要代理的类
private Object object;
/**
* 生成绑定后的动态代理类
*
* @param object 需要代理的类
* @return Object 动态代理类
*/
public Object bind(Object object) {
this.object = object;
return Proxy.newProxyInstance(this.object.getClass().getClassLoader(),
this.object.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnObject = null;
...
returnObject = method.invoke(object, args);
...
return returnObject;
}
}
在需要动态代理时,我们只需要创建WorkHandler并调用bind方法即可快速创建一个动态代理类。
更多扩展选择
JAVA自带的反射java.lang.reflect包实现的动态代理,其目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。所以这样的动态代理功能是比较单一的,目前第三方开源的CGLIB的代理则更为强大,被广泛应用于大型第三方框架中,如SpringAOP、Mybatis等。
链接:https://www.jianshu.com/p/8679b15fc8b1