SpringBoot(二十八)SpringBoot实现动态代理模式

62 阅读7分钟

动态代理模式,是二十三种设计模式之一,巧了我之前使用C#学习过静态代理模式,动态代理模式,我还真就没学过。

 

刚好,这次连静态代理模式也复习一下。

 

一:静态代理模式

1:比如说,我现在需要卖演唱会门票,那么我需要先定义一个卖票的接口

public interface Ticket
{
    /**
     * 卖票接口
     */
    void sell();
}

2:卖票接口有了,我现在需要卖演唱会的门票,所以,我需要定义一个买演唱会门票的类实现卖票接口。

public class MusicTicket implements Ticket
{
    /**
     * 卖演唱会门票
     */
    @Override
    public void sell()
    {
        System.out.println("卖演唱会门票");
    }
}

3:但是呢,现在有一个小问题,我买票,但是呢,我没有票,我得委托印刷公司在我卖票之前给我把票做出来。我还要委托检票团队,在演唱会开启之前,检票。以上操作,被成为代理。

public class MusicConductor implements Ticket
{
    /**
     * 卖演唱会门票对象
     */
    private MusicTicket ticket;

    public MusicConductor(MusicTicket ticket)
    {
        this.ticket = ticket;
    }

    /**
     * 卖票
     */
    @Override
    public void sell()
    {
        before();
        this.ticket.sell();
        after();
    }

    /**
     * 委托印刷公司印票
     */
    private void before()
    {
        System.out.println("静态代理 - 方法前增强");
    }

    /**
     * 委托检票团队检票
     */
    private void after()
    {
        System.out.println("静态代理 - 方法后增强");
    }
}

 

4:测试一下

public static void main(String[] args)
{
    MusicConductor conductor = new MusicConductor(new MusicTicket());
    conductor.sell();
}

运行代码,控制台输出:

静态代理 - 方法前增强
卖演唱会门票
静态代理 - 方法后增强

 

优点: 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点: 静态代理模式有一些小小的问题,问题就出在静态两个字上,由于其显式的声明了对象类型,因此,他只能代理已声明的对象类型。也就是说我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。   

 

二:JDK实现动态代理模式

动态配置和替换被代理对象,通俗的说就是可以代理任意一类对象,甚至是任意对象。

注意:JDK 代理时被代理类必须实现接口


动态代理的例子跟静态代理是一样的,只是动态代理没有显式的声明类型。

 

jdk实现的动态代理由两个重要的成员组成,分别是Proxy、InvocationHandler

 

Proxy:是所有动态代理的父类,它提供了一个静态方法来创建动态代理的class对象和实例

 

InvocationHandler:每个动态代理实例都有一个关联的InvocationHandler,在代理实例上调用方法是,方法调用将被转发到InvocationHandler的invoke方法

 

1:声明卖票接口

public interface Ticket
{
    /**
     * 卖票接口
     */
    void sell();
}

2:实例化卖演唱会门票类

public class MusicTicket implements Ticket
{
    /**
     * 卖演唱会门票
     */
    @Override
    public void sell()
    {
        System.out.println("卖演唱会门票");
    }
}

3:实例化卖运动会门票类

public class SportTicket implements Ticket
{
    /**
     * 卖运动会门票
     */
    @Override
    public void sell()
    {
        System.out.println("卖运动会门票");
    }
}

4:创建动态代理类

public class TicketConductor implements InvocationHandler
{
    /**
     * 被代理对象
     */
    private Object target;

    /**
     * 可以代理任意门票,所以为 {@link Object}
     *
     * @param target 被代理对象,但是必须有统一的接口
     * @return 被代理对象
     */
    public Object getInstance(Object target)
    {
        this.target = target;

        Class<?> clazz = target.getClass(); // 获取代理对象(这里得?是精髓)

        return Proxy.newProxyInstance(
            clazz.getClassLoader(),     // 获取类加载器
            clazz.getInterfaces(),      // 获取接口
            this                        // 当前对象
        );
    }

    /**
     * 反射调用的方法
     *
     * @param proxy  代理对象
     * @param method 被代理对象需要执行的方法
     * @param args   被代理对象需要执行的方法 的参数
     * @return 被代理对象需要执行的方法 的返回值
     * @throws Throwable 抛出异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before() {
        System.out.println("动态代理 - JDK动态代理 - 方法前增强");
    }

    private void after() {
        System.out.println("动态代理 - JDK动态代理 - 方法后增强");
    }
}

 

5:测试一下

public static void main(String[] args) {
    // 代理演唱会门票
    Ticket musicTicket = (Ticket) new TicketConductor().getInstance(new MusicTicket());
    musicTicket.sell();

    System.out.println("===========================================");

    // 代理运动会门票
    Ticket sportTicket = (Ticket) new TicketConductor().getInstance(new SportTicket());
    sportTicket.sell();
}

运行代码,控制台输出:

动态代理 - JDK动态代理 - 方法前增强
卖演唱会门票
动态代理 - JDK动态代理 - 方法后增强
===========================================
动态代理 - JDK动态代理 - 方法前增强
卖运动会门票
动态代理 - JDK动态代理 - 方法后增强

 

JDK原生动态代理时java原生支持的、不需要任何外部依赖、但是它只能基于接口进行代理(因为它已经继承了proxy了,java不支持多继承)

 

三:CGLIB实现动态代理模式

CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承的方式实现代理。

 

CGLIB的实现也有两个重要的成员组成,Enhancer、MethodInterceptor,其实这两个的使用和jdk实现的动态代理的Proxy、InvocationHandler非常相似

 

Enhancer:来指定要代理的目标对象,实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象、对这个对象所有的非final方法的调用都会转发给MethodInterceptor

 

MethodInterceptor:动态代理对象的方法调用都会转发到intercept方法进行增强

 

下边我们使用代码来实现一下:

1:创建演唱会门票类

public class MusicTicket
{
    public void sellTicket()
    {
        System.out.println("演唱会票");
    }
}

2:创建运动会门票类

public class SportTicket
{
    public void sellTicket()
    {
        System.out.println("运动会票");
    }
}

3:创建CGLIB动态代理类

public class TicketConductor implements MethodInterceptor
{
    /**
     * CGLIB动态代理类,售票员(不论什么票都卖)
     */
    public Object getInstance(Class<?> clazz)
    {
        // Enhancer 相当于 JDK 动态代理的 Proxy 类
        Enhancer enhancer = new Enhancer();
        // 设置动态生成的对象的父类为传进来的 被代理类
        enhancer.setSuperclass(clazz);
        // MethodInterceptor 继承 Callback 接口
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * 代理对象执行的所有方法都会走这个方法
     *
     * @param o           被代理的对象
     * @param method      被代理对象需要执行的方法
     * @param objects     被代理对象需要执行的方法 参数
     * @param methodProxy 触发父类的方法对象
     * @return 被代理对象需要执行的方法 返回值
     * @throws Throwable 抛出的异常信息
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
    {
        before();
        // 调用生成代理对象的父类方法
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void before() {
        System.out.println("动态代理 - CGLIB动态代理 - 方法前增强");
    }

    private void after() {
        System.out.println("动态代理 - CGLIB动态代理 - 方法后增强");
    }
}

 

4:测试一下

public static void main(String[] args)
{
    MusicTicket musicTicket = (MusicTicket) new TicketConductor().getInstance(MusicTicket.class);
    musicTicket.sellTicket();

    System.out.println("==================================");

    SportTicket sportTicket = (SportTicket) new TicketConductor().getInstance(SportTicket.class);
    sportTicket.sellTicket();
}

运行代码,控制台输出:

动态代理 - CGLIB动态代理 - 方法前增强
演唱会票
动态代理 - CGLIB动态代理 - 方法后增强
==================================
动态代理 - CGLIB动态代理 - 方法前增强
运动会票
动态代理 - CGLIB动态代理 - 方法后增强

 

CGLIB通过继承的方式进行代理、无论目标对象没有没实现接口都可以代理,但是无法处理final的情况(final修饰的方法不能被覆写)

 

四:JDK 动态代理与 CGLIB 动态代理的区别

  1. JDK 动态代理生成的代理对象是实现了被代理对象的接口,CGLIB 动态代理生成的代理对象是继承了被代理对象。
  2. JDK 和 CGLIB 都是在运行期生成字节码,JDK 是直接写 class 字节码,CGLIB 使用 ASM 框架写 class 字节码,CGLIB 代理实现更复杂,CGLIB 生成代理类的效率比 JDK 生成代理类效率低。
  3. JDK 调用代理方法,是通过反射机制调用,CGLIB 是通过 FastClass 机制直接调用方法,CGLIB 的被代理类执行效率比 JDK 的被代理类更高。

 

以上大概就是java实现的动态代理。

 

有好的建议,请在下方输入你的评论。