前言
我们都知道Java是一门面向对象(OOP
)的语言,所谓万物皆对象。但是它也存在着一些个弊端:当你需要给多个不具有继承关系的对象引入同一个公共的行为的时候,例如日志,安全检测等等,我们只能在每个对象中去引入这个公共行为,这样就产生了大量的重复代码,并且耦合度也会很高,不利于维护。正因如此就产生了面向切面(AOP
)编程。可以说有了AOP使得面向对象更加完善,是对其的一个补充,AOP所关注的方式是横向的,不同于OOP的纵向,接下来我们详细讲解一下spring中的AOP。
AOP的使用
我们先从动态AOP开始
首先引入Aspect
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version></dependency>
创建用于拦截的测试Bean
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}
创建Advisor
spring中一改以往摒弃了它最原始的繁杂的配置方式,目前采用@AspectJ
注解的方式对POJO进行标注,使得AOP的工作大大简化。例如在AspectJTest类中,我们要做的就是在所有类的test方法执行前在控制台输出beforeTest,在所有类的test方法执行后打印afterTest,同时又使用环绕通知的方式在所有类的方法执行前后在此分别打印around......before和around......after
AspectJTest代码
@AspectpublicclassAspectJTest{@Pointcut("execution(**.test(..))")publicvoidtest(){}@Before("test()")publicvoidbeforeTest(){System.out.println("beforeTest");}@Around("test()")publicObjectaroundTest(ProceedingJoinPointjoinPoint){System.out.println("around.........before");Objectproceed=null;try{proceed=joinPoint.proceed();}catch(Throwablethrowable){throwable.printStackTrace();}System.out.println("around.........after");returnproceed;}@After("test()")publicvoidafterTest(){System.out.println("afterTest");}}
创建配置文件
<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启AOP自动配置--><aop:aspectj-autoproxy/><beanid="testBean"class="com.vipbbo.selfdemo.spring.aop.test.TestBean"><propertyname="message"value="一个苦逼的程序员"/></bean><beanid="aspect"class="com.vipbbo.selfdemo.spring.aop.test.AspectJTest"/></beans>
在编写配置文件中要注意图中的声明、命名空间:
测试类
publicclassTest{publicstaticvoidmain(String[]args){ApplicationContextac=newClassPathXmlApplicationContext("spring-aop.xml");TestBeantestBean=(TestBean)ac.getBean("testBean");testBean.test();}}
运行结果如下:
通过上述代码可以看出,Spring实现了对所有类的test方法进行了增强,使得辅助功能(日志等)可以独立出来,也做到了解耦和对程序的扩展。那么Spring是如何实现AOP的呢? 实现我们知道,Spring是由一个配置文件控制是否支持注解的AOP,也就是<aop:aspectj-autoproxy/>
,当配置文件有了这句声明的时候,Spring就会支持注解的AOP,那么分析从这里开始。
AOP自定义注解源码解读
我们知道Spring中的自定义注解,如果声明了自定义注解,那么在Spring中的一个地方一定注册了对应的解析器,我们从aspectj-autoProxy
入手:
在Spring源码中全局搜索,我们发现了在包`org.springframework.aop.config`下的`AopNamespaceHandler`,然后我们打开这个类
在AopNamespaceHandler
类中我们发现了这个init
函数
publicclassAopNamespaceHandlerextendsNamespaceHandlerSupport{/***Registerthe{@linkBeanDefinitionParserBeanDefinitionParsers}forthe*'{@codeconfig}','{@codespring-configured}','{@codeaspectj-autoproxy}'*and'{@codescoped-proxy}'tags.*/@Overridepublicvoidinit(){//In2.0XSDaswellasin2.5+XSDsregisterBeanDefinitionParser("config",newConfigBeanDefinitionParser());registerBeanDefinitionParser("aspectj-autoproxy",newAspectJAutoProxyBeanDefinitionParser());registerBeanDefinitionDecorator("scoped-proxy",newScopedProxyBeanDefinitionDecorator());//Onlyin2.0XSD:movedtocontextnamespacein2.5+registerBeanDefinitionParser("spring-configured",newSpringConfiguredBeanDefinitionParser());}}
从上述代码可以看出,在解析配置文件的时候,一旦遇到aspectj-autoproxy
就会使用AspectJAutoProxyBeanDefinitionParser
解析器进行解析,接下来我们该函数的具体实现:
注册AnnotationAwareAspectJAutoProxyCreator
所有的解析器都是对接口BeanDefinitionParser
的实现,入口都是从parse
函数开始的,AnnotationAwareAspectJAutoProxyCreator的parse函数如下:
看源码(具体实现在AspectJAutoProxyBeanDefinitionParser.class
)
@Override@NullablepublicBeanDefinitionparse(Elementelement,ParserContextparserContext){//注册AnnotationAwareAspectJAutoProxyCreatorAopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext,element);//对于注解中的子类进行处理extendBeanDefinition(element,parserContext);returnnull;}
从上述代码我们又看出具体实现逻辑是在registerAspectJAnnotationAutoProxyCreatorIfNecessary
方法中实现的,继续进入到函数方法体内:
看源码(具体实现在AopNamespaceUtils.class
)
/***注册AnnotationAwareAspectJAutoProxyCreator*@paramparserContext*@paramsourceElement*/publicstaticvoidregisterAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContextparserContext,ElementsourceElement){//注册或升级AutoProxyCreator定义beanName为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinitionBeanDefinitionbeanDefinition=AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(),parserContext.extractSource(sourceElement));//对于proxy-target-class以及expose-proxy属性的处理useClassProxyingIfNecessary(parserContext.getRegistry(),sourceElement);//注册组件并通知,便于监听器作进一步处理registerComponentIfNecessary(beanDefinition,parserContext);}
看上述源码可知在函数registerAspectJAnnotationAutoProxyCreatorIfNecessary
中主要做了三件事,基本是每行代码做了一件。接下来我们一一解析:
函数体内的registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法
注册或升级AnnotationAwareAspectJAutoProxyCreator
对于AOP的实现基本都是靠AnnotationAwareAspectJAutoProxyCreator来完成的,它可以根据@Pointcut
注解定义的节点来自动代理相匹配的bean,但是为了配置简单,Spring使用了自动配置来帮我们自动注册AnnotationAwareAspectJAutoProxyCreator,其过程就是在这里实现的。我们继续跟进方法内部:
看源码(具体实现在AopConfigUtils.class
)
@NullablepublicstaticBeanDefinitionregisterAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistryregistry,@NullableObjectsource){returnregisterOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class,registry,source);}
在上面代码中我们看到了函数registerOrEscalateApcAsRequired
继续跟进:
看源码(具体实现在AopConfigUtils.class
)
@NullableprivatestaticBeanDefinitionregisterOrEscalateApcAsRequired(Class<?>cls,BeanDefinitionRegistryregistry,@NullableObjectsource){Assert.notNull(registry,"BeanDefinitionRegistrymustnotbenull");//如果已经存在了自动代理创建器且存在的自动代理创建器与现在的不一致,那么需要根据优先级判断到底需要使用哪一个if(registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)){BeanDefinitionapcDefinition=registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);if(!cls.getName().equals(apcDefinition.getBeanClassName())){intcurrentPriority=findPriorityForClass(apcDefinition.getBeanClassName());intrequiredPriority=findPriorityForClass(cls);if(currentPriority<requiredPriority){//改变bean最重要的就是改变bean所对应的className属性apcDefinition.setBeanClassName(cls.getName());}}returnnull;}//注册BeanDefinition,Class为AnnotationAwareAspectJAutoProxyCreator.class,beanName为internalAutoProxyCreatorRootBeanDefinitionbeanDefinition=newRootBeanDefinition(cls);beanDefinition.setSource(source);beanDefinition.getPropertyValues().add("order",Ordered.HIGHEST_PRECEDENCE);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME,beanDefinition);returnbeanDefinition;}
同时我们也要看一下AopConfigUtils
类中的这部分代码:
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}0
以上代码实现了自动注册AnnotationAwareAspectJAutoProxyCreator
类的功能,同时这里还设计到一个优先级的问题,假设如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底使用哪一个
处理proxy-target-class以及expose-proxy属性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);这部分做了对proxy-target-class以及expose-proxy属性的处理。
看源码(具体实现在AopNamespaceUtils。class
)
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}1
在上述代码中使用到了**两个强制使用的方法**分别是`forceAutoProxyCreatorToUseClassProxying`和`forceAutoProxyCreatorToExposeProxy`,强制使用的过程其实也是一个属性设置的过程,两个函数的具体实现如下(具体实现在`AopConfigUtils.class`):
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}2
接下来让我们说一下proxy-target-class
和expose-proxy
这两个属性
proxy-target-proxy :Spring AOP部分使用的JDK动态代理
或者是CGLIB
代理来为目标对象创建代理。(这里建议尽量使用JDK动态代理),如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有目标类型实现的接口都将被代理;倘若目标对象没有实现任何接口,则会创建一个CGLIB代理。
另外如果你想强制使用CGLIB代理的话,(例如希望代理目标对象的所有方法,而不只是实现子接口的方法)那也是可以的,但是需要考虑两个问题。
与之相比较,JDK本身就提供了动态代理,强制使用CGLIB代理需要将<aop-config>
中的proxy-target-class属性设置为true。
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}3
无法通知(advise)Final方法,因为它们不能被重写
你需要将CGLIB二进制发行包放在classpath下面
当你使用CGLIB代理
和@AspectJ
自动代理支持,可以按照以下方式设置
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}4
expose-proxy:有时候目标对象内部的自我调用将无法实施切面中的增强,如下:
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}5
此处的this指向目标对象,因此调用this.b将不会执行b的事务切面,即不会执行事务增强,因此b方法的事务定义` @Transactional(propagation = Propagation.REQUIRES_NEW) `将不会实施,为了解决这个问题,我们可以这样做:
packagecom.vipbbo.selfdemo.spring.aop.test;publicclassTestBean{privateStringmessage="TestMessage";publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidtest(){System.out.println(this.message);}}6
然后将以上代码中的`this.b()`修改为`((AService)AopContext.currentProxy()).b()`即可。
通过以上的修改便可完成对a
和b
方法的同时增强
简单说一下JDK动态代理的CGLIB代理
JDK动态代理:其对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的创建。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM
(开源的Java字节码编辑类库) 操作字节码类实现的,性能要比JDK强。