aop小结
来源:互联网 发布:windows 设备管理中心 编辑:程序博客网 时间:2024/06/03 21:18
aop 相关理论知识
一直再用aop,一直都没时间整理一下,得空整理一下,备忘!
基本概念
- 通知(Advice):
通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。 - 连接点(Joinpoint):
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。(@After,@Around,@Before.@AfterThrowing.) - 切入点(Pointcut)
通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,spring中允许我们方便的用正则表达式来指定(@After 中的 execution ) - 切面(Aspect)
通知和切入点共同组成了切面:时间、地点和要发生的“故事” - 引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(@DeclareParents,可以理解成 Adaptor模式) - 目标(Target)
即被Advice的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP 让他关注主业务, 日志,性能统计等可以交给Advice 来做) - 代理(proxy)
调用Advice的对象,详细内容参见设计模式里面的代理模式 - 织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码(很少用到,也是一个实现的思路)
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理基于JDK的动态代理技术
pointcut 切入点 格式
Pointcut可以有下列方式来定义或者通过&& || 和!的方式进行组合.
- args()
- @args()
- execution()
- this()
- target()
- @target()
- within()
- @within()
- @annotation
execution
模板
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
demo
任意公共方法的执行:execution(public * *(..))任何一个以“set”开始的方法的执行:execution(* set*(..))AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))定义在service包里的任意方法的执行:execution(* com.xyz.service.*.*(..))定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service..*.*(..))定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")
@within和@target针对类的注解,@annotation是针对方法的注解,@arg 针对参数(必须是在目标对象上声明注解,在接口上声明的不起作用)
带有@Transactional标注的所有类的任意方法.@within(org.springframework.transaction.annotation.Transactional)@target(org.springframework.transaction.annotation.Transactional)(注解类型必须是全限定类型名)带有@Transactional标注的任意方法.@annotation(org.springframework.transaction.annotation.Transactional)参数带有@Transactional标注的方法.@args(org.springframework.transaction.annotation.Transactional)
within,target,this
1. 当前 AOP 对象实现了IPointcutService 接口的任何方法(this 中使用的表达式必须是类型全限定名,不支持通配符) this(cn.javass.spring.chapter6.service.IIntroductionService)2. cn.javass 包及子包下的任何方法执行 within(cn.javass..*) 3. target :使用 “target( 类型全限定名 )” 匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意 target 中使用的表达式必须是类型全限定名,不支持通配符;
args
args表达式 有如下两个作用:① 提供了一种简单的方式来访问目标方法的参数。② 可用于对切入表达式增加额外的限制。
demo
@AfterReturning(returning="retVal", pointcut="execution(* com.chapa.annotation.service.*.*(..)) && args(var,word)") public void access(String var, String word, Object retVal){ //todo } @AfterReturning(returning="retVal", pointcut="execution(* com.chapa.annotation.service.*.*(..)) && args(String,String)") public void access( Object retVal){ //todo }
aop 的两种织入实现方式
spring aop 运行时通过动态代理织入
aop 通过spring容器,基于代理类运行时实现aop功能,注意所有的相关类必须是基于spring容器初始化,否则无法实现aop功能
- jdk dynamic proxy
- 基于cglib的 动态代理
cglib 能基于具体实现类实现动态代理,默认的jdk动态代理的不能对具体类实现代理,只能基于接口.
spring aop 的两种实现
基于配置文件
<aop:config proxy-target-class="false"> <aop:aspect id="TestAspect" ref="aspectAdvice" order="1"> <!--配置com.spring.service包下所有类或接口的所有方法--> <aop:pointcut id="businessService" expression="execution(* com.chapa.service.*.*(..))" /> <aop:before pointcut-ref="businessService" method="doBefore"/> <aop:after pointcut-ref="businessService" method="doAfter"/> <aop:around pointcut-ref="businessService" method="doAround"/> <aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex"/> </aop:aspect> <aop:aspect id="secondAspect" ref="secondAdvice" order="2"> <!--配置com.spring.service包下所有类或接口的所有方法--> <aop:pointcut id="businessService" expression="execution(* com.chapa.service.*.*(..))" /> <aop:before pointcut-ref="businessService" method="doBefore"/> <aop:after pointcut-ref="businessService" method="doAfter"/> <aop:around pointcut-ref="businessService" method="doAround"/> <aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex"/> </aop:aspect> </aop:config>
基于注解
<context:component-scan base-package="com.chapa.annotation.*"/> <context:annotation-config/> <aop:aspectj-autoproxy proxy-target-class="true"/>
- @Aspect 织入点class 注解
- @After , @Around,@Before@AfterThrowing 对应上文配置文件中的织入方式, method注解
基于aspectj 编译时weaver
aspectj实现,在编译期已经完成了相关aop功能的织入.基于 aspectj 实现时,需要用ajc 自己的编译器,
同时因为aspectj实现,实际上是已经在编译期把对应aop代码织入到目标对象,所以不需要通过spring容器初始化对象,实际上aspectj实现已经和spring没有关系了.
ajc maven编译插件
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <configuration> <complianceLevel>1.8</complianceLevel> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions></plugin>
编译器设置ajc编译,idea参见下文,eclipse需要安装插件,自行百度
aop 中的几点关键申明
1. scoped-proxy
coped-proxy 指定由ScopedProxyFactoryBean创建bean,ScopedProxyFactory为这个Bean增加了getTargetObject的方法(使用Introduction),因此所有带上了这个标签的Bean,也就默认实现了ScopedObject的接口,可以调用getTargetObject方法。这个方法的意义在于,因为代理Bean的scope是默认singleton的,这也就意味着,我们每次调用applicationContext.getBean方法,总是返回同一个代理bean,如果我们想要获得scope下真正的bean的话,就实际上调用getTargetObject方法了。
不加scoped-proxy,引用该对象时,只有在最开始注入的时候,获得该对象的代理,也就是说实际上这个引用指向spring初始话的唯一代理对象,无法实现真正意义上的这个对象对因的scope周期.(该注解 合适使用在httpsession 场景,session Scope是web环境下的,有兴趣的可以自行验证)
<aop:aspect> <aop:declare-parents types-matching="com.chapa.service.TestServiceImpl" implement-interface="com.chapa.service.IIntroduce" default-impl="com.chapa.service.IntroduceImpl"/></aop:aspect><bean id="testService" class="com.chapa.service.TestServiceImpl" scope="prototype"> <aop:scoped-proxy/></bean><bean class="com.chapa.service.TestServiceWithoutInterface"></bean><bean id="singletonBean" class="com.chapa.service.SingletonBean" > <property name="testService"> <ref bean="testService" /> </property></bean>
注:coped-proxy 和 Introduction (aop:declare-parents) 共同使用的时候不能设置对像scope为prototype,会导致注入的类强制转换失败(参见demo,具体原因,有兴趣的可以研读一下代码,坐等大神解惑)
2. order && @DeclarePrecedence
这两个都是用来 指定织入顺序,有些业务逻辑必须指定织入顺序否则会导致结果不一致.aop的织入顺序类似 serverlet的 filter ,先进后出,环形嵌套织入的规则. @Order(1) 值越小优先级越大, 只对spring aop 有效,对aspectj 编译实现的aop无效.@DeclarePrecedence ,aspectj 专用的指定顺序的注解,使用改注解必须使用 ajc 编译,使用javac编译会报错.
3. declare-parents
允许我们向现有的类添加新的方法和属性,类似Adatper模式. (@DeclareParents 注解方式,config方式参见上文). value表示目标对象,defaultImpl 待引用对象的实现类.
@DeclareParents(value = "com.chapa.annotation.service.TestServiceImpl",defaultImpl =com.chapa.annotation.service.IntroduceImpl.class) IIntroduce testService;
4.expose-proxy
aop 默认无法对 对象调用自己的方法实现增强,必须基于expose-proxy,实现AOP代理对象的ThreadLocal支持.
<aop:aspectj-autoproxy expose-proxy="true"/><!—注解风格支持--><aop:config expose-proxy="true"><!—xml风格支持-->
设置expose-proxy后,按下面的方式调用当前类的方法,就能实现aop
public void hhh() { System.out.println("TestServiceImpl with interface"); ((ITestService) AopContext.currentProxy()).hprintTime(); }
demo 验证
环境准备
- jdk8
- idea16
- spring 4.1.6
配置idea 使用ajc 编译
关键代码分析
初始化Spring代理对象 (基于 Spring 2.5.6 早期版本 )
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // isProxyTargetClass() 通过spring 设置 是否强制 使用 cglib <aop:config proxy-target-class="true"> 默认 false if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) { return new JdkDynamicAopProxy(config); } else { 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."); } else if(targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } else if(!cglibAvailable) { throw new AopConfigException("Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces."); } else { return DefaultAopProxyFactory.CglibProxyFactory.createCglibProxy(config); } } } //通过类加载器 判断 当前环境是否加载了 cglib 相关类static { cglibAvailable = ClassUtils.isPresent("net.sf.cglib.proxy.Enhancer", DefaultAopProxyFactory.class.getClassLoader()); }
注:早期版本项目必须依赖cglib 否则 对无接口类的 代理实现 会报错
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
注:从 spring 3.2.2 版本开始 spring 提供了 cglib 的实现 org.springframework.cglib.proxy.Enhancer,不用在强制依赖cglib
scope-proxy 实现 源码分析
aop Advice demo
@Aspect@Component // @Repository,@Service,@Controller 表识 需要spring 加载到容器中 ,本质上目前没发现 他们四个有任何区别,不过语义上最好用在对应的层级上@Scope(value=ConfigurableBeanFactory.SCOPE_SINGLETON,proxyMode= ScopedProxyMode.DEFAULT) //指定 spring容器生成改对像的方式(单列.原型) spring 生成代理的方式(基于interface的jdk dynamic proxy,基于cglib)@Order(3) // 指定3spring aop 的植入顺序 ,必须基于代理的模式 才有效@DeclarePrecedence("TestAdvice,SecondAdvice") //指定aop植入顺序 (ajc注解 需要指定使用ajc编译代码)public class TestAdvice { @After("execution(* com.chapa.annotation.service.*.*(..))") public void doAfter(JoinPoint jp) { System.out.println("TestAdvice annotation log Ending method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName()); } @Around("execution(* com.chapa.annotation.service.*.*(..))") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { long time = System.currentTimeMillis(); Object retVal = pjp.proceed(); time = System.currentTimeMillis() - time; System.out.println("TestAdvice annotation process time: " + time + " ms"); return retVal; } @Before("execution(* com.chapa.annotation.service.*.*(..))") public void doBefore(JoinPoint jp) { System.out.println("TestAdvice annotation log Begining method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName()); } @AfterThrowing(throwing="ex",pointcut="execution(* com.chapa.annotation.service.*.*(..))") public void doThrowing(JoinPoint jp, Throwable ex) { System.out.println("TestAdvice annotation method " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + " throw exception"); System.out.println(ex.getMessage()); }}
gitHub demo
gitHub 地址
https://github.com/chen8238065/aop-study
简介
- mvn -f pom-ajc.xml clean compiler (使用ajc编译项目),观察编译后的class,你会发现是有config的aop编译期都没有生成对应功能的字节码,只有是有aspectj注解的才有生成相应的字节码.
- test aspectj编译期织入 效果
目录结构
相关文献
- aop小结
- spring aop小结
- Spring AOP 学习小结
- Spring AOP学习小结
- 【原创】Spring AOP小结
- spring aop 小结
- AOP IOC 初学者小结
- Spring AOP 学习小结
- AOP 专业术语 小结
- Spring AOP 学习小结
- Spring AOP小结
- 学习Spring aop 小结
- Spring知识小结--AOP
- spring Aop小结
- Spring AOP框架体系小结
- Spring AOP xml配置实现(sxt小结)
- spring aop小结(概念解析)
- Spring AOP 学习小结---转载自ITeye
- python学习群发邮件
- HashMap的实现原理-博客总结
- mui 地址管理
- 架构设计:负载均衡层设计方案(2)——Nginx安装
- 移植搭建阿里云linux服务器
- aop小结
- mui 低用全
- 工资多少不在于用什么语言,在于你用它做什么事。 web 工程师赚的是对 Web 这块的了解,机器学习工程师赚的是对机器学习的了解。
- Java:按值传递还是按引用传递详细解说
- [Unity]Ray射线物理检测碰撞和LayerMask的使用
- mui 订单管理
- # 美团点评CodeM编程大赛-题五
- Spring Data MongoDB 五:进阶文档查询(分页、Morphia)(二)
- AGV调度方法入门