Java动态代理技术(JDK和CGLIB)
1. 动态代理技术
动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。代理的作用就是,在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象。我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象之间建立代理关系,所以代理必须分为两个步骤:
- 代理对象和真实对象建立代理关系
- 实现代理对象和代理逻辑方法
在这里将介绍两种常用代理技术:
- JDK动态代理技术:这是JDK自带的功能
- CGLIB动态代理技术:这是第三方提供的一个技术
2. JDK动态代理技术
2.1. 定义接口
JDK动态代理使Java.lang.reflect*包提供的方式,它必须借助一个接口才能产生代理对象,所以先定义接口。
public interface JDKReflect {
public void print();
public void print2(String str);
}
2.2. 提供实现类
package com.itlearn.service.impl; import com.itlearn.service.JDKReflect; public class JDKReflectImpl implements JDKReflect { @Override public void print() { System.out.println("这是JDK方式的代理机制..."); } @Override public void print2(String str) { System.out.println("这是JDK方式的代理机制..."+str); } }
2.3. 动态代理绑定和代理逻辑实现
有了接口和实现类,就可以开始动态代理了。按照要求,先要建立代理对象和真实服务对象的关系,然后实现代理逻辑,所以一个分为两个步骤。
在JDK动态代理中,要实现代理逻辑类必须实现 java.lang.reflect.InvocationHandler 接口,它里面定义了一个invoke方法,并提供接口数组用于下挂代理对象,代码如下:
package com.itlearn.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//动态代理绑定和代理逻辑实现
public class JdkProxyExample implements InvocationHandler {
//真实对象
private Object target = null;
/**
* 建立代理对象和真实对象的代理关系,并返回代理对象
* @param target 真实对象
* @return 代理对象
*/
public Object bind(Object target){
this.target = target;
//创建代理对象,并与真实对象绑定
Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
return proxyInstance;
}
/**
* 代理方法逻辑
* @param proxy :代理对象
* @param method :当前调度方法
* @param args:当前方法参数
* @return 代理结构返回
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入代理逻辑方法...");
System.out.println("在调度真实方法之前的服务...");
Object obj = method.invoke(target, args);//相当于调用print方法。
System.out.println("在调度真实对象之后的服务...");
return obj;
}
}
第一步:建立代理对象和真实对象的关系,这里是使用了bind方法去完成的,方法里面首先用类的属性target保存了真实对象,然后通过如下代码建立并生成了代理对象:
Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
找到newProxyInstance方法的源码如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {其中,newProxyInstance方法包含的三个参数:
- 第一个是类加载器,在这里我们采用了target即真实对象本身的类加载器。
- 第二个是把生成的动态代理对象下挂在哪些接口下,这个写法就是放在target实现的接口下,即:JDKReflect接口下。那么代理对象就可以这样声明:JDKReflect proxy = xxxx;
- 第三个是定义实现方法逻辑的代理类,this表示当前对象,它必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法的现实方法。
第二步:实现代理逻辑方法。invoke方法可以实现代理逻辑,invoke方法的3个参数含义如下:
- proxy :代理对象,即bind方法生成的对象。
- method:当前调度的方法
- args:调度方法的参数
当我们使用了代理对象调度了方法后,它就会进入到invoke方法内,而不会再进入真实对象的方法内部。
Object obj = method.invoke(target, args);这行代码就相当于调度真实对象的方法,只是通过反射实现而已。
2.4. 测试JDK动态代理
2.4.1. 测试无参方法
@Test public void testJdkProxy1(){ JdkProxyExample jdkProxyExample = new JdkProxyExample(); //绑定代理对象与真实对象关系,因为挂在JDKReflect接口下,所以声明代理对象类型位JDKReflect接口类型 JDKReflect proxy = (JDKReflect) jdkProxyExample.bind(new JDKReflectImpl()); //使用代理对象,利用反射技术调用方法 proxy.print(); }进入代理逻辑方法... 在调度真实方法之前的服务... 这是JDK方式的代理机制... 在调度真实对象之后的服务... 经过调试可知:在执行到proxy.print()时,会进入代理对象的invoke方法内部,而不会进入JdkProxyExample类的print()方法。所以会实现代理逻辑,完成动态代理。
2.4.2. 测试有参方法
@Test public void testJdkProxy2(){ JdkProxyExample jdkProxyExample = new JdkProxyExample(); //绑定代理对象与真实对象关系,因为挂在JDKReflect接口下,所以声明代理对象类型位JDKReflect接口类型 JDKReflect proxy = (JDKReflect) jdkProxyExample.bind(new JDKReflectImpl()); //使用代理对象,利用反射技术调用方法 proxy.print2("调用有参方法"); }进入代理逻辑方法... 在调度真实方法之前的服务... 这是JDK方式的代理机制...调用有参方法 在调度真实对象之后的服务...
3. CGLIB动态代理
3.1. CGLIB动态代理介绍
CGLIB动态代理机制与JDK代理机制不同,CGLIB动态代理不需要提供接口,只要一个非抽象类就能实现动态代理。
根据代理技术思想,CGLIB代理技术也分为两步,建立代理关系,与实现代理对象和代理逻辑方法。而CGLIB产生动态代理对象是采用的继承的思想,即代理对象就是被代理对象的一个子类,该代理对象继承被代理对象的所有方法,并且拥有自己的拦截方法。通过拦截方法实现或者重写被代理对象的方法对其进行增强。
代理类必须实现:net.sf.cglib.proxy.MethodInterceptor接口,并且重写:intercept方法,该方法即代理类的拦截方法。
3.2. 导入CGLIB所需依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
3.3. 创建被代理对象(不需要接口)
package com.itlearn.service.impl; public class CglibReflect { public void print() { System.out.println("这是CGLIB方式的代理机制..."); } public void print2(String str) { System.out.println("这是CGLIB方式的代理机制..."+str); } }
3.4. 实现动态代理
package com.itlearn.proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxyExample implements MethodInterceptor { /** * 生成CGLIB代理对象,即被代理对象的子类 * @param cls :Class类,即被代理的真实对象(父类) * @return 返回Class类的CGLIB代理对象(子类) */ public Object getProxy(Class cls){ //CGLIB 增强类对象:enhancer Enhancer enhancer = new Enhancer(); //设置增强类型||设置父类类型,创建的代理对象实际上就是Class cls的子类, // 代理对象会继承Class cls所有非private方法,并生成自己的拦截方法:intercept enhancer.setSuperclass(cls); //定义代理对象为当前对象,要求当前对象实现MethodInterceptor接口的intercept方法 enhancer.setCallback(this); //生成代理对象 Object proxy = enhancer.create(); //返回代理对象 return proxy; } /** * 代理逻辑方法 * @param proxy:代理对象 * @param method:当前调度方法 * @param args:当前调度方法参数 * @param methodProxy:方法代理 * @return:代理逻辑返回 * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("调用真实对象方法前..."); //CGLIB反射调用真实对象方法 Object result = methodProxy.invokeSuper(proxy, args); //使用method方法执行,则会产生死循环 //Object result = method.invoke(proxy,args); System.out.println("调用真实对象方法后..."); return result; } }注:代理对象继承的父类方法是不可见的。但可以在拦截方法intercept中通过MethodProxy methodProxy对象调用invokeSuper方法来反射执行父类方法。
另外,如果要调用父类方法,则不可以使用Method method的invoke方法调用。
Object result = method.invoke(proxy,args);可见,传入参数为当前代理对象,所以调用此方法实际就是调用该类的intercept本身,从而会产生死循环。
3.5. CGLIB代理测试
@Test public void testCglibProxy(){ CglibProxyExample cls = new CglibProxyExample(); //得到代理对象 CglibReflect proxy = (CglibReflect) cls.getProxy(CglibReflect.class); proxy.print2("哈哈"); }运行结果:
调用真实对象方法前... 这是CGLIB方式的代理机制...哈哈 调用真实对象方法后...
本文详细介绍了Java动态代理技术的两种实现方式:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口实现,需实现InvocationHandler接口并重写invoke方法;CGLIB动态代理则无需接口,通过继承被代理类生成子类实现。文中提供了具体代码示例,展示了如何创建代理对象、实现代理逻辑以及测试代理效果。
&spm=1001.2101.3001.5002&articleId=115185118&d=1&t=3&u=a218888be1b94bedb159943a08f4a6e4)
255

被折叠的 条评论
为什么被折叠?



