1. 介绍
- 什么是代理模式
代理的思想在生活中很常见,比如:- 你的对象喜欢海淘买化妆品,把钱交给代购或者海外店铺去采购,他们会帮你买好物品,邮寄到你的地址;
- 你想查询一些技术问题或者其他合法操作时,你会使用梯子代理访问目标网站;
- 你想听周杰伦的演唱会了,可是永远都抢不到票,你可能就得找黄牛把钱给他,他帮你抢票
可见生活中,代理的思想应用无处不在。在Java中,代理是一种设计模式,可以在不修改原有类的基础上,对类的方法进行拦截和增强。Java代理主要有两种实现方式:静态代理和动态代理,动态代理里面又分JDK动态代理和CGLIB动态代理。在实际开发中我们常常不会直接去写代理的代码,而是写如AOP,或者使用JPA,事务等注解时,这些底层的实现均是由代理实现,所以首先得熟悉代理,才能真正搞动上层的东西。看不明白没关系,咱慢慢往下看。
- 代理模式的应用场景
Java代理主要的应用场景有:日志记录、性能监控、事务管理、安全控制鉴权等。
2. 静态代理
- 静态代理的概念与实现
静态代理是指在编译期间手动创建代理类。代理类和目标类通常会实现同一个接口,以便在代理类中可以调用目标类的方法。代理类中还可以实现横切关注点,例如日志记录、性能监控等。 - 使用静态代理实现一个简单的功能扩展
假设我们有一个接口表示打印机:
public interface Printer {
void print(String message);
}
实现该接口的具体类:
public class RealPrinter implements Printer {
@Override
public void print(String message) {
System.out.println("RealPrinter: " + message);
}
}
接下来我们创建一个静态代理类,实现相同的接口,并持有RealPrinter的实例:
public class StaticPrinterProxy implements Printer {
private RealPrinter realPrinter;
public StaticPrinterProxy(RealPrinter realPrinter) {
this.realPrinter = realPrinter;
}
@Override
public void print(String message) {
System.out.println("StaticPrinterProxy: Before print");
realPrinter.print(message);
System.out.println("StaticPrinterProxy: After print");
}
}
在客户端代码中使用代理:
public class Client {
public static void main(String[] args) {
RealPrinter realPrinter = new RealPrinter();
Printer proxy = new StaticPrinterProxy(realPrinter);
proxy.print("Hello, World!");
}
}
运行结果:
StaticPrinterProxy: Before print
RealPrinter: Hello, World!
StaticPrinterProxy: After print
由此可见,静态代理需要手动创建代理类,对目标类进行代理。可你应该就会想到,如果我有很多类需要代理呢,难道我每个都要写对应的代理类吗?有没有自动生成代理类的方式呢?还真有,偷懒是人类进步的阶梯,下面我们谈谈动态代理!
3. 动态代理
动态代理是指在运行期间动态生成代理类的字节码,并加载到内存中。和静态代理一样,动态代理生成的代理类也会实现目标类所实现的接口。这使得代理对象可以在运行时处理方法调用,并在实际调用目标对象之前或之后执行额外的逻辑。
3.1 JDK 动态代理
示例:使用 JDK 动态代理实现一个简单的功能扩展。假设我们有一个接口表示打印机:
public interface Printer {
void print(String message);
}
实现该接口的具体类:
public class RealPrinter implements Printer {
@Override
public void print(String message) {
System.out.println("RealPrinter: " + message);
}
}
如果想对print方法进行增强,在打印前后执行一些逻辑,则可以使用JDK动态代理实现。下面我们创建一个实现InvocationHandler接口的类,用于处理代理方法调用:
public class PrinterInvocationHandler implements InvocationHandler {
private Object target;
public PrinterInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before printing");
Object result = method.invoke(target, args);
System.out.println("After printing");
return result;
}
}
测试main方法编写如下:
public class Client {
public static void main(String[] args) {
RealPrinter realPrinter = new RealPrinter();
PrinterInvocationHandler handler = new PrinterInvocationHandler(realPrinter);
// 获取动态代理类
Printer proxy = (Printer) Proxy.newProxyInstance(
realPrinter.getClass().getClassLoader(),
realPrinter.getClass().getInterfaces(),
handler
);
// 调用代理类的打印方法
proxy.print("Hello, World!");
}
}
执行结果打印如下:
Before printing
RealPrinter: Hello, World!
After printing
可以看到,在不修改RealPrinter类的情况下,JDK动态代理实现了在打印方法前后添加额外逻辑。可能你也在好奇,他是如何实现的呢?此处简述下原理,本篇章不做详细讲解,可以关注后续博文(此处埋个坑,我将写一篇介绍Proxy类核心源码即动态代理的实现逻辑的源码阅读笔记,敬请期待!):
- 获取动态代理类的对象(生成的代理类中通过反射的方式加载了目标类的方法)
- 执行代理类方法方法,会加载自定义的PrinterInvocationHandler中的invoke方法,从而加载增强逻辑
- method.invoke(target, args); 则通过反射加载真正目标类的具体方法
Proxy类是jdk的核心类,我们看下方法的入参
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
- ClassLoader参数为类加载器
- interfaces参数为实现的接口,可以传数组
- InvocationHandler参数为InvocationHandler的实现类,这个实现类的构造函数入参需要包含目标实现的接口类
从方法入参interfaces可知,需要有具体实现类,才可以使用jdk的动态代理,这个要记住,重点中的重点,必考题,不信的话王老师就给你赌个面包机。哈哈!这样好像还是有点麻烦,我有些类就是没有实现类,岂不是不能代理了?当然可以!下面我们来了解下CGLIB动态代理!
3.1 CGLIB动态代理
CGLIB:即英文Code Generation Library 缩写,意为代码生成库。下面介绍下如何使用CGLIB动态代理:
首先使用maven或者手动引入cglib依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
假如有个计算工具类,需要对add方法进行增强
public class CalculateUtil {
public int add(int a, int b) {
System.out.println("方法执行中...");
return a + b;
}
}
首先定义一个MethodInterceptor
public class MyMethodInterceptor<T> implements MethodInterceptor {
private T target;
public MyMethodInterceptor(T target) {
this.target = target;
}
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("方法执行前...." + method.getName());
Object res = methodProxy.invokeSuper(target, args);
System.out.println("方法执行后...." + res);
return res;
}
}
CGLIB动态代理类测试
public static void main(String[] args) {
CalculateUtil calculateUtil = new CalculateUtil();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(calculateUtil.getClass());
MyMethodInterceptor<CalculateUtil> calculateUtilMyMethodInterceptor = new MyMethodInterceptor<CalculateUtil>(calculateUtil);
enhancer.setCallback(calculateUtilMyMethodInterceptor);
CalculateUtil proxy = (CalculateUtil) enhancer.create();
proxy.add(2,3);
}
执行结果打印如下:
方法执行前....add
方法执行中...
方法执行后....5
简述下动态代理的原理,Enhancer会生成动态代理类(底层使用ASM字节码生成框架生成代理类),这个生成的代理类是继承目标类的,即生成的是目标类的子类,重写了目标类的所有方法。需要额外注意的是,通过继承来实现的代理方法的调用,就不能对final类和final方法进行代理了。
4. 如何选择合适的代理方式
经过上文的介绍,大概也能总结出什么时候使用静态代理,什么时候使用动态代理。静态代理需要手写代理类代码,来一个写一个,造成代码量庞大且冗余,不推荐使用;动态代理中,如果目标类有实现类,即使用JDK动态代理,如果没有实现类则使用CGLIB动态代理。
性能方面,静态代理的肯定是最好的,因为代码写出来了直接编译运行。动态代理都是在运行时动态生成的,两者性能上来说差异不太明显,非要说的话,CGLIB性能应该是优于JDK动态代理(因为JDK代理是基于反射实现,反射比较耗性能),但JDK动态代理毕竟是JDK核心类下的Proxy类,在JDK版本升级时性能都有所提升。
5. 感谢
至此,打完收工!感谢您的浏览和支持!这篇文章写了有三四个星期,一直酝酿在草稿箱里,希望每篇文章不仅是对自己的知识的巩固提升,也更希望能帮助阅读的每位程序员工程师,加油!
如果您对技术有兴趣,友好交流,可以加v进技术群一起沟通,v:zzs1067632338,备注csdn即可