AOP基础————动态代理简介

AOP最为Spring的一大核心卖点,在开发当中经常使用到。对于切面、切点的定义我们可能很熟悉,但是对于实现该技术的底层技术细节,却知之甚少。利用周末的时间,博主将AOP的底层实现细节研究了一下,在此做为记录。

AOP

AOP是Aspect Oriented Programing的简称,可译为“面向切面编程”。在业务实现过程当中,有很多横切面上的业务是存在共通之处的,比如,在每一个业务模块的入口,需要打印业务开始的日志信息,在完成业务后需要打印业务调用耗时,对于这些共通的业务,由于不能通过多态继承的方式来实现,会导致很多相似代码出现在不同的业务逻辑当中,AOP就是为了解决这些问题而存在的。下面让我们来一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface ForumService {
public void removeTopic(int topidId);
public void createForum(Forum forum);
}

public class ForumServiceImpl {
private TransactionManager transManager;
private PerformanceMotitor pmonitor;
private TopicDao topicDao;
private ForumDao forumDao;

public void removeTopic(int topidId) {
pmomitor.start();
transManager.beginTransaction();
topicDao.remove(topidId);
transManager.commit();
pmonitor.end();
}

public void createForum(Forum forum) {
pmomitor.start();
transManager.beginTransaction();
forumDao.create(forum);
pmonitor.end();
}
}

在该类的两个方法中,存在共通的逻辑

1
2
pmomitor.start();
transManager.beginTransaction();
1
pmonitor.end();

若没有办法将这些共通的代码提炼出来,则会在业务逻辑中产生大量重新性的代码(该例子虽然可以提取公共逻辑到一个辅助函数中来解决,但是很多公共领域内如事务的开启与关闭并不是要业务逻辑所关心的,而是应该有基础的框架来为客户提供)。那么如何解决这种问题呢?其实最简单的方法便是在调用这些业务方法之前,对调用进行拦截,并执行公共的与业务无关的代码,然后再调用实际的业务逻辑。AOP就是基于这种思想来实现的。在设计模式当中,存在代理模式,而Spring中的AOP实现,便是基于这一设计模式来实现的。接下来让我们看一下,有关Spring中动态代理的一些相关介绍。

Spring AOP的核心基础——动态代理

JDK动态代理

从JDK1.3开始,Java为我们提供了动态代理技术,允许开发者在运行期间创建接口的动态代理实例。JDK的动态代理主要涉及到Java.lang.reflect中的两个类,一个是Proxy,一个是InvocationHander,InvocationHander是一个接口,可以通过该接口定义横切的公共逻辑,并通过反射机制来调用目标类的代码,动态地将横切逻辑与业务逻辑编织在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PerformanceHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) {
pmomitor.start();
transManager.beginTransaction();
Object obj = method.invoke(target, args);
pmonitor.end();
}
}

public class ForumServiceTest {
@Test
public void proxy() {
ForumService target = new ForumServiceImpl();
PerformanceHandler handler = new PerformanceHandler(target);
ForumService proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
proxy.createForum(10);
proxy.removeTopic(1012);
}
}

在上面代码中,我们定义了一个横向的切面逻辑PerformanceHandler,其实现了InvocationHandler。利用Proxy.newProxyInstance函数创建出一个目标对象的代理对象proxy,当调用代理对象的createForum及removeTopic函数时,便会先执行pmomitor.start()和transManager.beginTransaction(),之后才执行目标对象真是的业务逻辑,最后执行pmonitor.end()。如此,也就完成了公共逻辑的提取。让我们看一下InvocationHandler接口

1
2
3
4
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

该接口只有一个接口方法,参数分别为proxy——需要代理的目标对象,method——被拦截的目标对象的方法,args——方法参数。以及Proxy.newProxyInstance:

1
2
3
4
5
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
...
}

对于函数的逻辑暂时无需关心,重点是该函数的入参,loader——目标类的类加载器,interfaces——目标类实现的接口列表,h——调用处理器的实例。

CGLib 动态代理

使用JDK的动态代理有一个限制,即它只能为接口创建代理实例,从Proxy的newProxyInstance方法入参就可以看出,第二个入参interfaces就是需要代理实例实现的接口列表。那么,对于不是interfaces的下接口的实例函数,是否就没有办法实现动态代理呢?CGLib作为一个替代者,填补了这项空缺。
CGLib采用底层的字节码技术,可以为一个类生成子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑,下面是采用CGLib技术编写的一个可以为任何类织入相关逻辑的代理对象的代理创建器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();

public Object getProxy(Class clazz) {
//通过字节码技术动态创建子类实例
enhancer.setSuperClass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
pmomitor.start();
transManager.beginTransaction();
Object result = proxy.invokeSuper(obj, args);
pmonitor.end();
return result;
}
}

public class ForumServiceTest {
@Test
public void proxy() {
CglibProxy proxy = new CglibProxy();
ForumService forumServiceProxy = (ForumService) proxy.getProxy(ForumServiceImpl.class)
forumServiceProxy.createForum(10);
forumServiceProxy.removeTopic(1012);
}
}

在上面的代码中,用户可以通过getProxy方法为一个类创建动态代理对象,无论这个类中的函数是否是从接口实现而来还是类本身的函数,当调用getProxy返回的对象时,横切逻辑都能得到执行。
基于以上两个简单的例子,让我们来看一下Spring中实现动态代理的逻辑。

Spring中代理的实现逻辑

在Spring中,动态代理的创建是在Bean实例化并初始化之后,调用的几个BeanPostProcessor中的进行的,这几个BeanPostProcessor后处理器分别是BeanNameAutoProxyCreator,DefaultAdvisorAutoProxyCreator以及AnnotationAwareAspectJAutoProxyCreator。这三个类都是AbstractAutoProxyCreator类的子类,在该抽象类中有一个保护方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {

if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);

if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}

Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);

proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}

return proxyFactory.getProxy(getProxyClassLoader());
}

我们重点看ProxyFactory proxyFactory = new ProxyFactory();该函数使用了一个代理工厂生成目标类的代理,而ProxyFactory类的getProxy方法:

1
2
3
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}

中我们看到其调用了ProxyFacory父类ProxyCreatorSupport中的createAopProxy方法:

1
2
3
4
5
6
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}

getAopProxyFactory放回的是一个AopProxyFactory接口的实例,而AopProxyFactory唯一的一个实例便是DefaultAopProxyFactory,其createAopProxy逻辑为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}

从这个函数中我们可以看出,Spring使用了之前介绍的两种代理技术——JDK与CGLib,根据不同的条件,则会使用不同的代理技术来创建代理。其中,ObjenesisCglibAopProxy(继承自CglibAopProxy)使用类CgLib动态代理技术创建代理,而JdkDynamicAopProxy使用JDK动态代理技术创建代理。如果通过ProxyFactory的setInterfaces方法指定了目标接口进行代理,则ProxyFactory使用JdkDynamicAopProxy;如果是针对类的代理,则使用CglibAopProxy。此外,还可以通过ProxyFacory的setOptimize(true)方法让ProxyFactory启动优化代理方式,这样,针对接口的代理也会使用CglibAopProxy。

至此,我们介绍了Spring中AOP实现的基础——动态代理的原理以及Spring对两种代理技术的集成。可以看出,JDK的动态代理是基于组合模式的,其代理对象持有一个目标类的实例。而CGLib则是利用字节码技术,基于继承实现的动态代理子类。本文没有介绍具体的AOP的使用方式,重点是介绍AOP的底层实现细节。由于AOP的使用方式介绍起来篇幅过大,有机会的话一定会好好总结下~

欢迎关注个人公众号:
个人公号