Spring AOP高级——源码实现(1)动态代理技术

jdk1.8.0_144  

  在正式进入Spring AOP的源码实现前,我们需要准备一定的基础也就是面向切面编程的核心——动态代理。 动态代理实际上也是一种结构型的设计模式,JDK中已经为我们准备好了这种设计模式,不过这种JDK为我们提供的动态代理有2个缺点:

  1. 只能代理实现了接口的目标对象;
  2. 基于反射,效率低

  鉴于以上2个缺点,于是就出现了第二种动态代理技术——CGLIB(Code Generation Library)。这种代理技术一是不需要目标对象实现接口(这大大扩展了使用范围),二是它是基于字节码实现(这比反射效率高)。当然它并不是完全没有缺点,因为它不能代理final方法(因为它的动态代理实际是生成目标对象的子类)。

  Spring AOP中生成代理对象时既可以使用JDK的动态代理技术,也可以使用CGLIB的动态代理技术,本章首先对这两者动态代理技术做简要了解,便于后续源码的理解。

JDK动态代理技术

  JDK动态代理技术首先要求我们目标对象需要实现一个接口:

 package proxy;

 /**
* Created by Kevin on 2017/11/8.
*/
public interface Subject {
void sayHello();
}

  接下来就是我们需要代理的真实对象,即目标对象:

 package proxy;

 /**
* 目标对象,即需要被代理的对象
* Created by Kevin on 2017/11/8.
*/
public class RealSubject implements Subject{
public void sayHello() {
System.out.println("hello world");
}
}

  这是一个真实的对象,我们希望在不更改原有代码逻辑的基础上增强该类的sayHello方法,利用JDK动态代理技术需要我们实现InvocationHandler接口中的invoke方法:

 package proxy;

 import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class ProxySubject implements InvocationHandler {
private Object target; public ProxySubject(Object target) {
this.target = target;
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前");
Object object = method.invoke(target, args);
System.out.println("调用后");
return object;
}
}

  第15行,在invoke方法中可以看到,在调用目标对象的方法前后我们对方法进行了增加,这其实就是AOP中Before和After通知的奥义所在。

  加入测试代码:

 package proxy;

 import java.lang.reflect.Proxy;

 /**
* Created by Kevin on 2017/11/8.
*/
public class Test {
public static void main(String[] args) {
Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), new ProxySubject(new RealSubject()));
subject.sayHello(); //查看subject对象的类型
System.out.println(subject.getClass().getName());
}
}

  执行结果:

Spring AOP高级——源码实现(1)动态代理技术

  可以看到和AOP几乎一样,前面提到过,动态代理就是AOP的核心。同时我们可以看到被代理的类的类型是:com.sun.proxy.$Proxy0。等会深入JDK源码时我们将会看到为什么。

  回到上面的例子,我们通过Proxy. newProxyInstance生成了一个代理类,显然这个类是在Run-Time(运行时)生成的,也就是说,JDK动态代理中代理类的生成来自于Java反射机制的支撑。

  上面例子中我们将实现InvocationHandler的类取名为“ProxySubject”,这其实是不准确的,我们看到了最后代理类的类型并不是ProxySubject,这个类实际上是处理需要增强的方法,也就是在invoke中的实现逻辑,最后并不是生成这个类型的代理类,这也不是生成的代理类,所以取名这个是不准确的。

  首先从Proxy.newProxyInstance开始,来研究JDK是如何生成代理类的。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

  该方法有3个参数,了解JVM类加载的可能知道确定为同一个类需要有2个条件:

  • 类的全限定名称相同

  • 加载类的类加载器相同

  要想生成目标对象的代理首先就要确保其类加载器相同,所以需要将目标对象的类加载器作为参数传递;其次JDK动态代理技术需要代理类和目标对象都继承自同一接口,所以需要将目标对象的接口作为参数传递;最后,传递InvocationHandler,这是主角,因为我们对目标对象的增强逻辑在这个实现类中,传递该对象使得代理类能够对其进行调用。

  在Proxy.newProxyInstance方法中创建代理类的过程主要有3步:

Spring AOP高级——源码实现(1)动态代理技术

1.检查

 public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h); //1.1检查参数是否为空 final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager(); //获取安全管理器,安全管理器用于对外部资源的访问控制
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs); //1.2检查是否有访问权限
}

  在上面源码中有一个获取安全管理器以及检查是否具有访问权限的过程。安全管理器可能在实际中不太常用,它是为了程序在某些敏感资源的访问上做的权限控制,也就是起到保护程序的作用。在这里暂时不用仔细去探究,只需要大概了解即可。这里做的权限检查实际上是对ClassLoader的检查,例如:有的程序不允许你对类进行代理,此时加入安全管理器即可防止你对该类的代理。

2.获取代理类型

Class<?> cl = getProxyClass0(loader, intfs);    //获取代理类类型

  这句话通过目标对象的类加载器,以及它所继承的接口,即可获取代理类的类型。

 /**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*从注释中可以看到,这个方法用于生成代理类,在调用此方法前必须要确保
*已经做过权限检查。
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) { //一个类最多实现65535个接口
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces); //先从缓存中获取代理类,如果不存在则通过ProxyClassFactory创建,这其中会涉及到比较复杂的代理缓存机制,本篇主要讲动态代理过程的源码实现,对于动态代理的缓存机制在以后再研究。
}

  上面的方法返回的是com.sun.proxy.$Proxy0代理类型,下面就会通过这个代理类型生成代理类。

3.生成代理类

 try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl); //这里还需要做一次检查,检查的是生成的代理类型做权限检查,当然前提还是通过System.setSecurityManager设置安全管理类
} final Constructor<?> cons = cl.getConstructor(constructorParams); //通过反射获取构造器,cl是代理类型其构造器的参数类型为InvocationHandler,所以参数传入InvocationHandler
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) { //判断目标对象的构造器修饰符是我否为public,如果不是则不能生成代理类,返回null
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h}); //最后生成代理类
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}

  以上就是通过JDK动态代理生成代理类的过程,其中会涉及到动态代理的缓存机制,以及代理类字节码的生成过程,由于比较复杂,在本文暂不做介绍。由此可以清楚的看到,JDK的动态代理底层是通过Java反射机制实现的,并且需要目标对象继承自一个接口才能生成它的代理类。

  接下来探讨另一种动态代理技术——CGLib。

CGLib动态代理技术

  通过CGLib来创建一个代理需要引入jar包,其pom.xml依赖如下所示:

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

  前面提到了CGLib动态代理技术不需要目标对象实现自一个接口:

 package cglibproxy;

 /**
* 目标对象(需要被代理的类)
* Created by Kevin on 2017/11/6.
*/
public class RealSubject {
public void sayHello() {
System.out.println("hello");
}
}

  下面我们就使用CGLib代理这个类:

 package cglibproxy;

 import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /**
* 代理类
* Created by Kevin on 2017/11/6.
*/
public class ProxySubject implements MethodInterceptor {
private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create(); //用于创建无参的目标对象代理类,对于有参构造器则调用Enhancer.create(Class[] argumentTypes, Object[] arguments),第一个参数表示参数类型,第二个参数表示参数的值。
} @Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("调用前");
Object result = methodProxy.invokeSuper(object, args);
System.out.println("调用后");
return result;
}
}

  可以看到同样是需要实现一个接口——MethodIntercept,并且实现一个和invoke类似的方法——intercept。

  加入测试代码:

 package cglibproxy;

 /**
* Created by Kevin on 2017/11/6.
*/
public class Main {
public static void main(String[] args) {
RealSubject subject = (RealSubject) new ProxySubject().getProxy(RealSubject.class);
subject.sayHello();
System.out.println(subject.getClass().getName());
}
}

  执行结果:

Spring AOP高级——源码实现(1)动态代理技术

  可以看到的执行结果和JDK动态代理的结果一样,不同的是代理类的类型是cglibproxy.RealSubject$$EnhancerByCGLIB$$cb568e93。接着我们来看CGLib是如何生成代理类的。

  生成代理类的是ProxySubject类中的getProxy方法,而其中又是传入两个参数:

enhancer.setSuperclass(clazz);    //设置需要代理的类
enhancer.setCallback(this); //设置回调方法

  参数设置好后就调用enhancer.create()方法创建代理类。

 public Object create() {
classOnly = false; //这个字段设置为false表示返回的是具体的Object代理类,在createClass()方法中设置的是classOnly=true表示的返回class类型的代理类。
argumentTypes = null; //创建的是无参目标对象的代理类,故没有参数,所以参数类型设置为null
return createHelper();
}

  看来还在调用一个叫createHelper的方法。

 private Object createHelper() {
preValidate(); //提前作一些校验
//
……
}
 private void preValidate() {
if (callbackTypes == null) {
callbackTypes = CallbackInfo.determineTypes(callbacks, false);
validateCallbackTypes = true;
} //检查回调方法是否为空
if (filter == null) { //检查是否设置过滤器,如果设置了多个回调方法就需要设置过滤器
if (callbackTypes.length > 1) {
throw new IllegalStateException("Multiple callback types possible but no filter specified");
}
filter = ALL_ZERO;
}
}

  接着查看createHelper的剩余代码:

 private Object createHelper() {
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key; //在CGLib中也使用到了缓存机制,这段代码也比较复杂,有关缓存的策略暂时也不做分析吧
Object result = super.create(key); //利用字节实现并创建代理类对象
return result;
}

  马马虎虎地只能说是介绍了JDK与CGLib两种动态代理技术,并没有很深入地研究,特别是在两者在缓存机制上的实现,略感遗憾。

  另外,在开头提到了CGLib的性能比JDK高,这实际上并不准确。或许这在特别条件下的确如此,因为在我实测发现JDK8的动态代理效率非常高,甚至略高于CGLib,但是在JDK6的环境下的效率就显得比较低了。所以,通常所说的CGLib性能比JDK动态代理要高,是传统的挂念,实际上Java一直都在不断优化动态代理性能,在比较高版本的JDK条件下可以放行大胆的使用JDK原生的动态代理。

这是一个能给程序员加buff的公众号 

Spring AOP高级——源码实现(1)动态代理技术

上一篇:[转]sql server 常用脚本(日常查询所需)


下一篇:执行打的maven jar包时出现“Exception in thread "main" java.lang.SecurityException: Invalid signature file digest for Manifest main attributes”