1 为什么需要代理?
简单来说,就是为了保证单一性原则问题,也就是每个类的功能尽可能单一,这样才能是该类被改动的可能性最小。比如,如果当我们需要为类里加上权限,日志等功能时,如果每个类都需要这种功能,那就要修改每个类,这样工作量很大,所以我们需要一个代理,让我们可以在不改动原有代码的前提下,实现一些其他功能,即增强。我们在进入目标类之前,先进入代理类,在代理类中写我们需要的额外功能,这样原有类不动,不影响原有功能。
下面就是一个关于静态代理的实例,能帮助理解代理的意义
1 2 3 4 5 6 7 8
|
public interface ProgrammerInterface { void goToWork();
void goOffWork(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public class Programmer implements ProgrammerInterface{ @Override public void goToWork() { System.out.print("开始上班了\n"); }
@Override public void goOffWork() { System.out.print("终于下班了\n"); } }
|
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
|
public class ProgrammerProxy implements ProgrammerInterface { Programmer programmer;
public ProgrammerProxy(Programmer programmer) { this.programmer = programmer; }
@Override public void goToWork() { System.out.print("进入了上班代理\n"); programmer.goToWork(); System.out.print("上班时间为:" + new Date() + "\n\n"); }
@Override public void goOffWork() { System.out.print("进入了下班代理\n"); programmer.goOffWork(); System.out.print("下班时间为:" + new Date() + "\n"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ProxyDemoApplication {
public static void main(String[] args) {
Programmer programmer = new Programmer(); ProgrammerProxy programmerProxy = new ProgrammerProxy(programmer); programmerProxy.goToWork(); programmerProxy.goOffWork(); } }
|
运行结果
1 2 3 4 5 6 7
| 进入了上班代理 开始上班了 上班时间为:Sun Feb 20 16:51:09 CST 2022
进入了下班代理 终于下班了 下班时间为:Sun Feb 20 16:51:09 CST 2022
|
但静态代理有两个问题
- 如果其他类需要实现同样的功能,则需要在写一个代理类,这样会很麻烦。
- 当我们需要新增功能时,需要在接口,类,代理类上都要新增,项目规模较大时不好维护
所以,就出现了动态代理,下面来聊聊Java中动态代理的几种方式和区别。
2 JDK动态代理
JDK动态代理主要是通过,反射包中的Porxy类和InvokationHandler接口。它们结合在一起后可以创建动态代理类。Porxy类基于传递的参数创建动态代理类。InvokationHandler则用于激发动态代理类的方法。这个过程是在程序执行过程中动态生成与处理的,所以叫动态代理。
下面来看看使用示例
实现InvokationHandler接口
JDK 动态代理类必须实现反射包中的 java.lang.reflect.InvocationHandler 接口,在此接口中只有一个 invoker 方法:
在InvocationHandler#invoker
中必须调用目标类被代理的方法,否则无法做到代理的实现。下面为实现 InvocationHandler 的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TargetInvoker implements InvocationHandler { private Object target;
public TargetInvoker(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("jdk 代理执行前"); Object result = method.invoke(target, args); System.out.println("jdk 代理执行后"); return result; } }
|
在实现InvocationHandler#invoker
时,该方法里有三个参数:
- proxy 代理目标对象的代理对象,它是真实的代理对象。
- method 执行目标类的方法
- args 执行目标类的方法的参数
创建JDK动态代理类
创建 JDK 动态代理类实例同样也是使用反射包中的 java.lang.reflect.Proxy 类进行创建。通过调用Proxy#newProxyInstance
静态方法进行创建。
1 2 3 4 5 6 7 8 9 10 11
| public class DynamicProxyAnimal {
public static Object getProxy(Object target) throws Exception { Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetInvoker(target) ); return proxy; } }
|
Proxy#newProxyInstance
中的三个参数(ClassLoader loader、Class<?>[] interfaces、InvocationHandler h):
- loader 加载代理对象的类加载器
- interfaces 代理对象实现的接口,与目标对象实现同样的接口
- h 处理代理对象逻辑的处理器,即上面的 InvocationHandler 实现类。
最后实现执行 DynamicProxyAnimal 动态代理:
3 Cglib动态代理
CGLIB 动态代理的实现机制是生成目标类的子类,通过调用父类(目标类)的方法实现,在调用父类方法时再代理中进行增强。
实现 MethodInterceptor 接口
相比于 JDK 动态代理的实现,CGLIB 动态代理不需要实现与目标类一样的接口,而是通过方法拦截的方式实现代理,代码实现如下,首先方法拦截接口 net.sf.cglib.proxy.MethodInterceptor
。
1 2 3 4 5 6 7 8 9 10
| public class TargetInterceptor implements MethodInterceptor {
@Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLIB 调用前"); Object result = proxy.invokeSuper(obj, args); System.out.println("CGLIB 调用后"); return result; } }
|
通过方法拦截接口调用目标类的方法,然后在该被拦截的方法进行增强处理,实现方法拦截器接口的 intercept 方法里面有四个参数:
- obj 代理类对象
- method 当前被代理拦截的方法
- args 拦截方法的参数
- proxy 代理类对应目标类的代理方法
创建Cglib动态代理类
创建 CGLIB 动态代理类使用 net.sf.cglib.proxy.Enhancer 类进行创建,它是 CGLIB 动态代理中的核心类,首先创建个简单的代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class CglibProxy {
public static Object getProxy(Class<?> clazz){ Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(clazz.getClassLoader()); enhancer.setSuperclass(clazz); enhancer.setCallback(new TargetInterceptor()); return enhancer.create(); } }
|
设置被代理类的信息和代理类拦截的方法的回调执行逻辑,就可以实现一个代理类。 实现 CGLIB 动态代理调用:
1 2 3 4 5 6 7
| public class Main { @Test public void dynamicProxy() throws Exception { Animal cat = (Animal) CglibProxy.getProxy(Cat.class); cat.call(); } }
|
4 总结
两种机制的不同实现:
- JDK 动态代理是通过实现目标类的接口,然后将目标类在构造动态代理时作为参数传入,使代理对象持有目标对象,再通过代理对象的 InvocationHandler 实现动态代理的操作。
- CGLIB 动态代理是通过配置目标类信息,然后利用 ASM 字节码框架进行生成目标类的子类。当调用代理方法时,通过拦截方法的方式实现代理的操作。
从而,我们知道:
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
后续再出一篇关于源码阅读的文章详解,这两种方式是如何实现代理的。
参考
JDK 动态代理与 CGLIB 有哪些区别? - 掘金 (juejin.cn)
你必须会的 JDK 动态代理和 CGLIB 动态代理 - 知乎 (zhihu.com)