Spring AOP高级——源码实现(1)动态代理技术
jdk1.8.0_144
在正式进入Spring AOP的源码实现前,我们需要准备一定的基础也就是面向切面编程的核心——动态代理。 动态代理实际上也是一种结构型的设计模式,JDK中已经为我们准备好了这种设计模式,不过这种JDK为我们提供的动态代理有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());
}
}
执行结果:
可以看到和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步:
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());
}
}
执行结果:
可以看到的执行结果和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)动态代理技术的更多相关文章
- Spring AOP高级——源码实现(3)AopProxy代理对象之JDK动态代理的创建过程
spring-aop-4.3.7.RELEASE 在<Spring AOP高级——源码实现(1)动态代理技术>中介绍了两种动态代理技术,当然在Spring AOP中代理对象的生成也是运用 ...
- Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)
本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E9%A ...
- Spring AOP中的JDK和CGLib动态代理哪个效率更高?
一.背景 今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib动态代理哪个效率更高? 二.基本概念 首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理, ...
- Spring AOP部分源码分析
Spring源码流程分析-AOP相关 根据Spring源码整理,其中Calculator为自定义的实现方法. AnnotationConfigApplicationContext()加载配置类的流程 ...
- 分析spring aop的源码实现
AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较. spring AOP是Sp ...
- Spring AOP的源码流程
一.AOP完成日志输出 1,导入AOP模块 <dependency> <groupId>org.springframework</groupId> <arti ...
- mybatis源码学习: 动态代理的应用(慢慢改)
动态代理概述 在学spring的时候知道使用动态代理实现aop,入门的列子:需要计算所有方法的调用时间.可以每个方法开始和结束都获取当前时间咋办呢.类似这样: long current=system. ...
- mybatis源码阅读(动态代理)
这一篇文章主要是记录Mybatis的动态代理学习成果,如果对源码感兴趣,可以看一下上篇文章 https://www.cnblogs.com/ChoviWu/p/10118051.html 阅读本篇的 ...
- Spring AOP简介与底层实现机制——动态代理
AOP简介 AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想.AOP 是 OOP(面向对象编程 Object Oriented Programmi ...
随机推荐
- ObjectSNMP
下面的例子,就是使用ObjectSNMP获取RFC1213-MIB的例子:其中的system和ifTable对象就是对应的SNMPMIB中的system组合interface中的ifTable表. p ...
- 怎样使用自定义标签简化 js、css 引入?
国庆将至,工作兴致全无,来总结点项目里平时不起眼干货. 前端引入 js .css 一般是这样: <script type="text/javascript" src=&quo ...
- vue-chat项目之重构与体验优化
前言 vue-chat 也就是我的几个月之前写的一个基于vue的实时聊天项目,到目前为止已经快满400star了,注册量也已经超过了1700+,消息量达2000+,由于一直在实习,没有时间对它频繁地更 ...
- bzoj1143 祭祀river(最大独立集)
[CTSC2008]祭祀river Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 2175 Solved: 1098[Submit][Status] ...
- Easy sssp
Easy sssp 时间限制: 1 Sec 内存限制: 128 MB提交: 103 解决: 20[提交][状态][讨论版] 题目描述 输入数据给出一个有N(2 < = N < = ...
- Codeforces Round #430 (Div. 2)
A. Kirill And The Game time limit per test 2 seconds memory limit per test 256 megabytes input stand ...
- Windows+Apache2.4.10+PHP7.0+MySQL5.6.21安装
一.安装包下载 apache2.4.10 http://www.apachelounge.com/download/win64/ PHP7.0.7 http://windows.php.net/dow ...
- C#分布式事务解决方案-TransactionScope
引用一下别人的导读:在实际开发工作中,执行一个事件,然后调用另一接口插入数据,如果处理逻辑出现异常,那么之前插入的数据将成为垃圾数据,我们所希望的是能够在整个这个方法定义为一个事务,Transacti ...
- 个人工作中ssd、audio python脚本总结
1.os.system(cmd)或者os.popen(cmd)调用外部命令 cmd中需要注意特殊字符的转义功能,如: USBSTOR\DISK&VEN_GENERIC-&PROD_SD ...
- Servlet 笔记-异常处理
当一个 Servlet 抛出一个异常时,Web 容器在使用了 exception-type 元素的 web.xml 中搜索与抛出异常类型相匹配的配置. 必须在 web.xml 中使用 error-pa ...