⑦JAVA 反射
⑦JAVA 反射
学习核心
- 反射
- 什么是反射?反射的作用是什么?
- 动态代理有几种实现方式?有什么特点?
- JDK动态代理和CGLIB代理有什么区别?
学习资料
反射机制
反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
反射作用:通过反射机制可以在运行时访问Java对象的属性、方法、构造方法等
应用场景:动态代理、应用框架开发、注解、可扩展性功能等
反射机制的优点
- 可以让代码更加灵活,为各种框架提供了提供开箱即用的功能开发提供了便利
反射机制的缺点
主要关注安全和性能问题
- 性能开销:由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中避免
- 破坏封装性:反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题(例如泛型参数的安全检查发生在编译时,引入反射可以无视泛型参数的安全检查)
- 内部曝光:由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为
综上,反射机制的优点就是可以实现动态创建对象和编译,具备灵活性(在J2EE开发中可体现),运行期类型的判断、动态类加载、动态代理使用反射;缺点在于对程序的性能有影响(使用反射基本上是一种解释操作,告知JVM要做的事情,这类操作总是慢于只直接执行相同的操作)
1.反射基础案例
结合正常创建对象调用和基于反射构建方法调用进行分析
定义Apple实体
// Apple实体定义
class Apple{
private int price;
// 无参构造函数
public Apple(){}
public Apple(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
正常创建对象调用&基于反射调用
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 正常创建对象调用
Apple apple = new Apple(100);
System.out.println("apple price:" + apple.getPrice());
/**
* 使用反射调用
*/
// 加载指定类
Class clazz = Class.forName("com.noob.base.reflect.Apple");
// 加载指定类的方法
Method setPriceMethod = clazz.getMethod("setPrice",int.class);
Method getPriceMethod = clazz.getMethod("getPrice");
// 加载指定类的构造方法,并通过构造方法创建实例(如果重载了构造函数,则需显示指定构造函数,否则此处查找无参构造函数失败)
Constructor constructor = clazz.getConstructor();
Object appleObj = constructor.newInstance();
// 通过invoke实现方法调用
setPriceMethod.invoke(appleObj,100);
System.out.println("reflect apple price:" + getPriceMethod.invoke(appleObj));
// 通过指定构造函数构建对象
Constructor paramsConstructor = clazz.getConstructor(int.class);
Object paramsObj = paramsConstructor.newInstance(99);
System.out.println("reflect apple price:" + getPriceMethod.invoke(paramsObj));
}
}
// output
apple price:100
reflect apple price:100
reflect apple price:99
基于上述案例,最简单的反射调用,基于无参构造方法创建对象,其核心构建思路如下
- (1)获取类的Class对象实例
Class clazz = Class.forName("com.noob.base.reflect.Apple");
- (2)根据Class对象实例获取Constructor对象
Constructor constructor = clazz.getConstructor();
- (3)使用Construct对象的newInstance方法获取反射类对象
Object appleObj = constructor.newInstance();
- (4)方法调用:通过Class对象实例先获取到对应方法的Method对象
Method setPriceMethod = clazz.getMethod("setPrice",int.class);
Method getPriceMethod = clazz.getMethod("getPrice");
- (5)方法调用:然后借助invoke实现方法调用
// setPrice方法调用
setPriceMethod.invoke(appleObj,100);
// getPrice方法调用
getPriceMethod.invoke(appleObj)
此处需要注意,通过clazz.getConstructor()
不指定参数的时候默认是获取到类的无参构造方法,如果Apple类中重载了多个构造方法,则需要显式定义无参构造方法,否则在使用该方法额时候就会报找不到构造方法的异常。
相应的,如果根据方法名和指定参数类型获取到对应的方法Method,如果指定方法名或者方法参数类型不匹配则无法定位到对应的具体方法,也会抛出方法不存在的异常
2.反射常用API
java.lang.reflect包
Java 中的 java.lang.reflect
包提供了反射功能。java.lang.reflect
包中的类都没有 public
构造方法。
java.lang.reflect
包的核心接口和类如下:
Member
接口:反映关于单个成员(字段或方法)或构造函数的标识信息。Field
类:提供一个类的域的信息以及访问类的域的接口。Method
类:提供一个类的方法的信息以及访问类的方法的接口。Constructor
类:提供一个类的构造函数的信息以及访问类的构造函数的接口。Array
类:该类提供动态地生成和访问 JAVA 数组的方法。Modifier
类:提供了 static 方法和常量,对类和成员访问修饰符进行解码。Proxy
类:提供动态地生成代理类和类实例的静态方法
结合上述案例中构建反射对象和方法调用去理解
(1)获取反射中的Class对象
Java API中获取反射中的Class对象方式有3种
- Class.forName方法
- 类名.class方法
- Object.getClass方法
class ReflectAPI{
// 1.获取反射中的Class对象
public static void testGetReflectClass() throws ClassNotFoundException {
// 方式1:通过Class.forName("类全路径");获取
Class clz1 = Class.forName("com.noob.base.reflect.Apple");
System.out.println(clz1);
// 方式2:使用.class方法(适用于在编译前就知道操作的Class)
Class clz2 = Apple.class;
System.out.println(clz2);
// 方式3:使用类对象的getClass()方法
String str = new String("hello");
Class clz3 = str.getClass();
System.out.println(clz3);
}
public static void main(String[] args) throws ClassNotFoundException {
testGetReflectClass();
}
}
(2)通过反射创建类对象
// 2.通过反射创建类对象
public static void testGetClassObject() throws Exception {
// 方式1:通过Class对象的newInstance()方法
Class clz1 = Apple.class;
Apple apple = (Apple)clz1.newInstance();
// 方式2:通过Constructor对象的newInstance()方法
Class clz2 = Apple.class;
Constructor constructor = clz2.getConstructor();
Apple appleObj = (Apple)constructor.newInstance();
}
通过Class对象只能选择无参构造方法,而通过通过Constructor对象的newInstance可以指定使用带有参数的构造方法。
Constructor paramsConstructor = clazz.getConstructor(int.class);
Object paramsObj = paramsConstructor.newInstance(99);
(3)通过反射获取类属性、方法、构造器
获取类属性(getFields、getDeclaredFields),其中getFields不能获取私有属性,而getDeclaredFields可以获取到所有属性。
类似的,和获取属性一样,当获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法
// 3.通过反射获取类属性、方法、构造器
public static void testGetReflectMoreInfo() throws Exception {
Class clz = Apple.class;
// 获取类属性(getFields、getDeclaredFields)
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
// 获取方法(getMethods、getDeclaredMethods)
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
// 获取构造器(getConstructors、getDeclaredConstructors)
Constructor[] constructors = clz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName());
}
}
属性
Class
对象提供以下方法获取对象的成员(Field
):
getFiled
- 根据名称获取公有的(public)类成员。getDeclaredField
- 根据名称获取已声明的类成员。但不能得到其父类的类成员。getFields
- 获取所有公有的(public)类成员。getDeclaredFields
- 获取所有已声明的类成员。
方法
Class
对象提供以下方法获取对象的方法(Method
):
getMethod
- 返回类或接口的特定方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象getDeclaredMethod
- 返回类或接口的特定声明方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象getMethods
- 返回类或接口的所有 public 方法,包括其父类的 public 方法getDeclaredMethods
- 返回类或接口声明的所有方法,包括 public、protected、默认(包)访问和 private 方法,但不包括继承的方法
获取一个 Method
对象后,可以用 invoke
方法来调用这个方法。
构造器
Class
对象提供以下方法获取对象的构造方法(Constructor
):
getConstructor
- 返回类的特定 public 构造方法。参数为方法参数对应 Class 的对象getDeclaredConstructor
- 返回类的特定构造方法。参数为方法参数对应 Class 的对象getConstructors
- 返回类的所有 public 构造方法getDeclaredConstructors
- 返回类的所有构造方法
获取一个 Constructor
对象后,可以用 newInstance
方法来创建类实例
(4)判断是否为某个类的示例
判断是否为某个类的实例有两种方式:
用
instanceof
关键字用
Class
对象的isInstance
方法(它是一个 Native 方法)
public class InstanceofDemo {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
if(arrayList instanceof List){
System.out.println("ArrayList is List");
}
if(List.class.isInstance(arrayList)){
System.out.println("ArrayList is List");
}
}
}
// output
ArrayList is List
ArrayList is List
(5)绕开访问限制
有时需要通过反射访问私有成员、方法,可以使用Constructor/Field/Method.setAccessible(true)
来绕开 Java 语言的访问限制
3.反射机制详解
类加载过程
类加载的完整过程如下:
(1)在编译时,Java 编译器编译好 .java
文件之后,在磁盘中产生 .class
文件。.class
文件是二进制文件,内容是只有 JVM 能够识别的机器码。
(2)JVM 中的类加载器读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息。类加载器会根据类的全限定名来获取此类的二进制字节流;然后,将字节流所代表的静态存储结构转化为方法区的运行时数据结构;接着,在内存中生成代表这个类的 java.lang.Class
对象。
(3)加载结束后,JVM 开始进行连接阶段(包含验证、准备、初始化)。经过这一系列操作,类的变量会被初始化。
Class对象
要想使用反射,首先需要获得待操作的类所对应的 Class 对象。Java 中,无论生成某个类的多少个对象,这些对象都会对应于同一个 Class 对象。这个 Class 对象是由 JVM 生成的,通过它能够获悉整个类的结构。所以,java.lang.Class
可以视为所有反射 API 的入口点。
反射的本质就是:在运行时,把 Java 类中的各种成分映射成一个个的 Java 对象。
举例来说,假如定义了以下代码:
User user = new User();
步骤说明:
JVM 加载方法的时候,遇到
new User()
,JVM 会根据User
的全限定名去加载User.class
。JVM 会去本地磁盘查找
User.class
文件并加载 JVM 内存中。JVM 通过调用类加载器自动创建这个类对应的
Class
对象,并且存储在 JVM 的方法区。注意:一个类有且只有一个Class
对象。
方法的反射调用(源码分析)
方法的反射调用:Method.invoke()
方法
public final class Method extends Executable {
...
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
}
基于上述源码分析,Method的invoke方法实际上是委派给MethodAccessor接口进行处理(它有两个已有的具体实现)
public interface MethodAccessor {
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}
NativeMethodAccessorImpl
:本地方法来实现反射调用DelegatingMethodAccessorImpl
:委派模式来实现反射调用
每个 Method
实例的第一次反射调用都会生成一个委派实现(DelegatingMethodAccessorImpl
),它所委派的具体实现便是一个本地实现(NativeMethodAccessorImpl
)。本地实现:当进入了 Java 虚拟机内部之后,便拥有了 Method
实例所指向方法的具体地址,此时的反射调用无非就是将传入的参数准备好,然后调用进入目标方法
【示例】通过抛出异常方式 打印 Method.invoke
调用轨迹
class ReflectMethodDemo1{
// 定义目标方法
public static void target(int i) {
new Exception("#" + i).printStackTrace();
}
public static void main(String[] args) throws Exception {
// 通过抛出异常的方式打印Method.invoke的调用轨迹
Class<?> clazz = Class.forName("com.noob.base.reflect.ReflectMethodDemo1");
Method method = clazz.getMethod("target", int.class);
method.invoke(null, 0);
}
}
// output
java.lang.Exception: #0
at com.noob.base.reflect.ReflectMethod.target(ReflectMethodDemo.java:9)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at com.noob.base.reflect.ReflectMethod.main(ReflectMethodDemo.java:16)
结合output分析:先调用 DelegatingMethodAccessorImpl
;然后调用 NativeMethodAccessorImpl
,最后调用实际方法。为什么反射调用DelegatingMethodAccessorImpl
作为中间层,而不是直接交给本地实现?
其实,Java 的反射调用机制还设立了另一种动态生成字节码的实现(下称动态实现),直接使用 invoke 指令来调用目标方法。之所以采用委派实现,便是为了能够在本地实现以及动态实现中切换。动态实现和本地实现相比,其运行效率要快上 20 倍。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上 3 到 4 倍。
考虑到许多反射调用仅会执行一次,Java 虚拟机设置了一个阈值 15(可以通过 -Dsun.reflect.inflationThreshold
来调整),当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现,这个过程称之为 Inflation。
【示例】校验反射的Method.invoke方法阈值15
class ReflectMethodDemo2{
// 定义目标方法
public static void target(int i) {
new Exception("#" + i).printStackTrace();
}
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.noob.base.reflect.ReflectMethodDemo2");
Method method = clazz.getMethod("target", int.class);
// 校验阈值15
for (int i = 0; i < 20; i++) {
method.invoke(null, i);
}
}
}
前15次都是通过DelegatingMethodAccessorImpl=》NativeMethodAccessorImpl=》target
第16次直接使用DelegatingMethodAccessorImpl,而不再使用NativeMethodAccessorImpl
java.lang.Exception: #14
at com.noob.base.reflect.ReflectMethodDemo2.target(ReflectMethodDemo.java:23)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at com.noob.base.reflect.ReflectMethodDemo2.main(ReflectMethodDemo.java:31)
java.lang.Exception: #15
at com.noob.base.reflect.ReflectMethodDemo2.target(ReflectMethodDemo.java:23)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at com.noob.base.reflect.ReflectMethodDemo2.main(ReflectMethodDemo.java:31)
java.lang.Exception: #16
at com.noob.base.reflect.ReflectMethodDemo2.target(ReflectMethodDemo.java:23)
at jdk.internal.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at com.noob.base.reflect.ReflectMethodDemo2.main(ReflectMethodDemo.java:31)
反射调用的开销
方法的反射调用会带来不少性能开销,原因主要有三个:
- 变长参数方法导致的 Object 数组
- 基本类型的自动装箱、拆箱
- 还有最重要的方法内联
Class.forName
会调用本地方法,Class.getMethod
则会遍历该类的公有方法。如果没有匹配到,它还将遍历父类的公有方法。可想而知,这两个操作都非常费时。
以 getMethod
为代表的查找方法操作,会返回查找得到结果的一份拷贝。因此,应当避免在热点代码中使用返回 Method
数组的 getMethods
或者 getDeclaredMethods
方法,以减少不必要的堆空间消耗。在实践中,往往会在应用程序中缓存 Class.forName
和 Class.getMethod
的结果
关注反射调用本身的性能开销
(1)由于 Method.invoke 是一个变长参数方法,在字节码层面它的最后一个参数会是 Object 数组。Java 编译器会在方法调用处生成一个长度为传入参数数量的 Object 数组,并将传入参数一一存储进该数组中
(2)由于 Object 数组不能存储基本类型,Java 编译器会对传入的基本类型参数进行自动装箱
这两个操作除了带来性能开销外,还可能占用堆内存,使得 GC 更加频繁。(可以用虚拟机参数 -XX:+PrintGC 试试。)那么,如何消除这部分开销呢?
反射机制的应用
反射的应用场景
- 开发通用框架 - 反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,因此需要引入反射,运行时动态加载需要加载的对象。
- 动态代理 - 在切面编程(AOP)中,需要拦截特定的方法,通常会选择动态代理方式。
- 注解 - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。
- 可扩展性功能 - 应用程序可以通过使用完全限定名称创建可扩展性对象实例来使用外部的用户定义类
1.动态代理&静态代理
动态代理
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)。
实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了上面提到的反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)、Javassist 等。
静态代理
静态代理:为其他对象提供一种代理模式,以控制对这个对象的访问(设计模式中的代理模式)
Subject(公共接口:可以是抽象类、接口)、RealSubject(被代理对象)、Proxy(代理类)
// 公共接口(可以是抽象类、接口)
abstract class Subject{
// 定义方法
public abstract void request();
}
// 被代理对象(真实的实体)
class RealSubject extends Subject{
@Override
public void request() {
System.out.println("真实请求");
}
}
// 代理类定义
class Proxy extends Subject{
// 定义被代理对象
private RealSubject realSubject;
@Override
public void request() {
if(null == realSubject){
realSubject = new RealSubject();
}
// 执行真正的代理方法
realSubject.request();
}
}
/**
* 静态代理
*/
public class StaticProxyDemo {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
}
静态代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于 Proxy 和 RealSubject 的功能本质上是相同的,Proxy 只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散
JDK动态代理
动态代理:通过实现InvocationHandler进行构建
// 定义公共接口,声明方法
interface Item{
void hello(String name);
String bye();
}
// 定义类实现接口
class RealItem implements Item{
@Override
public void hello(String name) {
System.out.println("hello " + name);
}
@Override
public String bye() {
System.out.println("bye");
return "bye";
}
}
// 定义动态代理类(实现InvocationHandler接口)
class DynamicProxy implements InvocationHandler {
// 定义要代理的真实对象
private Item item;
// 初始化代理对象
public DynamicProxy(Item item) {
this.item = item;
}
// 代理方法定义
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在代理真实对象前后可以自定义一些操作
System.out.println("before proxy");
System.out.println("call method" + method);
// 代理真实对象(调代理对象调用真实的方法时,会自动跳转到代理对象关联的handler对象的invoke方法进行调用)
Object obj = method.invoke(item, args);
System.out.println("after proxy");
// 最终返回代理结果
return obj;
}
}
/**
* 动态代理
*/
public class DynamicProxyDemo {
public static void main(String[] args) {
// 要代理的真实对象
RealItem realItem = new RealItem();
// 传入要代理的对象,通过该真实对象来调用方法
InvocationHandler handler = new DynamicProxy(realItem);
// 通过反射创建代理对象
Item item = (Item)Proxy.newProxyInstance(handler.getClass().getClassLoader(),realItem.getClass().getInterfaces(),handler );
System.out.println(item.getClass().getName());
item.hello("noob");
item.bye();
}
}
// output
com.noob.base.reflect.proxy.$Proxy0
before proxy
call methodpublic abstract void com.noob.base.reflect.proxy.Item.hello(java.lang.String)
hello noob
after proxy
before proxy
call methodpublic abstract java.lang.String com.noob.base.reflect.proxy.Item.bye()
bye
after proxy
剖析代码:
Item item = (Item)Proxy.newProxyInstance(handler.getClass().getClassLoader(),realItem.getClass().getInterfaces(),handler );
通过Proxy.newProxyInstance方法创建的代理对象,这个代理对象是jvm运行时动态生成的一个对象(并不是InvocationHandler)
// 参数1:handler.getClass().getClassLoader()
- 使用handler这个类的ClassLoder来加载代理对象
// 参数2:realItem.getClass().getInterfaces()
- 为代理对象提供真实对象所实现的接口,表示要代理的是真实对象,进而调用这组接口中的方法
// 参数3:handler
- 将代理对象关联到上方定义的InvocationHandler
当通过代理对象来调用方法的时候,实际上就是委托由其关联的handler对象的invoke方法进行调用(通过代理的方式进行调用)
JDK动态代理总结
- 通过代理类实现InvocatioHandler并重写invoke方法来进行动态代理(在invoke方法中扩展额外的操作)
- 特点:
- 优点:相对于静态代理,不需要硬编码接口,代码复用效率高
- 缺点:强制要求代理类实现InvocationHandler接口
CGLIB动态代理
CGLIB 提供了与 JDK 动态代理不同的方案。很多框架,例如 Spring AOP 中,就使用了 CGLIB 动态代理。
CGLIB 底层,其实是借助了 ASM 这个强大的 Java 字节码框架去进行字节码增强操作。
CGLIB 动态代理的工作步骤:
- 生成代理类的二进制字节码文件;
- 加载二进制字节码,生成
Class
对象( 例如使用Class.forName()
方法 ); - 通过反射机制获得实例构造,并创建代理类对象。
CGLIB 动态代理特点:
优点:使用字节码增强,比 JDK 动态代理方式性能高。可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口。
缺点:不能对 final
类以及 final
方法进行代理
最典型的场景应:AOP 通过(动态)代理机制可以让开发者从这些繁琐事项中抽身出来,大幅度提高了代码的抽象程度和复用度
反射源码(todo)
反射中最终是Method的invoke方法
关注MethodAccessor:具体去生成反射类的入口,查看源码注释进行分析
实际的 MethodAccessor 实现有两个版本,一个是 Native 版本,一个是 Java 版本。
Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。
也就是说invoke 方法内部有两种实现方式,一种是 native 原生的实现方式,一种是 Java 实现方式,这两种各有千秋。而为了最大化性能优势,JDK 源码使用了代理的设计模式去实现最大化性能