1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > springaop实现原理_spring AOP的实现原理

springaop实现原理_spring AOP的实现原理

时间:2022-06-29 06:29:40

相关推荐

springaop实现原理_spring AOP的实现原理

基于代理(Proxy)的AOP实现

首先,这是一种基于代理(Proxy)的实现方式。下面这张图很好地表达了这层关系:

这张图反映了参与到AOP过程中的几个关键组件(以@Before Advice为例):

调用者Beans - 即调用发起者,它只知道目标方法所在Bean,并不清楚代理以及Advice的存在目标方法所在Bean - 被调用的目标方法生成的代理 - 由Spring AOP为目标方法所在Bean生成的一个代理对象Advice - 切面的执行逻辑

它们之间的调用先后次序反映在上图的序号中:

调用者Bean尝试调用目标方法,但是被生成的代理截了胡代理根据Advice的种类(本例中是@Before Advice),对Advice首先进行调用代理调用目标方法返回调用结果给调用者Bean(由代理返回,没有体现在图中)

为了理解清楚这张图的意思和代理在中间扮演的角色,不妨看看下面的代码:

@Componentpublic class SampleBean { public void advicedMethod() { } public void invokeAdvicedMethod() { advicedMethod(); }}@Aspect@Componentpublic class SampleAspect { @Before("execution(void advicedMethod())") public void logException() { System.out.println("Aspect被调用了"); }}sampleBean.invokeAdvicedMethod(); // 会打印出 "Aspect被调用了" 吗?

SampleBean扮演的就是目标方法所在Bean的角色,而SampleAspect扮演的则是Advice的角色。很显然,被AOP修饰过的方法是advicedMethod(),而非invokeAdvicedMethod()。然而,invokeAdvicedMethod()方法在内部调用了advicedMethod()。那么会打印出来Advice中的输出吗?

答案是不会

如果想不通为什么会这样,不妨再去仔细看看上面的示意图。

这是在使用Spring AOP的时候可能会遇到的一个问题。类似这种间接调用不会触发Advice的原因在于调用发生在目标方法所在Bean的内部,和外面的代理对象可是没有半毛钱的关系哦。我们可以把这个代理想象成一个中介,只有它知道Advice的存在,调用者Bean和目标方法所在Bean知道彼此的存在,但是对于代理或者是Advice却是一无所知的。因此,没有通过代理的调用是绝无可能触发Advice的逻辑的。如下图所示:

Spring AOP的两种实现方式

Spring AOP有两种实现方式:

基于接口的动态代理(Dynamic Proxy)基于子类化的CGLIB代理

我们在使用Spring AOP的时候,一般是不需要选择具体的实现方式的。Spring AOP能根据上下文环境帮助我们选择一种合适的。那么是不是每次都能够这么”智能”地选择出来呢?也不尽然,下面的例子就反映了这个问题:

@Componentpublic class SampleBean implements SampleInterface { public void advicedMethod() { } public void invokeAdvicedMethod() { advicedMethod(); }}public interface SampleInterface {}

在上述代码中,我们为原来的Bean实现了一个新的接口SampleInterface,这个接口中并没有定义任何方法。这个时候,再次运行相关测试代码的时候就会出现异常(摘录了部分异常信息):

org.springframework.beans.factory.BeanCreationException: Error ceating bean with name 'com.destiny1020.SampleBeanTest': Injection of autowired dependencies failedCaused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.destiny1020.SampleBean] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.

也就是说在Test类中对于Bean的Autowiring失败了,原因是创建SampleBeanTest Bean的时候发生了异常。那么为什么会出现创建Bean的异常呢?从异常信息来看并不明显,实际上这个问题的根源在于Spring AOP在创建代理的时候出现了问题。

这个问题的根源可以在这里得到一些线索:

Spring AOP Reference - AOP Proxies

文档中是这样描述的(每段后加上了翻译):

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.Spring AOP默认使用标准的JDK动态代理来实现AOP代理。这能使任何借口(或者一组接口)被代理。Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.Spring AOP也使用CGLIB代理。对于代理classes而非接口这是必要的。如果一个业务对象没有实现任何接口,那么默认会使用CGLIB。由于面向接口而非面向classes编程是一个良好的实践;业务对象通常都会实现一个或者多个业务接口。强制使用CGLIB也是可能的(希望这种情况很少),此时你需要advise的方法没有被定义在接口中,或者你需要向方法中传入一个具体的对象作为代理对象。

因此,上面异常的原因在于:

强制使用CGLIB也是可能的(希望这种情况很少),此时你需要advise的方法没有被定义在接口中。

我们需要advise的方法是SampleBean中的advicedMethod方法。而在添加接口后,这个方法并没有被定义在该接口中。所以正如文档所言,我们需要强制使用CGLIB来避免这个问题。

强制使用CGLIB很简单:

@Configuration@EnableAspectJAutoProxy(proxyTargetClass = true)@ComponentScan(basePackages = "com.destiny1020")public class CommonConfiguration {}

向@EnableAspectJAutoProxy注解中添加属性proxyTargetClass = true即可。

CGLIB实现AOP代理的原理是通过动态地创建一个目标Bean的子类来实现的,该子类的实例就是AOP代理,它建立起了目标Bean到Advice的联系。

当然还有另外一种解决方案,那就是将方法定义声明在新创建的接口中并且去掉之前添加的proxyTargetClass = true:

@Componentpublic class SampleBean implements SampleInterface { @Override public void advicedMethod() { } @Override public void invokeAdvicedMethod() { advicedMethod(); }}public interface SampleInterface { void invokeAdvicedMethod(); void advicedMethod();}@Configuration@EnableAspectJAutoProxy@ComponentScan(basePackages = "com.destiny1020")public class CommonConfiguration {}

从Debug Stacktrace的角度也可以看出这两种AOP实现方式上的区别:JDK动态代理CGLIB关于动态代理和CGLIB这两种方式的简要总结如下:JDK动态代理(Dynamic Proxy)基于标准JDK的动态代理功能只针对实现了接口的业务对象CGLIB通过动态地对目标对象进行子类化来实现AOP代理,上面截图中的SampleBean$$EnhancerByCGLIB$$1767dd4b即为动态创建的一个子类需要指定@EnableAspectJAutoProxy(proxyTargetClass = true)来强制使用当业务对象没有实现任何接口的时候默认会选择CGLIB

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。