Spring框架的关键组件之一是面向方面编程(AOP)框架。 面向方面的编程需要将程序逻辑分解成不同的部分。
此教程将通过简单实用的方法来学习Spring框架提供的AOP/面向方面编程。
Spring AOP 概述
Spring框架的关键组件之一是面向方面编程(AOP)。 面向方面的编程需要将程序逻辑分解成不同的部分。 跨越应用程序的多个点的功能被称为交叉切割问题,这些交叉关切在概念上与应用程序的业务逻辑分开。有如:日志记录,审计,声明式事务,安全性和缓存等方面的各种常见的的例子。
OOP模块化的关键单位是类,而在AOP中,模块化单位是方面。 依赖注入可帮助您将应用程序对象彼此分离,并且AOP可帮助您将交叉问题与其影响的对象分离。AOP就像Perl
,.NET
,Java等编程语言中的触发器。
Spring AOP模块提供截取拦截应用程序的拦截器,例如,当执行方法时,可以在执行方法之前或之后添加额外的功能。
在开始使用AOP之前,让我们先来熟悉AOP的概念和术语。 这些术语不是Spring特有的,而是与面向方面编程(AOP)有关。
术语 | 描述 |
---|---|
方面/切面(Aspect ) | 一个具有一组API的模块,提供交叉要求。例如,日志记录模块被称为AOP方面用于记录。应用程序可以根据需要具有任意数量的方面。 |
加入点(Join point ) | 这表示您的应用程序中可以插入AOP方面的一点。也可以说,这是应用程序中使用Spring AOP框架采取操作的实际位置。 |
通知(Advice ) | 这是在方法执行之前或之后采取的实际操作。 这是在Spring AOP框架的程序执行期间调用的实际代码片段。 |
切入点(Pointcut ) | 这是一组一个或多个连接点,其中应该执行通知(Advice )。 您可以使用表达式或模式指定切入点,我们将在AOP示例中看到。 |
介绍(Introduction ) | 介绍允许向现有类添加新的方法或属性。 |
目标对象(Target object ) | 对象被一个或多个方面通知(Advice ),该对象将始终是代理的对象。也称为通知(Advice )对象。 |
编织(Weaving ) | 编织是将方面与其他应用程序类型或对象进行链接以创建通知(Advice )对象的过程。 这可以在编译时,加载时间或运行时完成。 |
Spring AOP中可以使用以下五种建议:
通知 | 描述 |
---|---|
before | 在方法执行之前运行通知。 |
after | 在方法执行后运行通知,无论其结果如何。 |
after-returning | 只有方法成功完成后才能在方法执行后运行通知。 |
after-throwing | 只有在方法通过抛出异常而退出方法执行之后才能运行通知。 |
around | 在调用通知方法之前和之后运行通知。 |
<aop:aspectj-autoproxy/>
基于@AspectJ
@AspectJ
是指将Java方法注释为Java 5注释的常规Java类的方式。 @AspectJ
是指将Java方法注释为Java 5注释的常规Java类的方式。通过在基于XML Schema的配置文件中包含以下元素来启用@AspectJ
支持。
声明一个方面(aspect)
方面(aspect
)的类就像任何其他正常的bean一样,并且可以像任何其他类一样具有方法和字段,不过它们使用@Aspect
进行注释,如下所示:
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>
声明一个切入点
切入点(pointcut
)有助于确定要用不同通知执行的关联点(即方法)。 在使用基于@AspectJ
的配置时,切入点声明有两部分:
- 一个切入点表达式,确定哪些方法执行。
- 切入点签名包括名称和任意数量的参数。 该方法的实体是无关紧要的,也可以是空的。
以下示例定义了一个名为“businessService
”的切入点,该切入点将匹配com.xyz.myapp.service
包下的类中可用的每个方法的执行:
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // expression
private void businessService() {} // signature
以下示例定义了一个名为“getname
”的切入点,该切入点将与com.yiibai
包下的Student
类中的getName()
方法的执行相匹配:
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.yiibai.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(){
...
}
让我们写一个Spring Boot AOP基于注解的应用例子,它将使用基于注解的配置实现通知。打开并使用Eclipse IDE,并按照以下步骤创建一个maven应用程序:
maven pom:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
Java Bean:
package com.milo.entity;
import java.sql.Timestamp;
import org.springframework.stereotype.Component;
@Component
public class BatchResultBean{
String id;
String progName="ProgName";
String fileName="fileName";
//getting setting....
}
写一个Aspectj 类:
package com.milo.spring.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Logging {
@Before("execution (* com.milo.entity..*(..))")//指定需要横切的包名或者类名。。。。
public void logBeforeBean() {
System.out.println("[beforeAdvice] Going to setup student profile.");
}
}
在src/main/resources下定义一个application.properties文件:
##自动加载开启spring aop
spring.aop.auto=true
再创建一个Spring Boot Application:
package com.milo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
接下来编写一个spring boot 测试类:
package com.spring.aop;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.milo.App;
import com.milo.entity.BatchResultBean;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class TestLogging {
@Autowired
BatchResultBean bean;
@Before
public void setUp(){}
@After
public void tearDown(){}
@Test
public void testMain(){
String fileName=this.bean.getFileName();
System.out.println("FileName:"+fileName);
String progName=this.bean.getProgName();
System.out.println("ProgName:"+progName);
}
}
验证输出结果如下:
[beforeAdvice] Going to setup student profile.
FileName:fileName
[beforeAdvice] Going to setup student profile.
ProgName:ProgName
摘录于--------《易百教程》