Spring之AOP(面向切面)
1. 什么是AOP?
AOP (Aspect Oriented Programing) 称为:面向切面编程,是一种编程思想。
如果我们需要在业务需求中插入系统需求,就需要使用Aop。我们可以通过代理模式将系统需求功能快速的植入到业务需求功能中。Aop底层是使用CGLIB动态代理实现的。
2. AOP相关的概念
joinpoint(连接点):指那些被拦截到的点。在spring中指的可以被代理(增强)的方法。
poingcut(切入点):对哪些连接点进行拦截的定义。在Spring中指的真正需要被代理(增强)的方法。
advice(通知/增强):指拦截到连接点之后要做的事情。真正增强的那些代码(逻辑)。
通知/增强分为:前置通知,后置通知,异常通知,最终通知,环绕通知。
aspect(切面):是切入点和通知/增强的结合过程。
introduction(引介):一种特殊的通知在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或者字段。
target(目标对象):代码的目标对象。
weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入。而AspectJ采用编译期织入和类装在期织入。
proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
3. AOP[面向切面编程]的具体实现 (重点是切入点表达式的编写)
如下例子:
1.任意公共方法的执行:execution(public * *(..))
2.任何一个名字以“set”开始的方法的执行:execution(* set*(..))
3.AccountService接口定义的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
4.在service包中定义的任意方法的执行:execution(* com.xyz.service.*.*(..))
5.在service包或其子包中定义的任意方法的执行:execution(* com.xyz.service..*.*(..))
6.在service包中的任意连接点(在Spring AOP中只是方法执行):within(com.xyz.service.*)
7.在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):within(com.xyz.service..*)
8.实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):this(com.xyz.service.AccountService)'this'在绑定表单中更加常用。
9.实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):target(com.xyz.service.AccountService)'target'在绑定表单中更加常用。
10.任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)
args(java.io.Serializable)'args'在绑定表单中更加常用。请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。
11.目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行)
@target(org.springframework.transaction.annotation.Transactional)'@target'在绑定表单中更加常用。
12.任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行):
@within(org.springframework.transaction.annotation.Transactional)'@within'在绑定表单中更加常用。
13.任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行)
@annotation(org.springframework.transaction.annotation.Transactional)'@annotation'在绑定表单中更加常用。
14.任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行)
@args(com.xyz.security.Classified)'@args'在绑定表单中更加常用。
15任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):bean(tradeService)
16.任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):bean(*Service)
3.1 基于XML文件 (Spring配置文件) 的AOP实现
创建普通maven项目,完善项目结构,pom.xml文件导入依赖:
可以去maven官网下载
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
创建业务需求类 StudentService :
package qing;
//学生信息业务需求类
public class StudentService {
public void insertStudent(){
System.out.println("添加学生信息!!");
}
public void updateStudent(){
System.out.println("修改学生信息!!");
}
public void deleteStudent(){
System.out.println("删除学生信息!!");
int tm=10/0;
}
public void selectStudent(){
System.out.println("查询学生信息!!");
}
}
增强/通知类 MyInform :
package qing;
import org.aspectj.lang.ProceedingJoinPoint;
//通知类
public class MyInform {
//系统功能需求实现方法
public void saveLog() {
System.out.println("保存日志信息!!!!");
}
//环绕通知系统需求功能实现
public void testAround(ProceedingJoinPoint joinPoint) throws Throwable {
saveLog();
joinPoint.proceed();
saveLog();
}
}
applicationContext.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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 创建业务类对象 -->
<bean id="stuService" class="qing.StudentService"></bean>
<!-- 创建通知类对象 -->
<bean id="myInform" class="qing.MyInform"></bean>
<!-- 基于xml文件配置实现AOP -->
<aop:config>
<!-- 创建切入点 -->
<aop:pointcut id="point1" expression="execution(* qing.StudentService.insertStudent*())"/>
<aop:pointcut id="point2" expression="execution(* qing.StudentService.updateStudent())"/>
<aop:pointcut id="point3" expression="execution(* qing.StudentService.deleteStudent(..))"/>
<aop:pointcut id="point4" expression="execution(* qing.StudentService.selectStudent*(..))"/>
<!-- 配置切面 -->
<aop:aspect ref="myInform">
<!-- 配置前置通知 -->
<aop:before method="saveLog" pointcut-ref="point1"></aop:before>
<!-- 配置后置通知 -->
<aop:after-returning method="saveLog" pointcut-ref="point2"></aop:after-returning>
<!-- 配置异常通知 -->
<aop:after-throwing method="saveLog" pointcut-ref="point3"></aop:after-throwing>
<!-- 配置环绕通知 -->
<aop:around method="testAround" pointcut-ref="point4"></aop:around>
<!-- 配置最终执行通知与环绕通知相似 -->
<aop:after method="saveLog" pointcut-ref="point1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
测试类:
package qing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService=(StudentService) ac.getBean("stuService");
studentService.insertStudent(); //测试前置通知
studentService.updateStudent(); //测试后置通知
studentService.deleteStudent(); //测试异常通知
studentService.selectStudent(); //测试环绕通知
}
}
结果:
前置通知:
后置通知:
异常通知:
环绕通知:
最终通知 (和环绕通知相似)
3.2 基于注解的Aop实现
用户业务需求类 UserService :
package fu;
import org.springframework.stereotype.Component;
//用户业务需求类
@Component("userService")
public class UserService {
public void insertUser() {
System.out.println("添加用户信息。。。");
}
public void updateUser() {
System.out.println("修改用户信息。。。");
}
public void deleteUser() {
System.out.println("删除用户信息。。。");
int temp = 10 / 0;
}
public void selectUser() {
System.out.println("查询用户信息。。。");
}
}
增强/通知类 MyInform :
package fu;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//通知类
@Component
@Aspect
public class MyInform {
@Before("execution(* fu.UserService.insertUser())")
@AfterReturning("execution(* fu.UserService.updateUser(..))")
@AfterThrowing("execution(* fu.UserService.deleteUser*())")
public void saveLog() {
System.out.println("记录执行日志!!");
}
//执行环绕通知系统需求功能实现方法
@Around("execution(* fu.UserService.selectUser*(..))")
public void testAround(ProceedingJoinPoint joinPoint) throws Throwable {
saveLog();
joinPoint.proceed(); //调用正在业务方法
saveLog();
}
}
Application.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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用context命名空间通过spring扫描指定目录,进行注解解析 -->
<context:component-scan base-package="fu"></context:component-scan>
<!-- 开启aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试代码:
package fu;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("Application.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.insertUser(); //前置通知
userService.updateUser(); //后置通知
userService.deleteUser(); //异常通知
userService.selectUser(); //环绕通知
}
}
结果:
前置通知:
后置通知:
异常通知:
环绕通知:
注意事项:前置通知、后置通知、异常通知、环绕通知 每次只能开启执行一个,其他应当注释掉,不然程序会报错。