静态代理简单使用
静态代理是代理模式的一种实现方式,它在编译时就已经确定了被代理对象和代理对象的关系。在静态代理中,需要手动创建一个代理类,该代理类与被代理对象实现相同的接口或继承相同的父类,并在代理类的方法中调用被代理对象的方法。
静态代理的例子:
package com.yimeng.proxy;import com.yimeng.service.BuyPhoneService;/*** @Author yimeng* @Date 2024/9/26 23:22* @PackageName:com.yimeng.proxy* @ClassName: BuyPhoneProxy* @Description: TODO* @Version 1.0*/
public class BuyPhoneProxy implements BuyPhoneService {private final BuyPhoneService buyPhoneService;public BuyPhoneProxy(BuyPhoneService buyPhoneService) {this.buyPhoneService = buyPhoneService;}@Overridepublic void buyPhone() {System.out.println("内部优惠打八折");buyPhoneService.buyPhone();System.out.println("送手机大礼包!!!");}
}
package com.yimeng.service.impl;import com.yimeng.service.BuyPhoneService;/*** @Author yimeng* @Date 2024/9/26 23:21* @PackageName:com.yimeng.service.impl* @ClassName: BuyPhoneServiceImpl* @Description: TODO* @Version 1.0*/
public class BuyPhoneServiceImpl implements BuyPhoneService {@Overridepublic void buyPhone() {System.out.println("买手机");}
}
package com.yimeng.service;/*** @Author yimeng* @Date 2024/9/26 23:21* @PackageName:com.yimeng.service* @ClassName: BuyPhoneService* @Description: TODO* @Version 1.0*/
public interface BuyPhoneService {void buyPhone();
}
package com.yimeng;import com.yimeng.proxy.BuyPhoneProxy;
import com.yimeng.service.BuyPhoneService;
import com.yimeng.service.impl.BuyPhoneServiceImpl;/*** @Author yimeng* @Date 2024/9/26 23:22* @PackageName:com.yimeng* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {BuyPhoneService buyPhoneService = new BuyPhoneServiceImpl();System.out.println("使用代理前");System.out.println("----------------------");buyPhoneService.buyPhone();System.out.println("----------------------");System.out.println("使用代理后");System.out.println("----------------------");BuyPhoneProxy buyPhoneProxy = new BuyPhoneProxy(buyPhoneService);buyPhoneProxy.buyPhone();}
}
结果:
在编译的时候,就知道要代理的对象是谁了。
静态代理最大的缺点就是:代码是写死的。如果要给很多类都要加上执行前的输出和执行后的输出,那么就给每一个需要增强的类,创建一个代理类了,这样的话会导致代码冗余,增加维护成本,如果要修改,那么就要把所有的代理类都修改。但是如果使用动态代理,那么就灵活了,只要写一个代理类就行了,这代理类原来执行什么方法,通过反射或者FastClass机制就可以知道了。这种情况下,如果要修改增强的内容,只要改这一个代理类就行了。
动态代理简单使用
动态代理是一种在运行时生成代理类的机制,它允许我们在不事先知道被代理对象的具体类型的情况下创建代理对象。在动态代理中,代理类是在运行时动态生成的,而不是在编译时静态生成的。所以他最大的好处,就是动态性和灵活性。
JDK代理(提供实现类)
jdk代理演示:
package com.yimeng.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @Author yimeng* @Date 2024/9/25 23:50* @PackageName:com.yimeng.proxy* @ClassName: HelloServiceProxy* @Description: TODO* @Version 1.0*/
public class HelloServiceProxy implements InvocationHandler {/*** 真实的服务对象*/private Object target;/**** @param target* @return 绑定委托对象并返回一个代理类*/public Object bind(Object target) {this.target = target;//取得代理对象//public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException//loader服务对象的类加载器,interfaces是加载服务对象的接口,h是执行这个代理类的方法的执行者return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}/*** 通过代理对象调用方法首先进入这个方法* invoke方法的参数分别是:proxy是代理对象,method是被调用的方法,args是方法的参数*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("#########我是JDK动态代理##########");Object result = null;//反射方法前调用System.out.println("我准备说hello");result = method.invoke(target, args);//反射方法后调用System.out.println("我说过hello了");return result;}
}
package com.yimeng.service.impl;import com.yimeng.service.HelloService;/*** @Author yimeng* @Date 2024/9/25 23:49* @PackageName:com.yimeng.service.impl* @ClassName: HelloServiceImpl* @Description: TODO* @Version 1.0*/
public class HelloServiceImpl implements HelloService {@Overridepublic void sayHello(String name) {System.out.println("hello "+name);}
}
package com.yimeng.service;/*** @Author yimeng* @Date 2024/9/25 23:49* @PackageName:com.yimeng.service* @ClassName: HelloService* @Description: TODO* @Version 1.0*/
public interface HelloService {public void sayHello(String name);
}
package com.yimeng;import com.yimeng.proxy.HelloServiceProxy;
import com.yimeng.service.HelloService;
import com.yimeng.service.impl.HelloServiceImpl;/*** @Author yimeng* @Date 2024/9/25 23:51* @PackageName:com.yimeng* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {HelloServiceProxy HelloHandler = new HelloServiceProxy();HelloService proxy = (HelloService)HelloHandler.bind(new HelloServiceImpl());proxy.sayHello("张三");}
}
结果:
使用关键:
-
创建一个类作为代理类,实现InvocationHandler接口,并重写invoke方法。在
invoke
方法中我们一般会调用原生方法(被代理类的方法)并自定义一些增强的逻辑;(当然也可以不调用原生方法,这样就相当于是替代了,不是增强了,看你想怎么用吧) -
使用Proxy.newProxyInstance(类加载器, 被代理类实现的接口数组, InvocationHandler接口的实现类对象);创建出一个代理对象。并且创建出来的对象表面一般声明为接口那个类型。
newProxyInstance()方法需要三个参数:
- 类加载器:类加载器对象。不一定要是"目标对象.getClass().getClassLoader()"才能获取到类加载器,任何一个非java内置类的对象A,都可以通过
对象A.getClass().getClassLoader()
获取加载该对象A对应类的类加载器,并且只要获取到的类加载器可以加载目标接口的实现类就行,我们使用“目标对象.getClass().getClassLoader()”和“第三方类库中类的对象或者自定义类的对象.getClass().getClassLoader()”获取到的类加载器都是一个,因为目标对象一般也是我们自定义的类或者第三方类库的类,只要是自定义的类或者第三方类库的类都是用一个类加载器加载的。为什么说不能是java内置类的对象呢?因为java内置类是由特定类加载器Bootstrap ClassLoader加载的(这里其实有涉及到类加载器的知识,什么双委派机制等,这里不展开讲)。java内置类是由Bootstrap ClassLoader
加载的,而Bootstrap ClassLoader
由于是使用本地代码实现的,所以它不是真正的 Java 类加载器实例,也不能用于动态代理,所以说不能用java内置类的对象.getClass().getClassLoader()
。 - 接口数组:被代理类实现的接口数组。
目标对象.getClass().getInterfaces()
可以获取到目标对象实现的所有接口们。目标对象实现的所有接口的所有方法都会被增强。 - 处理器:InvocationHandler接口的实现类对象。到时候执行被代理方法的时候,就是执行InvocationHandler接口的实现类对象的invoke方法的。
- 类加载器:类加载器对象。不一定要是"目标对象.getClass().getClassLoader()"才能获取到类加载器,任何一个非java内置类的对象A,都可以通过
-
使用这个声明为接口类型但实际为代理对象的这个变量调用接口被增强的方法。
注意:
这里的代理是,对原来的业务进行增强。
JDK动态代理和静态代理的区别:JDK动态代理用到了反射,即,代理类编译的时候不需要知道要代理谁,去执行什么方法,但是,运行的时候,程序能判断出来去代理那个方法了。静态代理是,编译的时候就需要知道要代理哪个类,执行什么方法了。
JDK代理(不提供实现类)
JDK代理,代理的是接口,那么可以想一想,既然代理的是接口,那如果没有实现类怎么办,能不能代理。答案是可以的,Mybatis就是这样的。
例子:
package com.yimeng;/*** @Author yimeng* @Date 2024/10/6 10:13* @PackageName:PACKAGE_NAME* @ClassName: com.yimeng.IHello* @Description: TODO* @Version 1.0*/
public interface IHello {String say(String aa);
}
package com.yimeng;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @Author yimeng* @Date 2024/10/6 10:14* @PackageName:com.yimeng* @ClassName: FacadeProxy* @Description: TODO* @Version 1.0*/
public class FacadeProxy implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args){System.out.println("接口方法调用开始");//执行方法System.out.println("method toGenericString:"+method.toGenericString());System.out.println("method name:"+method.getName());System.out.println("method args:"+(String)args[0]);System.out.println("接口方法调用结束");return "调用返回值";}public <T> T bind(Class<T> mapperInterface) {// 也可以使用接口来获取类加载器ClassLoader classLoader = mapperInterface.getClassLoader();Class<?>[] interfaces = new Class[]{mapperInterface};return (T) Proxy.newProxyInstance(classLoader, interfaces, this);}
}
package com.yimeng;/*** @Author yimeng* @Date 2024/10/6 10:14* @PackageName:com.yimeng* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {FacadeProxy facadeProxy=new FacadeProxy();IHello hello = facadeProxy.bind(IHello.class);System.out.println(hello.say("hello world"));}
}
结果:
看到,这里没有传实现类,结果也是一样可以的。
在invoke方法中少了result = method.invoke(target, args);而已,就没有调用实现类的方法了,因为根本没有实现类嘛。
对于创建代理对象的方法中(这里是bind方法,其实叫什么方法名都行),我们看到和原来有实现类的做法也是类似的,都是通过Proxy.newProxyInstance方法来创建代理对象的。就是使用的参数有一些区别:
- 获取类加载,这里使用了接口来获取类加载器(接口也可以获取类加载器,这一点我之前不知道)(不一定要用那个要增强的接口,随便用一个你自定义类或者第三方类库的类来获取类加载器也行,反正只是为了获取类加载器而已)
- 直接使用接口来创建数组了。之前是使用“实现类对象.getClass().getInterfaces()”来获取这个实现类实现的所有接口,然后对所有接口中的方法进行增强。我们直接使用接口来创建数组,那么只有这个数组中的接口的方法才能得到增强。
- 第三个参数还是一样
mybatis其实就是通过这里这样的做法,拿到接口的方法上面的信息,知道代理对象中的对应方法应该完成什么样的功能的。
CGLib代理
没有引入spring的情况下,需要单独引入cglib架包。spring3.2以后的环境下,不用单独引入cglib架包了,因为从spring3.2以后,cglib.jar已经被spring项目的jar包集成进去了。
这里我使用非spring环境执行的,所以需要引入jar包:
<dependencies><dependency><groupId>cglib</groupId><artifactId>cglib-nodep</artifactId><version>3.2.4</version></dependency>
</dependencies>
package invocation;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;/*** @Author yimeng* @Date 2024/9/28 23:24* @PackageName:invocation* @ClassName: LawInterceptor* @Description: TODO* @Version 1.0*/
public class LawInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// MethodInterceptor#intercept拦截方法的实现逻辑时,最好指定你想要增强目标对象的哪些方法,否则CGLIB默认会增强目标对象及其父类的所有非final方法、非private方法。// 指定要增强的方法也很简单,只要使用方法名来判断一般就可以了,如果有重载,那么你把参数也加上判断就可以了。if(isMethodsThatNeedToBeEnhanced(method)){// 增强的内容System.out.print("`律师向原告了解案情,并代替`");// 调用被代理对象的方法(被增强前的方法)Object result = methodProxy.invokeSuper(o, objects);// 如果要改造增强后方法的返回值,那么可以这样做return "被增强的collect(String evidence)返回值,替代了增强前方法的返回值";// 如果要使用增强方法的返回值作为增强后方法的返回值,下面这样直接返回就行了// return result;}else {// 不增强(原来类所有的非final方法、非private方法都会被增强,为了让不想增强的保持原样,我们可以这样,就让增强后的效果是:执行原来方法,并返回增强后的方法返回值就行了)Object result = methodProxy.invokeSuper(o, objects);return result;}}// 判断是否是需要增强的方法。通过方法名和形参来判断就行了private boolean isMethodsThatNeedToBeEnhanced(Method method) {// 判断方法名称是否为"collect"或"lawsuit"if (!"collect".equals(method.getName()) && !"lawsuit".equals(method.getName())) {return false;}// 获取方法的参数类型数组Class<?>[] parameterTypes = method.getParameterTypes();if ("collect".equals(method.getName())) {
// // 如果目标方法 "collect" 接受一个 String 和一个 int 作为参数。那么可以下面这样写:
// return Arrays.equals(parameterTypes, new Class<?>[]{String.class, int.class});// 这里我们目标方法 "collect" 只接受一个 String 参数,所以这样写// 判断参数类型是否匹配return Arrays.equals(parameterTypes, new Class<?>[]{String.class});}// 假设目标方法 "lawsuit" 不接受任何参数if ("lawsuit".equals(method.getName())) {// 判断是否是无参数方法return parameterTypes.length == 0;}return false;}
}
package service.impl;import service.LawEvidence;/*** @Author yimeng* @Date 2024/9/28 23:21* @PackageName:service.impl* @ClassName: LawEvidenceImpl* @Description: TODO* @Version 1.0*/
public class LawEvidenceImpl implements LawEvidence {@Overridepublic String collect(String evidence) {System.out.println(evidence);return "没有被增强的collect(String evidence)返回值";}@Overridepublic String collect() {System.out.println("没有被增强的collect方法执行了");return "没有被增强的collect()返回值,没有被增强";}
}
package service.impl;import service.Lawsuit;/*** @Author yimeng* @Date 2024/9/28 23:22* @PackageName:service.impl* @ClassName: LawsuitImpl* @Description: TODO* @Version 1.0*/
public class LawsuitImpl implements Lawsuit {@Overridepublic void lawsuit() {System.out.println("原告打官司");}
}
package service;/*** @Author yimeng* @Date 2024/9/28 23:20* @PackageName:service* @ClassName: LawEvidence* @Description: TODO* @Version 1.0*/
public interface LawEvidence {String collect(String evidence);String collect();
}
package service;/*** @Author yimeng* @Date 2024/9/28 23:22* @PackageName:service.impl* @ClassName: Lawsuit* @Description: TODO* @Version 1.0*/
public interface Lawsuit {void lawsuit();
}
import invocation.LawInterceptor;import net.sf.cglib.proxy.Enhancer;
import service.impl.LawEvidenceImpl;
import service.impl.LawsuitImpl;/*** @Author yimeng* @Date 2024/9/28 23:15* @PackageName:PACKAGE_NAME* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {Enhancer evidenceEnhancer = new Enhancer();evidenceEnhancer.setSuperclass(LawEvidenceImpl.class);evidenceEnhancer.setCallback(new LawInterceptor());LawEvidenceImpl evidenceProxy = (LawEvidenceImpl) evidenceEnhancer.create();System.out.println(evidenceProxy.collect("原告提供证据"));System.out.println(evidenceProxy.collect());Enhancer lawsuitEnhancer = new Enhancer();lawsuitEnhancer.setSuperclass(LawsuitImpl.class);lawsuitEnhancer.setCallback(new LawInterceptor());LawsuitImpl lawsuitProxy=(LawsuitImpl)lawsuitEnhancer.create();lawsuitProxy.lawsuit();}
}
结果:
MethodInterceptor接口的intercept方法有4个参数
- obj: 增强的对象
- method: 被拦截的方法
- objects: 被拦截方法的参数
- methodProxy: java.lang.reflect.Method类的代理类,可以实现对目标类方法的调用
MethodInterceptor接口的intercept方法的返回值
在 CGLIB 的
MethodInterceptor
拦截器中,intercept()
方法的返回值决定了被代理对象的增强方法(即被拦截的方法)执行后的返回结果。
intercept()
方法的返回值的作用:
- 传递实际返回值: 通常情况下,你会调用
methodProxy.invokeSuper()
来执行原始目标方法,并将其结果返回。这样,代理对象的方法调用和未代理时的调用行为保持一致,返回原本应该返回的值。例如,如果目标方法返回一个计算结果或者查询的值,拦截器的返回值就成为调用方接收到的值。- 修改返回结果: 你可以通过
intercept()
方法拦截目标方法,执行某些自定义逻辑,然后根据需求修改目标方法的返回值。例如,如果目标方法返回一个数字,你可以在拦截器中对该数字进行修改,然后返回一个不同的值。- 阻止目标方法的执行: 如果你不调用
methodProxy.invokeSuper()
,而是直接返回某个值,这样可以避免实际调用目标方法。这种情况下,目标方法的逻辑会被完全跳过,调用方只会接收到你返回的值,而不会知道原始方法是否被执行。常见的几种使用方式:
1. 直接返回目标方法的执行结果:
这是最常见的使用方式,拦截器增强方法后,将原方法的结果返回。
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 执行目标方法并返回其结果Object result = methodProxy.invokeSuper(o, args);return result; }
2. 修改返回值:
你可以在目标方法执行后修改其返回值。
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 执行目标方法Object result = methodProxy.invokeSuper(o, args);// 如果返回的是一个数字,可以在这里做修改if (result instanceof Integer) {result = (Integer) result + 100; // 修改返回值}return result; }
3. 阻止方法执行并返回自定义值:
如果你不希望目标方法被执行,直接返回一个自定义值即可。
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 如果方法名称为某个特定值,直接返回自定义的结果if ("someMethod".equals(method.getName())) {return "Method execution was intercepted!"; // 自定义返回值}// 否则执行原方法return methodProxy.invokeSuper(o, args); }
总结:
- 如果你希望目标方法执行,并将结果返回给调用方,
intercept()
方法需要返回methodProxy.invokeSuper()
的结果。- 如果你想修改或替换返回结果,则可以在
intercept()
方法中自定义返回值。- 如果你不希望目标方法执行,直接返回你自定义的值,这样目标方法的逻辑将不会被触发。
注意事项:
-
MethodInterceptor接口里如果想要调用原目标对象的方法,必须使用methodProxy#invokeSuper方法,而不是使用methodProxy#invoke方法。如果使用invoke方法会发生无限循环调用的问题,你可以简单理解为invokeSuper是调用的目标对象的原方法,而invoke是调用的代理对象的增强方法,这就导致了程序再一次进入到增强的拦截方法intercept里,周而复始。
-
在写MethodInterceptor#intercept拦截方法的实现逻辑时,最好指定你想要增强目标对象的哪些方法,如果不指定,那么CGLIB默认会增强目标对象及其父类的所有非final方法、非private方法。
-
JDK的增强方式,代理类是实现InvocationHandler接口,而CGLIB的增强方式代理类是实现MethodInterceptor接口
-
CGLIB不是通过Java反射去做到对目标方法的动态调用的。CGLIB是采用FastClass方式去调用目标方法的,这个方式比反射快得多,反射比较慢是因为,反射需要,经过一系列权限校验、JVM方法区查找方法定义、native方法调用等动作,这些动作都是比较慢的。但是,FastClass方式调用目标对象的方法就类似于直接调用某个具体对象的方法,就不需要经过反射那些繁重的步骤了,FastClass底层其实是通过一系列规则,让invokeSuper方法能找到要执行的目标方法,然后去执行对应的方法就行了,就类似于直接调用目标方法,所以速度很快。
总之:FastClass方式算是一种技巧层面的东西,他通过在java内存里边维护一个index值和目标对象的方法之间的逻辑映射,然后运行期可以根据index和实例来动态调用方法、且不用使用比较“重”的Java反射功能。为什么说这个是利用了FastClass的方式来调用目标方法的,其实是因为,CGLib生成的代理类我们通过反编译可以发现,他们其实都是继承FastClass这个类的,并且重写了FastClass的getIndex()方法和invoke()方法。
研究
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM(Java 字节码操作和分析框架)的字节码生成库,用于在运行时动态生成 Java 类的子类,实现动态代理的功能。CGLIB 动态代理是一种基于类的动态代理方式,不需要接口。
CGLIB 动态代理的原理如下:
- 首先,CGLIB 通过动态地创建一个继承被代理类的子类。
- 然后在子类中,通过方法拦截器(MethodInterceptor)来拦截被代理类的方法调用。
- 当调用代理对象的方法时,子类会委托给方法拦截器处理,在处理逻辑中可以加入我们自己的增强逻辑。
- 最后,方法拦截器可以决定是否调用被代理类的原始方法或直接返回结果。
CGLIB 动态代理的优点主要是:
- 不需要被代理类实现接口:相比基于接口的JDK动态代理,CGLIB 可以代理没有实现任何接口的类。
- CGLIB动态代理比JDK动态代理快。使用CGLib去执行增强的方法时,他去执行原有方法的时候,不是通过反射去执行的,而是使用FastClass机制,类似于直接调用的(和我们自己手动写了一个静态代理一样),所以速度快。反射的话,需要经过一系列的权限验证、通过native方法请求jvm去方法区查找方法定义、以及最后的invoke仍然可能要通过JNI调用native方法,所以速度会比较慢。
CGLIB 动态代理也有一些限制和注意事项:
- 无法代理 final 方法和 final 类。
- 对于私有方法和方法实现为 final 的方法,也无法直接代理。
- 对于静态方法,CGLIB 会直接调用原始方法,不会进行代理。