一、JDK动态代理实现
动态代理最重要的当然是动态两个字,学习动态代理的过程,最重要的就是理解何为动态,话不多说,马上开整。
我们来明确一点:动态代理解决的问题是面对新的需求时,不需要修改代理对象的代码,只需要新增接口和真实对象,在客户端调用即可完成新的代理。
这样做的目的:
满足软件工程的开闭原则,提高类的可维护性和可扩展性。
二、JDK proxy
JDK Proxy
是 JDK
提供的一个动态代理机制,它涉及到两个核心类,分别是Proxy
和InvocationHandler
,我们先来了解如何使用它们。
可以看到和静态代理区别不大,唯一的变动是代理对象,我做了标注:由代理工厂生产。
这句话的意思是:代理对象是在程序运行过程中,由代理工厂动态生成,代理对象本身不存在 Java 源文件。
那么,我们的关注点有2个:
1、如何实现一个代理工厂
2、如何通过代理工厂动态生成代理对象
首先,代理工厂需要实现InvocationHanlder接口并实现其invoke()方法。
这里先定义接口和真实对象
UserDao
package cn.kexing.SpringAOP.JDKProxy;
public interface UserDao {
String getName(String id);
}
真实对象
package cn.kexing.SpringAOP.JDKProxy;
public class UserDaoImpl implements UserDao {
@Override
public String getName(String id) {
System.out.println("张三"+id);
return null;
}
}
代理工厂(需要实现InvocationHandler接口)
//UserDao增强方法
//代理工厂
class UserDaoProxy implements InvocationHandler {
//代理真实的对象
private Object objectReal;
UserDaoProxy(Object objectReal){
this.objectReal = objectReal;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行方法之前(增强方法)
System.out.println("正在执行"+method+"-----"+"传入参数"+ Arrays.toString(args));
//被增强方法执行
Object res = method.invoke(objectReal,args);
//执行方法之后(增强方法)
System.out.println("增强完毕");
return res;
}
}
invoke()
方法有3个参数:
Object proxy
:代理对象Method method
:真正执行的方法Object[] agrs
:调用第二个参数method 时传入的参数列表值
invoke() 方法是一个代理方法,也就是说最后客户端请求代理时,执行的就是该方法。代理工厂类到这里为止已经结束了,我们接下来看第二点:如何通过代理工厂动态生成代理对象。
生成代理对象需要用到Proxy
类,它可以帮助我们生成任意一个代理对象,里面提供一个静态方法newProxyInstance
。
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
实例化代理对象时,需要传入3个参数:
ClassLoader loader
:加载动态代理类的类加载器Class<?>[] interfaces
:代理类实现的接口,可以传入多个接口InvocationHandler h
:指定代理类的调用处理程序,即调用接口中的方法时,会找到该代理工厂h,执行invoke()方法
我们在客户端请求代理时,就需要用到上面这个方法。
public class JDKProxy {
public static void main(String[] args) {
//实例化一个真实对象
UserDaoImpl user = new UserDaoImpl();
//实例化代理工厂,传入真实对象引用控制,控制对其访问
UserDaoProxy proxy = new UserDaoProxy(user);
//实例化代理对象,该对象可以代理执行真实对象方法
UserDao userDao = (UserDao) Proxy.newProxyInstance(user.getClass().getClassLoader(),user.getClass().getInterfaces(),proxy);
//代理对象执行
userDao.getName("11111");
}
}
执行结果和静态代理的结果相同,但二者的思想是不一样的,一个是静态,一个是动态。
如果现在要新增一个代理对象时,比如我想新增一个帮我跑腿买奶茶的对象,这是如果是静态代理就需要去修改代理对象里面的逻辑,那如果是动态代理只需要两步:
- 创建买奶茶的借口
- 创建卖奶茶的奶茶店(真实对象)
再回想开闭原则:面向扩展开放,面向修改关闭。动态代理正是满足了这一重要原则,在面对功能需求扩展时,只需要关注扩展的部分,不需要修改系统中原有的代码。
三、主要实现源码
下面是jdk动态代理实现的重要源码,老张现在还是一知半解,但还是放在下面供大家参考:
private static final Class<?>[] constructorParams ={ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 获取代理类的 Class 对象
Class<?> cl = getProxyClass0(loader, intfs);
// 获取代理对象的显示构造器,参数类型是 InvocationHandler
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 反射,通过构造器实例化动态代理对象
return cons.newInstance(new Object[]{h});
}
我们看到第 6
行获取了一个动态代理对象,那么是如何生成的呢?接着往下看。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 去代理类对象缓存中获取代理类的 Class 对象
return proxyClassCache.get(loader, interfaces);
}
发现里面用到一个缓存 proxyClassCache,从结构来看类似于是一个 map 结构,根据类加载器loader和真实对象实现的接口interfaces查找是否有对应的 Class 对象,我们接着往下看 get() 方法。
public V get(K key, P parameter) {
// 先从缓存中查询是否能根据 key 和 parameter 查询到 Class 对象
// ...
// 生成一个代理类
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
}
在 get() 方法中,如果没有从缓存中获取到 Class 对象,则需要利用 subKeyFactory
去实例化一个动态代理对象,而在 Proxy 类中包含一个 ProxyClassFactory
内部类,由它来创建一个动态代理类,所以我们接着去看 ProxyClassFactory 中的 apply() 方法。
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
// 非常重要,这就是我们看到的动态代理的对象名前缀!
private static final String proxyClassNamePrefix = "$Proxy";
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 一些状态校验
// 计数器,该计数器记录了当前已经实例化多少个代理对象
long num = nextUniqueNumber.getAndIncrement();
// 动态代理对象名拼接!包名 + "$Proxy" + 数字
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 生成字节码文件,返回一个字节数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 利用字节码文件创建该字节码的 Class 类对象
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
apply()
方法中注意有两个非常重要的方法:
ProxyGenerator.generateProxyClass()
:它是生成字节码文件的方法,它返回了一个字节数组,字节码文件本质上就是一个字节数组,所以proxyClassFile数组就是一个字节码文件defineClass0()
:生成字节码文件的 Class 对象,它是一个 native 本地方法,调用操作系统底层的方法创建类对象
动态代理还有一个常用的实现那就是 Cglib动态代理,在后续的学习中继续更新