文章摘要
GPT 4
此内容根据文章生成,仅用于文章内容的解释与总结
投诉

动态代理

什么是动态代理

为了避免代码侵入式的修改,在程序运行时,目标对象不变,对目标对象生成代理对象,代理对象中的方法是目标对象方法的增强方法,最终达到目标对象增强的效果。

Java中动态代理的实现

第一种:基于接口实现动态代理

基于接口的动态代理,用到的类是Proxy的newProxyInstance静态方法创建,要求被代理对象至少实现一个接口,如果没有,则不能创建代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface MyInterface {
void doSomething();
}

public class DynamicProxyDemo {
public static void main(String[] args) {
/**
* 生成对象的代理对象,对被代理对象进行所有方法日志增强
* 参数:原始对象
* 返回值:被代理的对象
* JDK 动态代理
* 基于接口的动态代理
* 被代理类必须实现接口
* JDK提供的
*/
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(
/**
* 创建对象的代理对象
* 参数一:类加载器
* 参数二:对象的接口
* 参数三:调用处理器,代理对象中的方法被调用,都会在执行方法。对所有被代理对象的方法进行拦截
*/
MyInterface.class.getClassLoader(),
MyInterface.getClass().getInterface(), //new Class[] { MyInterface.class },多个接口的情况
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(new MyInterfaceTarget(), args); //假设MyInterfaceTarget是实现MyInterface的类
System.out.println("After method: " + method.getName());
return result;
}
}
);

proxyInstance.doSomething(); // 调用代理实例的方法
}

// 假设的实现类
static class MyInterfaceTarget implements MyInterface {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
}

可以看到,参数三InvocationHandler是一个函数式接口,这里采用匿名内部类的形式重写方法,这个方法中的这行代码:

1
Object result = method.invoke(new MyInterfaceTarget(), args);

就是利用反射,拿到目标对象的方法,而上下两行的打印语句就是被增强的地方。

方法返回原始方法method。

1
return result;  

最终返回结果是原始方法,因为增强的功能已经执行了。

第二种:基于第三方库CGLib

要导入cglib第三方库,使用的类是Enhancer的create静态方法创建,要求被代理类不能是最终类,即不能用final修饰,如String类。

CGLib 实现步骤

  1. 创建一个实现接口 MethodInterceptor 的代理类,重写 intercept 方法;
  2. 创建获取被代理类的方法 getInstance(Object target);
  3. 获取代理类,通过代理调用方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static Object getObjectByCGLib(final Object obj){
/**
* 使用CGLib的Enhancer创建代理对象
* 参数一:对象的字节码文件
* 参数二:方法的拦截器
*/
Object proxyObj = Enhancer.create(obj.getClass(), new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//方法执行前
long startTime = System.currentTimeMillis();

Object invokeObject = method.invoke(obj, objects);//执行方法的调用

//方法执行后
long endTime = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat();
System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n"
, method.getName()), sdf.format(endTime), endTime - startTime);
return invokeObject;
}
});
return proxyObj;
}
// 注意:这个方法返回的是代理对象的实例,但它与被代理对象(obj)在内存中是两个不同的实例。
// 如果你需要代理对象和被代理对象在某些场景下是同一个实例(比如单例模式),这种方法就不适用了。

两者区别

JDK Proxy 和 CGLib 的区别主要体现在以下方面:

  • JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
  • Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新,Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
  • JDK Proxy 是通过拦截器加反射的方式实现的;
  • JDK Proxy 只能代理实现接口的类;
  • JDK Proxy 实现和调用起来比较简单;
  • CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  • CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。

ASM

ASM是一个Java字节码操作和分析框架,它提供了一套直接操作Java字节码的API。与Java反射机制相比,ASM通过直接操作字节码来避免了许多反射带来的性能开销,因此能够显著提高应用程序的性能。ASM的API设计得非常灵活且强大,它允许开发者在类被加载到Java虚拟机之前动态地修改类的行为,包括添加、删除或修改方法、字段等。

具体来说,当CGLib需要创建一个代理对象时,它会利用ASM来动态生成这个代理类的字节码。这个过程中,CGLib会根据被代理类的结构、需要增强的方法等信息,生成相应的字节码,并加载到Java虚拟机中。这样,CGLib就能够在运行时动态地创建代理类,而无需编写大量的模板代码。

反射

Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性、方法、构造函数、访问修饰符等信息。例如,使用它能获得 Java 类中各成员的名称并显示出来。在运行时动态加载类等。第三方主流的框架如:spring, springMVC, mybatis 等内部都大量的使用反射技术。

面试官问你什么是反射

只需要答出来,反射机制指的是在程序运行时(JVM时期)可以获取自身的信息。在Java中只需要给定类名,就可以通过反射机制获取该类所有的属性和方法。

  1. JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

  2. Java反射机制主要提供了以下功能:

    1. 在运行时判断任意一个对象所属的类;
    1. 在运行时构造任意一个类的对象;
    2. 在运行时判断任意一个类所具有的成员变量和方法;
    3. 在运行时调用任意一个对象的方法;
    4. 生成动态代理。
    • 反射 java.lang.Class 实例:表示正在运行的类的信息
    • 通过 java.lang.Class反射Constructor(构造函数)实例
    • 通过 java.lang.Class反射Field(字段、属性)实例
    • 通过 java.lang.Class反射Method(方法)实例

Java代码实现反射

反射 Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ReflectionConstructor {
public static void main(String[] args) {
//反射类
try {
//方式1.
//获得 Employee类在JVM 中运行时的java.lang.Class实例
java.lang.Class clazz = Class.forName("com.lanqiao.reflect.Employee");

//如果Class<Employee>使用了泛型,则创建实例直接返回实际的类型
// Employee employee = clazz.newInstance();
// System.out.println(employee);

//方式2.
java.lang.Class<?> clazz2 = Employee.class;
//System.out.println(Employee.class.getName());


//方式3.
Employee e = new Employee();
Class<? extends ?> aClass = e.getClass();


} catch (ClassNotFoundException e) { //找不到类的异常(字符串类名错误)
e.printStackTrace();
}
}
}

反射构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ReflectionConstructor {
public static void main(String[] args) {
try {
//方式1.
//获得 Employee类在JVM 中运行时的java.lang.Class实例
Class clazz = Class.forName("com.lanqiao.reflect.Employee");
//反射构造函数
//getDeclaredConstructors(...)得到类中声明的所有构造函数,包括private,public,default
Constructor[] constructors = clazz.getDeclaredConstructors();
//仅得到public 声明的构造函数
constructors = clazz.getConstructors();

for (Constructor constructor : constructors) {
System.out.println(constructor);
}

//得到指定参数列表的构造函数
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor);

//修改构造函数的访问性为可访问,即 public
constructor.setAccessible(true);

//通过构造函数创建实例(反射调用构造)
//调用 private Employee(String ename, int month)
Object emp = constructor.newInstance("james", 11);
System.out.println(emp);

//调用 Employee(String ename, Double sal, int month)
constructor = clazz.getDeclaredConstructor(String.class, Double.class, int.class);
emp = constructor.newInstance("mike", 999.9, 11);
System.out.println(emp);

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) { // 找不到方法或构造函数的异常
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}

反射成员属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ReflectionField {
public static void main(String[] args) {
try {
//获得 Employee类在JVM 中运行时的java.lang.Class实例
Class clazz = Class.forName("com.lanqiao.reflect.Employee");
//通过默认构造函数创建 Employee实例
Object instance = clazz.newInstance();

//public声明的字段
Field[] fields = clazz.getFields();
//反射所有字段(属性)
fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}

//得到指定字段
Field field = clazz.getDeclaredField("ename");
//修改属性的可访问性为 public
field.setAccessible(true);
//给name字段赋值
field.set(instance, "jerry"); //等价于:instance.ename= "jerry";

field = clazz.getDeclaredField("sal");
field.setAccessible(true);
field.set(instance, 999.88);

System.out.println(instance);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}

反射成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ReflectionMethod {
public static void main(String[] args) {
try {
//获得 Employee类在JVM 中运行时的java.lang.Class实例
Class clazz = Class.forName("com.lanqiao.reflect.Employee");
//通过默认构造函数创建 Employee实例
Object instance = clazz.newInstance();

//得到所有 public 的方法,包括类声明的和从父类继承的
Method[] methods = clazz.getMethods();
//得到本类所有声明的方法,不包括从父类继承的
methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}

//通过指定参数列表类型得到某个方法实例
//也就是:public void setEname(String s){ ... } 方法
Method method = clazz.getDeclaredMethod("setEname", String.class);
//反射调用方法,如果方法无返回则为 null
Object returnValue = method.invoke(instance,"jerry");
//instance.setEname("jerry");

System.out.println("调用 setEname 方法的返回值:" + returnValue);
System.out.println(instance);

//得到 String test(String msg, int a){ ... } 方法
method = clazz.getDeclaredMethod("test", String.class, int.class);
method.setAccessible(true);
//反射调用test方法
returnValue = method.invoke(instance, "hello ", 22);
System.out.println("调用 test 方法的返回值:" + returnValue);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) { //调用异常
e.printStackTrace();
}
}
}

反射为什么慢

Java虚拟机限制

在JVM中,将字节码翻译成机械码的方式有:Java自带的解释器和JIT即时编译器。

由于反射涉及到动态解析的类型,对此不能执行java虚拟机的某些优化机制, 比如JIT优化。

这两种方式的区别在于,前者启动速度快但运行速度慢,后者启动速度慢但运行速度快,原因是因为解释器不需要像JIT编译器一样,将所有字节码都转化为机器码,自然就少去了优化时间。

一文看懂为什么java反射性能慢、效率低_反射为什么消耗性能大-CSDN博客

Java的反射机制? 为什么反射慢_java反射性能低下的原因-CSDN博客

需要反复装箱拆箱

在使用反射时, 参数需要包装成Object类型, 但是真正执行方法的时候, 又需要拆包成真正的类型

触发GC次数多

需要额外的内存分配,反射操作可能涉及到临时的对象创建,比如method,field等对象,导致垃圾回收的成本变高,造成额外的开销