代理设计模式

本文介绍了代理设计模式的概念和作用,通过租房和打官司的例子解释了代理模式的基本思想。接着详细讲解了静态代理的实现,通过租房中介的角色展示了如何在不修改原对象的情况下扩展功能。然后讨论了JDK和CGLIB动态代理的使用,JDK动态代理基于接口,而CGLIB则不需要,但CGLIB可能与新JDK版本存在兼容问题。最后对比了两者的优势和劣势,并指出SpringAOP中动态代理的选择策略。
摘要由CSDN通过智能技术生成

一、什么是代理设计模式

代理设计模式,其实从名字上我们就可以知道这个设计模式就是会多出来一个代理,原本我们要通过一个对象进行的操作,现在由这个代理来完成

举个例子,就像我们去租房子,这本来是要由我们来做的,但是我们可能对一个地方的情况不熟悉,不知道哪里有房源,这时候我们可以叫中介帮我们找房子。达到的目的都是租房子,但是中介来做就可以更快,且有更多房源,这里的中介就相当于是一个代理

再举一个例子就像打官司,我们对法律知识不熟悉,想打赢官司,就可以找律师,这个律师就是一个代理

那么这个代理的好处,我们就可以理解为,更好,更全面的完成一件事情

使用代理设计模式可以在实现目标对象功能的基础上,增加额外的功能操作,扩展目标对象的功能,例如加上日志,事务

二、静态代理

以上面租房的例子来编写代理设计模式的例子

租房接口 - 表示租房这件事情

/**
 * @InterfaceName: IFindHouse
 * @Author: fang
 * @CreateTime: 
 * @Description: 租房接口
 */
public interface IFindHouse {
    void find();
}

租房实现类 - 表示我要租哪里的房子

/**
 * @ClassName: MyFindHouse
 * @Author: fang
 * @CreateTime: 
 * @Description: 租房实现类 - 找嘉禾望岗的房子
 */
public class MyFindHouse implements IFindHouse{
    @Override
    public void find() {
        System.out.println("我要找嘉禾望岗的房子");
    }
}

代理对象 - 租房中介

/**
 * @ClassName: HouseProxy
 * @Author: fang
 * @CreateTime: 
 * @Description: 代理 - 租房中介
 */
public class HouseProxy implements IFindHouse{
    private IFindHouse findHouse;

    public HouseProxy() {
        this.findHouse = new MyFindHouse();
    }

    @Override
    public void find() {
        before();
        findHouse.find();
        after();
    }

    /**
     * 前置方法,对目标方法的前置增强
     */
    private void before() {
        System.out.println("我有嘉禾望岗的房源");
    }

    /**
     * 后置方法,对目标方法的后置增强
     */
    private void after() {
        System.out.println("帮你和房东砍价");
    }
}

测试类

/**
 * @ClassName: Test
 * @Author: fang
 * @CreateTime:
 * @Description: 测试类
 */
public class MyTest {
    @Test
    public void test01(){
        IFindHouse findHouse = new HouseProxy();
        findHouse.find();
    }
}

可以看到,我们要去租嘉禾望岗的房子,使用了代理后,就是多了一个中介,为我们推送房源,还可以和房东砍价。对于目标方法,我们对其进行了前后的增强

可能有人会问,before 和 after 两个方法的内容,不是也可以直接写到 MyFindHouse 类的 find 方法中吗?为什么要特地写一个代理类来把目标方法包起来呢?

对此我的理解是这样的:

  1. 直接修改原有的方法,那会导致这个方法在其他地方的调用也受到影响,如果其他地方的调用不需要现在这样的增强呢?
  2. 这是一个简单的例子,实际的情况可能比这个要复杂得多,如果把全部的代码都写到一个方法中,那么一个方法的代码量太大,可读性太差
  3. Java 遵循“对扩展开放,对修改关闭”的原则,增加功能直接去修改原有代码这样违反了这一个原则
  4. 使用代理模式,我们需要增强一个方法就很灵活,在代理中编写的代码不会影响到原有方法的代码逻辑

使用代理模式,可以将功能划分更加清楚,便于后期的维护

三、动态代理

通过上面的静态代理,我们知道了代理设计模式可以未原本的方法进行加工,但是可以发现,静态代理也不是很方便,每多一个人找房子,我们就需要一个中介,代理类会很多

所以我们需要一种方式,可以实现动态的代理一些对象

3.1 JDK的动态代理

这个是JDK提供的一种动态代理方式,他只能代理接口

首先提供一个接口

/**
 * @InterfaceName: Source
 * @Author: fang
 * @CreateTime: 
 * @Description: 接口
 */
public interface Source {
    void method();
}

提供一个接口的实现类

/**
 * @ClassName: SourceImpl
 * @Author: fang
 * @CreateTime: 
 * @Description: 接口的实现类
 */
public class SourceImpl implements Source{
    @Override
    public void method() {
        System.out.println("核心业务流程");
    }
}

测试类

/**
 * @ClassName: ProxyTest
 * @Author: fang
 * @CreateTime: 
 * @Description:
 */
public class ProxyTest {
    private Source source = new SourceImpl();

    @Test
    public void test01() {
        Source sourceProxy = (Source) Proxy.newProxyInstance(source.getClass().getClassLoader(), source.getClass().getInterfaces(), new MyInvocationHandler(source));

        sourceProxy.method();
    }
}

JDK提供的代理类是 java.lang.reflect.Proxy ,调用 Proxy 中的 newProxyInstance() 方法,方法中需要传递三个参数

  1. ClassLoader loader:被代理对象的类加载器
  2. Class<?>[] interfaces:被代理对象实现的接口的集合
  3. InvocationHandler h:具体的执行器

被代理对象需要执行的方法和增强的操作就在执行器中定义

MyInvocationHandler 执行器的定义

/**
 * @ClassName: MyInvocationHandler
 * @Author: fang
 * @CreateTime: 
 * @Description: 执行器
 */
public class MyInvocationHandler implements InvocationHandler {
    private Object object;

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object invoke = method.invoke(object, args);
        after();
        return invoke;
    }

    private void before() {
        System.out.println("前置操作");
    }

    private void after() {
        System.out.println("后置操作");
    }

}

MyInvocationHandler 执行器的定义,需要实现 InvocationHandler 接口,实现接口中的 invoke() 方法,invoke() 方法中就是具体的业务逻辑代码,代理对象传递了执行器后,就会根据调用的方法,自动执行 invoke() 方法,使用反射的机制来实现被代理对象方法的调用

invoke() ,中有三个参数

  1. Object proxy:代理对象
  2. Method method:被代理类需要增强的方法
  3. Object[] args:方法的参数集合

注意: JDK提供的动态代理,代理的对象要实现接口,且只能代理接口,如果代理的是实现类,会报错
在这里插入图片描述

3.2 CGLIB的动态代理

CGLIB 的动态代理就不要求被代理的对象要实现接口,但是 CGLIB 的动态代理需要导入依赖

导入依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

需要代理的类

/**
 * @ClassName: SourceImpl
 * @Author: fang
 * @CreateTime:
 * @Description:
 */
public class Source {
    public void method() {
        System.out.println("核心业务流程");
    }
}

测试类

/**
 * @ClassName: ProxyTest
 * @Author: fang
 * @CreateTime: 
 * @Description:
 */
public class ProxyTest {
    public static void main(String[] args) {
        Source source = new Source();

        Enhancer enhancer = new Enhancer();
        //设置类加载器
        enhancer.setClassLoader(source.getClass().getClassLoader());
        //设置代理类
        enhancer.setSuperclass(source.getClass());
        //设置拦截的方法
        enhancer.setCallback(new MyMethodInterceptor(source));
        //创建代理对象
        Source sourceProxy = (Source) enhancer.create();
        sourceProxy.method();
    }
}

执行器

/**
 * @ClassName: MyMethodInterceptor
 * @Author: fang
 * @CreateTime: 
 * @Description:
 */
public class MyMethodInterceptor implements MethodInterceptor{
    private Object object;

    public MyMethodInterceptor(Object object) {
        this.object = object;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object invoke = methodProxy.invoke(object, objects);
        after();
        return invoke;
    }

    private void before() {
        System.out.println("前置增强");
    }

    private void after() {
        System.out.println("后置增强");
    }
}

CGLIB 的动态代理不要求代理的类一定要有接口,但是 CGLIB 对于新版的 JDK 可能不适配,如果运行程序报了以下的错,那就是 JDK 版本的问题,建议使用 JDK 1.8 的版本

在这里插入图片描述

3.3 JDK 动态代理和 CGLIB 代理的区别
  • JDK 动态代理:
    • 优势:
      • 官方提供的方式,不需要导依赖
      • 效率高
    • 劣势:
      • 只支持代理接口,要求类必须实现接口
  • CGLIB 动态代理:
    • 优势:
      • 不要求类是否实现接口,都可以进行代理
    • 劣势:
      • 需要导入依赖才可以使用
      • 效率略低

Spring 中 AOP 的底层就是动态代理

  • 在 Spring 中,如果代理的对象有接口,就默认使用 JDK 的动态代理,如果没有接口就使用 CGLIB 的动态代理
  • Spring Boot 在 2.0 版本之前和 Spring 一样,在 2.0 版本之后,Spring Boot 中的 AOP 就统一都使用 CGLIB 的动态代理方式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值