1.1,方法一

访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为JoinPoint类型,当该类型增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用的方法:

  • Object[] getArgs(): 返回执行目标方法时的参数。
  • Signature getSignature(): 返回被增强的方法的相关信息。
  • Object getTarget(): 返回被织入增强处理的目标对象。
  • Object getThis(): 返回AOP框架为目标对象生成的代理对象。

当使用Around的增加处理时,需要将第一个参数定义为ProceedingJoinPoint类型,该类型是JoinPoint类型的子类。

@Aspect
public class AuthAspect {
    //匹配所有Bean包下的所有类的所有方法作为切入点
    @Around("execution(* Bean.*.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("Around增强:执行目标方法之前,模拟开始事务");
        Object[] args = proceedingJoinPoint.getArgs();  //获取原始的调用参数
        if (args != null && args.length > 1) {
            //修改目标方法调用参数
            args[0] = "增强的前缀" + args[0];
        //以改变后的参数去执行目标方法,并保存目标方法执行后的返回值。
        Object rvt = proceedingJoinPoint.proceed(args);
        System.out.println("执行目标方法之后,模拟结束事务...");
        if (rvt != null && rvt instanceof Integer) {
            rvt = (Integer) rvt * (Integer) rvt;
        return rvt;
    @Before("execution(* Bean.*.*(..))")
    public void before(JoinPoint point) {
        System.out.println("Before增强:模拟执行权限检查");
        //返回被织入增强处理的目标方法
        System.out.println("Before增强:被织入增强处理的目标方法为:" + point.getSignature().getName());
        //访问执行目标方法的参数
        System.out.println("Before增强:目标方法的参数为:" + Arrays.toString(point.getArgs()));
        //访问被增强处理的目标对象
        System.out.println("Before增强:被织入增强处理的目标对象为:" + point.getTarget());
    @AfterReturning(pointcut = "execution(* Bean.*.*(..))",returning = "str")
    public void afterReturn(JoinPoint jp,Object str){
        System.out.println("AfterReturning增强:获取目标方法返回值:"+str);
        System.out.println("AfterReturning增强:模拟记录日志功能...");
        //返回被织入增强处理的目标方法
        System.out.println("AfterReturning增强:被织入增强处理的目标方法为:"+jp.getSignature().getName());
        //访问执行目标的参数
        System.out.println("AfterReturning增强:目标方法的参数为:"+Arrays.toString(jp.getArgs()));
        //访问被增强处理的目标对象
        System.out.println("AfterReturning增强,被织入增强处理的目标对象为:"+jp.getTarget());
    @After("execution(* Bean.*.*(..))")
    public void release(JoinPoint jp){
        System.out.println("After增强:模拟方法结束后的资源是否......");
        //返回被织入增强处理的目标方法
        System.out.println("After增强:被织入增强处理的目标方法为:"+jp.getSignature().getName());
        //返回执行目标方法的参数
        System.out.println("After增强:目标方法的参数为:"+Arrays.toString(jp.getArgs()));
        //访问被增强处理的目标对象
        System.out.println("After增强:被织入增强处理的目标对象为:"+jp.getTarget());
 
Around增强:执行目标方法之前,模拟开始事务
Before增强:模拟执行权限检查
Before增强:被织入增强处理的目标方法为:add
Before增强:目标方法的参数为:[30]
Before增强:被织入增强处理的目标对象为:Bean.HelloWord@d21a74c
执行目标方法之后,模拟结束事务...
After增强:模拟方法结束后的资源是否......
After增强:被织入增强处理的目标方法为:add
After增强:目标方法的参数为:[30]
After增强:被织入增强处理的目标对象为:Bean.HelloWord@d21a74c
AfterReturning增强:获取目标方法返回值:900
AfterReturning增强:模拟记录日志功能...
AfterReturning增强:被织入增强处理的目标方法为:add
AfterReturning增强:目标方法的参数为:[30]
AfterReturning增强,被织入增强处理的目标对象为:Bean.HelloWord@d21a74c
 

SpringAOP采用和AspectJ一样的有限顺序来织入增强处理:在“进入”连接点时,具有最高优先级的增强处理将先被织入(所以在给定的两个Before增强处理中,优先级高的那个会先执行)。在“退出”连接点时,具有最高优先级的增强处理回最后被织入(所以在给定的两个After增强处理中,优先级高的那个会后执行)。

当不同的切面里的两个增强处理需要在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理。如果应用需要指定不同切面类里增强处理的优先级,Spring提供了两个解决方案:

  • 让切面类实现org.springframework.core.Ordered接口,实现该接口只需要实现一个int getOrder()方法,改返回值越小,则优先级越高。
  • 直接使用@Order注解来修饰一个切面类,使用@Order注解时可指定一个int型的value属性,该属性值越小,则优先级越高。

1.2,方法二

除了上面那种,Spring还提供了一种简单的方式:可以在程序中使用args切入点表达式来绑定目标方法的参数。如果在一个args表达式中指定了一个或多个参数,则该切入点将只匹配具有对应形参的方法,且目标方法的参数值将被传入增强处理方法。

@Aspect
public class AuthAspect {
    @Before("execution(* Bean.*.*(..)) && args(arg0,arg1)")
    public void before(String arg0,String arg1) {
        System.out.println("调用目标方法第1个参数:"+arg0);
        System.out.println("调用目标方法第2个参数:"+arg1);
 

该切入点表达式增加了&&args(arg0,arg1)部分,这意味着可以在增强处理方法(before()方法)中定义arg0、arg1两个形参。

2,重用切入点

正如之前的程序,切面类中定义了4个增强处理,定义4个增强处理时分别指定了相同的切入点表达式,这种做饭显然不符合软件设计原则:居然将那个切入点表达式重复了4次!如果有一天需要修改该切入点表达式,岂不是需要修改4次?

为了解决这个问题,Spring允许定义切入点。所谓定义切入点,其实质就是为一个切入点表达式起一个名字,从而允许在多个增强处理中重用该名称。

SpringAOP只支持将SpringBean的方法执行作为连接点,所以可以把切入点看成所有能和切入点表达式匹配的Bean方法。切入点表达式包含两个部分:

  • 一个切入点表达式。
  • 一个包含名字和任意参数的方法名称。

其中切入点表达式用于指定切入点和哪些方法进行匹配,包含名字和任意参数的方法签名将作为该切入点的名称。切入点签名采用一个普通的方法定义(方法体通常为空)来提供,且该方法的返回值必须为void;切入点表达式需要使用@Pointcut注解来标注。

@Pointcut("execution(* Bean.*.*(..))")
public void log(){
    System.out.println("我到底是干啥的?");
 

切入点表达式,也就是组成@Pointcut注解的值,是正规的AspectJ切入点表达式。一旦采用上面的代码片段定义了名为anyOldTransfer的切入点之后,程序就可多次重复使用该切入点,甚至可以在其他切面类、其他包的切面类里使用该切入点,至于是否可以在其他切面类、其他包的切面类里访问该切入点,则取决于该方法签名前的访问控制。

@Aspect
public class AuthAspect {
    @AfterReturning(pointcut = "Bean.LogAspect.log()")
    public void afterReturning() {

3,切入点指示符

3.1,概述

前面定义切入点表达式时大量采用了execution表达式,其中execution就是一个切入点指示符。SpringAOP仅支持部分AspectJ的切入点指示符,但Spring AOP还额外支持一个bean切入点指示符。不仅如此,因为Spring AOP只支持使用方法调用作为连接点,所以Spring AOP的切入点指示符仅匹配方法执行的连接点。

Spring一共支持如下几种切入点指示符:

  • execution:用于匹配方法执行的连接点。
  • within:用于匹配指定类型内的方法执行。
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配。
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配。
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法。

execution(<注解>? <修饰符模式>? <返回类型模式> <类型声明>?<方法名模式>(<参数模式>) <异常模式>?)

 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

  • 注解:可选,方法上持有的注解,如@Deprecated;
  • 修饰符:可选,如public、protected;
  • 返回值类型:必填,可以是任何类型模式;“*”表示所有类型;
  • 类型声明:可选,可以是任何类型模式;
  • 方法名:必填,可以使用“*”进行模式匹配;
  • 参数列表:

“()”表示方法没有任何参数。

“(..)”表示匹配接受任意个参数的方法,“(..,String)”表示匹配接受String类型的参数结束,且其前边可以接受有任意个参数的方法;“(String,..)” 表示匹配接受String类型的参数开始,且其后边可以接受任意个参数的方法;“(*,String)” 表示匹配接受String类型的参数结束,且其前边接受有一个任意类型参数的方法。

异常列表:可选,以“throws 异常全限定名列表”声明,异常全限定名列表如有多个以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。

类型匹配语法

  • 匹配任何数量字符;
  • ..  匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
  • +  匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
  • java.lang.String:匹配String类型; 
  • java.*.String:匹配java包下的任何“一级子包”下的String类型;如匹配java.lang.String,但不匹配java.lang.ss.String 
  • java..*:匹配java包及任何子包下的任何类型; 如匹配java.lang.String、java.lang.annotation.Annotation 
  • java.lang.*ing:匹配任何java.lang包下的以ing结尾的类型; 
  • java.lang.Number+:匹配java.lang包下的任何Number的子类型;如匹配java.lang.Integer,也匹配java.math.BigInteger

组合切入点表达式

  • &&:要求连接点同时匹配两个切入点表达式。
  • ||:只要连接点匹配任意一个切入点表达式。
  • !:要求连接点不匹配指定的切入点表达式。

3.2,切入点使用示例

通过方法签名定义切点

  • execution(public * *(..))匹配所有目标类的public方法。第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法。
  • execution(* *To(..))匹配目标类所有以To为后缀的方法。它匹配greetTo()和serveTo()方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法。

通过类定义切点

  • execution(* com.test.Waiter.*(..)):匹配Waiter接口的所有方法,它匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()方法。第一个*代表返回任意类型,com.test.Waiter.*代表Waiter包中的所有方法。
  • execution(* com.test.Waiter+.*(..)):匹配Waiter接口及其所有实现类的方法,它不但匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()这两个Waiter接口定义的方法,同时还匹配NaiveWaiter#smile()和NaughtyWaiter#joke()这两个不在Waiter 接口中定义的方法。

通过包定义切点

  • execution(* com.test.*(..)):匹配com.test包下所有类的所有方法。
  • execution(* com.test..*(..)):匹配com.test包、子孙包下所有类的所有方法,如com.test.dao,com.test.servier以及 com.test.dao.user包下的所有类的所有方法都匹配。“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类。
  • execution(* com...Dao.find*(..)):匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。

通过方法入参定义切点

  • execution(* joke(String,int))):匹配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参是int。它匹配 NaughtyWaiter#joke(String,int)方法。如果方法中的入参类型是java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int)。
  • execution(* joke(String,*))):匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)则不匹配。
  • execution(* joke(String,..))):匹配目标类中的joke()方法,该方法第 一个入参为String,后面可以有任意个入参且入参类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。
  • execution(* joke(Object+))):匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。它匹配joke(String s1)和joke(Client c)。如果我们定义的切点是execution(* joke(Object)),则只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。

另外,当定义一个Around增强处理时,该方法的第一个形参必须是ProceedingJoinPoint类型(就是说至少包含一个形参),在增强处理方法体内,调用ProceedingJoinPoint的proceed()方法才会执行目标方法——这就是Around增强处理可以完全控制目标方法的执行时机、如何执行的关键,如果增强处理的方法体内没有调用这个proceed()方法,则目标方法不会执行。优先级高的切面类里的增强处理的优先级总是比优先级低的切面类中的增强处理的优先级高。 类似于使用@Before注解可以修饰Before增强处理,使用@AfterReturning可修饰AfterReturning增强处理,AfterReturning增强处理将在目标方法正常完成后被织入。 使用@AfterReturning注解可指定如下两个常用属性。 1)        pointcut/value:这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式。一样既可以是已 经过AOP的配置后,可以切入日志功能、访问切入、事务管理、性能监测等功能。 首先实现这个织入增强需要的jar包,除了常用的 com.springsource.org.apache.commons.logging-1.1.1.jar, com.springsource.org.apache.log4j-1.2.15.jar, spring-bean... 觉得这个两个分开的话都好短,那就放在一起讲吧 增强方法的执行顺序,就是说如果程序中定义了不止一个增强处理的类,并且是对同一个方法进行增强,那么在默认情况下这些增强方法的执行顺序是随机的,spring无法决定那个先那个后,如果需要指定那个增强方法需要先执行的话(只能是说让那个类里的同类增强方法先执行),可以在增强类里使用order注解实现,orfer注解可以传入一个int类型的参数,这个参数越小,... AOP(Aspect-Oriented Programming):面向切面编程 是一种通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态添加功能的技术AOP相关jar包: spring-aop-4.2.5.RELEASE.jar aopalliance-1.0.jar aspectjweaver-1.6.9.jar cglib-nodep-2.1_3.jar使用AOP之前先 bean(bean Id/bean name) Bean(*Service)增强以service结尾的所有bean 例如 bean(customerService) 增强customerService的bean中所有方法 execution(<访问修饰符>?<返回类型>空格<方法名>(<参数>)<异常>?) executio... Spring 事务机制回顾    Spring事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务.  结果造成开发人员在设计事务方法时束手束脚,生怕一不小心就踩到地雷。    其实这是不认识Spring事务传播机制而造成... 网上一大堆 Spring AOP 切面打日志的方法,笔者使用注解切入,不再赘述。 笔者在抽离 “同步更新缓存” 的切面时,遇到各种困难,最终实践找到一个比较简单的方法,来实现任意自定义方法的切入。 首先,AOP 的底层原理是动态代理,也就是 Spring 托管的 Bean 容器。至少具备以下条件: 它是个 Bean (创建Bean的六种方式),一般 Bean 都是 public 修饰的(动态代理会忽视 private) 的方法。 那么,怎么达成条件呢? 一、将它升级为新 Bean Service 接口 问题:看了很多关于Spring AOP的文章,在讲各种切入方式(before、around、after-returnning、thrown等)时,被切入的业务主体Bean的方法,基本都是无参数的。 也有提到有参数的,但都是一个String型的参数。 以before为例,无参数方法的切点配置为 [code="xml"] [/code] 如果方法有一个String型的参...