谈谈静态代理和动态代理

一、静态代理

在运行前,已经通过逻辑代码有了代理类的字节码文件,只能代理单一的服务对象,有多个服务对象的话每个对象都得创建自己的代理类
在这里插入图片描述
图中的 Subject 是程序中的业务逻辑接口,RealSubject 是实现了 Subject 接口的真正业务类,Proxy 是实现了 Subject 接口的代理类,封装了一个 RealSubject 引用。在程序中不会直接调用 RealSubject 对象的方法,而是使用 Proxy 对象实现相关功能。

Proxy.operation() 方法的实现会调用其中封装的 RealSubject 对象的 operation() 方法,执行真正的业务逻辑。代理的作用不仅仅是正常地完成业务逻辑,还会在业务逻辑前后添加一些代理逻辑,也就是说,Proxy.operation() 方法会在 RealSubject.operation() 方法调用前后进行一些预处理以及一些后置处理。这就是我们常说的“代理模式”。

使用代理模式可以控制程序对 RealSubject 对象的访问,如果发现异常的访问,可以直接限流或是返回,也可以在执行业务处理的前后进行相关的预处理和后置处理,帮助上层调用方屏蔽底层的细节。例如,在 RPC 框架中,代理可以完成序列化、网络 I/O 操作、负载均衡、故障恢复以及服务发现等一系列操作,而上层调用方只感知到了一次本地调用。

代理模式还可以用于实现延迟加载的功能。我们知道查询数据库是一个耗时的操作,而有些时候查询到的数据也并没有真正被程序使用。延迟加载功能就可以有效地避免这种浪费,系统访问数据库时,首先可以得到一个代理对象,此时并没有执行任何数据库查询操作,代理对象中自然也没有真正的数据;当系统真正需要使用数据时,再调用代理对象完成数据库查询并返回数据。常见 ORM 框架(例如,MyBatis、 Hibernate)中的延迟加载的原理大致也是如此。


/**
 * 代理接口
 * @author xnn
 * @date 2021-09-30 10:58
 */
public interface Subject {

    void doSomeThing(String thing);
}
/**
 * 真正执行任务的类
 */
public class RealSubject implements Subject {

    /**
     * 执行任务。
     * @param thing
     */
    @Override
    public void doSomeThing(String thing) {
        System.out.println("搞事情:"+ thing);
    }
}
/**
 * 代理类,实现了代理接口
 * @author xnn
 * @date 2021-09-30 11:02
 */
public class ProxySubject implements Subject{
    /**
     * 代理类持有一个委托类的对象引用
     */
    private Subject delegate;

    public ProxySubject(Subject delegate) {
        this.delegate = delegate;
    }

    /**
     * 将请求分派给委托类执行
     *
     * @param thing
     */
    @Override
    public void doSomeThing(String thing) {
        //将请求分派给委托类处理
        delegate.doSomeThing(thing);
    }

}
/**
 * 静态代理类工厂
 * @author xnn
 * @date 2021-09-30 11:02
 */
public class SubjectStaticFactory {

    //调用此工厂方法获得代理对象,其并不知道返回的是代理类对象还是委托类对象。
    public static Subject getInstance(){
        return new ProxySubject(new RealSubject());
    }

    public static void main(String[] args) {
        Subject proxy = SubjectStaticFactory.getInstance();
        proxy.doSomeThing("我要搞事情了!");
    }
}

二、动态代理

代理关系是在运行时期确定的

JDK和CGLIB动态代理的区别

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类;
  • CGLIB对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final

2.1、JDK代理

对于需要代理的业务类,只需要提供一个 InvocationHandler 接口实现类即可。对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑。

public class DemoInvokerHandler implements InvocationHandler {

    private Object target; // 真正的业务对象,也就是RealSubject对象

    public DemoInvokerHandler(Object target) { // 构造方法

        this.target = target;

    }

    public Object invoke(Object proxy, Method method, Object[] args)

             throws Throwable {

        // ...在执行业务方法之前的预处理...

        Object result = method.invoke(target, args);

        // ...在执行业务方法之后的后置处理...

        return result;

    }

    public Object getProxy() {

        // 创建代理对象

        return Proxy.newProxyInstance(Thread.currentThread()

            .getContextClassLoader(),

                target.getClass().getInterfaces(), this);

    }

}

创建一个 main() 方法来模拟上层调用者,创建并使用动态代理:

public class Main {

    public static void main(String[] args) {

        Subject subject = new RealSubject();

        DemoInvokerHandler invokerHandler = 

            new DemoInvokerHandler(subject);

        // 获取代理对象

        Subject proxy = (Subject) invokerHandler.getProxy();

        // 调用代理对象的方法,它会调用DemoInvokerHandler.invoke()方法

        proxy.operation();

    }

}

JDK 动态代理相关实现的入口是 Proxy.newProxyInstance() 这个静态方法,它的三个参数分别是加载动态生成的代理类的类加载器、业务类实现的接口和上面介绍的InvocationHandler对象。Proxy.newProxyInstance()方法的具体实现如下:

public static Object newProxyInstance(ClassLoader loader,

     Class[] interfaces, InvocationHandler h) 

         throws IllegalArgumentException {

    final Class<?>[] intfs = interfaces.clone();

    // ...省略权限检查等代码

    Class<?> cl = getProxyClass0(loader, intfs);  // 获取代理类

    // ...省略try/catch代码块和相关异常处理

    // 获取代理类的构造方法

    final Constructor<?> cons = cl.getConstructor(constructorParams);

    final InvocationHandler ih = h;

    return cons.newInstance(new Object[]{h});  // 创建代理对象

}

通过 newProxyInstance()方法的实现可以看到,JDK 动态代理是在 getProxyClass0() 方法中完成代理类的生成和加载。getProxyClass0() 方法的具体实现如下:

private static Class getProxyClass0 (ClassLoader loader, 

        Class... interfaces) {

    // 边界检查,限制接口数量(略)

    // 如果指定的类加载器中已经创建了实现指定接口的代理类,则查找缓存;

    // 否则通过ProxyClassFactory创建实现指定接口的代理类

    return proxyClassCache.get(loader, interfaces);

}

proxyClassCache 是定义在 Proxy 类中的静态字段,主要用于缓存已经创建过的代理类。如果根据提供的类加载器和接口数组能在缓存中找到代理类就直接返回该代理类,否则会调用ProxyClassFactory工厂去生成代理类。这里用到的缓存是二级缓存,它的一级缓存key是根据类加载器生成的,二级缓存key是根据接口数组生成的。

private static final WeakCache[], Class> proxyClassCache

     = new WeakCache<>(new KeyFactory(), 

           new ProxyClassFactory());

WeakCache.get() 方法会首先尝试从缓存中查找代理类,如果查找不到,则会创建 Factory 对象并调用其 get() 方法获取代理类。Factory 是 WeakCache 中的内部类,Factory.get() 方法会调用 ProxyClassFactory.apply() 方法创建并加载代理类。

ProxyClassFactory.apply() 方法首先会检测代理类需要实现的接口集合,然后确定代理类的名称,之后创建代理类并将其写入文件中,最后加载代理类,返回对应的 Class 对象用于后续的实例化代理类对象。该方法的具体实现如下:

public Class apply(ClassLoader loader, Class[] interfaces) {

    // ... 对interfaces集合进行一系列检测(略)

    // ... 选择定义代理类的包名(略)

    // 代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的

    long num = nextUniqueNumber.getAndIncrement();

    String proxyName = proxyPkg + proxyClassNamePrefix + num;

    // 生成代理类,并写入文件

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(

            proxyName, interfaces, accessFlags);

    

    // 加载代理类,并返回Class对象

    return defineClass0(loader, proxyName, proxyClassFile, 0, 

      proxyClassFile.length);

}

ProxyGenerator.generateProxyClass() 方法会按照指定的名称和接口集合生成代理类的字节码,并根据条件决定是否保存到磁盘上。该方法的具体代码如下:

public static byte[] generateProxyClass(final String name,

       Class[] interfaces) {

    ProxyGenerator gen = new ProxyGenerator(name, interfaces);

    // 动态生成代理类的字节码,具体生成过程不再详细介绍,感兴趣的读者可以继续分析

    final byte[] classFile = gen.generateClassFile();

    // 如果saveGeneratedFiles值为true,会将生成的代理类的字节码保存到文件中

    if (saveGeneratedFiles) { 

        java.security.AccessController.doPrivileged(

            new java.security.PrivilegedAction() {

                public Void run() {

                    // 省略try/catch代码块

                    FileOutputStream file = new FileOutputStream(

                        dotToSlash(name) + ".class");

                    file.write(classFile);

                    file.close();

                    return null;

                }

            }

        );

    }

    return classFile; // 返回上面生成的代理类的字节码

}

最后,为了清晰地看到JDK动态生成的代理类的真正定义,我们需要将上述生成的代理类的字节码进行反编译。上述示例为RealSubject生成的代理类,反编译后得到的代码如下:

public final class $Proxy37 

      extends Proxy implements Subject {  // 实现了Subject接口

    // 这里省略了从Object类继承下来的相关方法和属性

    private static Method m3;

    static {

        // 省略了try/catch代码块

        // 记录了operation()方法对应的Method对象

        m3 = Class.forName("com.xxx.Subject")

          .getMethod("operation", new Class[0]);

    }

    // 构造方法的参数就是我们在示例中使用的DemoInvokerHandler对象

    public $Proxy11(InvocationHandler var1) throws {

        super(var1); 

    }

    public final void operation() throws {

        // 省略了try/catch代码块

        // 调用DemoInvokerHandler对象的invoke()方法

        // 最终调用RealSubject对象的对应方法

        super.h.invoke(this, m3, (Object[]) null);

    }

}

JDK 动态代理的实现原理是动态创建代理类并通过指定类加载器进行加载,在创建代理对象时将InvocationHandler对象作为构造参数传入。当调用代理对象时,会调用 InvocationHandler.invoke() 方法,从而执行代理逻辑,并最终调用真正业务对象的相应方法。

2.2、CGLib动态代理

JDK 动态代理是 Java 原生支持的,不需要任何外部依赖,但是正如上面分析的那样,它只能基于接口进行代理,对于没有继承任何接口的类,JDK 动态代理就没有用武之地了。

如果想对没有实现任何接口的类进行代理,可以考虑使用 CGLib。

CGLib(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLib 采用字节码技术实现动态代理功能,其底层原理是通过字节码技术为目标类生成一个子类,并在该子类中采用方法拦截的方式拦截所有父类方法的调用,从而实现代理的功能。

因为 CGLib 使用生成子类的方式实现动态代理,所以无法代理 final 关键字修饰的方法(因为final 方法是不能够被重写的)。这样的话,CGLib 与 JDK 动态代理之间可以相互补充:在目标类实现接口时,使用 JDK 动态代理创建代理对象;当目标类没有实现接口时,使用 CGLib 实现动态代理的功能

CGLib 的实现有两个重要的成员组成。

Enhancer:指定要代理的目标对象以及实际处理代理逻辑的对象,最终通过调用 create() 方法得到代理对象,对这个对象所有的非 final 方法的调用都会转发给 MethodInterceptor 进行处理。
MethodInterceptor:动态代理对象的方法调用都会转发到intercept方法进行增强。
这两个组件的使用与 JDK 动态代理中的 Proxy 和 InvocationHandler 相似。

2.2.1、定义实体

public class User {

    /**
     * user's name.
     */
    private String name;

    /**
     * user's age.
     */
    private int age;

    /**
     * init.
     *
     * @param name name
     * @param age  age
     */
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.2.2、被代理的类

对被代理的类中的方法进行增强

public class UserServiceImpl {

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return Collections.singletonList(new User("pdai", 18));
    }

    /**
     * add user
     */
    public void addUser() {
        // do something
    }

}

2.2.3、cglib代理

实现MethodInterceptor接口,并指定代理目标类target

public class UserLogProxy implements MethodInterceptor {

    /**
     * 业务类对象,供代理方法中进行真正的业务方法调用
     */
    private Object target;

    public Object getUserLogProxy(Object target) {
        //给业务对象赋值
        this.target = target;
        //创建加强器,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        enhancer.setSuperclass(this.target.getClass());
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
        enhancer.setCallback(this);
        // 创建动态代理类对象并返回
        return enhancer.create();
    }

    // 实现回调方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // log - before method
        System.out.println("[before] execute method: " + method.getName());

        // call method
        Object result = proxy.invokeSuper(obj, args);

        // log - after method
        System.out.println("[after] execute method: " + method.getName() + ", return value: " + result);
        return null;
    }
}

调用代理目标并执行

public class ProxyDemo {

    /**
     * main interface.
     *
     * @param args args
     */
    public static void main(String[] args) {
        // proxy
        UserServiceImpl userService = (UserServiceImpl) new UserLogProxy().getUserLogProxy(new UserServiceImpl());

        // call methods
        userService.findUserList();
        userService.addUser();
    }
}

//运行结果:
//[before] execute method: findUserList
//[after] execute method: findUserList, return value: [User{name='pdai', age=18}]
//[before] execute method: addUser
//[after] execute method: addUser, return value: null
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值