问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

Spring源码之AOP的使用

发布网友 发布时间:2024-09-25 16:26

我来回答

1个回答

热心网友 时间:2024-10-04 14:23

前言

我们都知道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:SpringAOP部分使用的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指向目标对象,因此调用th

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
荣耀MagicVs2怎么使用更省电荣耀MagicVs2使用更省电方法【详解】_百度... 荣耀MagicV2怎么开启应用通知荣耀MagicV2开启应用通知方法【步骤... 面部脂溢性皮炎能根治吗 脸部皮炎能根治吗 清蒸螃蟹蒸多长时间 清蒸螃蟹做法英语介绍过程 关于退档问题。我是湖南文科生,今年分数583,正好压一本线。 ...背上有很多痘痘,用了很多药,也没感觉有什么效果,怎么才可以去掉啊... ...好多年了,还有好多痘印,用了很多药物均没有效果,怎么办哦?_百度知 ... 脸上总是起痘痘,起了几年了,在医院也开过药,也用过祛痘的洗面奶,可是... 《我们的父辈》里死了的主角都是怎么死的?脸盲加字幕里人名都是德文... android gson null怎么处理 为什么我的账号总被封号? 《梦幻西游》梦幻西游跑商代练攻略 02022135106是哪里电话? 02028123775是什么电话? 1688铺货下单收款到哪里 1688铺货成功后怎么操作 阿里巴巴运营:企业融易收升级步骤流程详解 双流爱华学院到锦江区水彬街蓝谷地怎么走 离蓝谷地五期最近的元祖在哪里? 大便不成形是什么导致的 痔疮会不会让大便变细 做混合痔手术的后遗症 大便不成形肛门坠胀怎么回事 痔疮内外混合痔的症状 中国十大磁砖品牌博华陶瓷也是十大品牌! 便秘和混合痔无必然联系 博华陶瓷质量可以吗,身边很多朋友都介绍用这个牌子。 我打记事以来 大便就不成形 40岁后又得了混合痔 就更不成形 每天早上准 ... 选购海南奶椰时需要注意什么细节问题? 如何挑选优质的泰国奶椰? 怎样才能判断海南奶椰的是否新鲜? 如何选购优质的椰子奶? 海南奶椰有哪些独特的口感? Q宠怎么长的快? 海南奶椰的口感该怎么描述? 宠物如何快速长大? 有哪些好喝的椰子奶推荐? 海南奶椰的口感有什么独特之处? 怎么才能养好Q宠 qq宠物怎样驯养 qq宠物怎么能好好养 QQ宠物怎么养才能快速长大? 怎样让QQ宠物快点长大 QQ宠物怎么让他快点长大啊? 如何让QQ宠物赶快长大?急 怎样才可以让QQ宠物快点长大 《我们的父辈》——被战争杀死的一代 崩坏星穹铁道克拉拉带什么遗器好-克拉拉遗器推荐