代理详解(java代理和CGLIB动态代理)
【代理】大家都知道,特别是在spring中aop、spring中的事务、spring解析注解@Configuration,以及最原始的解析spring.xml的配置,这些都是使用代理来进行实现的,所以今天进行总结下代理。
开始之前,我们需要解决一下3个问题:
1.为什么需要使用代理?
2.jdk代理怎么玩?
3.cglib代理怎么玩?
案例剖析:
1.创建一个接口。有3个方法。
public interface IService { void m1(); void m2(); void m3(); }
2.Aservice实现类
public class Aservice implements IService { @Override public void m1() { System.out.println("我是ServiceA中的m1方法!"); } @Override public void m2() { System.out.println("我是ServiceA中的m2方法!"); } @Override public void m3() { System.out.println("我是ServiceA中的m3方法!"); } }
3.Bservice实现类
public class Bservice implements IService { @Override public void m1() { System.out.println("我是ServiceB中的m1方法!"); } @Override public void m2() { System.out.println("我是ServiceB中的m2方法!"); } @Override public void m3() { System.out.println("我是ServiceB中的m3方法!"); } }
4.测试类
public class ProxyTest { @Test public void test() { IService serviceA = new Aservice(); IService serviceB = new Bservice(); serviceA.m1(); serviceA.m2(); serviceA.m3(); serviceB.m1(); serviceB.m2(); serviceB.m3(); } }
5.输出结果如下:
我是ServiceA中的m1方法!
我是ServiceA中的m2方法!
我是ServiceA中的m3方法!
我是ServiceB中的m1方法!
我是ServiceB中的m2方法!
我是ServiceB中的m3方法!
比较好的方式就是:我们可以使用Iservice接口创建一个代理类,通过这个代理类来间接的访问Iservice接口的实现类,在代理类中做接口耗时以及发送到监控的代码:做法如下:
public class ServiceProxy implements IService { /** * 目标对象,被代理的对象 */ private IService target; @Override public void m1() { long starTime = System.nanoTime(); this.target.m1(); long endTime = System.nanoTime(); System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime)); } @Override public void m2() { long starTime = System.nanoTime(); this.target.m1(); long endTime = System.nanoTime(); System.out.println(this.target.getClass() + ".m2()方法耗时(纳秒):" + (endTime - starTime)); } @Override public void m3() { long starTime = System.nanoTime(); this.target.m1(); long endTime = System.nanoTime(); System.out.println(this.target.getClass() + ".m3()方法耗时(纳秒):" + (endTime - starTime)); } }
ServiceProxy是Iservice的接口的代理类,target是被代理的对象,即实际需要访问的对象,也实现了Iservice接口,并且3个方法进行统计耗时的代码,当我们需要进行访问Iservice的其他的实现类的时候,可以使用ServiceProxy来进行简介的访问,用法如下:
@Test public void test2() { IService serviceA = new ServiceProxy(new Aservice()); IService serviceB = new ServiceProxy(new Bservice()); serviceA.m1(); serviceA.m2(); serviceA.m3(); serviceB.m1(); serviceB.m2(); serviceB.m3(); }
输出结果如下:
我是ServiceA中的m1方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):86300
我是ServiceA中的m1方法!
class com.hg.代理.Aservice.m2()方法耗时(纳秒):37900
我是ServiceA中的m1方法!
class com.hg.代理.Aservice.m3()方法耗时(纳秒):37000
我是ServiceB中的m1方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):51500
我是ServiceB中的m1方法!
class com.hg.代理.Bservice.m2()方法耗时(纳秒):36100
我是ServiceB中的m1方法!
class com.hg.代理.Bservice.m3()方法耗时(纳秒):38900
我们可以看到并没有去修改ServiceA和ServiceB的方法,只是给Iservice接口创建了一个代理类,通过代理类去访问目标对象,需要添加一个公共的方法都放在了代理中,当领导有其他需求的时候只需要修改ServiceProxy的代码,方便系统扩展和测试。
如果现在我们需要给系统的每一个接口都加上统计耗时的功能,如果按照上面的方式,那么每一接口都要创建代理类,此时代码量和工作量巨大,我们强烈的需要一个通用的代理类对象。
Jdk代理详解:
jdk中自带的代理类
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
第一种方式:使用步骤就是:
1.调用Proxy.getProxyClass方法获取代理类的Class对象
2.使用InvocationHandler接口创建代理类的处理器
3.通过代理类和InvocationHandler创建代理对象
4.上面已经创建好代理对象了,接着我们就可以使用代理对象了
@Test public void test3() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { // 1. 获取接口对应的代理类 Class<?> proxyClass = Proxy.getProxyClass(this.getClass().getClassLoader(), IService.class); // 2. 创建代理类的处理器 InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName()); return null; } }; // 3. 创建代理实例 IService proxyService = (IService) proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler); // 4. 调用代理的方法 proxyService.m1(); proxyService.m2(); proxyService.m3(); }
第二种方式:步骤如下
1.使用InvocationHandler接口创建代理类的处理器
2.使用Proxy类的静态方法newProxyInstance直接创建代理对象
3.使用代理对象
代码如下:
@Test public void test4() { //1.创建代理类的处理器 InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName()); return null; } }; // 2. 创建代理实例 IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler); // 3. 调用代理的方法 proxyService.m1(); proxyService.m2(); proxyService.m3(); }
这两种效果一样的。
案例:任意接口中的方法耗时统计
public class CostTimeInvocationHandler implements InvocationHandler { /** * 目标类对象 */ private Object target; public CostTimeInvocationHandler(Object target) { this.target = target; } /** * 执行方法 * * @param proxy proxy * @param method method * @param args args * @return Object * @throws Throwable e */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long starTime = System.nanoTime(); Object result = method.invoke(this.target, args); long endTime = System.nanoTime(); System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime)); return result; } /** * 用来创建targetInterface接口的代理对象 * * @param target 需要被代理的对象 * @param targetInterface 被代理的接口 * @param <T> * @return */ public static <T> T createProxy(Object target, Class<T> targetInterface) { if (!targetInterface.isInterface()) { throw new IllegalStateException("targetInterface必须是接口类型!"); } else if (!targetInterface.isAssignableFrom(target.getClass())) { throw new IllegalStateException("target必须是targetInterface接口的实现类!"); } return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CostTimeInvocationHandler(target)); } }
测试代码:
@Test public void test5() { IService serviceA = CostTimeInvocationHandler.createProxy(new Aservice(), IService.class); IService serviceB = CostTimeInvocationHandler.createProxy(new Bservice(), IService.class); serviceA.m1(); serviceA.m2(); serviceA.m3(); serviceB.m1(); serviceB.m2(); serviceB.m3(); }
我是ServiceA中的m1方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):367500
我是ServiceA中的m2方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):34800
我是ServiceA中的m3方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):28200
我是ServiceB中的m1方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):49500
我是ServiceB中的m2方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):26700
我是ServiceB中的m3方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):23500
可以看到正常运行。当我们需要创建一个新的接口的时候不需要新建一个代理类,只需要使用CostTimeInvocationHandler.createProxy 去创建一个新的代理对象就行了,方便了很多。
Proxy使用的注意事项:
1.jdk代理只能给接口生成代理类,如果想给某一个类生成代理对象那么就可以考虑使用cglib代理
2.Proxy类中提供的几个常用的静态方法需要掌握
3.通过Proxy创建代理对象,当调用代理对象任意方法的时候,会被invocationHandler接口中的invoke方法进行处理,接口是关键。
cglib代理:
在Java中,cglib是一个强大的代码生成库,它可以用来生成Java类的子类,同时能够拦截被代理类的方法调用并添加额外的功能。
在cglib中,代理生成的方式分为两种:基于接口的动态代理和基于类的动态代理,其中基于类的动态代理是借助于ASM框架实现的,下面我们来详细了解一下cglib的基于类的动态代理原理。
cglib基于类的动态代理是通过继承被代理类来实现的,它的代理机制可以分为两步:
1.生成代理对象的子类: 在这一步中,cglib会在内存中动态生成一个被代理类的子类,同时该子类会重写被代理类中的所有非final方法,并在方法前后插入增强逻辑。
这里需要注意的是,为了避免出现循环调用,cglib还会将被代理类的非private方法和代理类中重写的方法区分开来,并且调用它们的时候使用不同的方法索引。
2.代理对象实例化: 在这一步中,cglib会创建代理对象的子类实例,并将被代理对象的引用传递给代理类,从而通过代理类调用被代理类中的方法时,就会自动执行代理类中添加的增强逻辑。
总而言之,cglib生成代理子类的过程中,就是使用ASM框架生成代理类的字节码,并通过字节码重写非final方法的实现,从而添加额外的逻辑增强,完成了对代理对象行为的扩展。与其他代理框架相比,cglib代理的效率更高,并且可以代理没有实现接口的类。同时,由于cglib生成代理类强制覆盖被代理类的所有非final方法,因此cglib代理的约束条件更严格,需要被代理类存在无参构造器才能生成代理子类,这是值得注意的。
CGLIB案例一:
1、定义一个普通的类
public class Service1 { public void m1() { System.out.println("我是m1方法"); } public void m2() { System.out.println("我是m2方法"); } }
2、使用代理
@Test public void test() { //使用Enhancer来给某个类创建代理类,步骤 //1.创建Enhancer对象 Enhancer enhancer = new Enhancer(); //2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类 enhancer.setSuperclass(Service1.class); /*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口, 此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口, 当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/ enhancer.setCallback(new MethodInterceptor() { /** * 代理对象方法拦截器 * @param o 代理对象 * @param method 被代理的类的方法,即Service1中的方法 * @param objects 调用方法传递的参数 * @param methodProxy 方法代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("调用方法:" + method); //可以调用MethodProxy的invokeSuper调用被代理类的方法 Object result = methodProxy.invokeSuper(o, objects); return result; } }); //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下 Service1 proxy = (Service1) enhancer.create(); //5.调用代理对象的方法 proxy.m1(); proxy.m2(); }
案例2:
1、创建普通类对象,但是方法m1中进行调用了m2();
public class Service2 { public void m1() { System.out.println("我是m1方法"); this.m2(); //@1 } public void m2() { System.out.println("我是m2方法"); } }
2、
@Test public void test2() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service2.class); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("调用方法:" + method); Object result = methodProxy.invokeSuper(o, objects); return result; } }); Service2 proxy = (Service2) enhancer.create(); proxy.m1(); //@1 }
输出结果:
调用方法:public void com.hg.代理.cglib.Service2.m1()
我是m1方法
调用方法:public void com.hg.代理.cglib.Service2.m2()
我是m2方法
只是调动了m1()方法,但是出现了2次方法调用。
案例三:拦截方法并且返回固定的值:
我们可以使用FixedValue接口。具体做法如下:
1、创建普通类
public class Service3 { public String m1() { System.out.println("我是m1方法"); return "hello:m1"; } public String m2() { System.out.println("我是m2方法"); return "hello:m2"; } }
2、测试类,可以看到设置callback的时候使用函数式接口,进行返回固定值。
@Test public void test3() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service3.class); enhancer.setCallback(new FixedValue() { @Override public Object loadObject() throws Exception { return "固定值"; } }); Service3 proxy = (Service3) enhancer.create(); System.out.println(proxy.m1());//@1 System.out.println(proxy.m2()); //@2 System.out.println(proxy.toString());//@3 }
输出结果是:
固定值
固定值
固定值
案例四:直接放行不做任何操作:这个是搞笑的吧,直接放行?
原理是使用callback下的一个子接口,NoOp,将他当做callback的时候,被调用的直接放行了,像没有做任何代理一样,直接进入目标类的方法了。
@Test public void test6() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service3.class); enhancer.setCallback(NoOp.INSTANCE); Service3 proxy = (Service3) enhancer.create(); System.out.println(proxy.m1()); System.out.println(proxy.m2()); }
输出结果是:
我是m1方法
hello:m1
我是m2方法
hello:m2
案例五:不同的方法使用不同的拦截器(callbackFilter)
1.创建类
public class Service4 { public void insert1() { System.out.println("我是insert1"); } public void insert2() { System.out.println("我是insert2"); } public String get1() { System.out.println("我是get1"); return "get1"; } public String get2() { System.out.println("我是get2"); return "get2"; } }
2.我的需求是给insert开头的方法统计耗时,以get开头的方法返回固定的字符串。
@Test public void test4() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service4.class); Callback[] callbacks = { new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { long starTime = System.nanoTime(); Object result = methodProxy.invokeSuper(o, objects); long endTime = System.nanoTime(); System.out.println(method + ",耗时(纳秒):" + (endTime - starTime)); return result; } }, new FixedValue() { @Override public Object loadObject() throws Exception { return "固定值"; } } }; enhancer.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { return 0; } }); enhancer.setCallbacks(callbacks); enhancer.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { String methodName = method.getName(); return methodName.startsWith("insert") ? 0 : 1; } }); Service4 proxy = (Service4) enhancer.create(); System.out.println("---------------"); proxy.insert1(); System.out.println("---------------"); proxy.insert2(); System.out.println("---------------"); System.out.println(proxy.get1()); System.out.println("---------------"); System.out.println(proxy.get2()); }
执行结果:
---------------
我是insert1
public void com.hg.代理.cglib.Service4.insert1(),耗时(纳秒):10729500
---------------
我是insert2
public void com.hg.代理.cglib.Service4.insert2(),耗时(纳秒):54600
---------------
固定值
---------------
固定值
可以对例子五进行一个优化:使用callbackHelper
@Test public void test5() { Enhancer enhancer = new Enhancer(); //创建2个Callback Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> { long starTime = System.nanoTime(); Object result = methodProxy.invokeSuper(o, objects); long endTime = System.nanoTime(); System.out.println(method + ",耗时(纳秒):" + (endTime - starTime)); return result; }; //下面这个用来拦截所有get开头的方法,返回固定值的 Callback fixdValueCallback = (FixedValue) () -> "固定值"; CallbackHelper callbackHelper = new CallbackHelper(Service4.class, null) { @Override protected Object getCallback(Method method) { return method.getName().startsWith("insert") ? costTimeCallback : fixdValueCallback; } }; enhancer.setSuperclass(Service4.class); //调用enhancer的setCallbacks传递Callback数组 enhancer.setCallbacks(callbackHelper.getCallbacks()); /** * 设置CallbackFilter,用来判断某个方法具体走哪个Callback 运行输出: 输出效果和案例4一模一样的,上面重点在于 CallbackHelper ,里面做了一些封装,有兴趣的可 以去看一下源码,比较简单。 */ enhancer.setCallbackFilter(callbackHelper); Service4 proxy = (Service4) enhancer.create(); System.out.println("---------------"); proxy.insert1(); System.out.println("---------------"); proxy.insert2(); System.out.println("---------------"); System.out.println(proxy.get1()); System.out.println("---------------"); System.out.println(proxy.get2()); }
执行结果:
---------------
我是insert1
public void com.hg.代理.cglib.Service4.insert1(),耗时(纳秒):13787200
---------------
我是insert2
public void com.hg.代理.cglib.Service4.insert2(),耗时(纳秒):64900
---------------
固定值
---------------
固定值
例子六:实现通用的统计任意类方法耗时的代理类
public class CostTimeProxy implements MethodInterceptor { //目标对象 private Object target; public CostTimeProxy(Object target) { this.target = target; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { long starTime = System.nanoTime(); //调用被代理对象(即target)的方法,获取结果 Object result = method.invoke(target, objects); long endTime = System.nanoTime(); System.out.println(method + ",耗时(纳秒):" + (endTime - starTime)); return result; } /** * 创建任意类的代理对象 * * @param target * @param <T> * @return */ public static <T> T createProxy(T target) { CostTimeProxy costTimeProxy = new CostTimeProxy(target); Enhancer enhancer = new Enhancer(); enhancer.setCallback(costTimeProxy); enhancer.setSuperclass(target.getClass()); return (T) enhancer.create(); } }
@Test public void test7() { //创建Service1代理 Service1 service1 = CostTimeProxy.createProxy(new Service1()); service1.m1(); //创建Service3代理 Service3 service3 = CostTimeProxy.createProxy(new Service3()); System.out.println(service3.m1()); }
执行结果:
我是m1方法
public void com.hg.代理.cglib.Service1.m1(),耗时(纳秒):86600
我是m1方法
public java.lang.String com.hg.代理.cglib.Service3.m1(),耗时(纳秒):77200
hello:m1
CGLIB和JDK代理的区别
本文来自博客园,作者:程序员鲜豪,转载请注明原文链接:https://www.cnblogs.com/hg-blogs/p/17314887.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix