Java 动态代理的理解和介绍

199 阅读5分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

首先讲代理模式

定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

代理模式中,是需要代理对象和目标对象实现同一个接口

目的:不改变目标对象方法的情况下对方法进行增强

对于代理模式,其大概结构如下:

dd81de65ea764a988ce34f839823478d.png

动态代理

而这种方式为什么叫动态代理,是因为在运行期间动态生成的。

动态代理有两种,分别是jdk 动态代理,cglib 动态代理。

jdk 动态代理要基于接口实现,cglib 要通过子类来实现

jdk 动态代理

实现动态代理的方法如下:

Proxy.newProxyInstance()

其中有三个参数:

668735f4afc34d2c8207c659b8dcef14.png

第一个是类加载器,用来动态加载动态生成的类的字节码。

第二个是一个class 数组,是接口中的方法。接口中可以实现多个方法,所以把接口中所有方法的实现一遍。jdk 的动态代理既然想调用用户端method1,那么通过技术手段,通过反射get 到接口,生成一个类,实现了所有接口中所有方法的类,是动态生成的Proxy。 然后如何加载呢,需要使用类加载器。是使用被代理类的加载器。

第三个参数是一个Handler,即用户想调用A 的method1,那么不直接给出A,为了把一段公共代码脱离出来,所以不能给出A。这个时候生成一个代理类。Handler 指导如何生成一个代理类。(可以理解为代理类的模板)。第三个参数其实就是“代理逻辑实现类”,这个类的编写要去实现java.lang.reflect.InvocationHandler 接口。

为啥叫动态代理,因为是要动态生成的。

上代码

一个接口。

public interface UserDao {

  public Integer addUser();

  public void editUser();
}

接口的实现类,也就是要执行“动态代理”的目标类。

public class UserDaoImpl implements UserDao {
  @Override
  public Integer addUser() {
    System.out.println("调用add 方法");
    return 1;
  }

  @Override
  public void editUser() {
    System.out.printf("调用edit 方法");
  }
}

模拟一个切面Aspect,里面是对目标类的增强的两个操作,一个是检查权限,一个是日志记录。

public class MyAspect {

  public void check_permission() {
    System.out.println("检查权限");
  }

  public void log() {
    System.out.println("日志记录...");
  }
}

动态代理相关的Handler 实现,即如何把增强的逻辑写入到目标类中。(这个就是动态代理逻辑类)

// 此类 决定我们如何动态去生成或者编织我们的代理类 实现接口的方法 如何被我们修改的
public class MyInvocationHandler implements InvocationHandler {

  // 我们要编织的对象(这里是要通过写的那个Aspect 来做一部分"模仿切面操作")
  // 或者可以理解为我们要进行动态代理操作的类(就是不改变这个类的原有属性和方法,去为这个类添加一些额外的功能)
  private Object object;

  // 构造方法
  // 实现我们要编织的对象(在这个例子里就是要用那个Aspect 来进行一个"伪切面")
  public MyInvocationHandler(Object object) {
    this.object = object;
  }

  /**
   * @param proxy  代理类的实例对象。动态生成的实例类的对象
   * @param method 用户请求的方法
   * @param args   方法的参数
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 对于动态代理类的方法,如何修改。就在这个invoke 方法里进行修改。
    MyAspect myAspect = new MyAspect();
    myAspect.check_permission();

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

    myAspect.log();

    // 对于这个result 的理解。
    // 举例来说,比如说调用要进行动态代理的对象"UserDaoImpl" 里的addUser() 方法,是有个返回值的,
    // 对应上面method.invoke 方法,返回结果就是result。
    return result;
  }
}

测试:

public class JdkTest {
  public static void main(String[] args) {
    // 1. 原始代理对象(目标对象)
    UserDao userDao = new UserDaoImpl();
    // 2. 调用如何织入我们的代理类 实现哪些接口是如何被我们修改的(就是为目标类中的方法进行增强的逻辑)
    MyInvocationHandler myInvocationHandler = new MyInvocationHandler(userDao);
    // 3. 通过jdk proxy 的代理方法 动态生成了原始类实例实现接口的动态代理类 返回的是动态代理类的实例
    UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader()
            , userDao.getClass().getInterfaces()
            , new MyInvocationHandler(userDao));
    // 调用一下这个方法进行验证
    Integer result = userDaoProxy.addUser();
    System.out.println(result);
    userDaoProxy.editUser();


    // 注意这里,输出是UserDaoImpl
    System.out.println(userDaoProxy);
    // 输出"true",发现userDaoProxy 就是Proxy 的子类。是基于刚才那个模板生成的。
    // 因为java 中,每个类只能有一个父类,所以是Proxy 的类,就不能是其他类的子类了。
    // 所以是对接口进行"动态编织"
    System.out.println(userDaoProxy instanceof Proxy);
  }
}

输出结果:

检查权限
调用add 方法
日志记录...
        1
检查权限
调用edit 方法日志记录...

CGLib动态代理

上面代理方式,目标对象UserServiceimpl 实现了一个接口。

如果只是一个普通的类,没有实现任何接口,该如何进行代理呢?这就引出了CGLib动态代理。

CGLib动态代理也叫子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

要用cglib代理,需要导入相应的包,好在spring已经集成了它,引入spring即可。

上代码

代理类

public class CGLibProxy implements MethodInterceptor {

  private Object target;

  // 或者定义函数签名为:
  // public Object getProxy(Object target) {}
  public Object bind(Object target) {
    this.target = target;
    // CGLib enhancer 增强类对象
    Enhancer enhancer = new Enhancer();
    // 设置增强类型
    enhancer.setSuperclass(target.getClass());
    // 设置回调函数
    //(定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor 方法)
    enhancer.setCallback(this);
    // 创建并返回子类对象
    return enhancer.create();
  }

  /**
   * @param obj
   * @param method
   * @param args
   * @param proxy
   * @return
   * @throws Throwable
   */
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    Object object = proxy.invoke(obj, args);
    System.out.println("打印日志");
    return object;

    // Object result = proxy.invokeSuper(proxy, args);
    // System.err.println("调用真实对象后");
    // result result;

  }
}

测试类:

public class CGLibProxyTest {

  public static void main(String [] args) {
    CGLibProxy cgLibProxy = new CGLibProxy();
    UserService userService = (UserService) cgLibProxy.bind(new UserServiceImpl());
    userService.addUser();
    userService.updateUser();
  }
}

至此,java 动态代理相关的一些知识及使用方法已讲解差不多了,后续还会继续更新其他内容。