代理模式详解

什么是代理

代理是一种设计模式,给真实对象提供代理,由代理对象进行真实对象的访问,就是隐藏真实对象对方法的直接调用,而是通过代理对象去调用真实的方法。

为什么要使用代理以及应用场景

因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问,比如在日常写业务代码中,一个类只处理自己相关的业务,但如果要在这个业务的前后打上日志,增加权限功能,直接添加到代码里不合适,而且每个类都需要修改,这时我们需要一个代理,在不修改源代码的前提下,实现一些功能,即增强,我们在进入目标类之前,先进入代理类,在代理类中写我们需要的额外功能。

代理分类

代理分为静态代理和动态代理,接下来我将用代码举例方式讲解这两种代理,并在最后给出结论
模拟场景:在实际业务代码的前后打上日志
前期准备:日志类,通用代码

/**
 * 增强类 打印日志
 */
public class LogHandle {
    public void before() {
        System.out.println("增加日志,输出前。。。。。");
    }

    public void after() {
        System.out.println("增加日志,输出后。。。。。");
    }
}

真实的业务对象,也就是要增强(代理)的对象,通用代码

/**
 * 真实业务对象, 本来这里要增加日志,需要在每个方法的执行前后都加上,现在代码无进入,不用修改
 */
public interface RealObject {
    void eat();
    void walk();
}

class RealObjectImpl implements RealObject {
    @Override
    public void eat() {
        System.out.println("eat.......");
    }

    @Override
    public void walk() {
        System.out.println("walk.......");
    }
}

重点开始,静态代理

// 创建代理对象
class StaticProxy implements RealObject {
    // 日志处理
    LogHandle logHandle = new LogHandle();

    // 相当于注入真实对象,以至于能调用真实对象的方法
    private RealObject target;

    public StaticProxy(RealObject target) {
        this.target = target;
    }

    @Override
    public void eat() {
        logHandle.before();
        target.eat();
        logHandle.after();
    }

    @Override
    public void walk() {
        logHandle.before();
        target.walk();
        logHandle.after();
    }
}

模拟客户端调用

public class StaticTest {
    // 模拟客户端调用
    public static void main(String[] args) {
        RealObjectImpl impl = new RealObjectImpl();
        // 代理对象进行调用,你要调用真实对象的方法,所以代理类必须实现真实对象的接口
        StaticProxy staticProxy = new StaticProxy(impl);
        staticProxy.eat();
        staticProxy.walk();
    }
}

分析:静态代理到此结束,通过上面的举例可以发现虽然静态代理实现简单,且不侵入原代码,但真实的业务对象很多,有很多类需要进行代理时,只有两种方法,要么一个代理类继承多个接口,要么创建多个代理类,这种缺点一般是难以接受的,所以才会有接下来的动态代理,而动态代理又分为两类,JDK动态代理和CGLIB动态代理

JDK动态代理

场景不变,还是在前面的通用代码,也是在真实对象的前后加上日志
静态代理是直接创建代理类,动态代理顾名思义代理类不是由我们手动创建的,而是在程序执行过程中自动帮我们创建的
JDK动态代理首先要创建处理类(注意这里不是代理类)实现InvocationHandler 接口,并且实现invoke方法

class DynamicJDKHandle implements InvocationHandler {

    LogHandle logHandle = new LogHandle();

    // 真实对象 类是Object,实际的方法执行者
    private Object target;

    public DynamicJDKHandle(Object target) {
        this.target = target;
    }

    /**
     *
     * @param proxy   代理实例
     * @param method  真实对象方法
     * @param args    方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logHandle.before();
        // 调用 target 的 method 方法
        Object invoke = method.invoke(target, args);
        logHandle.after();
        return invoke;
    }
}

模拟客户端

/**
JDK动态代理 这里代码是不会出现代理类的,代理类是动态生成的
*/
public class DynamicJDKTest {
    public static void main(String[] args) {
        RealObjectImpl realObject = new RealObjectImpl();

        ClassLoader classLoader = realObject.getClass().getClassLoader();
        Class<?>[] interfaces = realObject.getClass().getInterfaces();
        DynamicJDKHandle dynamicJDKProxy = new DynamicJDKHandle(realObject);

        // 创建一个代理对象 通过JDK帮我们创建的 强转为实现类 原来是Object ->>>> 转为真实对象 必须使用接口接收,否则会报类型转换异常
        RealObject proxy = (RealObject) Proxy.newProxyInstance(classLoader, interfaces, dynamicJDKProxy);
        // 代理对象调用真实方法,就会进入代理类的invoke
        proxy.eat();
    }
}

处理步骤如下:

  1. 处理类必须先实现InvocationHandler接口(这里不是代理类)
  2. 实现里面的invoke方法
  3. 创建代理对象:RealObject proxy =(RealObject)Proxy.newProxyInstance(classLoader, interfaces, dynamicJDKProxy);
  4. proxy.eat();代理对象调用真正方法,实际上是调用handle的invoke 这个就是调用真正的方法:Object invoke= method.invoke(target, args);

关键一点:为什么JDK动态代理只能代理接口

jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口

CGLIB动态代理

基于ASM机制实现,通过生成业务类的子类作为代理类
代码实现如下

/**
 * CGLIB动态代理 无需接口  通用类
 */
class RealCglibClass {
    public void eat() {
        System.out.println("eat.......");
    }

    public void walk() {
        System.out.println("walk.......");
    }
}

这里创建代理类实现MethodInterceptor接口,用于拦截方法回调

/**
 * 实现MethodInterceptor  用户方法回调
 */
class DynamicCglibInterceptor implements MethodInterceptor {

    LogHandle logHandle = new LogHandle();

    /**
     *
     * @param object   表示要进行增强的对象;
     * @param method   表示要被拦截的方法;
     * @param objects  表示要被拦截方法的参数;
     * @param methodProxy  表示要触发父类的方法对象。
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        logHandle.before();
        // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
        Object result = methodProxy.invokeSuper(object, objects);
        logHandle.after();
        return result;
    }

}

// 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
class CatFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("eat".equals(method.getName())) {
            System.out.println("执行eat方法");
            return 0;   // Callback 列表第1个拦截器
        }
        return 1;   // Callback 列表第2个拦截器,return 2 则为第3个,以此类推
    }
}

模拟客户端,需要导入asm和cglib的包

import net.sf.cglib.proxy.*;
import java.lang.reflect.Method;
/**
 1. CGLIB实现动态代理,通过生成业务类的子类作为代理类
 2. */
public class DynamicCglibTest {
    public static void main(String[] args) {
    // 增强器
        Enhancer enhancer = new Enhancer();
        // 代理类继承了业务类,所以业务类是超类
        enhancer.setSuperclass(RealCglibClass.class);
        // 回调方法
        enhancer.setCallback(new DynamicCglibInterceptor());

        // 设置过滤器
       /** DynamicCglibInterceptor dynamicCglibInterceptor = new DynamicCglibInterceptor();
        CatFilter catFilter = new CatFilter();
        enhancer.setCallback(dynamicCglibInterceptor);
        enhancer.setCallbackFilter(catFilter);
        */
		// 创建代理对象
        RealCglibClass proxy = (RealCglibClass) enhancer.create();
        proxy.eat();
        proxy.walk();
    }
}

步骤如下:

  1. 查找目标类上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求

JDK动态代理与CGLIB动态代理对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类

JDK动态代理的优缺点

jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
优点:解决了静态代理中冗余的代理实现类问题。而且是基于JDK实现的,不用额外导包
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

CGLIB动态代理

由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:CGLib在创建代理对象时所花费的时间却比JDK多一点

Spring AOP的实现就是动态代理,那它是采用哪种呢?
Spring Boot2.0 之前

如果代理对象有接口,就用 JDK 动态代理,否则就是 Cglib 动态代理。
如果代理对象没有接口,那么就直接是 Cglib 动态代理。

开发者可以通过配置文件进行配置

如果开发者设置了 spring.aop.proxy-target-class 为 false,则使用 JDK 代理。
如果开发者设置了spring.aop.proxy-target-class 为 true,则使用 Cglib 代理。
如果开发者一开始就没配置spring.aop.proxy-target-class 属性,则使用 JDK 代理

PS:从 Spring Boot2.0 开始,如果用户什么都没有配置,那么默认情况下使用的是 Cglib 代理。如果想使用JDK动态代理得手动配置

参考链接:
https://juejin.cn/post/6844903744954433544#comment
https://juejin.cn/post/7035880349422845960

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值