动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法。
Java 中实现动态的方式:
- JDK 中的动态代理
- Java类库 CGLib
应用场景:
- 统计每个 api 的请求耗时
- 统一的日志输出
- 校验被调用的 api 是否已经登录和权限鉴定
- Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程
怎么实现
- JDK 动态代理
- CGLib 动态代理
- 使用 Spring aop 模块完成动态代理功能
动态代理的应用:Spring的AOP,加事务,加权限,加日志,自定义拦截方法。
下面深入了解一下:
JDK 动态代理
首先我们先来看一下动态代理的执行过程
在 JDK 动态代理中,实现了 InvocationHandler 的类可以看作是 代理类(因为类也是一种对象,所以我们上面为了描述关系,把代理类形容成了代理对象)。JDK 动态代理就是围绕实现了 InvocationHandler 的代理类进行的,比如下面就是一个 InvocationHandler 的实现类,同时它也是一个代理类。
public class UserHandler implements InvocationHandler {
private UserDao userDao;
public UserHandler(UserDao userDao){
this.userDao = userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
saveUserStart();
Object obj = method.invoke(userDao, args);
saveUserDone();
return obj;
}
public void saveUserStart(){
System.out.println("---- 开始代理 ----");
}
public void saveUserDone(){
System.out.println("---- 代理完成 ----");
}
}
代理类一个最最最重要的方法就是 invoke 方法,它有三个参数
Object proxy: 动态代理对象,关于这个方法我们后面会说。
Method method: 表示最终要执行的方法,method.invoke 用于执行被代理的方法,也就是真正的目标方法
Object[] args: 这个参数就是向目标方法传递的参数。
我们构造好了代理类,现在就要使用它来实现我们对目标对象的调用,那么如何操作呢?请看下面代码。
public static void dynamicProxy(){
UserDao userDao = new UserDaoImpl();
InvocationHandler handler = new UserHandler(userDao);
ClassLoader loader = userDao.getClass().getClassLoader();
Class<?>[] interfaces = userDao.getClass().getInterfaces();
UserDao proxy = (UserDao)Proxy.newProxyInstance(loader, interfaces, handler);
proxy.saveUser();
}
如果要用 JDK 动态代理的话,就需要知道目标对象的类加载器、目标对象的接口,当然还要知道目标对象是谁。构造完成后,我们就可以调用 Proxy.newProxyInstance方法,然后把类加载器、目标对象的接口、目标对象绑定上去就完事儿了。
这里需要注意一下 Proxy 类,它就是动态代理实现所用到的代理类。
Proxy 位于java.lang.reflect 包下,这同时也旁敲侧击的表明动态代理的本质就是反射。
下面我们就围绕 JDK 动态代理,来深入理解一下它的原理,以及搞懂为什么动态代理的本质就是反射。
动态代理的实现原理
在了解动态代理的实现原理之前,我们先来了解一下 InvocationHandler 接口
InvocationHandler 接口
注释: 每个代理实例都有一个关联的调用处理程序。
*当在代理实例上调用方法时
*调用被编码并调度到{@code invoke}
*其调用处理程序的方法
动态代理的优势在于能够很方便的对代理类中方法进行集中处理,而不用修改每个被代理的方法。因为所有被代理的方法(真正执行的方法)都是通过在 InvocationHandler 中的 invoke 方法调用的。所以我们只需要对 invoke 方法进行集中处理。
invoke 方法只有三个参数
public Object invoke(Object proxy, Method method, Object[] args)
-
proxy:代理对象
-
method: 代理对象调用的方法
-
args:调用方法中的参数。
动态代理的整个代理过程不像静态代理那样一目了然,清晰易懂,因为在动态代理的过程中,我们没有看到代理类的真正代理过程,也不明白其具体操作,所以要分析动态代理的实现原理,我们必须借助源码。
那么问题来了,首先第一步应该从哪分析?如果不知道如何分析的话,干脆就使用倒推法,从后往前找,我们直接先从 Proxy.newProxyInstance入手,看看是否能略知一二。
Proxy.newInstance 方法分析
Proxy 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
Proxy.newProxyInstance 源码(java.lang.reflect.Proxy)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
...
}
从上图中我们也可以看出,newProxyInstsance 方法最重要的几个环节就是获得代理类、获得构造器,然后构造新实例。
对反射有一些了解的同学,应该会知道获得构造器和构造新实例是怎么回事。
所以我们的重点就放在了获得代理类,这是最关键的一步,对应源码中的 Class<?> cl = getProxyClass0(loader, intfs); 我们进入这个方法一探究竟
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
这个方法比较简单,首先会直接判断接口长度是否大于 65535(刚开始看到这里我是有点不明白的,我心想这个判断是要判断什么?interfaces 这不是一个 class 类型吗,从 length 点进去也看不到这个属性,细看一下才明白,这居然是可变参数,Class ... 中的 ... 就是可变参数,所以这个判断我猜测应该是判断接口数量是否大于 65535。)
然后会直接从 proxyClassCache 中根据 loader 和 interfaces 获取代理对象实例。如果能够根据 loader 和 interfaces 找到代理对象,将会返回缓存中的对象副本;否则,它将通过 ProxyClassFactory 创建代理类。
proxyClassCache.get 就是一系列从缓存中的查询操作,注意这里的 proxyClassCache 其实是一个 WeakCache,WeakCahe 也是位于 java.lang.reflect 包下的一个缓存映射 map,它的主要特点是一个弱引用的 map,但是它内部有一个 SubKey ,这个子键却是强引用的。
这里我们不用去追究这个 proxyClassCache 是如何进行缓存的,只需要知道它的缓存时机就可以了:即在类加载的时候进行缓存。
如果无法找到代理对象,就会通过 ProxyClassFactory 创建代理,ProxyClassFactory 继承于 BiFunction
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{...}
ProxyClassFactory 里面有两个属性一个方法。
其中apply方法是一个根据接口和类加载器进行代理实例创建的工厂方法,下面是这段代码的核心。
我们看到代理类继承了 Proxy 类,所以也就决定了 Java 动态代理只能对接口进行代理。
总结: JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类实现JDK动态代理,只能对接口进行代理。
cglib的动态代理
基于父类的动态代理技术
例子:
1. 创建一个目标类
public class Target{
public void coreWork() {
System.out.println("===核心业务方法运行===");
}
}
2. 创建一个增强方法类
public class Advice {
public void before(){
System.out.println("对核心业务方法执行前的增强......");
}
public void after(){
System.out.println("后置增强......");
}
}
3. 动态代理测试
public class ProxyMain {
public static void main(String[] args) {
Target target = new Target();
Advice advice = new Advice();
// 1. 创建增强器
Enhancer enhancer = new Enhancer();
// 2. 设置增强目标类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
advice.before(); // 前置增强
Object invoke = method.invoke(target, args);
advice.after(); //后置增强
return invoke;
}
});
// 4. 创建代理
Target targetProxy = (Target) enhancer.create();
targetProxy.coreWork();
}
}
总结: CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承。所以不能对final修饰的类进行代理。
总体总结:CGLib创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLib创建对象时所花费的时间却比JDK多的的。目的是为了降低耦合度,提高代码的复用性。