2023年04月04日
今天在复习Spring AOP的内容,在看到JDK动态代理时,积攒多年的疑问又发生了。
半年前在学习设计模式时的JDK动态代理时,没有学明白,似懂非懂,迷迷糊糊就混过去了,今天复习AOP,打算彻底把JDK动态代理弄懂。
JDK动态代理是基本流程:
看代码,这是创建代理对象的类
import com.liumingkai.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 刘明凯
* @version 0.0.1
* @date 2023年4月4日 11:40
*/
public class MyProxy implements InvocationHandler {
// 声明目标类接口
private UserDao userDao;
// 创建代理方法
public Object createProxy(UserDao userDao) {
this.userDao = userDao;
// 1. 类加载器,本类的类加载器
ClassLoader classLoader = MyProxy.class.getClassLoader();
// 2. 被代理对象实现所有接口
Class<?>[] interfaces = userDao.getClass().getInterfaces();
// 3. 使用代理类进行增强,返回的是代理对象
return Proxy.newProxyInstance(classLoader, interfaces, this);
/**
* 第一个参数classLoader 表示当前类的类加载器
* 第二个参数 interfaces 表示被代理对象身上的所有接口
* 第三个参数是 this 表示代理类JdkProxy本身,因为本类实现了InvocationHandler接口中的Invoke()方法
*/
}
/**
* 所有动态代理类的方法调用,都会交由invoke()方法区处理
*
* @param proxy 目标对象的代理对象
* @param method 将要被执行的方法信息(反射)
* @param args 执行方法需要的参数
* @return
* @throws Throwable
*/ @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前增强
System.out.println("模拟权限校验...")
// 在目标类调用目标方法,传入参数
Object obj = method.invoke(userDao, args);
// 后增强
System.out.println("请你确认....");
// 返回原始方法的返回值
return obj;
}
}
测试一下
// 创建代理对象
MyProxy myProxy = new MyProxy();
// 创建目标对象
UserDao userdao = new UserDaoImpl();
// 从代理对象中获取增强后的目标对象
UserDao userDaoPro = (UserDao) myProxy.createProxy(userdao);
// 执行方法
userDaoPro.addUser();
在研究时,发现invoke方法的第一个参数并没有使用,那为什么还要把这个参数写上呢???
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
....
}
黑马的教材对此参数的解析是 被代理的对象。
如果proxy参数是真实的目标对象, 那为什么不通过method.invoke(proxy,args)激活原始方法?而是通过将目标对象成为属性呢?
我尝试修改,发现陷入无限地递归中。
显然,黑马的教材中对此处的解释是错误的。
然后开始看Proxy的源码,看了半天看不懂。
搜索JDK动态代理的文章,也都没有对这个参数进行详细的解析。
后来终于找到了一个相关对这个参数解释的文章。
# Java中InvocationHandler接口中第一个参数proxy详解
我总结:
第一个参数是真正的代理对象,在invoke中可以通过该参数获取到代理对象本身,可以获取到代理对象身上的其他方法和信息。可以将此对象作为参数返回,实现代理对象的层层调用
看测试代码,我们验证获取到的代理对象与invoke()中的第一个参数proxy是否是同一个对象,我们打印一下两个对象的字节码对象。
// 创建代理对象
MyProxy myProxy = new MyProxy();
// 创建目标对象
UserDao userdao = new UserDaoImpl();
// 从代理对象中获取增强后的目标对象
UserDao userDaoPro = (UserDao) myProxy.createProxy(userdao);
// 打印代理对象的字节码对象
System.out.println(userDaoPro.getClass());
// 执行方法
userDaoPro.addUser();
在invoke()中也打印一下字节码对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前增强
System.out.println("模拟权限认证.....");
// 在目标类调用目标方法,传入参数
Object obj = method.invoke(userDao, args);
// 后增强
System.out.println("请你确认....");
return obj;
}
对于一些链式编程实现,例如构建者、装饰者等经典的设计模式,都是将本对象作为返回值。
代理也可以实现这种链式编程的设计。
来看设计
接口设计
public interface UserDao {
UserDao fun();
}
实现类
public class UserDaoImpl implements UserDao {
@Override
public UserDao fun() {
System.out.println("方法执行");
return this; }
}
代理类
package com.liumingkai.aspect;
import com.liumingkai.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 刘明凯
* @version 0.0.1
* @date 2023年4月4日 11:40
*/
public class MyProxy implements InvocationHandler {
// 声明目标类接口
private UserDao userDao;
// 创建代理方法
public Object createProxy(UserDao userDao) {
this.userDao = userDao;
// 1. 类加载器
ClassLoader classLoader = MyProxy.class.getClassLoader();
// 2. 被代理对象实现所有接口
Class<?>[] interfaces = userDao.getClass().getInterfaces();
// 3. 使用代理类进行增强,返回的是代理对象
return Proxy.newProxyInstance(classLoader, interfaces, this);
}
/**
* 所有动态代理类的方法调用,都会交由invoke()方法区处理
*
* @param proxy 被代理的对象
* @param method 将要被执行的方法信息(反射)
* @param args 执行方法需要的参数
* @return
* @throws Throwable
*/ @Override
public UserDao invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强了...");
method.invoke(userDao,args);
return (UserDao) proxy;
}
}
测试
// 创建代理对象
MyProxy myProxy = new MyProxy();
// 创建目标对象
UserDao userdao = new UserDaoImpl();
// 从代理对象中获取增强后的目标对象
UserDao userDaoPro = (UserDao) myProxy.createProxy(userdao);
UserDao userDaoPro2 = userDaoPro.fun();
// 判断两个对象是否是同一个对象
System.out.println(userDaoPro == userDaoPro2);
JDK动态代理的思路:
Proxy类的静态方法newProxyInstance()方法,通过类加载器、目标对象的所有接口、InvocationHandler的实现类,这三个参数能够创建代理对象。
当代理对象的方法执行时,会统一交给InvocationHandler的invoke()方法处理,同时将代理对象本身this作为第一个参数传入。
InvocationHandler中的invoke()方法有三个参数
public UserDao invoke(Object proxy, Method method, Object[] args)