摘要:相比硬编码,反射要复杂的多,但其给我们带来了更大的灵活性。实际上构造函数也是类的静态方法,因此使用关键字创建类的新对象也会被当做对类的静态引用,从而触发类加载器对类的加载。基础基础主要是为反射提供通用特性的接口或基类。
1. Java类型系统
获取Java类型系统,主要有两个方式:一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;另一种是反射(Reflect),它允许我们在程序运行时获取并使用类型信息。
假如有一个简单的继承体系,让我们看下在RTTI和Reflect不同情况下如何获取类型信息。
Animal为接口,定义getType以返回不同动物的类型,Cat、Dog、Elephant等为具体实现类,均实现getType接口。一般情况下,我们会创建一个具体的对象(Cat,Dog,Elephant等),把它向上转型为Animal,并在程序后面直接使用Animal引用。
具体样例代码如下:
/** * 动物 */ public interface Animal { /** * 获取动物类型 * @return */ String getType(); } /** * 动物具体子类 猫 */ public class Cat implements Animal{ @Override public String getType() { return "猫"; } } /** * 动物具体子类 狗 */ public class Dog implements Animal{ @Override public String getType() { return "狗"; } } /** * 动物具体实现 大象 */ public class Elephant implements Animal{ @Override public String getType() { return "大象"; } }
让我们看下相同的功能通过硬编码与反射两个机制如何实现。
1.1. 硬编码RTTI假定在编译期,已经知道了所有的类型信息。在编码时,可以直接使用具体的类型信息,这是我们最常见的类型用法。
在编译期,编译器通过容器、泛型保障类型系统的完整性;在运行时,由类型转换操作来确保这一点。
硬编码样例如下:
public static void main(String... args){ Listanimals = createAnimals(); for (Animal animal : animals){ System.out.println(animal.getType()); } } /** * RTTI假定我们在编译时已经知道了所有的类型 * @return */ private static List createAnimals() { List animals = new ArrayList<>(); animals.add(new Cat()); // 已知类型Cat animals.add(new Elephant()); // 已知类型Elephant animals.add(new Dog()); // 已知类型 Dog return animals; }
在这个例子中,我们把Cat、Elephant、Dog等向上转型为Animal并存放于List
Reflect允许我们在运行时获取并使用类型信息,它主要用于在编译阶段无法获得所有的类型信息的场景,如各类框架。
反射样例如下:
private static final String[] ANIMAL_TYPES = new String[]{ "com.example.reflectdemo.base.Cat", "com.example.reflectdemo.base.Elephant", "com.example.reflectdemo.base.Dog" }; public static void main(String... args){ List
反射,可以通过一组特殊的API,在运行时,动态执行所有Java硬编码完成的功能(如对象创建、方法调用等)。
相比硬编码,Java反射API要复杂的多,但其给我们带来了更大的灵活性。
2. Class对象要理解RTTI在Java中的工作原理,首先需要知道类型信息在Java中是如何表示的。这个工作是由称为Class对象的特殊对象完成的,它包含了与类相关的所有信息。Java使用Class对象来执行RTTI。
类是程序的一部分,每个类都会有一个Class对象。每当编写并编译一个新类(动态代理、CGLIB、运行时编译都能创建新类),就会产生一个Class对象,为了生成这个类的对象,运行这个程序的JVM将使用称为“类加载器”的子系统。
2.1. Class Loader类加载器子系统,是JVM体系重要的一环,主要完成将class二进制文件加载到JVM中,并将其转换为Class对象的过程。
类加载器子系统实际上是一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是可信类,包括Java API类,他们通常是从本地加载。在这条链中,通常不需要添加额外的类加载器,但是如果有特殊需求,可以挂载新的类加载器(比如Web容器)。
所有的类都是在第一次使用时,动态加载到JVM中的,当程序创建第一次对类的静态成员引用时,就会加载这个类。实际上构造函数也是类的静态方法,因此使用new关键字创建类的新对象也会被当做对类的静态引用,从而触发类加载器对类的加载。
Java程序在它开始运行之前并非被全部加载,各个部分是在需要时按需加载的。类加载器在加载类之前,首先检查这个类的Class是否已经加载,如果尚未加载,加载器会按照类名查找class文件,并对字节码进行有效性校验,一旦Class对象被载入内存,它就用来创建这个类的所有对象。
static初始化块在类加载时调用,因此可以用于观察类在什么时候进行加载,样例如下:
static class C1{ static { System.out.println("C1"); } } static class C2{ static { System.out.println("C2"); } } static class C3{ static { System.out.println("C3"); } } public static void main(String... args) throws Exception{ System.out.println("new start"); // 构造函数为类的静态引用,触发类型加载 new C1(); new C1(); System.out.println("new end"); System.out.println(); System.out.println("Class.forName start"); // Class.forName为Class上的静态函数,用于强制加载Class Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2"); Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2"); System.out.println("Class.forName end"); System.out.println(); System.out.println("C3.class start"); // Class引用,会触发Class加载,但是不会触发初始化 Class c1 = C3.class; Class c2 = C3.class; System.out.println("C3.class end"); System.out.println(); System.out.println("c1.newInstance start"); // 调用class上的方法,触发初始化逻辑 c1.newInstance(); System.out.println("c1.newInstance end"); }
输出结果为:
new start C1 new end Class.forName start C2 Class.forName end C3.class start C3.class end c1.newInstance start C3 c1.newInstance end
看结果,C3.class的调用不会自动的初始化该Class对象(调用static块)。为了使用Class而做的准备工作主要包括三个步骤:
加载,这个是由类加载器执行。该步骤将查找字节码文件,并根据字节码创建一个Class对象。
链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,如果必要的话,将解析这个类创建的对其他类的引用。
初始化,如果该类有超类,则对其进行初始化,执行静态初始化器和静态初始化块。初始化被延时到对静态方法或非常数静态域进行首次访问时才执行。
2.2. Class 实例获取Class对象作为Java类型体系的入口,如何获取实例成为第一个要解决的问题。
Class对象的获取主要有以下几种途径:
ClassName.class,获取Class对象最简单最安全的方法,其在编译时会受到编译检测,但上例中已经证实,该方法不会触发初始化逻辑。
Class.forName,这是反射机制最常用的方法之一,可以在不知具体类型时,通过一个字符串加载所对应的Class对象。
object.getClass,这也是比较常用的方式之一,通过一个对象获取生成该对象的Class实例。
对于基本数据类型对于的包装器类,还提供了一个TYPE字段,指向对应的基本类型的Class对象。
基本类型 | TYPE类型 |
---|---|
boolean.class | Boolean.TYPE |
char.class | Char.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
Class对象存储了一个class的所有类型信息,当获取到Class对象后,便能通过API获取到所有信息。
在进入Class类型信息之前,需要简单的了解下几个反射的基类,以便更好的理解反射实现体系。
2.3.1 ClassAPI 基础Class API基础主要是为反射API提供通用特性的接口或基类。由于其通用性,现统一介绍,在具体的API中将对其进行忽略。2.3.1.1 AnnotatedElement
AnnotatedElement为Java1.5新增接口,该接口代表程序中可以接受注解的程序元素,并提供统一的Annotation访问方式,赋予API通过反射获取Annotation的能力,当一个Annotation类型被定义为运行时后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement接口是所有注解元素(Class、Method、Field、Package和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的下列方法来访问Annotation信息:
方法 | 含义 |
---|---|
返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null | |
Annotation[] getAnnotations() | 返回该程序元素上存在的所有注解 |
boolean is AnnotationPresent(Class annotationClass) | 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false |
Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。 |
AnnotatedElement子类涵盖所有可以出现Annotation的地方,其中包括:
Constructor 构造函数
Method 方法
Class 类型
Field 字段
Package 包
Parameter 参数
AnnotatedParameterizedType 泛型
AnnotatedTypeVariable 变量
AnnotatedArrayType 数组类型
AnnotatedWildcardType
样例如下:
public class AnnotatedElementTest { public static void main(String... args){ System.out.println("getAnnotations:"); for (Annotation annotation : A.class.getAnnotations()){ System.out.println(annotation); } System.out.println(); System.out.println("getAnnotation:" + A.class.getAnnotation(TestAnn1.class)); System.out.println(); System.out.println("isAnnotationPresent:" + A.class.isAnnotationPresent(TestAnn1.class)); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnn1{ } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnn2{ } @TestAnn1 @TestAnn2 public class A{ } }
输出结果如下:
getAnnotations: @com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1() @com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn2() getAnnotation:@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1() isAnnotationPresent:true2.3.1.2. Member
Member用于标记反射中简单元素。
所涉及方法如下:
方法 | 含义 |
---|---|
getDeclaringClass | 元素所在类 |
getName | 元素名称 |
getModifiers | 元素修饰 |
isSynthetic | 是否为Synthetic,synthetic是由编译器引入的字段、方法、类或其他结构,主要用于JVM内部使用。 |
其子类主要包括:
Class 类型
Field 字段
Method 方法
Constructor 构造函数
2.3.1.3. AccessibleObjectAccessibleObject可访问对象,其对元素的可见性进行统一封装。同时实现AnnotatedElement接口,提供对Annotation元素的访问。
所涉及方法如下:
方法 | 含义 |
---|---|
isAccessible | 是否可访问 |
setAccessible | 重新访问性 |
其中AccessibleObject所涉及的子类主要包括:
Field 字段
Constructor 构造函数
Method 方法
AccessibleObject 对可见性提供了强大的支持,使我们能够通过反射扩展访问限制,甚至可以对private成员进行访问。
样例代码如下:
public class TestBean { private String id; public String getId() { return id; } private void setId(String id) { this.id = id; } } public class AccessibleObjectBase { public static void main(String... args) throws Exception{ TestBean testBean = new TestBean(); // private方法, 不能直接调用 Method setId = TestBean.class.getDeclaredMethod("setId", String.class); System.out.println("setId:" + setId.isAccessible()); try { setId.invoke(testBean, "111"); }catch (Exception e){ System.out.println("private不能直接调用"); } setId.setAccessible(true); System.out.println("设置可访问:" + setId.isAccessible()); setId.invoke(testBean, "111"); System.out.println("设置可访问后,可以绕过private限制,进行调用,结果为:" + testBean.getId()); } }
输出结果如下:
setId:false private不能直接调用 设置可访问:true 设置可访问后,可以绕过private限制,进行调用,结果为:1112.3.1.4. Executable
Executable表示可执行元素的一种封装,可以获取方法签名相关信息。
所涉及方法如下:
方法 | 含义 |
---|---|
getName | 获取名称 |
getModifiers | 获取修饰符 |
getTypeParameters | 获取类型参数(泛型) |
getParameterTypes | 获取参数列表 |
getParameterCount | 获取参数数量 |
getGenericParameterTypes | 获取参数类型 |
getExceptionTypes | 获取异常列表 |
getGenericExceptionTypes | 获取异常列表 |
锁涉及的子类主要有:
Constructor 构造函数
Method 方法
样例代码如下:
public class TestBean { private String id; publicTestBean(String id) throws IllegalArgumentException, NotImplementedException { this.id = id; } public String getId() { return id; } private void setId(String id) { this.id = id; } } public class ExecutableTest { public static void main(String... args) throws Exception{ for (Constructor constructor : TestBean.class.getConstructors()){ System.out.println("getName: " + constructor.getName()); System.out.println(); System.out.println("getModifiers: " + Modifier.toString(constructor.getModifiers())); System.out.println(); System.out.println("getTypeParameters:"); for (TypeVariable t : constructor.getTypeParameters()){ System.out.println("type var:" + t.getName()); } System.out.println(); System.out.println("getParameterCount:" + constructor.getParameterCount()); System.out.println(); System.out.println("getParameterTypes:"); for (Class cls : constructor.getParameterTypes()){ System.out.println(cls.getName()); } System.out.println(); System.out.println("getExceptionTypes:"); for (Class cls : constructor.getExceptionTypes()){ System.out.println(cls.getName()); } } } }
输出结果为:
getName: com.example.reflectdemo.reflectbase.TestBean getModifiers: public getTypeParameters: type var:T type var:R getParameterCount:1 getParameterTypes: java.lang.String getExceptionTypes: java.lang.IllegalArgumentException sun.reflect.generics.reflectiveObjects.NotImplementedException2.3.1.5. 方法命名规则
整个反射机制存在着通用的命名规则,了解这些规则,可以大大减少理解方法的阻力。
getXXX和getDeclaredXXX, 两者主要区别在于获取元素的可见性不同,一般情况下getXXX返回public类型的元素,而getDeclaredXXX获取所有的元素,其中包括private、protected、public和package。
2.3.2. 类型信息Class自身信息包括类名、包名、父类以及实现的接口等。
Class类实现AnnotatedElement接口,以提供对注解的支持。除此以外,涉及方法如下:
方法 | 含义 |
---|---|
getName | 获取类名 |
getCanonicalName | 得到目标类的全名(包名+类名) |
getSimpleName | 等同于getCanonicalName |
getTypeParameters | 获取类型参数(泛型) |
getSuperclass | 获取父类 |
getPackage | 获取包信息 |
getInterfaces | 获取实现接口 |
getModifiers | 获取修饰符 |
isAnonymousClass | 是否匿名类 |
isLocalClass | 是否局部类 |
isMemberClass | 是否成员类 |
isEnum | 是否枚举 |
isInterface | 是否是接口 |
isArray | 是否是数组 |
getComponentType | 获取数组元素类型 |
isPrimitive | 是否是基本类型 |
isAnnotation | 是否是注解 |
getEnumConstants | 获取枚举所有类型 |
getClasses | 获取定义在该类中的public类型 |
getDeclaredClasses | 获取定义在该类中的类型 |
实例如下:
class Baseimplements Callable { @Override public T call() throws Exception { return null; } } public final class BaseClassInfo extends Base implements Runnable, Serializable { @Override public void run() { } public static void main(String... args){ Class cls = BaseClassInfo.class; System.out.println("getName:" + cls.getName()); System.out.println(); System.out.println("getCanonicalName:" + cls.getCanonicalName()); System.out.println(); System.out.println("getSimpleName:" + cls.getSimpleName()); System.out.println(); System.out.println("getSuperclass:" + cls.getSuperclass()); System.out.println(); System.out.println("getPackage:" + cls.getPackage()); System.out.println(); for (Class c : cls.getInterfaces()){ System.out.println("interface : " + c.getSimpleName()); } System.out.println(); for (TypeVariable > typeVariable : cls.getTypeParameters()){ System.out.println("type var : " + typeVariable.getTypeName()); } System.out.println(); System.out.println("getModifiers:" + Modifier.toString(cls.getModifiers())); } }
输出结果为:
getName:com.example.reflectdemo.classdetail.BaseClassInfo getCanonicalName:com.example.reflectdemo.classdetail.BaseClassInfo getSimpleName:BaseClassInfo getSuperclass:class com.example.reflectdemo.classdetail.Base getPackage:package com.example.reflectdemo.classdetail interface : Runnable interface : Serializable type var : T type var : R getModifiers:public final
Class类型判断,实例如下:
public class ClassTypeTest { public static void main(String... args){ Runnable runnable = new Runnable() { @Override public void run() { printClassType(getClass()); } }; System.out.println("匿名内部类"); runnable.run(); class M implements Runnable{ @Override public void run() { printClassType(getClass()); } } System.out.println("方法内部类"); new M().run(); System.out.println("内部类"); new ClassTypeTest().new T().run(); System.out.println("静态内部类"); new S().run(); System.out.println("枚举"); printClassType(EnumTest.class); System.out.println("接口"); printClassType(Runnable.class); System.out.println("数组"); printClassType(int[].class); System.out.println("int"); printClassType(int.class); System.out.println("注解"); printClassType(AnnTest.class); } class T implements Runnable{ @Override public void run() { printClassType(getClass()); } } static class S implements Runnable{ @Override public void run() { printClassType(getClass()); } } enum EnumTest{ A, B, C } @interface AnnTest{ } private static void printClassType(Class cls){ System.out.println("Class:" + cls.getName()); System.out.println("isAnonymousClass:" + cls.isAnonymousClass()); System.out.println("isLocalClass:" + cls.isLocalClass()); System.out.println("isMemberClass:" + cls.isMemberClass()); System.out.println("isEnum:" + cls.isEnum()); System.out.println("isInterface:" + cls.isInterface()); System.out.println("isArray:" + cls.isArray()); System.out.println("isPrimitive:" + cls.isPrimitive()); System.out.println("isAnnotation:" + cls.isAnnotation()); if (cls.isEnum()){ System.out.println("getEnumConstants:"); for (Object o : cls.getEnumConstants()){ System.out.println(o); } } if (cls.isArray()){ System.out.println("getComponentType:" + cls.getComponentType()); } System.out.println(); } }
输出结果如下:
匿名内部类 Class:com.example.reflectdemo.classdetail.ClassTypeTest$1 isAnonymousClass:true isLocalClass:false isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 方法内部类 Class:com.example.reflectdemo.classdetail.ClassTypeTest$1M isAnonymousClass:false isLocalClass:true isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 内部类 Class:com.example.reflectdemo.classdetail.ClassTypeTest$T isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 静态内部类 Class:com.example.reflectdemo.classdetail.ClassTypeTest$S isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 枚举 Class:com.example.reflectdemo.classdetail.ClassTypeTest$EnumTest isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:true isInterface:false isArray:false isPrimitive:false isAnnotation:false getEnumConstants: A B C 接口 Class:java.lang.Runnable isAnonymousClass:false isLocalClass:false isMemberClass:false isEnum:false isInterface:true isArray:false isPrimitive:false isAnnotation:false 数组 Class:[I isAnonymousClass:false isLocalClass:false isMemberClass:false isEnum:false isInterface:false isArray:true isPrimitive:false isAnnotation:false getComponentType:int int Class:int isAnonymousClass:false isLocalClass:false isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:true isAnnotation:false 注解 Class:com.example.reflectdemo.classdetail.ClassTypeTest$AnnTest isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:false isInterface:true isArray:false isPrimitive:false isAnnotation:true
内部类型样例如下:
public class InnerClassTest { public static void main(String... args){ System.out.println("getClasses"); for (Class cls : InnerClassTest.class.getClasses()){ System.out.println(cls.getName()); } } public interface I{ } public class A implements I{ } public class B implements I{ } }
输出结果如下:
getClasses com.example.reflectdemo.classdetail.InnerClassTest$B com.example.reflectdemo.classdetail.InnerClassTest$A com.example.reflectdemo.classdetail.InnerClassTest$I2.3.3. 对象实例化
对象实例化,主要通过Constructor实例完成,首先通过相关方法获取Constructor对象,然后进行实例化操作。
所涉及的方法如下:
方法 | 含义 |
---|---|
newInstance | 使用默认构造函数实例化对象 |
getConstructors | 获取public构造函数 |
getConstructor(Class>... parameterTypes) | 获取特定public构造函数 |
getDeclaredConstructors | 获取所有的构造函数 |
getDeclaredConstructor | 获取特定构造函数 |
实例化涉及的核心类为Constructor,Constructor继承自Executable,拥有AnnotatedElement、AccessibleObject、Executable等相关功能,其核心方法如下:
方法 | 含义 |
---|---|
newInstance | 调用构造函数,实例化对象 |
样例如下:
public class TestBean { private final Integer id; private final String name; publicTestBean(Integer id, String name) throws IllegalArgumentException, NotImplementedException { this.id = id; this.name = name; } @Override public String toString() { return "TestBean{" + "id=" + id + ", name="" + name + """ + "}"; } } public class ConstructorTest { public static void main(String... args) throws Exception{ for (Constructor constructor : TestBean.class.getConstructors()){ TestBean bean = (TestBean) constructor.newInstance(1, "Test"); System.out.println("newInstance:" + bean); } } }
输出结果为:
newInstance:TestBean{id=1, name="Test"}2.3.4. 属性信息
对象属性是类型中最主要的信息之一,主要通过Field表示,首先通过相关方法获取Field实例,然后进行属性值操作。
所涉及的方法如下:
方法 | 含义 |
---|---|
getFields | 获取public字段 |
getField(String name) | 获取特定public字段 |
getDeclaredFields | 获取所有的的属性 |
getDeclaredField | 获取特定字段 |
Field继承自AccessibleObject实现Member接口,拥有AccessibleObject、AnnotatedElement、Member相关功能,其核心方法如下:
方法 | 含义 |
---|---|
isEnumConstant | 是否枚举常量 |
getType | 获取类型 |
get | 获取属性值 |
getBoolean | 获取boolean值 |
getByte | 获取byte值 |
getChar | 获取chat值 |
getShort | 获取short值 |
getInt | 获取int值 |
getLong | 获取long值 |
getFloat | 获取float值 |
getDouble | 获取double值 |
set | 设置属性值 |
setBoolean | 设置boolean值 |
setByte | 设置byte值 |
setChar | 设置char值 |
setShort | 设置short值 |
setInt | 设置int值 |
setLong | 设置long值 |
setFloat | 设置float值 |
setDouble | 设置double值 |
实例如下:
public enum EnumTest { A } public class FieldBean { private EnumTest aEnum; private String aString; private boolean aBoolean; private byte aByte; private char aChar; private short aShort; private int anInt; private long aLong; private float aFloat; private double aDouble; } public class FieldTest { public static void main(String... args) throws NoSuchFieldException, IllegalAccessException { FieldBean fieldBean = new FieldBean(); Field aEnum = getByName("aEnum"); Field aString = getByName("aString"); Field aBoolean = getByName("aBoolean"); Field aByte = getByName("aByte"); Field aChar = getByName("aChar"); Field aShort = getByName("aShort"); Field anInt = getByName("anInt"); Field aLong = getByName("aLong"); Field aFloat = getByName("aFloat"); Field aDouble = getByName("aDouble"); aEnum.set(fieldBean, EnumTest.A); System.out.println("isEnumConstant: " + aEnum.isEnumConstant()); System.out.println("set and get enum : " + aEnum.get(fieldBean)); aString.set(fieldBean, "Test"); System.out.println("set and get String : " + aString.get(fieldBean)); aBoolean.setBoolean(fieldBean, true); System.out.println("set and get Boolean : " + aBoolean.getBoolean(fieldBean)); aByte.setByte(fieldBean, (byte) 1); System.out.println("set and get Byte : " + aByte.getByte(fieldBean)); aChar.setChar(fieldBean, "a"); System.out.println("set and get Char : " + aChar.getChar(fieldBean)); aShort.setShort(fieldBean, (short) 1); System.out.println("set and get Short : " + aShort.getShort(fieldBean)); anInt.setInt(fieldBean, 1); System.out.println("set and get Int : " + anInt.getInt(fieldBean)); aLong.setLong(fieldBean, 1L); System.out.println("set and get Long : " + aLong.getLong(fieldBean)); aFloat.setFloat(fieldBean, 1f); System.out.println("set and get Float : " + aLong.getFloat(fieldBean)); aDouble.setDouble(fieldBean, 1.1); System.out.println("set and get Double : " + aLong.getDouble(fieldBean)); } private static Field getByName(String name) throws NoSuchFieldException { Field field = FieldBean.class.getDeclaredField(name); field.setAccessible(true); return field; } }2.3.5. 方法信息
类型中的方法通过Method表示,首先通过相关方法获取Method实现,然后通过反射执行方法。
所涉及的方法如下:
方法 | 含义 |
---|---|
getMethods | 获取public方法 |
getMethod(String name, Class>... parameterTypes) | 获取特定public方法 |
getDeclaredMethods | 获取所有方法 |
getDeclaredMethod | 获取特定方法 |
Method继承自Executable,拥有AnnotatedElement、AccessibleObject、Executable等相关功能,其核心方法如下:
方法 | 含义 |
---|---|
getReturnType | 获取方法返回类型 |
invoke | 调用方法 |
isBridge | 是否为桥接方法。桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。我们可以通过Method.isBridge()方法来判断一个方法是否是桥接方法。 |
isDefault | 是否为默认方法 |
实例如下:
public interface SayHi { String get(); default void hi(){ System.out.println("Hi " + get()); } } public class MethodBean implements Function, SayHi { private final String name; public MethodBean(String name) { this.name = name; } @Override public String get() { return "Hi " + name; } @Override public String apply(String s) { return s + name; } } public class MethodTest { public static void main(String... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method strMethod = MethodBean.class.getDeclaredMethod("apply", String.class); Method objMethod = MethodBean.class.getDeclaredMethod("apply", Object.class); Method hiMethod = SayHi.class.getDeclaredMethod("hi"); MethodBean methodBean = new MethodBean("张三"); System.out.println("Return Type:"); System.out.println("getMethod(String):" + strMethod.getReturnType()); System.out.println("getMethod(Object):" + objMethod.getReturnType()); System.out.println("hi():" + hiMethod.getReturnType()); System.out.println(); System.out.println("isBridge:"); System.out.println("getMethod(String):" + strMethod.isBridge()); System.out.println("getMethod(Object):" + objMethod.isBridge()); System.out.println("hi():" + hiMethod.isBridge()); System.out.println(); System.out.println("isDefault:"); System.out.println("getMethod(String):" + strMethod.isDefault()); System.out.println("getMethod(Object):" + objMethod.isDefault()); System.out.println("hi():" + hiMethod.isDefault()); System.out.println(); System.out.println("invoke:"); System.out.println("invoke(String):" + strMethod.invoke(methodBean, "Test")); System.out.println("invoke(Object):" + objMethod.invoke(methodBean, "Test")); System.out.println("hi():" + hiMethod.invoke(methodBean)); } }
输出结果:
Return Type: getMethod(String):class java.lang.String getMethod(Object):class java.lang.Object hi():void isBridge: getMethod(String):false getMethod(Object):true hi():false isDefault: getMethod(String):false getMethod(Object):false hi():true invoke: invoke(String):Test张三 invoke(Object):Test张三 Hi Hi 张三 hi():null2.3.6. 其他
除上述核心方法外,Class对象提供了一些使用方法。
所涉及方法如下:
方法 | 含义 |
---|---|
isInstance | 判断某对象是否是该类的实例 |
isAssignableFrom | 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false。 |
getClassLoader | 获取加载当前类的ClassLoader |
getResourceAsStream | 根据该ClassLoader加载资源 |
getResource | 根据该ClassLoader加载资源 |
public class Task implements Runnable{ @Override public void run() { } } public class OtherTest { public static void main(String...args){ Task task = new Task(); System.out.println("Runnable isInstance Task:" + Runnable.class.isInstance(task)); System.out.println("Task isInstance Task:" + Task.class.isInstance(task)); System.out.println("Task isAssignableFrom Task:" + Task.class.isAssignableFrom(Task.class)); System.out.println("Runnable isAssignableFrom Task :" + Runnable.class.isAssignableFrom(Task.class)); } }
输出结果:
Runnable isInstance Task:true Task isInstance Task:true Task isAssignableFrom Task:true Runnable isAssignableFrom Task :true3. 动态代理
代理是基本的设计模式之一,它是我们为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常与“实际”对象通信,因此代理通常充当中间人的角色。
例如,我们已有一个Handler接口,和一个实现类HandlerImpl,现需要对其进行性能统计,使用代理模式,代码如下:
/** * handler接口 */ public interface Handler { /** * 数据处理 * @param data */ void handle(String data); } /** * Handler 实现 */ public class HandlerImpl implements Handler{ @Override public void handle(String data) { try { TimeUnit.MILLISECONDS.sleep(100); System.out.println(data); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * Handler代理
* 实现Handler接口,记录耗时情况,并将请求发送给目标对象 */ public class HandlerProxy implements Handler{ private final Handler handler; public HandlerProxy(Handler handler) { this.handler = handler; } @Override public void handle(String data) { long start = System.currentTimeMillis(); this.handler.handle(data); long end = System.currentTimeMillis(); System.out.println("cost " + (end - start) + " ms"); } } public static void main(String... args){ Handler handler = new HandlerImpl(); Handler proxy = new HandlerProxy(handler); proxy.handle("Test"); }
采用代理模式,比较优雅的解决了该问题,但如果Handler接口存在多个方法,并且需要对所有方法进行性能监控,那HandlerProxy的复杂性将会提高。
Java动态代理比代理更进一步,因为它可以动态的创建代理并动态的处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上。
InvocationHandler 是由动态代理处理器实现的接口,对代理对象的方法调用,会路由到该处理器上进行统一处理。
其只有一个核心方法:
/** * proxy : 代理对象 * method : 调用方法 * args : 调用方法参数 **/ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;3.2. Proxy
Proxy 用于生成代理对象。
其核心方法为:
/** * 获取代理类3.3. demo
* loader : 类加载器 * interfaces: 类实现的接口 * */ Class> getProxyClass(ClassLoader loader, Class>... interfaces); /* * 生成代理对象
* loader : 类加载器 * interfaces : 类实现的接口 * h : 动态代理回调 */ Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h); /* * 判断是否为代理类
* * cl : 待判断类 */ public static boolean isProxyClass(Class> cl); /* * 获取代理对象的InvocationHandler
* * proxy : 代理对象 */ InvocationHandler getInvocationHandler(Object proxy);
对于之前的性能监控,使用Java动态代理怎么实现?
/** * 定义代理方法回调处理器 */ public class CostInvocationHandler implements InvocationHandler { // 目标对象 private final Object target; public CostInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("call method " + method + " ,args " + args); long start = System.currentTimeMillis(); try { // 将请求转发给目标对象 return method.invoke(this.target, args); }finally { long end = System.currentTimeMillis(); System.out.println("cost " + (end - start) + "ms"); } } } public static void main(String... args){ Handler handler = new HandlerImpl(); CostInvocationHandler invocationHandler = new CostInvocationHandler(handler); Class cls = Proxy.getProxyClass(DHandlerMain.class.getClassLoader(), Handler.class); Handler proxy = (Handler) Proxy.newProxyInstance(DHandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler); System.out.println("invoke method"); proxy.handle("Test"); System.out.println("isProxyClass: " + Proxy.isProxyClass(cls)); System.out.println("getInvocationHandler: " + (invocationHandler == Proxy.getInvocationHandler(proxy))); }4. 基于SPI的Plugin
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现,它是一种动态替换发现的机制。
具体用法是在JAR包的"META-INF/services/"目录下建立一个文件,文件名是接口的全限定名,文件的内容可以有多行,每行都是该接口对应的具体实现类的全限定名。然后使用 ServiceLoader.load(Interface.class) 对插件进行加载。
假定,现有个场景,需要对消息进行处理,但消息处理器的实现需要放开,及可以动态的对处理器进行加载,当有新消息到达时,依次调用处理器对消息进行处理,让我们结合SPI和反射构造一个简单的Plugin系统。
首先我们需要一个插件接口和若干个实现类:
/** * 插件接口 */ public interface Handler { void handle(String msg); } /** * 实现1 */ public class Handler1 implements Handler{ @Override public void handle(String msg) { System.out.println("Handler1:" + msg); } } /** * 实现2 */ public class Handler2 implements Handler{ @Override public void handle(String msg) { System.out.println("Handler2:" + msg); } }
然后,我们添加SPI配置,及在META-INF/services/com.example.reflectdemo.plugin.Handler添加配置信息:
com.example.reflectdemo.plugin.Handler1 com.example.reflectdemo.plugin.Handler2
其次,我们实现DispatcherInvocationHandler类继承自InvocationHandler接口,将方法调用分发给目标对象。
/** * 分发处理器
* 将请求挨个转发给目标对象 */ public class DispatcherInvocationHandler implements InvocationHandler { // 目标对象集合 private final Listtargets; public DispatcherInvocationHandler(List targets) { this.targets = targets; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { for (Object target : targets){ // 将请求转发给目标对象 method.invoke(target, args); } return null; } }
实现主流程,通过SPI加装插件,将插件作为转发对象实例化DispatcherInvocationHandler,在通过Proxy构建动态代理对象,最后调用handle方法进行业务处理。
public static void main(String... args){ // 使用SPI加载插件 ServiceLoaderserviceLoader = ServiceLoader.load(Handler.class); List handlers = new ArrayList<>(); Iterator handlerIterator = serviceLoader.iterator(); while (handlerIterator.hasNext()){ Handler handler = handlerIterator.next(); handlers.add(handler); } // 将加载的插件组装成InvocationHandler,以进行分发处理 DispatcherInvocationHandler invocationHandler = new DispatcherInvocationHandler(handlers); // 生成代理对象 Handler proxy = (Handler) Proxy.newProxyInstance(HandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler); // 调用handle方法 proxy.handle("Test"); }
运行结果如下:
Handler1:Test Handler2:Test5. 总结
Java类型系统、反射、动态代理,作为Java的高级应用,大量用于各大框架中。对其的掌握有助于加深对框架的理解。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/76818.html
摘要:适配器模式将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。 1、常用设计模式 单例模式:懒汉式、饿汉式、双重校验锁、静态加载,内部类加载、枚举类加载。保证一个类仅有一个实例,并提供一个访问它的全局访问点。 代理模式:动态代理和静态代理,什么时候使用...
摘要:动态编程使用场景通过配置生成代码,减少重复编码,降低维护成本。动态生成字节码操作字节码的工具有,其中有两个比较流行的,一个是,一个是。 作者简介 传恒,一个喜欢摄影和旅游的软件工程师,先后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,现在转战 Java,目前负责物流策略组分流相关业务的开发。 什么是动态编程 动态编程是相对于静态编程而言的,平时我们讨论比较多的静态编程语言例如Java, 与动态...
阅读 3398·2021-10-11 11:06
阅读 2182·2019-08-29 11:10
阅读 1944·2019-08-26 18:18
阅读 3255·2019-08-26 13:34
阅读 1559·2019-08-23 16:45
阅读 1037·2019-08-23 16:29
阅读 2797·2019-08-23 13:11
阅读 3226·2019-08-23 12:58