Spring 框架的 AOP
Spring框架的一个关键组件是面向切面编程(AOP)框架,面向切面的编程需要把程序逻辑分解成不同的部分称为关注点.跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑.有各种各样的常见的很好的方面的例子.如:日志,审计,声明式事务,安全性和缓存等.
在OOP中,关键单元模块度类,而在AOP中单元模块度是方面.依赖注入帮助你对应用程序对象相互理解解耦和AOP可以帮助你从他们所影响的对象中对横切关注点解耦.AOP是像编程语言的触发物,如Perl,NET,java 或者其他.
Spring AOP模块是提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能.
AOP术语
在我们开始使用AOP工作之前,让我们熟悉一些AOP概念的术语,这些术语并不特定与Spring,而是与AOP有关的.
项 | 描述 |
---|---|
Aspect | 一个模块具有一组提供横切需求的APIs.例如,一个日志模块为了记录日志将被AOP方面调用.应用程序可以用有任意数量的方面,这取决于需求. |
Join Point | 在你的应用程序中它代表一个点,你可以在插件AOP方面.你也能说,它是在实际应用程序中,其中一个操作将使用Spring AOP 框架. |
Advice | 这是实际行动之前或之后执行的方法.这是在应用程序执行期间通过Spring AOP 框架实际被调用的代码 |
Pointcut | 这是一组一个或多个连接点,通知应该被执行,你可以使用表达式或模式指定切入点正如我们将AOP的例子中看到的. |
Introduction | 引用允许你添加新方法或属性到现有的类中. |
Target Object | 被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象.也称为被通知对象. |
Weaving | Weaving 把方面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象.这些可以在编译时,类加载时和运行时完成. |
Spring 方面可以使用下面提到的五种通知工作
通知 | 描述 |
---|---|
前置通知 | 在一个方法执行前,执行通知 |
后置通知 | 在一个方法执行后,不考虑其结果,执行通知. |
返回后通知 | 在一个方法执行后,只有在方法成功完成时,才能执行通知. |
抛出异常通知 | 在一个方法执行后,只有在方法退出抛出异常时,才能执行通知. |
环绕通知 | 在建议方法调用之后和之前,执行通知. |
实现自定义方面
Spring 支持 @AspectJannotation style 的方法和基于模式的方法来实现自定义方面. 这两种方法已经在下面两个子节点进行了详细的介绍.
方法 | 描述 |
---|---|
XML Schema based | 方面是使用常规类一级基于配置的XML来实现的. |
@AspectJ based | @AspectJ 引用一种声明方面的风格作为带有java5注释的常规的Java注释. |
Spring 中基于AOP的XML架构
为了在本届描述使用AOP命名空间标签,你需要导入spring-aop架构,如下所示
<?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>
你还需要再你的应用程序的ClassPath中使用一下AspectJ库文件,这些库文件在一个AspectJ装置的lib目录中是可以用的,否则你可以在Internet中下载它们.
jar |
---|
aspectjrt.jar |
aspectjweaver.jar |
aspectj.jar |
aopalliance.jar |
声明一个aspect
一个aspcet是使用元素声明的,支持bean时使用ref属性引用的.
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
这里的”aBean“将被配置和依赖注入,就像前面的章节中你看到的其他的Spring bean 一样.
声明一个切点
一个切点有注入确定使用不同建议执行的感兴趣的连接点(即方法),在处理基于配置的XML架构时,切入点将会按照如下所示定义.
<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() 方法相匹配
<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>
声明建议
你可以使用
<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>
你可以对不同的建议使用相同的doRequiredTask 或者不同的方法。这些方法将会作为 aspect 模块的一部分来定义.
基于AOP的xml架构的示例
步骤 | 描述 |
---|---|
1 | 创建一个名为 SpringExample 的项目,并且在所创建项目的 c src 文件夹下创建一个名为 com.tutorialspoint的包 |
2 | 使用 Add External JARs 选项添加所需的 Spring 库文件,就如在 Spring Hello World Example 章节中解释的 |
3 | 在项目中添加 Spring AOP 指定的库文件 r aspectjrt.jar, aspectjweaver.jar 和 aspectj.jar。 |
4 | 在 com.tutorialspoint 包下创建 Java 类 Logging, Student 和 MainApp |
5 | 在src 文件夹下创建 Beans 配置文件 Beans.xml 。 |
6 | 最后一步是创建所有 Java 文件和 Bean 配置文件的内容,并且按如下解释的那样运行应用程序。 |
Logging.java
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());
}
}
Student.java
package com.tutorialspoint;
public class Student {
private Integer age;
private String name;
public Integer getAge() {
System.out.println("Age : " + age);
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
System.out.println("Name : " + name);
return name;
}
public void setName(String name) {
this.name = name;
}
public void printThrowException() {
System.out.println("Exception raised");
throw new IllegalArgumentException();
}
}
MainApp.java
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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();
}
}
Beans.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: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>
运行程序后会报错
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
Exception in thread "main" java.lang.IllegalArgumentException
让我们来解释一下上面定义的com.tutorialspoint中选择所有的方法,让我们假设一下,你想要在一个特殊的方法之前或者之后执行你的建议,你可以通过替换使用真实类和方法名称的切点定义中的星号(*)来定义你的切入点来缩短你的执行
<?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.Student.getName(..))" />
<aop:before pointcut-ref="selectAll" method="beforeAdvice" />
<aop:after pointcut-ref="selectAll" method="afterAdvice" />
</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>
Spring 中基于AOP的@AspectJ
@AspectJ作为Java5 注释的普通java类,它指明的是上面aspects的一种风格,通过你再基于架构的XML配置文件中包含以下元素,@AspectJ支持是可以用的.
<aop:aspectj-autoproxy/>
你还需要再你的应用程序的classPath中使用一下AspectJ库文件.这些库文件子一个AspectJ装置的”lib”目录中是可用的,否则你可以在internet中下载它们
jar |
---|
aspectjr.jar |
aspectjweaver.jar |
aspectj.jar |
aopalliance.jar |
声明一个aspect
Aspects类和其他任何正常的bean一样,除了他们将会用@AspectJ注释之外,它和其他类一样可能有方法和字段 如下:
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AspectModule {
}
它们将在XML中按照如下进行配置,就和其他任何bean一样.
<bean id="myAspect" class="org.xyz.AspectModule">
<!-- configure properties of aspect here as normal -->
</bean>
声明一个切入点
一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法).在处理基于配置的XML架构时,欺辱的的生命有两部分.
- 一个切入点表达式决定了我们感兴趣的哪个方法会真正被执行
- 一个切入点标签包含一个名称和任意数量的参数.方法的真正内容是不相干的,并且实际上它应该是空的.
下面的实例中定义了一个名为”businessService”的切入点,该切入点将与com.tutorialspoint包下的类中可用的每一个方法相匹配:
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // expression
private void businessService() {} // signature
下面的示例中定义了一个名为”getname”的切入点,该切入点将与com.tutorialspoint包下的Student类中的getName() 方法相匹配.
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.tutorialspoint.Student.getName(..))")
private void getname() {}
声明建议
你可以使用 @{ADVICE-NAME} 注释来声明五个建议中的任意一个,如下所示.这个假设你已经定义了一个切入点标签方法businessService()
@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(){
...
}
你可以为任意一个建议定义你的切入点内联.下面是在建立之前定义内联切入点的一个示例:
@Before("execution(* com.xyz.myapp.service.*.*(..))")
public doBeforeTask(){
...
}
基于AOP 的@AspectJ示例
步骤 | 描述 |
---|---|
1 | 创建一个名为 SpringExample 的项目,并且在所创建项目的 c src 文件夹下创建一个名为 com.tutorialspoint 的包。 |
2 | 使用 Add External JARs 选项添加所需的 Spring 库文件,就如在 Spring Hello World Example 章节中解释的那样。 |
3 | 在项目中添加 Spring AOP 指定的库文件 r aspectjrt.jar, aspectjweaver.jar 和 aspectj.jar。 |
4 | 在 com.tutorialspoint 包下创建 Java 类 Logging, Student 和 MainApp |
5 | 在src 文件夹下创建 Beans 配置文件 Beans.xml |
6 | 最后一步是创建所有 Java 文件和 Bean 配置文件的内容,并且按如下解释的那样运行应用程序。 |
Logging.java
package com.tutorialspoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
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());
}
}
Student.java
package com.tutorialspoint;
public class Student {
private Integer age;
private String name;
public Integer getAge() {
System.out.println("Age : " + age);
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
System.out.println("Name : " + name);
return name;
}
public void setName(String name) {
this.name = name;
}
public void printThrowException() {
System.out.println("Exception raised");
throw new IllegalArgumentException();
}
}
MainApp.java
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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();
}
}
Beans.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: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>
结果
Age : 11
Exception raised
java.lang.IllegalArgumentException