aop小结

来源:互联网 发布:windows 设备管理中心 编辑:程序博客网 时间:2024/06/03 21:18

aop 相关理论知识

一直再用aop,一直都没时间整理一下,得空整理一下,备忘!

基本概念

  1. 通知(Advice):
    通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。
  2. 连接点(Joinpoint):
    程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。(@After,@Around,@Before.@AfterThrowing.)
  3. 切入点(Pointcut)
    通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,spring中允许我们方便的用正则表达式来指定(@After 中的 execution )
  4. 切面(Aspect)
    通知和切入点共同组成了切面:时间、地点和要发生的“故事”
  5. 引入(Introduction)
    引入允许我们向现有的类添加新的方法和属性(@DeclareParents,可以理解成 Adaptor模式)
  6. 目标(Target)
    即被Advice的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP 让他关注主业务, 日志,性能统计等可以交给Advice 来做)
  7. 代理(proxy)
    调用Advice的对象,详细内容参见设计模式里面的代理模式
  8. 织入(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-study

相关文献