本文介绍在Java种常用的3种动态代理。
代理模式是23种模式中的一种,属于结构型设计模式。这种模式的作用就是要创建一个中间对象(相当于中介或者代理对象),通过操作中间对象来间接调用目的对象的方法,字段等,真正做到0接触,而且还可以提供额外的服务(比如增加日志,修改请求中的参数等等)Spring框架中的AOP就使用了代理模式,这是符合OCP开闭原则的:在尽量不改动原有代码的基础上,增加新的功能,使得代码的扩展性更强。
代理模式在代码实现上有2种方式:

  1. 静态代理
  2. 动态代理

1. 静态代理

假如现在有一个业务需求:在不改变原有代码的情况下,给业务方法加上日志记录。此时该怎么办呢?
这里我们以代码演示静态代理:
一个接口类 :BusinessService

package com.guitarzheng.proxy;

/**
 * @title: 业务Service
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public interface BusinessService {

    /**
     * 正常的业务方法
     */
    void doBusiness();

}

一个目标类:BusinessServiceImpl

package com.guitarzheng.proxy.impl;

import com.guitarzheng.proxy.BusinessService;

/**
 *  业务Service实现类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class BusinessServiceImpl implements BusinessService {

    @Override
    public void doBusiness() {
        System.out.println("---业务逻辑执行片段---");
    }

}

使用静态代理的方式解决:
此时,我们再添加一个测试类,用于测试:****

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.impl.BusinessServiceImpl;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main(String[] args) {
        BusinessService businessService = new BusinessServiceImpl();
        businessService.doBusiness();
    }

}

此时执行上面的main方法,控制台结果如下:
在这里插入图片描述
但是如何解决增加日志的需求呢???
答:增加一个代理类同时和目标类实现同一个接口:BusinessServiceImplStaticProxy

package com.guitarzheng.proxy.impl;

import com.guitarzheng.proxy.BusinessService;

/**
 * @title: 静态代理
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class BusinessServiceImplStaticProxy implements BusinessService {

    @Override
    public void doBusiness() {
        System.out.println("---记录日志---");
        System.out.println("---业务逻辑执行片段---");
    }

}

对静态代理类进行测试:
在这里插入图片描述
结果如下:
在这里插入图片描述
满足业务需求!
但是,缺点也很明显:如果系统中的业务类和方法很多,那么我们要写很多代理类和方法,会出现类爆炸,这显然是我们不能接受的。怎么解决这个问题呢?此时要使用我们下来介绍的动态代理的技术。

2. 动态代理

动态代理为什么叫动态代理呢?它和静态代理有什么区别?
动态代理最重要的特点就是代理类是在内存中动态生成的,不需要我们手动new。

2.1 jdk 动态代理

jdk动态代理是Java官方自带的一种代理机制,jdk动态代理只能代理接口
下面我们演示如何使用jdk动态代理(这里接口和目标类还是上面给出的):
调用处理器类:JdkDynamicProxyLogHandler

package com.guitarzheng.proxy.handler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @title: 专门用于记录日志的jdk动态代理调用处理器
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class JdkDynamicProxyLogHandler implements InvocationHandler {

    // 目标对象
    private Object target;

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

    /**
     * 该方法由jdk负责调用,调用invoke方法执行目标对象的方法并对方法进行增强(本例中增加了记录日志的功能)
     * @param proxy 代理对象,此参数使用较少
     * @param method 目标对象上的目标方法,比如代理类本次调用目标对象的doBusiness方法,那么doBusiness方法就是目标方法
     * @param args 目标方法上的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("---记录日志---【jdk动态代理方式】");
        method.invoke(target, args);
        // 可以返回null,也可以返回其它值
        return null;
    }

}

问:为什么要在JdkDynamicProxyLogHandler类中定义一个目标类的成员变量?
答: 首先我们定义的日志调用处理器实现了InvocationHandler接口,这是jdk动态代理要求的:如果要使用jdk的动态代理机制,必须要自定义调用处理器必须要实现InvocationHandler接口,重写invoke方法。在invoke方法中,代码:

method.invoke(targer, args);

该行代码表示的意思是执行目标对象的目标方法,此时我们需要目标对象,因此需要从外界传入,这里我们使用成员属性定义,在new代理类时将目标对象传入即可达到目的。
测试代码:

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.handler.JdkDynamicProxyLogHandler;
import com.guitarzheng.proxy.impl.BusinessServiceImpl;

import java.lang.reflect.Proxy;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main(String[] args) {

        // jdk动态代理
        // 1. 创建目标对象
        BusinessService target = new BusinessServiceImpl();
        // 2. 创建代理对象
        BusinessService targetProxy = (BusinessService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkDynamicProxyLogHandler(target));
        targetProxy.doBusiness();

    }

}

控制台输出结果如下:
在这里插入图片描述
针对步骤2创建代理对象做一些说明:
Proxy类本身提供了创建代理类的方法:newProxyInstance(ClassLoader loader, @NotNull Class<?> interfaces, @NotNull InvocationHandler h),实际上,该方法执行后,会在内存中生成一个代理类的字节码数据,然后通过类加载器(这里要求代理类的类加载器和目标类的类加载器必须是同一个)将对象加载到java虚拟机,完成代理类对象的创建工作。
参数说明:

  1. ClassLoader loader:加载目标类的类加载器,jdk动态代理要求代理类的类加载器和目标类的类加载器必须是同一个类加载器;
  2. @NotNull Class<?> interfaces:目标类实现的接口;
  3. @NotNull InvocationHandler h:调用处理器,是一个接口类,增强功能的代码就写在这里!

2.2 cglib 动态代理

cglib作为一种动态代理的实现方式,功能就比较强大。它既可以代理接口,也可以代理类,底层采用继承的方式实现,所以被代理的目标类不能使用关键字final修饰。
由于cglib是第三方提供的技术,使用之前要引入依赖:

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

注意:若项目不是maven项目,导入jar包即可,现在大多数项目都是用maven管理jar包,因此导入步骤这里省略。
本例中演示代理类的方式实现动态代理。
准备一个类:BusinessServiceImpl2

package com.guitarzheng.proxy.impl;

/**
 * @title: 普通的业务类
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class BusinessServiceImpl2 {
    
    public void doBusiness() {
        System.out.println("---业务逻辑执行片段---");
    }
    
}

创建回调接口:CglibLogMethodInterceptor

package com.guitarzheng.proxy.interceptor;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @title: cglib动态代理日志回调接口拦截器
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class CglibLogMethodInterceptor implements MethodInterceptor {

    /**
     *
     * @param target 目标对象(代理对象)
     * @param method 目标方法(代理对象中的某个方法)
     * @param objects 目标方法参数
     * @param methodProxy 目标方法的代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("---记录日志---【cglib动态代理方式】");
        methodProxy.invokeSuper(target, objects);
        return null;
    }

}

使用clib动态代理方式在内存中动态的生成BusinessServiceImpl2的代理类,并创建代理类的对象:

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.impl.BusinessServiceImpl2;
import com.guitarzheng.proxy.interceptor.CglibLogMethodInterceptor;
import net.sf.cglib.proxy.Enhancer;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main(String[] args) {

        // cglib动态代理
        // 1. 创建字节码增强器(这里的enhancer就是代理类,但是它不能直接调用目标对象的方法,可以通过它创建目标对象的的子类或者目标类接口的实现类)
        Enhancer enhancer = new Enhancer();
        // 2. 设置代理类的父类(告诉代理类要代理哪个目标类,就把哪个目标类设置为父类)
        enhancer.setSuperclass(BusinessServiceImpl2.class);
        // 3. 设置回调接口(非常重要:对目标类的增强功能就写在回调接口中)
        enhancer.setCallback(new CglibLogMethodInterceptor());
        // 4. 创建class字节码文件并加载到jvm,生成代理对象(这个代理对象是目标对象的子类)
        BusinessServiceImpl2 businessServiceImpl2Proxy = (BusinessServiceImpl2) enhancer.create();
        // 5. 使用代理类
        businessServiceImpl2Proxy.doBusiness();

    }

}

在这里插入图片描述

注意:若项目不是maven项目,只引入cglib依赖,会出现以下错误:
在这里插入图片描述
cglib依赖asm的jar包,因此我们还要导入asm的jar包。
若项目是maven,则不会出现上述问题,因为在引入cglib依赖时,会自动帮我们引入和cglib关联的jar包。

2.3 Javassist 动态代理

我们已经知道,Java源代码经过编译之后生成class文件,而class文件中存储的正是二进制的Java字节码,jvm启动时依靠类加载器将Java字节码加载到jvm中,完成对象的初始化工作。Javassist就是一个用来处理Java字节码的类库。 它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,同时也可以通过完全手动的方式生成一个新的类对象。
声明一个业务类:BusinessServiceImpl3

package com.guitarzheng.proxy.impl;

/**
 * @title: 普通的业务类
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class BusinessServiceImpl3 {

    public void doBusiness() {
        System.out.println("---业务逻辑执行片段---");
    }

}

和jdk动态代理差不多,Javassist也需要一个回调接口处理器:JavassistProxyLogHandler

package com.guitarzheng.proxy.handler;

import javassist.util.proxy.MethodHandler;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @title: Javaassist回调接口处理器
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class JavassistProxyLogHandler implements MethodHandler {

    /**
     *
     * @param target 目标类
     * @param thisMethod 目标方法
     * @param proceed 代理方法(用来代理目标方法)
     * @param args 目标方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object target, Method thisMethod, Method proceed, Object[] args) throws Throwable {
    	// 增强代码
        System.out.println("---记录日志---【Javassist动态代理方式】");
        // 原业务方法放行
        proceed.invoke(target, args);
        // 若需要返回值,可以返回
        return null;
    }

}

生成代理类并调用方法:

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.handler.JavassistProxyLogHandler;
import com.guitarzheng.proxy.impl.BusinessServiceImpl3;
import com.guitarzheng.proxy.interceptor.CglibLogMethodInterceptor;
import javassist.ClassPool;
import javassist.util.proxy.ProxyFactory;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main (String[] args) throws InstantiationException, IllegalAccessException {
          
        // Javassist动态代理
        //1.创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        //2.设置父类
        proxyFactory.setSuperclass(BusinessServiceImpl3.class);
        //3.设置回调处理器(具体的增强功能写在这里)
        proxyFactory.setHandler(new JavassistProxyLogHandler());
        //4.创建代理类的Class实例
        Class proxyClass = proxyFactory.createClass();
        //5.创建代理对象
        BusinessServiceImpl3 businessServiceImpl3Proxy = (BusinessServiceImpl3) proxyClass.newInstance();
        //6.使用代理对象调用业务方法
        businessServiceImpl3Proxy.doBusiness();
    }

}

运行结果如下:
在这里插入图片描述
实际上,Javassist也可以之间创建一个新的类,并给类设置属性和方法等。具体可参考这里:https://www.jb51.net/article/205638.htm

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐