什么是动态代理
- 动态代理是一种在运行时动态生成代理对象,并在代理对象上进行方法调用的编程技术
- 主要用于在不修改原有代码基础上,增加或改变某些功能的执行流程
- 分两种:JDK 动态代理和 CGLIB 动态代理
JDK 动态代理
- JDK 动态代理是基于接口的代理技术
- 它使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来创建代理对象。当你调用代理对象的任何方法时,调用会被转发到 InvocationHandler 的 invoke 方法。你可以在这个 invoke 方法中定义拦截逻辑,比如前置处理、后置处理等
- 为了使用 JDK 动态代理,你的类必须实现一个或多个接口。JDK 动态代理的局限性在于,它只能代理接口方法,如果你有一个类并希望代理其非接口方法,则不能使用 JDK 动态代理
- 优点:原生支持,无需引入额外依赖。
- 缺点:只能代理接口,如果一个类没有实现任何接口,则不能使用 JDK 动态代理
// 定义一个接口
public interface Star {
String sing(String name);
void dance();
}
// 实现该接口的类
public class BigStar implements Star{
private String name;
public BigStar(String name) {
this.name = name;
}
public String sing(String name){
System.out.println(this.name + "正在唱:" + name);
return "谢谢!谢谢!";
}
public void dance(){
System.out.println(this.name + "正在优美的跳舞~~");
}
}
//Java中代理的代表类是:java.lang.reflect.Proxy,它提供了一个静态方法,用于为被代理对象,产生一个代理对象返回
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Star createProxy(BigStar bigStar) {
/* newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数1:用于指定一个类加载器
参数2:指定生成的代理长什么样子,也就是有哪些方法(即接口里的方法)
参数3:用来指定生成的代理对象要干什么事情
*/
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler(){
@Override // 回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理对象要做的事情,会在这里写代码
if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱20万");
}else if(method.getName().equals("dance")){
System.out.println("准备场地,收钱1000万");
}
return method.invoke(bigStar, args);
}
});
return starProxy;
}
}
public class Main {
public static void main(String[] args) {
BigStar s = new BigStar("SuperStar");
Star starProxy = ProxyUtil.createProxy(s);
String rs = starProxy.sing("好日子");
System.out.println(rs);
starProxy.dance();
}
}
CGLIB 动态代理
-
CGLIB(Code Generation Library)是一个强大的、高性能、高质量的 Code 生成库,它可以在运行时扩展 Java 类和实现 Java 接口。不同于 JDK 动态代理,CGLIB 不需要接口,它是通过继承方式实现代理的。
-
CGLIB 底层通过使用一个小而快的字节码处理框架 ASM ,来转换字节码并生成新的类。不仅可以代理普通类的方法,还能代理那些没有接口的类的方法。
-
优点:无需接口实现。在大量调用的场景下,其生成的代理对象在调用时性能比 JDK 动态代理高
-
缺点:对 final 方法无效,需添加额外的依赖。
-
操作步骤
- 创建一个需要被代理的类。
- 创建一个继承 MethodInterceptor 的代理类,在 intercept 方法中定义代理逻辑。
- 使用 Enhancer 类创建被代理类的子类,并设置回调。
<!--引入 cglib依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
//定义一个普通类
public class HelloService {
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}
//创建一个继承 MethodInterceptor 的代理类 实现 intercept 方法
public class HelloServiceCglib implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before sayHello");
// 执行目标类的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("After sayHello");
return result;
}
}
//实现
public static void main(String[] args) {
HelloService target = new HelloService();
HelloServiceCglib cglib = new HelloServiceCglib();
HelloService proxy = (HelloService) cglib.getInstance(target);
proxy.sayHello("World");
}
对比
- 实现方式:JDK 动态代理通过反射机制调用接口方法,CGLIB 通过生成目标对象的子类并覆盖其方法实现代理。
- 性能:在调用方法的次数非常多的情况下,CGLIB 的性能可能优于 JDK 动态代理,但差异通常不大。CGLIB 初始化的代理对象比 JDK 动态代理慢,因为它需要生成新的类。
- 使用场景:如果目标对象实现了接口,推荐使用 JDK 动态代理。如果目标对象没有实现接口,或者有特定需求需要通过继承方式代理,则使用 CGLIB。
补充:静态代理
静态代理是一种设计模式,它在程序运行前就已经存在代理类的代码,代理类和目标对象实现相同的接口或继承相同的父类。通过代理类来间接访问目标对象,从而在不修改目标对象代码的情况下,增加或改变某些功能的执行流程。静态代理通常用于控制对目标对象的访问,或在调用目标对象的方法前后添加额外的功能,如安全检查、事务处理、日志记录等。
- 静态代理的特点:
- 编译时增加功能:静态代理的实现在编译阶段就已经完成,所有的增强功能都需要在代理类中显式编写。
- 代码冗余:如果有多个类需要代理,每个类都需要一个对应的代理类,这会导致大量的重复代码。
- 紧耦合:代理类和目标对象之间的关系在编译时就已经确定,增加或修改代理类需要重新编译。
- 静态代理的实现:
- 接口( Interface ):定义了目标对象和代理对象共同遵循的操作集合。
- 目标对象( Target Object ):实现了接口的类,定义了要执行的具体操作。
- 代理对象( Proxy Object ):同样实现了接口,用于包装目标对象,可以在调用目标对象的方法前后执行一些附加操作。
- 客户端( Client ):使用代理对象的用户。
// 定义接口
public interface HelloService {
void sayHello(String name);
}
// 实现接口的目标类
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}
// 静态代理类 创建一个代理类来增强 sayHello 方法:
public class HelloServiceProxy implements HelloService {
private HelloService helloService;
public HelloServiceProxy(HelloService helloService) {
this.helloService = helloService;
}
@Override
public void sayHello(String name) {
System.out.println("Before sayHello"); // 前置增强
helloService.sayHello(name);
System.out.println("After sayHello"); // 后置增强
}
}
//实现
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
HelloService proxy = new HelloServiceProxy(helloService);
proxy.sayHello("World");
}