Java动态代理技术(JDK和CGLIB)

本文详细介绍了Java动态代理技术的两种实现方式:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口实现,需实现InvocationHandler接口并重写invoke方法;CGLIB动态代理则无需接口,通过继承被代理类生成子类实现。文中提供了具体代码示例,展示了如何创建代理对象、实现代理逻辑以及测试代理效果。

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方式的代理机制...哈哈
调用真实对象方法后...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值