Spring(4)_面向切面编程(AOP)

面向切面编程(AOP)

依赖注入让相互协作的软件组件保持松散耦合,而AOP编程允许把遍布应用的功能分离出来形成可重用的组件。

面向切面编程往往被定义为促使应用程序分离关注点的一项技术。系统通常由许多不同组件组成,每一个组件各负责一块特定功能。一些组件除了实现自身核心的功能之外,还经常承担额外的职责。诸如日志、事务管理和安全之类的系统服务经常融入到有自身核心业务逻辑的组件中去,这些系统业务通常称为横切关注点,因为它们总是跨越系统的多个组件。

如果将这些关注点,分散到多个组件中去,代码将出现双重复杂性:

  • 如果横切关注点实现代码将重复出现在多个组件(模块)中。如果要改变这些关注点的逻辑,则必须修改每个组件的相关代码。即是我们将其抽象成模块,其他模块只是调用其方法,但是方法的调用还是会重复出现在各个模块中。
  • 组件的代码可能会因为处理这些横切关注点的使用(也就是与自身核心业务无关的代码)变得混乱。一个像地址簿增加地址条目的方法应该只关注如何添加地址,而不是同时需要关注是不是安全或者是否需要支持事务。

下面图片展示了这种复杂性。左边的业务对象与系统级服务集合的过于紧密。每个对象不但需要知道它需要日志,安全控制,事务处理,而且还要亲自执行这些服务。

Spring_aop_example1

AOP使这些服务模块化,并以声明的方式讲它们应用到需要影响的组件中去。结果是功能组件具有更高内聚性以及更加关注自身业务,完全不需要了解系统服务的复杂性,从而保持POJO简单。

如下面图片所示,AOP可以让我们使用各种功能层去包裹核心业务。这些功能层也声明的方式应用到系统中,核心业务甚至根本不知道这些服务的存在。使得我们可以将横切关注点和业务逻辑模块分离。

Spring_aop_example2

假设knight在执行任务之前需要辅助者进行一些操作,最直接的方式是由knight自己直接调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.PrintStream;
public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream) {
this.stream = stream;
}
public void singBeforeQuest() {
stream.println("Fa la la, the knight is so brave!");
}
public void singAfterQuest() {
stream.println("Tee hee hee, the brave knight " +
"did embark on a quest!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() throws QuestException {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
//Should a knight manage his own minstrel?

这样做可以达到预期效果,但是knight引入不少与自身业务无关的代码,如果需要更多的战斗前准备,代码将会变得很混乱和难以修改。我们希望kinght只需关注自己的业务,Minstrel能自己判断并在knight战斗前后执行相应操作。

我们可以把Minstrel抽象为一个切面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" />
</bean>
<bean id="minstrel" class="com.springinaction.knights.Minstrel">
<constructor-arg value="#{T(System).out}" />
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<aop:pointcut id="embark"
expression="execution(* *.embarkOnQuest(..))"/>
<aop:before pointcut-ref="embark"
method="singBeforeQuest"/>
<aop:after pointcut-ref="embark"
method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>

这里使用Spring的AOP配置的命名空间把Minstrel Bean声明为一个切面。首先把其声明一个bean,然后在<aop:aspect>元素中引用该Bean。更加具体的语法说明会在后面介绍。

简而言之,横切关注点可以被描述为影响应用多处的功能,而切面可以帮助我们模块化横切关注点。

上面图片展示了一个被划分为模块的典型应用。每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类是的辅助功能,例如安全和事务管理。

继承和委托是最常见的实现重用通用功能的面向对象技术。但继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。

切面提供了取代继承和委托的另一种选择,而且在很多场景下更清晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是我们可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面。这样做有两个好处:首先,每个关注点现在都只集中于一处,而不是分散到多处代码;其次,服务被服务模块更加简洁,因为它们只包含主要关注点(自身核心功能)的代码,而横切关注点的代码被转移到切面中了。

AOP 术语

在我们开始使用 AOP 工作之前,让我们熟悉一下 AOP 概念和术语。这些术语并不特定于 Spring,而是与 AOP 有关的。

概念

描述
Aspect 也就是前面介绍的横切关注点,需要插入到多个其他核心模块中。Aspect是通知和切点的集合。通知和切点定义了切面的全部内容—需要完成什么操作,在何处,何时完成操作。
Advice 定义了切面的要完成的工作以及何时使用,这里的何时使用是相对于切点,如在切点之前调用,或者之后调用,或环绕调用。Spring切面可以应用5种类型的通知。
Join point 程序执行过程中的一个点,这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。
Pointcut 是join point的子集,包含一个或多个连接点,我们会将通知插入这些点执行。如果说Advice定义了切面(Aspect)的”什么”和”何时”,那么切点定义了切面的”何处”,切点定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(比如方法的参数值)来决定是否应用通知。
Introduction 引用允许你添加新方法或属性到现有的类中。
Target object 被一个或者多个方面所通知的对象,也就是被代理对象。也称为被通知对象。
Weaving Weaving是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被weaving(织入)到目标对象中。在目标对象的生命周期可以有多个点进行织入,包括编译期、类加载时和运行时完成。

通知的类型

Spring Aspect应用5中类型的通知:

通知 描述
Before 在方法被调用之前调用通知
After 在方法完成之后调用通知,无论方法执行是否成功
After-returning 在方法成功执行后调用通知
After-throwing 在方法抛出异常后调用通知
Around 在被通知的方法调用之前和调用之后执行自定义的行为

Spring对AOP的支持

并不是所有的AOP框架都是一样的,它们在连接点模型上可能有强弱之分。有的允许对字段修饰符级别应用通知,而另一些只支持与方法调用相关的连接点。它们织入切面的方式和时机也有所不同。但是无论如何,创建切点来定义切面织入的连接点是AOP框架的基本功能。

AOP框架主要有:

  • AspectJ
  • JBoss AOP
  • Spring AOP

这里主要介绍Spring AOP,Spring提供了4中各具特色的AOP支持:

  • 基于代理的经典AOP;
  • @AspectJ注解驱动的切面;
  • 纯POJO切面;
  • 注入式AspectJ切面

前3种都是Spring基于代理的AOP变体,因此Spring对AOP的支持局限于方法拦截。如果AOP需求超过了简单方法拦截的范畴(比如构造器或属性拦截),那么应该考虑在AspectJ里实现切面,利用Spring的DI把Spring Bean注入到AspectJ切面里。

Spring在运行期通知对象

通过在代理类中包括切面,Spring在运行期将切面织入到Spring管理的Bean中。如图所示,代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标Bean。

当拦截到方法调用时,在调用目标Bean方法之前,代理会执行切面逻辑。
直到应用需要被代理的Bean时,Spring才创建代理对象。如果使用的是ApplicationContext,在ApplicationContext从BeanFactory中加载所有bean时,Spring创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译期来织入Spring AOP的切面。

Spring只支持方法连接点

正如前面所提到过的,各种AOP实现支持多种不同的连接点模型。因为Spring基于动态代理,所以Spring只支持方法连接点。这与其他一些AOP框架是不同的,例如AspectJ和Jboss,除了方法切点,它们还提供了字段和构造器接入点。Spring缺少对字段连接点的支持,无法让我们创建细粒度的通知,例如拦截对象字段的修改。而且Spring也不支持构造器连接点,我们无法在Bean创建时应用通知。

但是方法可以满足绝大部分的需求,如果需要方法连接之外的连接点拦截,我们可以利用Aspect来协助Spring AOP。

Spring中基于XML的AOP

为了在本节的描述中使用 aop 命名空间标签,你需要导入 spring-aop j架构,如下所述:

1
2
3
4
5
6
7
8
9
10
11
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<!-- bean definition & AOP specific configuration -->
</beans>
AOP configuration element Purpose
<aop:advisor> Defines an AOP advisor.
<aop:after> Defines an AOP after advice (regardless of whether the advised method returns successfully).
<aop:after-returning> Defines an AOP after-returning advice.
<aop:after-throwing> Defines an AOP after-throwing advice.
<aop:around> Defines an AOP around advice.
<aop:aspect> Defines an aspect.
<aop:aspectj-autoproxy> Enables annotation-driven aspects using @AspectJ.
<aop:before> Defines an AOP before advice.
<aop:config> The top-level AOP element. Most \<aop:\*\> elements must be contained within \<aop:config\>.
<aop:declare-parents> Introduces additional interfaces to advised objects that are trans- parently implemented.
<aop:pointcut> Defines a pointcut.

声明一个 aspect

1
2
3
4
5
6
7
8
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

这样,“aBean”将被配置和依赖注入,并在aspect被使用。

声明一个切入点

一个切入点有助于确定使用不同通知执行的感兴趣的连接点(即方法)。在处理基于配置的 XML 架构时,切入点将会按照如下所示定义:

1
2
3
4
5
6
7
8
9
10
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

下面的示例定义了一个名为 “businessService” 的切入点,该切入点将与 com.tutorialspoint 包下的 Student 类中的 getName() 方法相匹配:

1
2
3
4
5
6
7
8
9
10
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.tutorialspoint.Student.getName(..))"/>
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

另外,Spring 2.5还引入了一个新的bean()指示器,该指示器允许我们在切点表达式中使用Bean的ID来表示Bean,用来限制切点只匹配特定的Bean。

1
execution(* com.tutorialspoint.Student.getName(..)) and bean(student1)

或者

1
execution(* com.tutorialspoint.Student.getName(..)) and !bean(student1)

更多的指示器,Spring uses AspectJ’s pointcut expression language to define Spring aspects.

AspectJ designator Description
args() Limits join-point matches to the execution of methods whose arguments are instances of the given types
@args() Limits join-point matches to the execution of methods whose arguments are annotated with the given annotation types
execution() Matches join points that are method executions
this() Limits join-point matches to those where the bean reference of the AOP proxy is of a given type
target() Limits join-point matches to those where the target object is of a given type
@target() Limits matching to join points where the class of the executing object has an annotation of the given type
within() Limits matching to join points within certain types
@within() Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)
@annotation Limits join-point matches to those where the subject of the join point has the given annotation

声明通知

你可以使用 元素在一个中声明五个通知中的任何一个,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<!-- a before advice definition -->
<aop:before pointcut-ref="businessService"
method="doRequiredTask"/>
<!-- an after advice definition -->
<aop:after pointcut-ref="businessService"
method="doRequiredTask"/>
<!-- an after-returning advice definition -->
<!--The doRequiredTask method must have parameter named retVal -->
<aop:after-returning pointcut-ref="businessService"
returning="retVal"
method="doRequiredTask"/>
<!-- an after-throwing advice definition -->
<!--The doRequiredTask method must have parameter named ex -->
<aop:after-throwing pointcut-ref="businessService"
throwing="ex"
method="doRequiredTask"/>
<!-- an around advice definition -->
<aop:around pointcut-ref="businessService"
method="doRequiredTask"/>
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.tutorialspoint;
public class Logging {
/**
* This is the method which I would like to execute
* before a selected method execution.
*/
public void beforeAdvice(){
System.out.println("Going to setup student profile.");
}
/**
* This is the method which I would like to execute
* after a selected method execution.
*/
public void afterAdvice(){
System.out.println("Student profile has been setup.");
}
/**
* This is the method which I would like to execute
* when any method returns.
*/
public void afterReturningAdvice(Object retVal){
System.out.println("Returning:" + retVal.toString() );
}
/**
* This is the method which I would like to execute
* if there is an exception raised.
*/
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("There has been an exception: " + ex.toString());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.tutorialspoint;
public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
System.out.println("Age : " + age );
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
System.out.println("Name : " + name );
return name;
}
public void printThrowException(){
System.out.println("Exception raised");
throw new IllegalArgumentException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
Student student = (Student) context.getBean("student");
student.getName();
student.getAge();
student.printThrowException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll"
expression="execution(* com.tutorialspoint.*.*(..))"/>
<aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
<aop:after pointcut-ref="selectAll" method="afterAdvice"/>
<aop:after-returning pointcut-ref="selectAll"
returning="retVal"
method="afterReturningAdvice"/>
<aop:after-throwing pointcut-ref="selectAll"
throwing="ex"
method="AfterThrowingAdvice"/>
</aop:aspect>
</aop:config>

<!-- Definition for student bean -->
<bean id="student" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

<!-- Definition for logging aspect -->
<bean id="logging" class="com.tutorialspoint.Logging"/>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Going to setup student profile.
Name : Zara
Student profile has been setup.
Returning:Zara
Going to setup student profile.
Age : 11
Student profile has been setup.
Returning:11
Going to setup student profile.
Exception raised
Student profile has been setup.
There has been an exception: java.lang.IllegalArgumentException
.....
other exception content

Spring中基于@AspectJ注解的 AOP

@AspectJ 作为通过 Java 5 注释注释的普通的 Java 类,它指的是声明 aspects 的一种风格。通过在你的基于架构的 XML 配置文件中包含以下元素,@AspectJ 支持是可用的。

1
<aop:aspectj-autoproxy/>

声明一个 aspect

Aspects 类和其他任何正常的 bean 一样,除了它们将会用 @AspectJ 注释之外,它和其他类一样可能有方法和字段,如下所示:

1
2
3
4
5
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AspectModule {
}

它们将在 XML 中按照如下进行配置,就和其他任何 bean 一样:

1
2
3
<bean id="myAspect" class="org.xyz.AspectModule">
<!-- configure properties of aspect here as normal -->
</bean>

声明一个切入点

一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法)。在处理基于配置的 XML 架构时,切入点的声明有两个部分:

一个切入点表达式决定了我们对哪个方法被执行感兴趣。

一个切入点标签包含一个名称和任意数量的参数。方法的真正内容是不相干的,并且实际上它应该是空的。
下面的示例中定义了一个名为 ‘businessService’ 的切入点,该切入点将与 com.tutorialspoint 包下的类中可用的每一个方法相匹配:

1
2
3
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // expression
private void businessService() {} // signature

下面的示例中定义了一个名为 ‘getname’ 的切入点,该切入点将与 com.tutorialspoint 包下的 Student 类中的 getName() 方法相匹配:

1
2
3
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.tutorialspoint.Student.getName(..))")
private void getname() {}

声明建议

你可以使用 @{ADVICE-NAME} 注释声明五个建议中的任意一个,如下所示。这假设你已经定义了一个切入点标签方法 businessService():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Before("businessService()")
public void doBeforeTask(){
...
}
@After("businessService()")
public void doAfterTask(){
...
}
@AfterReturning(pointcut = "businessService()", returning="retVal")
public void doAfterReturnningTask(Object retVal){
// you can intercept retVal here.
...
}
@AfterThrowing(pointcut = "businessService()", throwing="ex")
public void doAfterThrowingTask(Exception ex){
// you can intercept thrown exception here.
...
}
@Around("businessService()")
public void doAroundTask(){
...
}

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.tutorialspoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
@Aspect
public class Logging {
/** Following is the definition for a pointcut to select
* all the methods available. So advice will be called
* for all the methods.
*/
@Pointcut("execution(* com.tutorialspoint.*.*(..))")
private void selectAll(){}
/**
* This is the method which I would like to execute
* before a selected method execution.
*/
@Before("selectAll()")
public void beforeAdvice(){
System.out.println("Going to setup student profile.");
}
/**
* This is the method which I would like to execute
* after a selected method execution.
*/
@After("selectAll()")
public void afterAdvice(){
System.out.println("Student profile has been setup.");
}
/**
* This is the method which I would like to execute
* when any method returns.
*/
@AfterReturning(pointcut = "selectAll()", returning="retVal")
public void afterReturningAdvice(Object retVal){
System.out.println("Returning:" + retVal.toString() );
}
/**
* This is the method which I would like to execute
* if there is an exception raised by any method.
*/
@AfterThrowing(pointcut = "selectAll()", throwing = "ex")
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("There has been an exception: " + ex.toString());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.tutorialspoint;
public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
System.out.println("Age : " + age );
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
System.out.println("Name : " + name );
return name;
}
public void printThrowException(){
System.out.println("Exception raised");
throw new IllegalArgumentException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
Student student = (Student) context.getBean("student");
student.getName();
student.getAge();
student.printThrowException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<aop:aspectj-autoproxy/>

<!-- Definition for student bean -->
<bean id="student" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

<!-- Definition for logging aspect -->
<bean id="logging" class="com.tutorialspoint.Logging"/>

</beans>

一旦你已经完成的创建了源文件和 bean 配置文件,让我们运行一下应用程序。如果你的应用程序一切都正常的话,这将会输出以下消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Going to setup student profile.
Name : Zara
Student profile has been setup.
Returning:Zara
Going to setup student profile.
Age : 11
Student profile has been setup.
Returning:11
Going to setup student profile.
Exception raised
Student profile has been setup.
There has been an exception: java.lang.IllegalArgumentException
.....
other exception content

Further

环绕通知的编写

基于@AspectJ注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package concert;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Audience {
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
} }
}

基于XML的也是同样配置。

为通知传递参数

基于@AspectJ注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package soundsystem;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TrackCounter {
private Map<Integer, Integer> trackCounts =
new HashMap<Integer, Integer>();
@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack(int)) " +
"&& args(trackNumber)")
public void trackPlayed(int trackNumber) {}
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber)
} }

基于XML的配置:

通过切面引入新功能

我们前面做的其实就是为对象已经拥有的方法添加了新功能。同样的,我们也可以为一个对象添加新的方法。利用Spring的Introduction我们可以为Spring Bean添加新方法。

使用Spring AOP,我们可以为Bean引入新的方法。拦截调用并委托给实现该方法的其他对象。实际上,Bean的实现被拆分到了多个类。

1
2
3
4
package concert;
public interface Encoreable {
void performEncore();
}
1
2
3
4
5
6
7
8
9
package concert;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="concert.Performance+",
defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}

基于XML的配置:

1
2
3
4
5
6
7
<aop:aspect>
<aop:declare-parents
types-matching="concert.Performance+"
implement-interface="concert.Encoreable"
default-impl="concert.DefaultEncoreable"
/>
</aop:aspect>

注入AspectJ切面