springboot中动态代理的那些事

动态代理代理模式 & 静态代理动态代理动态生成的代理类切面编程AOP

在前一篇文章中,把springboot的基本流程梳理了一遍。但里面有一个问题没有往深入了说:springboot作为一个javaBean的大盒子,这些bean是什么时候被加载到盒子里的,又是怎么样被加载进去的,哪些会被加载进去。在讲这些东西之前,还有一个非常重要的东西需要拿出来说一说,那就是动态代理技术。

动态代理

代理模式 & 静态代理

代理模式的类图:

在上图的代理模式中,总共有4个角色:

  • ISubject:接口,定义了代理或者被代理的行为。

  • ConcreteSubject:实现行为的具体的事物,可以称作被代理者。

  • SubjectProxy:代理者,典型的结构就是这个代理者的类包含了一个ConcreteSubject类的实例。在实现了ISubject接口的基础上另外定义了一些周边的行为函数,比如途中的preAction/postAction。

  • 客户端,代理模式的使用者。

上面的都是IT专业用语,这里我用一个汽车经销商的例子来说明一下,并用代码来实现一下。

  • 接口

车这个概念就是可以定义为一个接口,这个概念中有一个action就是售卖——sell。那么就可以定义出这个接口来了

public interface car {
    void sellCar();
}
  • 被代理者

车只是一个抽象概念,拿来卖的话,还需要一个具体的车型。假设有两种车型:别克和奔驰。

public class buick implements car {
    public void sellCar(){
        System.out.println("buick: I'm a buick car");
    }
}
public class benz implements car {
    public void sellCar(){
        System.out.println("benz: I'm a benz car");
    }
}
  • 代理者

现在一般车厂都不是自己进行销售,都是委托经销商来销售,那么经销商(代理)可能就需要有这些车(上面提到的代理者类中需要有一个被代理者的实例),并同样实现这样一个行为:销售——sell。

而经销商在销售之前,可能会有自己的一些宣传和促销行为。在销售之后可能需要你附加买一些七七八八的东西。这都不是车这个实物具备的行为,所以需要定义在代理类里面。

public class yongtong_seller implements car {
    car seller_car;
​
    public yongtong_seller(car seller_car){
        this.seller_car = seller_car;
    }
​
    public void sellCar(){
        guanggao(seller_car);
        seller_car.sellCar();
        zengsong(seller_car);
    }
​
    private void guanggao(car seller_car){
        if (seller_car instanceof buick){
            System.out.println("buick adv by yongtong");
        }else if(seller_car instanceof benz){
            System.out.println("bez adv by yongtong");
        }
    }
​
    private void zengsong(car seller_car){
        if (seller_car instanceof buick){
            System.out.println("buick discont by yongtong");
        }else if(seller_car instanceof benz){
            System.out.println("bez discont by yongtong");
        }
    }
}
  • 客户端,也就是调用者。

//static proxy
System.out.println("###########static proxy demo begin#############");
buick buick_car = new buick();
​
yongtong_seller ys = new yongtong_seller(buick_car);  //定义一个行的经销商,需要售出一台别克车型。
ys.sellCar();
System.out.println("###########static proxy demo end#############");

输出结果为:

###########static proxy demo begin#############
buick adv by yongtong
buick: I'm a buick car
buick discont by yongtong
###########static proxy demo end#############

上面的这种称作静态代理。

我们可以看到,在静态代理里,每个代理都需要去创建,而且需要实现某个接口。那么问题来了,如果这个经销商不只是卖车,我还要卖火车,如果使用静态代理模式的话,就需要再创建一套接口和实物。

public interface train {
    void sellTrian();
}
public class train_A implements train {
    public void sellTrian(){
        System.out.println("I'm trian_A");
    }
}
public class train_B implements train {
    public void sellTrian(){
        System.out.println("I'm trian_B");
    }
}

同样的,增加一个经销商:

public class trainAseller implements train {
    train trainToSell;
    public trainAseller(train train){
        this.trainToSell = train;
    }
    public void sellTrian(){
        before();
        trainToSell.sellTrian();
        after();
    }

    private void before(){
        System.out.println("before");
    }

    private void after(){
        System.out.println("after");
    }
}

所以说,在静态代理里,每增加一个被代理者,除了需要定义这个被代理这的接口和实物类(car/buick,benz;train/train_A.train_B)之外,还需要定义个实现接口的代理类。如果我们业务系统中有非常多的这种接口和实物类,定义器代理类来也是一个非常大的工作量。

动态代理

所以,相对于静态代理,spring中使用到了动态代理的方式(还有其他的方式,这里我只拿jdk中的动态代理实现:InvocationHandler类来说一说)

定义一个动态代理类,这个类需要实现InvocationHandler接口,而不是像静态代理中的直接实现实物接口。在这个InvocationHandler接口中,只有一个函数invoke,在自己的动态代理类中重写这个方法。

public class ProxySeller implements InvocationHandler {
    Object target;  //实际被代理对象

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        guanggao();
        Object result = method.invoke(target, args);
        zengsong();

        return result;
    }

    private void guanggao(){
        if (target instanceof buick) {
            System.out.println("I'm Seller, buick adv");
        }else if(target instanceof benz){
            System.out.println("I'm Seller, benz adv");
        }else{
            System.out.println("train");
        }
    }

    private void zengsong(){
        if (target instanceof buick) {
            System.out.println("I'm jiucheng, buick discount");
        }else if(target instanceof benz){
            System.out.println("I'm jiucheng, benz discount");
        }else{
            System.out.println("train");
        }
    }
}

在客户端调用时,直接使用同一个代理类,就可以同时代理上述的汽车和火车了,而不需要和静态代理一样创建不同的代理类。

//dynamic proxy
System.out.println("###########dynamic proxy demo begin#############");
//创建一个动态代理器,我要为这个对象做代理
InvocationHandler busiHandler = new ProxySeller(buick_car);  //代理汽车
car seller = (car) Proxy.newProxyInstance(buick.class.getClassLoader(), buick.class.getInterfaces(), busiHandler);  

InvocationHandler busiHandler_train = new ProxySeller(train_tosell);  //代理火车
train train_seller = (train) Proxy.newProxyInstance(train_A.class.getClassLoader(), train_A.class.getInterfaces(), busiHandler_train);

seller.sellCar();
train_seller.sellTrian();
System.out.println("###########dynamic proxy demo end#############");

动态生成的代理类

使用动态代理,实际上是生成了一个类,一般情况下看不到,所以需要修改下系统配置。

System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

这样就可以在target里面形成一个com.sun.proxy目录下生成一个$Proxy#,#表示数字,从0开始的动态类。

我们可以打开这个类看一下。

package com.sun.proxy;

import com.zl.demo.business.car;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements car {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
public $Proxy0(InvocationHandler var1) throws  {
    super(var1);
}

public final boolean equals(Object var1) throws  {
    try {
        return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}
public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
public final void sellCar() throws  {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

public final int hashCode() throws  {
    try {
        return (Integer)super.h.invoke(this, m0, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

static {
    try {
        m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
        m2 = Class.forName("java.lang.Object").getMethod("toString");
        m3 = Class.forName("com.zl.demo.business.car").getMethod("sellCar");
        m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(var3.getMessage());
    }
}
}

这个代码一时半会看不明白,我自己画了一张调用的对比图,大家可以参考一下。 

动态代理类关系与调用图

我理解就是通过在自定义的proxySeller类中,调用Proxy.newInstance函数创建一个动态的$Proxy类,这个类就是之前的静态代理中的类:

我这里有两个类:$Proxy0, $Proxy1,其中一个是train的接口代理。

public final class $Proxy1 extends Proxy implements train {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

然后再调用实际的静态代理类中的sellCar方法,这个代理方法中,再通过反射调用到实际的业务接口类:

m3 = Class.forName("com.zl.demo.business.car").getMethod("sellCar");

简单来说,就是在静态代理的基础上又包装了一层,利用反射机制动态生成一个静态代理类。

切面编程AOP

spring框架的一大基石:面向切面编程(AOP)就是基于动态代理的方式来实现的。我理解的切面编程的应用场景:

在业务系统中,有若干的业务线,就像上图中画的,每个业务流程都是一条线。在每个业务流程中都会需要有日志输出的请求。以往的做法是在每条业务线中添加日志逻辑。这样就会设计到最起码4个业务类的编写和修改。那么切面编程的逻辑就是把这些逻辑抽取出来,形成一个切面类:就像上图画的,像在各个业务线中切上一刀,形成一个切面,所有的日志逻辑都可以写在这里,这种编写框架就可以称作切面编程。

在java体系中,所有的类都会被编译成字节码放到虚拟机中运行,那么切面类的代码运行的方式有很多中,可以把切面类的逻辑插入到业务逻辑中的静态aop,也可以生成一个代理类(上述逻辑提到的动态代理),还可以生成子类,把业务逻辑和切面逻辑共同处理,大致有如下几种方式。

img


版权声明:本文为pcgamer原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。