代理
动态代理和静态代理的区别
代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。
区别:
- 静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理。静态代理通常只代理一个类;
- 动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类。
实现动态代理的方式?
Java动态代理是一种在运行时动态生成代理类的机制,用于代理其他对象的访问。与静态代理不同,动态代理无需事先定义代理类,而是在程序运行时根据指定的接口和处理器生成代理类。
Java动态代理主要分为两种类型:
基于接口的代理(JDK动态代理): 这种类型的代理要求目标对象必须实现至少一个接口。Java动态代理会创建一个实现了相同接口的代理类,然后在运行时动态生成该类的实例。这种代理的实现核心是
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。每一个动态代理类都必须实现InvocationHandler
接口,并且每个代理类的实例都关联到一个handler
。当通过代理对象调用一个方法时,这个方法的调用会被转发为由InvocationHandler
接口的invoke()
方法来进行调用。基于类的代理(CGLIB动态代理): CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB代理不需要目标类实现接口,而是通过继承的方式创建代理类。因此,如果目标对象没有实现任何接口,可以使用CGLIB来创建动态代理。
使用动态代理可以避免为每个目标类编写具体的代理类,提高代码的灵活性和可维护性。它可以在运行时根据需要动态地生成代理类,适用于代理多个目标类的情况。
使用JDK动态代理的代码:
Java 中提供了一个
java.lang.reflect.Proxy
类,用于创建动态代理对象。该类提供了一个静态方法newProxyInstance()
,通过指定目标类的加载器、目标类实现的接口、以及一个InvocationHandler
来创建代理对象。
1、创建接口
1 | public interface HelloService { |
2、创建实现类
1 | public class HelloServiceImpl implements HelloService { |
3、创建一个类实现InvocationHandler
接口,重写invoke
方法,在里面调用目标类的方法,并在调用前后实现自定义的逻辑
1 | import java.lang.reflect.InvocationHandler; |
4、使用Proxy.newProxyInstance()
方法,传入响应参数,获得目标类的代理对象,并调用目标类的方法
1 | import java.lang.reflect.Proxy; |
CGlib是怎么实现的?
CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是 final 的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
CGLIB 底层:采用ASM字节码生成框架,使用字节码技术生成代理类,比使用 Java 反射效率要高。
CGLib(Code Generation Library)是一个强大的字节码生成库,它可以在运行时动态地生成字节码,用于创建代理对象。与 JDK 动态代理不同,CGLib 不要求目标对象必须实现接口,它是通过继承目标类来实现代理功能的。
原理核心步骤:
字节码生成
- CGLib 通过使用
Enhancer
类来创建代理对象。Enhancer
是 CGLib 的核心类,它可以动态地生成一个目标类的子类。例如,有一个普通的 Java 类TargetClass
,CGLib 会在运行时创建一个TargetClass
的子类,这个子类就是代理类。 - 在字节码生成过程中,CGLib 会使用
ASM
(一个 Java 字节码操作框架)来操作字节码。ASM
可以直接生成字节码或者对已有的字节码进行修改。CGLib 利用ASM
的功能,根据目标类的结构来生成代理类的字节码。
- CGLib 通过使用
方法拦截机制
- CGLib 通过实现
MethodInterceptor
接口来实现方法拦截。在代理类中,每个被重写的方法都会调用MethodInterceptor
接口的intercept
方法。例如,当调用代理对象的一个方法时,实际上是调用了代理类中重写后的方法,而这个重写后的方法会在内部调用intercept
方法。 - 在
intercept
方法中,可以在调用目标类的原始方法之前和之后添加自定义的逻辑。这个intercept
方法有四个参数:Object
类型的obj
(代表代理对象本身)、Method
类型的method
(代表被调用的方法)、Object[]
类型的args
(代表方法的参数)和MethodProxy
类型的methodProxy
(代表代理方法)。可以通过methodProxy.invokeSuper
来调用目标类的原始方法,
- CGLib 通过实现
假设我们有一个简单的类
SampleClass
,没有实现任何接口,如下:1
2
3
4
5public class SampleClass {
public void sampleMethod() {
System.out.println("Original method in SampleClass");
}
}我们可以使用 CGLib 来创建代理对象,如下:
1 | public class CglibProxyExample implements MethodInterceptor { |
- 在
main
方法中使用代理对象:
1 | public static void main(String[] args) { |
- 在这个示例中,
CglibProxyExample
类实现了MethodInterceptor
接口,通过Enhancer
类生成SampleClass
的代理对象。当调用代理对象的sampleMethod
时,会执行intercept
方法中的逻辑,在调用原始方法前后添加了自定义的打印语句。
实现静态代理和动态代理的代码
一、静态代理
静态代理是由程序员创建或工具生成代理类的代码,再对其编译。在程序运行前,代理类的.class 文件就已经存在了。
实现步骤如下:
- 定义一个业务接口,包含要代理的方法。
1 | public interface Subject { |
- 实现业务接口的真实业务类。
1 | public class RealSubject implements Subject { |
- 创建一个代理类,同样实现业务接口,在代理类中持有一个真实业务对象的引用,并在代理方法中调用真实业务对象的方法,同时可以添加一些额外的逻辑。
1 | public class StaticProxy implements Subject { |
- 使用代理类。
1 | public class Main { |
二、动态代理
动态代理是在程序运行时,通过反射机制动态地生成代理类的对象。Java 中主要有两种方式实现动态代理:基于 JDK 动态代理和基于 CGLIB 动态代理。
- 基于 JDK 动态代理:
- JDK 动态代理要求被代理的对象必须实现一个接口。
- 实现步骤如下:
- 定义一个业务接口,和静态代理中的接口一样。
- 实现业务接口的真实业务类。
- 创建一个动态代理类,实现
InvocationHandler
接口。
1 | import java.lang.reflect.InvocationHandler; |
- 使用代理类。
1 | public class Main { |
- 基于 CGLIB 动态代理:
- CGLIB 可以代理没有实现接口的类。
- 实现步骤如下:
- 引入 CGLIB 的依赖。
- 实现业务接口的真实业务类,或者不实现接口直接创建一个普通类。
- 创建一个方法拦截器类,实现
MethodInterceptor
接口。
1 | import net.sf.cglib.proxy.Enhancer; |
- 使用代理类。
1 | public class Main { |