1. Spring AOP 简介
Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的耦合目的。
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
程序需要------1.业务需求------需要实现程序的具体核心功能----添加用户
2.系统需求------实现程序的辅助功能----记录系统运行日志
AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。Spring AOP包含了AspectJ,因此本文只演示Spring AOP。
AOP 的相关术语:
名称 | 说明 |
Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点和通知的结合。 |
简单来说就是,一个类中有很多的方法,比作连接点,需要拦截的连接点(需要对这个方法进行配置),就是切入点,拦截后需要增强内容,就是通知,切入点和通知结合就叫切面。
举例说明,添加、修改、删除、查询每个方法里面,以往可能需要每个方法里面需要日志管理的代码,现在全部剥离出来,单独创建一个类,日志管理的代码,然后通过AOP配置,进入到需要的方法中。
Spring 通知的 5 种类型:
名称 | 说明 |
org.springframework.aop.MethodBeforeAdvice(前置通知) | 在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。 |
org.springframework.aop.AfterReturningAdvice(后置通知) | 在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。 |
org.aopalliance.intercept.MethodInterceptor(环绕通知) | 在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。 |
org.springframework.aop.ThrowsAdvice(异常通知) | 在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。 |
org.springframework.aop.IntroductionInterceptor(引介通知) | 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。 |
2. Spring AOP:基于XML
(1)依赖包:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
(2)AOP配置
在xml中对切点和切面进行配置
目标对象(通知需要切入的类)和切面(通知类)注册bean
通过<aop:pointcut>配置切入点,使用expression中的execution()方法来实现对切入点的范围控制
通过<aop:aspect ref="切面id">来实现切面类中的切面方法需要注入到哪一个切入点中
最重要的地方就是第二步,使用expression中的execution()方法
这个方法是为了找到切面需要织入的那个地方,可以是单个/多个方法,可以是整个类的所有方法
execution表达式的具体写法要求如下:
execution(
modifiers-pattern? —修饰符,比如public
ret-type-pattern —标识方法的返回值
declaring-type-pattern? —声明类型模式
name-pattern/param-pattern —指定方法名/指定方法参数的路径
throws-pattern? —抛出模式
)0
ret-type-pattern,name-pattern(param-pattern)是必须的.
ret-type-pattern:标识方法的返回值,需要使用全路径的类名如java.lang.String,也可以为*表示任何返回值;
name-pattern:指定方法名,*代表所有,例如set*,代表以set开头的所有方法.
param-pattern:指定方法参数(声明的类型):
(..)代表所有参数
(*)代表一个参数
(*,String)代表第一个参数为任何值,第二个为String类型.
表达式例子如下:
任意公共方法的执行:
execution(public * *(..))
任何一个以“set”开始的方法的执行:
execution(* set*(..))
UserService接口的任意方法的执行:
execution(* com.cheng.aop.service.UserService.*(..))
定义在service包里的任意类和任意方法的执行:
execution(* com.cheng.aop.service.*.*(..))
定义在service包和所有子包里的任意类的任意方法的执行(比上面多了个点):
execution(* com.cheng.aop.service..*.*(..))
定义在pointcutexp包和所有子包里的UserService的的任意方法的执行:
execution(* com.cheng.aop.service..UserService.*(..))
【注意】在java中默认使用的动态代理是JDK proxy基于接口的代理,在xml文件中配置如下代码:<aop:aspectj-autoproxy proxy-target-class="true"/>,测试类中getBean()括号中可以为类可以为接口,否则必须注入到接口上。 不然报错:
was actually of type 'com.sun.proxy.$Proxy3'
(3)前置通知
<!-- 引入系统需求功能实现类-->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<!--id:切入点名称-->
<!--expression:切入点表达式-->
<aop:pointcut id="point1" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.insertStudent())"/>
<!--配置前置通知 -->
<aop:before method="saveLog" pointcut-ref="point1"/>
</aop:aspect>
(4)后置通知
<aop:aspect ref="myAspect">
<aop:pointcut id="point2" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.updateStudent())"/>
<!--配置后置通知 -->
<aop:after method="saveLog" pointcut-ref="point2"/>
</aop:aspect>
(5)环绕通知
//实现环绕通知的具体方法
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
xxxxx(); // 开始
Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
xxxxx(); // 结束
return obj;
}
(6)异常通知
//异常通知实现方法
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知" + "出错了" + e.getMessage());
}
<aop:aspect ref="myAspect">
<aop:pointcut id="point4" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.selectStudent())"/>
<!--配置后置通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="point4" throwing="e"/>
</aop:aspect>
(7)完整代码演示:
//系统功能
public class MyAspect {
public void saveLog(){
System.out.println("记录用户操作的日志");
}
//实现环绕通知的具体方法
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
saveLog(); // 开始
Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
saveLog(); // 结束
return obj;
}
//异常通知实现方法
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知" + "出错了" + e.getMessage());
}
//异常通知实现方法
public void myAfterThrowing1(JoinPoint joinPoint) {
System.out.println("异常通知" + "出错了");
}
}
package com.weiwei.springAOPdemo1.service.impl;
import com.weiwei.springAOPdemo1.service.StudentService;
//Target(目标)
//业务功能
public class StudentServiceImpl implements StudentService {
//Joinpoint(连接点)
@Override
public void insertStudent() {
System.out.println("实现添加学生的业务方法----insertStudent");
}
//Joinpoint(连接点)
@Override
public void updateStudent() {
System.out.println("实现修改学生的业务方法----updateStudent");
}
// Joinpoint(连接点)
@Override
public void deleteStudent() {
System.out.println("实现删除学生的业务方法----deleteStudent");
}
//Joinpoint(连接点)
@Override
public void selectStudent() {
System.out.println("实现查询学生的业务方法----selectStudent");
int a=10/0;
}
}
package com.weiwei.springAOPdemo1.service;
public interface StudentService {
void insertStudent();
void updateStudent();
void deleteStudent();
void selectStudent();
}
<?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="studentService" class="com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl"/>
<!-- 创建系统功能实现类对象 -->
<bean id="myAspect" class="com.weiwei.springAOPdemo1.aspect.MyAspect"/>
<!-- AOP配置 -->
<!-- <aop:config proxy-target-class="true">-->
<aop:config>
<!-- 引入系统需求功能实现类-->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<!--id:切入点名称-->
<!--expression:切入点表达式-->
<aop:pointcut id="point1" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.insertStudent())"/>
<!--配置前置通知 -->
<aop:before method="saveLog" pointcut-ref="point1"/>
</aop:aspect>
<aop:aspect ref="myAspect">
<aop:pointcut id="point2" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.updateStudent())"/>
<!--配置后置通知 -->
<aop:after method="saveLog" pointcut-ref="point2"/>
</aop:aspect>
<aop:aspect ref="myAspect">
<aop:pointcut id="point3" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.deleteStudent())" />
<!--配置环绕通知 -->
<aop:around method="myAround" pointcut-ref="point3"/>
</aop:aspect>
<aop:aspect ref="myAspect">
<aop:pointcut id="point4" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.selectStudent())"/>
<!--配置异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="point4" throwing="e"/>
</aop:aspect>
<aop:aspect ref="myAspect">
<aop:pointcut id="point5" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.selectStudent())"/>
<!--配置异常通知 -->
<aop:after-throwing method="myAfterThrowing1" pointcut-ref="point5"/>
</aop:aspect>
</aop:config>
</beans>
package com.weiwei.springAOPdemo1;
import com.weiwei.springAOPdemo1.service.StudentService;
import com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = applicationContext.getBean("studentService", StudentService.class);
// StudentServiceImpl serviceimpl = applicationContext.getBean("studentService", StudentServiceImpl.class);
studentService.insertStudent();
System.out.println("------------------------------");
studentService.updateStudent();
System.out.println("------------------------------");
studentService.deleteStudent();
System.out.println("------------------------------");
studentService.selectStudent();
}
}
3. Spring AOP:基于Annotation
配置文件中需要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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描含com.weiwei.springAOP包下的所有注解-->
<context:component-scan base-package="com.weiwei.springAOP" />
<!-- 使切面开启自动代理 默认使用的动态代理是JDK proxy基于接口的代理-->
<aop:aspectj-autoproxy />
</beans>
package com.weiwei.springAOP;
import org.springframework.stereotype.Component;
@Component("stu")
public class StudentServiceImpl {
public void insertStudent(){
System.out.println("添加学生信息的业务方法---insertStudent");
}
public void updateStudent(){
System.out.println("修改学生信息的业务方法---updateStudent");
}
public void deleteStudent(){
System.out.println("删除学生信息的业务方法---deleteStudent");
}
public void selectStudent(){
System.out.println("查询学生信息的业务方法---selectStudent");
int a=10/0;
}
}
package com.weiwei.springAOP;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class myAspect {
@Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.insertStudent())")
//切点,使用execution表达式设置insertStudent方法为切入点,保存到point1()中;
public void point1(){}
@Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.deleteStudent())")
public void point2(){}
@Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.updateStudent())")
public void point3(){}
@Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.selectStudent())")
public void point4(){}
@Before("point1()")
public void saveLog1(){
System.out.println("记录系统运行日志");
}
@After("point2()")
public void saveLog2(){
System.out.println("记录系统运行日志");
}
//测试环绕通知
@Around("point3()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {
saveLog1(); // 开始
Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
saveLog1(); // 结束
return obj;
}
@AfterThrowing(value = "point4()",throwing = "e")
public void myAfterThrowing(Throwable e) {
System.out.println("异常通知" + "出错了" + e.getMessage());
}
}
package com.weiwei.springAOP;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hello world!
*
*/
public class App {
public static void main( String[] args ) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentServiceImpl stu = applicationContext.getBean("stu", StudentServiceImpl.class);
stu.insertStudent();
System.out.println("---------------------------");
stu.deleteStudent();
System.out.println("---------------------------");
stu.updateStudent();
System.out.println("---------------------------");
stu.selectStudent();
}
}
输出:
记录系统运行日志
添加学生信息的业务方法---insertStudent
---------------------------
删除学生信息的业务方法---deleteStudent
记录系统运行日志
---------------------------
记录系统运行日志
修改学生信息的业务方法---updateStudent
记录系统运行日志
---------------------------
查询学生信息的业务方法---selectStudent
异常通知出错了/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero