图片 12

AOP注解方式,AOP切点表达式用法总结

3.6 @annotation

       @annotation的使用方式与@within的相似,表示匹配使用@annotation指定注解标注的方法将会被环绕,其使用语法如下:

@annotation(annotation-type)

       如下示例表示匹配使用com.spring.annotation.BusinessAspect注解标注的方法:

@annotation(com.spring.annotation.BusinessAspect)

       这里我们继续复用3.5节使用的例子进行讲解@annotation的用法,只是这里需要对Apple和MyAspect使用和指定注解的方式进行修改,FruitAspect不用修改的原因是声明该注解时已经指定了其可以使用在类,方法和参数上:

// 目标类,将FruitAspect移到了方法上
public class Apple {
  @FruitAspect
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}

@Aspect
public class MyAspect {
  @Around("@annotation(com.business.annotation.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

       这里Apple.eat()方法使用FruitAspect注解进行了标注,因而该方法的执行会被切面环绕,其执行结果如下:

this is before around advice
Apple.eat method invoked.
this is after around advice

5 逻辑运算符

表达式可由多个切点函数通过逻辑运算组成

5.1 &&

与操作,求交集,也可以写成and

例如 execution(* chop(..)) && target(Horseman)
 表示Horseman及其子类的chop方法

5.2 ||

或操作,求并集,也可以写成or

例如 execution(* chop(..)) || args(String)
 表示名称为chop的方法或者有一个String型参数的方法

5.3 !

非操作,求反集,也可以写成not不过not前面需加一个空格

例如 execution( not * ……)

六、示例下载

点击下载

AOP(Aspect Oriented
Programming)面向切面编程,通过预编译方式和运行期动态代理实现…

3.2 within

       within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕。如下是within表达式的语法:

within(declaring-type-pattern)

       within表达式只能指定到类级别,如下示例表示匹配com.spring.service.BusinessObject中的所有方法:

within(com.spring.service.BusinessObject)

       within表达式路径和类名都可以使用通配符进行匹配,比如如下表达式将匹配com.spring.service包下的所有类,不包括子包中的类:

within(com.spring.service.*)

       如下表达式表示匹配com.spring.service包及子包下的所有类:

within(com.spring.service..*)

3 execution切点函数

 

execution函数用于匹配方法执行的连接点,语法为:

execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选)) 

 

参数部分允许使用通配符:

*  匹配任意字符,但只能匹配一个元素

.. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用

+  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

 

示例中的* chop(..)解读为:

方法修饰符  无

返回类型      *匹配任意数量字符,表示返回类型不限

方法名          chop表示匹配名称为chop的方法

参数               (..)表示匹配任意数量和类型的输入参数

异常模式       不限

二、使用注解配置AOP

图片 1

2.1、在上一个示例中修改被代理的类Math,为了实现IOC扫描在Math类上注解了@Service并命名bean为math。相当于上一个示例中在xml配置文件中增加了一个bean,<!–
被代理对象 –><bean id=”math”
class=”com.zhangguo.Spring052.aop01.Math”></bean>,Math类的代码如下:

package com.zhangguo.Spring052.aop02;

import org.springframework.stereotype.Service;

/**
 * 被代理的目标类
 */
@Service("math")
public class Math{
    //加
    public int add(int n1,int n2){
        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        return result;
    }

    //减
    public int sub(int n1,int n2){
        int result=n1-n2;
        System.out.println(n1+"-"+n2+"="+result);
        return result;
    }

    //乘
    public int mut(int n1,int n2){
        int result=n1*n2;
        System.out.println(n1+"X"+n2+"="+result);
        return result;
    }

    //除
    public int div(int n1,int n2){
        int result=n1/n2;
        System.out.println(n1+"/"+n2+"="+result);
        return result;
    }
}

 2.2、修改通知类Advices,代码中有3个注解,@Component表示该类的实例会被Spring
IOC容器管理;@Aspect表示声明一个切面;@Before表示before为前置通知,通过参数execution声明一个切点,Advices.java代码如下所示:

package com.zhangguo.Spring052.aop02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 *
 */
@Component
@Aspect
public class Advices {
    @Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
    public void before(JoinPoint jp){
        System.out.println("----------前置通知----------");
        System.out.println(jp.getSignature().getName());
    }

    @After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }
}

 上面的代码与下面的配置基本等同

    <!-- 通知 -->
    <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean>

    <!-- aop配置 -->
    <aop:config proxy-target-class="true">
        <!--切面 -->
        <aop:aspect ref="advices">
            <!-- 切点 -->
            <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/>
            <!--连接通知方法与切点 -->
            <aop:before method="before" pointcut-ref="pointcut1"/>
            <aop:after method="after" pointcut-ref="pointcut1"/>
        </aop:aspect>
    </aop:config>

2.3、新增配置文件aop02.xml,在配置IOC的基础上增加了aop:aspectj-autoproxy节点,Spring框架会自动为与AspectJ切面配置的Bean创建代理,proxy-target-class=”true”属性表示被代理的目标对象是一个类,而非实现了接口的类,主要是为了选择不同的代理方式。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
        <context:component-scan base-package="com.zhangguo.Spring052.aop02">
        </context:component-scan>
        <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>

2.4、测试运行代码Test.java如下:

package com.zhangguo.Spring052.aop02;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aop02.xml");
        Math math = ctx.getBean("math", Math.class);
        int n1 = 100, n2 = 5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
    }

}

运行结果:

图片 2

3.1 execution

       由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

       这里问号表示当前项可以有也可以没有,其中各项的语义如下:

  • modifiers-pattern:方法的可见性,如public,protected;
  • ret-type-pattern:方法的返回值类型,如int,void等;
  • declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
  • name-pattern:方法名类型,如buisinessService();
  • param-pattern:方法的参数类型,如java.lang.String;
  • throws-pattern:方法抛出的异常类型,如java.lang.Exception;

        如下是一个使用execution表达式的例子:

execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))

       上述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法。上述示例中我们使用了..通配符,关于通配符的类型,主要有两种:

  • *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。

       如下示例表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法:

execution(* com.spring.service.BusinessObject.*())

       下述示例表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法:

execution(* com.spring.service.Business*.*())
  • ..通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。

       如下示例表示匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数:

execution(* com.spring.service..*.businessService())

       这里需要说明的是,包路径service..*.businessService()中的..应该理解为延续前面的service路径,表示到service路径为止,或者继续延续service路径,从而包括其子包路径;后面的*.businessService(),这里的*表示匹配一个单词,因为是在方法名前,因而表示匹配任意的类。

       如下示例是使用..表示任意个数的参数的示例,需要注意,表示参数的时候可以在括号中事先指定某些类型的参数,而其余的参数则由..进行匹配:

execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))

Aop,  aspect object programming  面向切面编程

         功能: 让关注点代码与业务代码分离!

关注点,

         重复代码就叫做关注点;

切面,

          关注点形成的类,就叫切面(类)!

          面向切面编程,就是指
对很多功能都有的重复的代码抽取,再在运行的时候网业务方法上动态植入“切面类代码”。

切入点,

         执行目标对象方法,动态植入切面代码。

         可以通过切入点表达式,指定拦截哪些类的哪些方法;
给指定的类在运行的时候植入切面类代码。

1 所需jar文件:

  

 先引入aop相关jar文件         (aspectj 
aop优秀组件)                                      

        spring-aop-3.2.5.RELEASE.jar   【spring3.2源码】

  aopalliance.jar                             
【spring2.5源码/lib/aopalliance】

  aspectjweaver.jar                       【aspectj-1.8.2\lib】

  aspectjrt.jar                                  
【aspectj-1.8.2\lib】

 

注意: 用到spring2.5版本的jar文件,如果用jdk1.7可能会有问题。

                  
需要升级aspectj组件,即使用aspectj-1.8.2版本中提供jar文件提供。

 

 

2) bean.xml中引入aop名称空间

 

以下内容部分转载自  

三、AspectJ切点函数

切点函数可以定位到准确的横切逻辑位置,在前面的示例中我们只使用过execution(*
com.zhangguo.Spring052.aop02.Math.*(..)),execution就是一个切点函数,但该函数只什么方法一级,如果我们要织入的范围是类或某个注解则execution就不那么好用了,其实一共有9个切点函数,有不同的针对性。

@AspectJ使用AspectJ专门的切点表达式描述切面,Spring所支持的AspectJ表达式可分为四类:
方法切点函数:通过描述目标类方法信息定义连接点。
方法参数切点函数:通过描述目标类方法入参信息定义连接点。
目标类切点函数:通过描述目标类类型信息定义连接点。
代理类切点函数:通过描述代理类信息定义连接点。

常见的AspectJ表达式函数:
execution():满足匹配模式字符串的所有目标类方法的连接点
@annotation():任何标注了指定注解的目标方法链接点
args():目标类方法运行时参数的类型指定连接点
@args():目标类方法参数中是否有指定特定注解的连接点
within():匹配指定的包的所有连接点
target():匹配指定目标类的所有方法
@within():匹配目标对象拥有指定注解的类的所有方法
@target():匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
this():匹配当前AOP代理对象类型的所有执行方法

最常用的是:execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)切点函数,可以满足多数需求。

为了展示各切点函数的功能现在新增一个类StrUtil,类如下:

package com.zhangguo.Spring052.aop03;

import org.springframework.stereotype.Component;

@Component("strUtil")
public class StrUtil {
    public void show(){
        System.out.println("Hello StrUtil!");
    }
}

测试代码如下:

package com.zhangguo.Spring052.aop03;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aop03.xml");
        IMath math = ctx.getBean("math", Math.class);
        int n1 = 100, n2 = 5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);

        StrUtil strUtil=ctx.getBean("strUtil",StrUtil.class);
        strUtil.show();
    }

}

3.1、切点函数execution,通知与切面的定义如下:

package com.zhangguo.Spring052.aop03;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 *
 */
@Component
@Aspect
public class Advices {
    @Before("execution(* com.zhangguo.Spring052.aop03.Math.*(..))")
    public void before(JoinPoint jp){
        System.out.println("----------前置通知----------");
        System.out.println(jp.getSignature().getName());
    }

    //execution切点函数
    //com.zhangguo.Spring052.aop03包下所有类的所有方法被切入
    @After("execution(* com.zhangguo.Spring052.aop03.*.*(..))")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }
}

运行结果如下:

图片 3

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

3.2、切点函数within

    //within切点函数
    //com.zhangguo.Spring052.aop03包下所有类的所有方法被切入
    @After("within(com.zhangguo.Spring052.aop03.*)")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }

 图片 4

3.3、this切点函数

    //this切点函数
    //实现了IMath接口的代理对象的任意连接点
    @After("this(com.zhangguo.Spring052.aop03.IMath)")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }

图片 5

3.4、args切点函数

    //args切点函数
    //要求方法有两个int类型的参考才会被织入横切逻辑
    @After("args(int,int)")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }

 图片 6

如果参数类型不是基本数据类型则需要包名。

3.5、@annotation切点函数

先自定义一个可以注解在方法上的注解

package com.zhangguo.Spring052.aop03;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnno {
}

    //@annotation切点函数
    //要求方法必须被注解com.zhangguo.Spring052.aop03.MyAnno才会被织入横切逻辑
    @After("@annotation(com.zhangguo.Spring052.aop03.MyAnno)")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }

package com.zhangguo.Spring052.aop03;

import org.springframework.stereotype.Component;

@Component("strUtil")
public class StrUtil {
    @MyAnno
    public void show(){
        System.out.println("Hello StrUtil!");
    }
}

运行结果:

图片 7

其它带@的切点函数都是针对注解的

3.5 @within

       前面我们讲解了within的语义表示匹配指定类型的类实例,这里的@within表示匹配带有指定注解的类,其使用语法如下所示:

@within(annotation-type)

       如下所示示例表示匹配使用com.spring.annotation.BusinessAspect注解标注的类:

@within(com.spring.annotation.BusinessAspect)

       这里我们使用一个例子演示@within的用法(这里驱动类和xml文件配置与3.4节使用的一致,这里省略):

// 注解类
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitAspect {
}

// 目标类
@FruitAspect
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}

// 切面类
@Aspect
public class MyAspect {
  @Around("@within(com.business.annotation.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

       上述切面表示匹配使用FruitAspect注解的类,而Apple则使用了该注解,因而Apple类方法的调用会被切面环绕,执行运行驱动类可得到如下结果,说明Apple.eat()方法确实被环绕了:

this is before around advice
Apple.eat method invoked.
this is after around advice

2 注解说明 

2.1 @Aspect

作用是把当前类标识为一个切面供容器读取

2.2 @Before
标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有

2.3 @AfterReturning

后置增强,相当于AfterReturningAdvice,方法正常退出时执行

2.4 @AfterThrowing

异常抛出增强,相当于ThrowsAdvice

2.5 @After

final增强,不管是抛出异常或者正常退出都会执行

2.6 @Around

环绕增强,相当于MethodInterceptor

实例:

 1 bean.xml
 2 <!-- IOC容器的配置: 开启注解扫描和自动注入 -->
 3 
 4 <context:component-scan base-package="annoaop" />
 5     
 6 <aop:aspectj-autoproxy />
 7 
 8 AOP类
 9 @Component
10 @Aspect  // 指定当前类为切面类
11 public class Aop {
12 
13     // 指定切入点表单式: 拦截哪些方法; 即为哪些类生成代理对象
14     
15     @Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))")
16     public void pointCut_(){
17     }
18     
19     // 前置通知 : 在执行目标方法之前执行
20     @Before("pointCut_()")
21     public void begin(){
22         System.out.println("开始事务/异常");
23     }
24     
25     // 后置/最终通知:在执行目标方法之后执行  【无论是否出现异常最终都会执行】
26     @After("pointCut_()")
27     public void after(){
28         System.out.println("提交事务/关闭");
29     }
30     
31     // 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
32     @AfterReturning("pointCut_()")
33     public void afterReturning() {
34         System.out.println("afterReturning()");
35     }
36     
37     // 异常通知: 当目标方法执行异常时候执行此关注点代码
38     @AfterThrowing("pointCut_()")
39     public void afterThrowing(){
40         System.out.println("afterThrowing()");
41     }
42     
43     // 环绕通知:环绕目标方式执行
44     @Around("pointCut_()")
45     public void around(ProceedingJoinPoint pjp) throws Throwable{
46         System.out.println("环绕前....");
47         pjp.proceed();  // 执行目标方法
48         System.out.println("环绕后....");
49     }
50     
51 }
52 
53 测试类
54 ApplicationContext ac = 
55         new ClassPathXmlApplicationContext("cn/itcast/e_aop_anno/bean.xml");
56 
57     // 目标对象有实现接口,spring会自动选择“JDK代理”
58     @Test
59     public void testApp() {
60         IUserDao userDao = (IUserDao) ac.getBean("userDao");
61         System.out.println(userDao.getClass());//$Proxy001  
62         userDao.save();
63     }
64 
65 注意:如果目标对象有接口切使用jdk代理那么只能用接口接收(看jdk代理原理)

 

Spring学习总结(三)——Spring实现AOP的多种方式,springaop

AOP(Aspect Oriented
Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术。AOP是OOP的补充,是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP可以分为静态织入与动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高。动态织入则不需要改变目标模块。Spring框架实现了AOP,使用注解配置完成AOP比使用XML配置要更加方便与直观。上一篇随笔中已经详细讲了代理模式。

<project xmlns=””
xmlns:xsi=””
xsi:schemaLocation=”
;
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhangguo</groupId>
<artifactId>Spring052</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring052</name>
<url>; <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties> <dependencies> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope> <version>4.10</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version> </dependency>
<dependency> <groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version> </dependency>
<dependency> <groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version> </dependency>
</dependencies> </project>

1.2、创建要被代理的Math类,代码如下:

package com.zhangguo.Spring052.aop01;

/**
 * 被代理的目标类
 */
public class Math{
    //加
    public int add(int n1,int n2){
        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        return result;
    }

    //减
    public int sub(int n1,int n2){
        int result=n1-n2;
        System.out.println(n1+"-"+n2+"="+result);
        return result;
    }

    //乘
    public int mut(int n1,int n2){
        int result=n1*n2;
        System.out.println(n1+"X"+n2+"="+result);
        return result;
    }

    //除
    public int div(int n1,int n2){
        int result=n1/n2;
        System.out.println(n1+"/"+n2+"="+result);
        return result;
    }
}

1.3、编辑AOP中需要使用到的通知类Advices.java代码如下:

package com.zhangguo.Spring052.aop01;

import org.aspectj.lang.JoinPoint;

/**
 * 通知类,横切逻辑
 *
 */
public class Advices {

    public void before(JoinPoint jp){
        System.out.println("----------前置通知----------");
        System.out.println(jp.getSignature().getName());
    }

    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }
}

1.4、配置容器初始化时需要的XML文件,aop01.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <!-- 被代理对象 -->
    <bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean>

    <!-- 通知 -->
    <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean>

    <!-- aop配置 -->
    <aop:config proxy-target-class="true">
        <!--切面 -->
        <aop:aspect ref="advices">
            <!-- 切点 -->
            <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/>
            <!--连接通知方法与切点 -->
            <aop:before method="before" pointcut-ref="pointcut1"/>
            <aop:after method="after" pointcut-ref="pointcut1"/>
        </aop:aspect>
    </aop:config>

</beans>

1.5、测试代码Test.java如下:

package com.zhangguo.Spring052.aop01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aop01.xml");
        Math math = ctx.getBean("math", Math.class);
        int n1 = 100, n2 = 5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
    }

}

运行结果:

图片 8

3.8 @DeclareParents

       @DeclareParents也称为Introduction(引入),表示为指定的目标类引入新的属性和方法。关于@DeclareParents的原理其实比较好理解,因为无论是Jdk代理还是Cglib代理,想要引入新的方法,只需要通过一定的方式将新声明的方法织入到代理类中即可,因为代理类都是新生成的类,因而织入过程也比较方便。如下是@DeclareParents的使用语法:

@DeclareParents(value = "TargetType", defaultImpl = WeaverType.class)
private WeaverInterface attribute;

       这里TargetType表示要织入的目标类型(带全路径),WeaverInterface中声明了要添加的方法,WeaverType中声明了要织入的方法的具体实现。如下示例表示在Apple类中织入IDescriber接口声明的方法:

@DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class)
private IDescriber describer;

       这里我们使用一个如下实例对@DeclareParents的使用方式进行讲解,配置文件与3.4节的一致,这里略:

// 织入方法的目标类
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}

// 要织入的接口
public interface IDescriber {
  void desc();
}

// 要织入接口的默认实现
public class DescriberImpl implements IDescriber {
  @Override
  public void desc() {
    System.out.println("this is an introduction describer.");
  }
}

// 切面实例
@Aspect
public class MyAspect {
  @DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class)
  private IDescriber describer;
}

// 驱动类
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    IDescriber describer = (IDescriber) context.getBean("apple");
    describer.desc();
  }
}

       在MyAspect中声明了我们需要将IDescriber的方法织入到Apple实例中,在驱动类中我们可以看到,我们获取的是apple实例,但是得到的bean却可以强转为IDescriber类型,因而说明我们的织入操作成功了。

4 更多切点函数

除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询

4.1 @annotation()

表示标注了指定注解的目标类方法

例如
@annotation(org.springframework.transaction.annotation.Transactional)
表示标注了@Transactional的方法

4.2 args()

通过目标类方法的参数类型指定切点

例如 args(String) 表示有且仅有一个String型参数的方法

4.3 @args()

通过目标类参数的对象类型是否标注了指定注解指定切点

如 @args(org.springframework.stereotype.Service)
表示有且仅有一个标注了@Service的类参数的方法

4.4 within()

通过类名指定切点

如 with(examples.chap03.Horseman) 表示Horseman的所有方法

4.5 target()

通过类名指定,同时包含所有子类

如 target(examples.chap03.Horseman)  且Elephantman extends
Horseman,则两个类的所有方法都匹配

4.6 @within()

匹配标注了指定注解的类及其所有子类

如 @within(org.springframework.stereotype.Service)
给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配

4.7 @target()

所有标注了指定注解的类

如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法

4.8 this()

大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配

四、AspectJ通知注解

AspectJ通知注解共有6个,常用5个,引介少用一些。

先解决定义切点复用的问题,如下代码所示,切点函数的内容完全一样:

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 *
 */
@Component
@Aspect
public class Advices {
    @Before("execution(* com.zhangguo.Spring052.aop04.Math.*(..))")
    public void before(JoinPoint jp){
        System.out.println("----------前置通知----------");
        System.out.println(jp.getSignature().getName());
    }

    @After("execution(* com.zhangguo.Spring052.aop04.Math.*(..))")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }
}

可以先定义一个切点然后复用,如下所示:

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 */
@Component
@Aspect
public class Advices {
    //切点
    @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.*(..))")
    public void pointcut(){
    }

    @Before("pointcut()")
    public void before(JoinPoint jp){
        System.out.println("----------前置通知----------");
        System.out.println(jp.getSignature().getName());
    }

    @After("pointcut()")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }
}

修改Advices.java文件,增加各种通知类型如下:

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 */
@Component
@Aspect
public class Advices {
    //切点
    @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))")
    public void pointcut(){
    }

    //前置通知
    @Before("pointcut()")
    public void before(JoinPoint jp){
        System.out.println(jp.getSignature().getName());
        System.out.println("----------前置通知----------");
    }

    //最终通知
    @After("pointcut()")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }

    //环绕通知
    @Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println(pjp.getSignature().getName());
        System.out.println("----------环绕前置----------");
        Object result=pjp.proceed();
        System.out.println("----------环绕后置----------");
        return result;
    }

    //返回结果通知
    @AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result")
    public void afterReturning(JoinPoint jp,Object result){
        System.out.println(jp.getSignature().getName());
        System.out.println("结果是:"+result);
        System.out.println("----------返回结果----------");
    }

    //异常后通知
    @AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp")
    public void afterThrowing(JoinPoint jp,Exception exp){
        System.out.println(jp.getSignature().getName());
        System.out.println("异常消息:"+exp.getMessage());
        System.out.println("----------异常通知----------");
    }
}

运行结果:

图片 9

3.7 @args

       @within和@annotation分别表示匹配使用指定注解标注的类和标注的方法将会被匹配,@args则表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配。如下是@args注解的语法:

@args(annotation-type)

       如下示例表示匹配使用了com.spring.annotation.FruitAspect注解标注的类作为参数的方法:

@args(com.spring.annotation.FruitAspect)

       这里我们使用如下示例对@args的用法进行讲解:

<!-- xml配置文件 -->
<bean id="bucket" class="chapter7.eg1.FruitBucket"/>
<bean id="aspect" class="chapter7.eg6.MyAspect"/>
<aop:aspectj-autoproxy/>

// 使用注解标注的参数类
@FruitAspect
public class Apple {}

// 使用Apple参数的目标类
public class FruitBucket {
  public void putIntoBucket(Apple apple) {
    System.out.println("put apple into bucket.");
  }
}

@Aspect
public class MyAspect {
  @Around("@args(chapter7.eg6.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

// 驱动类
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    FruitBucket bucket = (FruitBucket) context.getBean("bucket");
    bucket.putIntoBucket(new Apple());
  }
}

       这里FruitBucket.putIntoBucket(Apple)方法的参数Apple使用了@args注解指定的FruitAspect进行了标注,因而该方法的调用将会被环绕。执行驱动类,结果如下:

this is before around advice
put apple into bucket.
this is after around advice

五、零配置实现Spring IoC与AOP

为了实现零配置在原有示例的基础上我们新增一个类User,如下所示:

package com.zhangguo.Spring052.aop05;

public class User {
    public void show(){
        System.out.println("一个用户对象");
    }
}

该类并未注解,容器不会自动管理。因为没有xml配置文件,则使用一个作为配置信息,ApplicationCfg.java文件如下:

package com.zhangguo.Spring052.aop05;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration  //用于表示当前类为容器的配置类,类似<beans/>
@ComponentScan(basePackages="com.zhangguo.Spring052.aop05")  //扫描的范围,相当于xml配置的结点<context:component-scan/>
@EnableAspectJAutoProxy(proxyTargetClass=true)  //自动代理,相当于<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
public class ApplicationCfg {
    //在配置中声明一个bean,相当于<bean id=getUser class="com.zhangguo.Spring052.aop05.User"/>
    @Bean
    public User getUser(){
        return new User();
    }
}

该类的每一部分内容基本都与xml
配置有一对一的关系,请看注释,这样做要比写xml方便,但不便发布后修改。测试代码如下:

package com.zhangguo.Spring052.aop05;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        // 通过类初始化容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationCfg.class);
        Math math = ctx.getBean("math", Math.class);
        int n1 = 100, n2 = 0;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        try {
            math.div(n1, n2);
        } catch (Exception e) {
        }

        User user=ctx.getBean("getUser",User.class);
        user.show();
    }

}

 advices.java 同上,没有任何变化,运行结果如下:

图片 10

2. AOP的各个扮演者

1. 简介

       面向对象编程,也称为OOP(即Object Oriented
Programming)最大的优点在于能够将业务模块进行封装,从而达到功能复用的目的。通过面向对象编程,不同的模板可以相互组装,从而实现更为复杂的业务模块,其结构形式可用下图表示:

图片 11

        面向对象编程解决了业务模块的封装复用的问题,但是对于某些模块,其本身并不独属于摸个业务模块,而是根据不同的情况,贯穿于某几个或全部的模块之间的。例如登录验证,其只开放几个可以不用登录的接口给用户使用(一般登录使用拦截器实现,但是其切面思想是一致的);再比如性能统计,其需要记录每个业务模块的调用,并且监控器调用时间。可以看到,这些横贯于每个业务模块的模块,如果使用面向对象的方式,那么就需要在已封装的每个模块中添加相应的重复代码,对于这种情况,面向切面编程就可以派上用场了。

       面向切面编程,也称为AOP(即Aspect Oriented
Programming),指的是将一定的切面逻辑按照一定的方式编织到指定的业务模块中,从而将这些业务模块的调用包裹起来。如下是其结构示意图:

图片 12

3. 切点表达式

3.9 perthis和pertarget

       在Spring
AOP中,切面类的实例只有一个,比如前面我们一直使用的MyAspect类,假设我们使用的切面类需要具有某种状态,以适用某些特殊情况的使用,比如多线程环境,此时单例的切面类就不符合我们的要求了。在Spring
AOP中,切面类默认都是单例的,但其还支持另外两种多例的切面实例的切面,即perthis和pertarget,需要注意的是perthis和pertarget都是使用在切面类的@Aspect注解中的。这里perthis和pertarget表达式中都是指定一个切面表达式,其语义与前面讲解的this和target非常的相似,perthis表示如果某个类的代理类符合其指定的切面表达式,那么就会为每个符合条件的目标类都声明一个切面实例;pertarget表示如果某个目标类符合其指定的切面表达式,那么就会为每个符合条件的类声明一个切面实例。从上面的语义可以看出,perthis和pertarget的含义是非常相似的。如下是perthis和pertarget的使用语法:

perthis(pointcut-expression)

pertarget(pointcut-expression)

       由于perthis和pertarget的使用效果大部分情况下都是一致的,我们这里主要讲解perthis和pertarget的区别。关于perthis和pertarget的使用,需要注意的一个点是,由于perthis和pertarget都是为每个符合条件的类声明一个切面实例,因而切面类在配置文件中的声明上一定要加上prototype,否则Spring启动是会报错的。如下是我们使用的示例:

<!-- xml配置文件 -->
<bean id="apple" class="chapter7.eg1.Apple"/>
<bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/>
<aop:aspectj-autoproxy/>

// 目标类实现的接口
public interface Fruit {
  void eat();
}

// 业务类
public class Apple implements Fruit {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}

// 切面类
@Aspect("perthis(this(com.spring.service.Apple))")
public class MyAspect {

  public MyAspect() {
    System.out.println("create MyAspect instance, address: " + toString());
  }

  @Around("this(com.spring.service.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

// 驱动类
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = context.getBean(Fruit.class);
    fruit.eat();
  }
}

       这里我们使用的切面表达式语法为perthis(this(com.spring.service.Apple)),这里this表示匹配代理类是Apple类型的类,perthis则表示会为这些类的每个实例都创建一个切面类。由于Apple实现了Fruit接口,因而Spring使用Jdk动态代理为其生成代理类,也就是说代理类与Apple都实现了Fruit接口,但是代理类不是Apple类型,因而这里声明的切面不会匹配到Apple类。执行上述驱动类,结果如下:

Apple.eat method invoked.

       结果表明Apple类确实没有被环绕。如果我们讲切面类中的perthis和this修改为pertarget和target,效果如何呢:

@Aspect("pertarget(target(com.spring.service.Apple))")
public class MyAspect {

  public MyAspect() {
    System.out.println("create MyAspect instance, address: " + toString());
  }

  @Around("target(com.spring.service.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

       执行结果如下:

create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47
this is before around advice
Apple.eat method invoked.
this is after around advice

       可以看到,Apple类被切面环绕了。这里target表示目标类是Apple类型,虽然Spring使用了Jdk动态代理实现切面的环绕,代理类虽不是Apple类型,但是目标类却是Apple类型,符合target的语义,而pertarget会为每个符合条件的表达式的类实例创建一个代理类实例,因而这里Apple会被环绕。

       由于代理类与目标类的差别非常小,因而与this和target一样,perthis和pertarget的区别也非常小,大部分情况下其使用效果是一致的。关于切面多实例的创建,其演示比较简单,我们可以将xml文件中的Apple实例修改为prototype类型,并且在驱动类中多次获取Apple类的实例:

<!-- xml配置文件 -->
<bean id="apple" class="chapter7.eg1.Apple" scope="prototype"/>
<bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/>
<aop:aspectj-autoproxy/>

public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = context.getBean(Fruit.class);
    fruit.eat();
    fruit = context.getBean(Fruit.class);
    fruit.eat();
  }
}

       执行结果如下:

create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47
this is before around advice
Apple.eat method invoked.
this is after around advice
create MyAspect instance, address: chapter7.eg6.MyAspect@56528192
this is before around advice
Apple.eat method invoked.
this is after around advice

       执行结果中两次打印的create MyAspect
instance表示当前切面实例创建了两次,这也符合我们进行的两次获取Apple实例。

3.3 args

       args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。这里需要注意的是,args指定的参数必须是全路径的。如下是args表达式的语法:

args(param-pattern)

       如下示例表示匹配所有只有一个参数,并且参数类型是java.lang.String类型的方法:

args(java.lang.String)

       也可以使用通配符,但这里通配符只能使用..,而不能使用*。如下是使用通配符的实例,该切点表达式将匹配第一个参数为java.lang.String,最后一个参数为java.lang.Integer,并且中间可以有任意个数和类型参数的方法:

args(java.lang.String,..,java.lang.Integer)

4. 小结

       本文首先对AOP进行了简单介绍,然后介绍了切面中的各个角色,最后详细介绍了切点表达式中各个不同类型表达式的语法。

3.4 this和target

       this和target需要放在一起进行讲解,主要目的是对其进行区别。this和target表达式中都只能指定类或者接口,在面向切面编程规范中,this表示匹配调用当前切点表达式所指代对象方法的对象,target表示匹配切点表达式指定类型的对象。比如有两个类A和B,并且A调用了B的某个方法,如果切点表达式为this(B),那么A的实例将会被匹配,也即其会被使用当前切点表达式的Advice环绕;如果这里切点表达式为target(B),那么B的实例也即被匹配,其将会被使用当前切点表达式的Advice环绕。

       在讲解Spring中的this和target的使用之前,首先需要讲解一个概念:业务对象(目标对象)和代理对象。对于切面编程,有一个目标对象,也有一个代理对象,目标对象是我们声明的业务逻辑对象,而代理对象是使用切面逻辑对业务逻辑进行包裹之后生成的对象。如果使用的是Jdk动态代理,那么业务对象和代理对象将是两个对象,在调用代理对象逻辑时,其切面逻辑中会调用目标对象的逻辑;如果使用的是Cglib代理,由于是使用的子类进行切面逻辑织入的,那么只有一个对象,即织入了代理逻辑的业务类的子类对象,此时是不会生成业务类的对象的。

       在Spring中,其对this的语义进行了改写,即如果当前对象生成的代理对象符合this指定的类型,那么就为其织入切面逻辑。简单的说就是,this将匹配代理对象为指定类型的类。target的语义则没有发生变化,即其将匹配业务对象为指定类型的类。如下是使用this和target表达式的简单示例:

this(com.spring.service.BusinessObject)

target(com.spring.service.BusinessObject)

       通过上面的讲解可以看出,this和target的使用区别其实不大,大部分情况下其使用效果是一样的,但其区别也还是有的。Spring使用的代理方式主要有两种:Jdk代理和Cglib代理(关于这两种代理方式的讲解可以查看本人的文章代理模式实现方式及优缺点对比)。针对这两种代理类型,关于目标对象与代理对象,理解如下两点是非常重要的:

  • 如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口;
  • 如果目标对象是一个类,并且其没有实现任意接口,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象。

       结合上述两点说明,这里理解this和target的异同就相对比较简单了。我们这里分三种情况进行说明:

  • this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理还是Cglib代理,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样;
  • this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口:这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样;
  • this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口:对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理,因而生成的代理对象都是SomeObject子类的对象,其是SomeObject类型的,因而this和target的语义都符合,其效果就是一致的。

       关于this和target的异同,我们使用如下示例进行简单演示:

// 目标类
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}

// 切面类
@Aspect
public class MyAspect {
  @Around("this(com.business.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

<!-- bean声明文件 -->
<bean id="apple" class="chapter7.eg1.Apple"/>
<bean id="aspect" class="chapter7.eg6.MyAspect"/>
<aop:aspectj-autoproxy/>

// 驱动类
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Apple fruit = (Apple) context.getBean("apple");
    fruit.eat();
  }
}

       执行驱动类中的main方法,结果如下:

this is before around advice
Apple.eat method invoked.
this is after around advice

       上述示例中,Apple没有实现任何接口,因而使用的是Cglib代理,this表达式会匹配Apple对象。这里将切点表达式更改为target,还是执行上述代码,会发现结果还是一样的:

target(com.business.Apple)

       如果我们对Apple的声明进行修改,使其实现一个接口,那么这里就会显示出this和target的执行区别了:

public class Apple implements IApple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}

public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = (Fruit) context.getBean("apple");
    fruit.eat();
  }
}

       我们还是执行上述代码,对于this表达式,其执行结果如下:

Apple.eat method invoked.

       对于target表达式,其执行结果如下:

this is before around advice
Apple.eat method invoked.
this is after around advice

       可以看到,这种情况下this和target表达式的执行结果是不一样的,这正好符合我们前面讲解的第三种情况。

2.1 AOP的主要角色

  • 切面:使用切点表达式表示,指定了当前切面逻辑所要包裹的业务模块的范围大小;
  • Advice:也即切面逻辑,指定了当前用于包裹切面指定的业务模块的逻辑。

2.2 Advice的主要类型

  • @Before:该注解标注的方法在业务模块代码执行之前执行,其不能阻止业务模块的执行,除非抛出异常;
  • @AfterReturning:该注解标注的方法在业务模块代码执行之后执行;
  • @AfterThrowing:该注解标注的方法在业务模块抛出指定异常后执行;
  • @After:该注解标注的方法在所有的Advice执行完成后执行,无论业务模块是否抛出异常,类似于finally的作用;
  • @Around:该注解功能最为强大,其所标注的方法用于编写包裹业务模块执行的代码,其可以传入一个ProceedingJoinPoint用于调用业务模块的代码,无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至其可以根据一定的条件而阻断业务模块的调用;
  • @DeclareParents:其是一种Introduction类型的模型,在属性声明上使用,主要用于为指定的业务模块添加新的接口和相应的实现。
  • @Aspect:严格来说,其不属于一种Advice,该注解主要用在类声明上,指明当前类是一个组织了切面逻辑的类,并且该注解中可以指定当前类是何种实例化方式,主要有三种:singleton、perthis和pertarget,具体的使用方式后面会进行讲解。

        这里需要说明的是,@Before是业务逻辑执行前执行,与其对应的是@AfterReturning,而不是@After,@After是所有的切面逻辑执行完之后才会执行,无论是否抛出异常。