一、Spring AOP
1.1 AOP概述
- 定义
AOP(Aspect Oriented Programming)面向切面编程,通过预编译和运行期动态代理的方式,实现了程序各层级业务逻辑的隔离,降低了程序的耦合性,提高了程序开发的效率
- 作用
在程序运行期间,通过动态代理的方式不改变源码实现对方法的增强
- 优势
- 降低了代码的耦合性
- 提高了开发效率
- 方便维护
1.2 相关术语
- 连接点 Joinpoint
被拦截到的点;spring中指方法,因为spring只支持方法的拦截
- 切入点 Pointcut
进行了增强的连接点
- 通知 Advice
在切入点进行的所有增强的操作
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知
- 切面 Aspect
切入点和通知的结合
- 目标对象 Target
被代理的对象
- 代理对象 Proxy
增强后的对象
- 织入 Weaving
把切面应用到目标对象产生增强后的代理对象的过程
注意:spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入
- 引入
二、XML配置实现
- xml配置步骤
- 使用aop:config标签开始spring aop的配置
- 使用aop:aspect标签开始配置切面
- id: 切面ID
- ref: 切面处理Bean
- 使用aop:aspect等子标签配置通知类型
- method: 通知处理的具体方法
- pointcut: 切入点表达式,用于描述哪些连接点可以作为切入点
- 切入点表达式
- 关键字:execution
- 表达式:访问修饰符 返回值 类限定名.方法名(参数列表)
- 标准写法:public void com.lizza.service.UserService.update(int)
- 访问修饰符可以省略:void com.lizza.service.UserService.update(int)
- 返回值可以使用通配符:* com.lizza.service.UserService.update(int)
- 包名可以使用通配符,有几级包写几个*:* ..*.UserService.update(int)
- 包名使用…表示当前包及其子包:* com.lizza…update(int)
- 类名和方法名都可以使用通配符:* com.lizza…*()
- 参数列表:* com.lizza…*(…)
- 基本类型:直接写名称,如int,char,double
- 应用类型:全限定名.类名,如:java.lang.String
- *:表示必须有参数
- …:表示有无参数均可
- 全通配形式:* ….*(…)
- beans.xml
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.lizza.service.UserService"></bean>
<bean id="log" class="com.lizza.util.Log"></bean>
<!--
Spring中配置AOP的步骤
1. 使用<aop:config>标签开始spring aop的配置
2. 使用<aop:aspect>标签开始配置切面
id: 切面ID
ref: 切面处理Bean
3. 使用<aop:aspect>等子标签配置通知类型
method: 通知处理的具体方法
pointcut: 切入点表达式,用于描述哪些连接点可以作为切入点
4. 切入点表达式
关键字:execution
表达式:访问修饰符 返回值 类限定名.方法名(参数列表)
标准写法:public void com.lizza.service.UserService.update(int)
访问修饰符可以省略:void com.lizza.service.UserService.update(int)
返回值可以使用通配符:* com.lizza.service.UserService.update(int)
包名可以使用通配符,有几级包写几个*:* *.*.*.UserService.update(int)
包名使用..表示当前包及其子包:* com.lizza..update(int)
类名和方法名都可以使用通配符:* com.lizza..*()
参数列表:* com.lizza..*(..)
基本类型:直接写名称,如int,char,double
应用类型:全限定名.类名,如:java.lang.String
*:表示必须有参数
..:表示有无参数均可
全通配形式:* *..*.*(..)
-->
<aop:config>
<aop:aspect id="logAdvice" ref="log">
<aop:before method="log" pointcut="execution(* com.lizza..*())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
三、注解配置实现
3.1 关键注解
@Aspect
:指定切面类@Component
:将切面类作为Spring容器的bean交由Spring容器管理@Pointcut
:指定切入点@Before
:前置通知@AfterReturning
:后置通知@AfterThrowing
:异常通知@After
:最终通知@Around
:环绕通知@EnableAspectJAutoProxy
:开启aop
3.2 使用步骤
- 使用
@EnableAspectJAutoProxy
注解开启AOP支持 - 编写切面,使用
@Component
注解和@Aspect
注解来指定切面类 - 使用
@Pointcut
注解指定切入点,并且编写execution(* com.lizza..*())
表达式来指定要切入的包 - 使用
@Before
,@AfterReturning
,@AfterThrowing
,@After
,@Around
注解来指定前置通知,后置通知,异常通知,最终通知,环绕通知
3.3 示例代码
- 配置切面
@Aspect
@Component
public class Log {
@Pointcut("execution(* com.lizza..*())")
public void pointCut() {}
/**
* 前置通知:切入点执行之前执行
*/
@Before("pointCut()")
public void beforeLog() {
System.out.println("前置通知:记录日志...");
}
/**
* 后置通知:切入点执行之后执行
*/
@AfterReturning("pointCut()")
public void afterLog() {
System.out.println("后置通知:记录日志...");
}
/**
* 异常通知:切入点执行发生异常时执行
*/
@AfterThrowing("pointCut()")
public void exceptionLog() {
System.out.println("异常通知:记录日志...");
}
/**
* 最终通知:无论切入点执行发生异常与否,都会执行
*/
@After("pointCut()")
public void finalLog() {
System.out.println("最终通知:记录日志...");
}
/**
* 环绕通知
*/
@Around("pointCut()")
public Object aroundLog(ProceedingJoinPoint point) {
Object result;
try {
System.out.println("环绕通知-前置:记录日志...");
Object[] args = point.getArgs(); // 获取方法执行的参数
result = point.proceed(args); // 执行切入点方法
System.out.println("环绕通知-后置:记录日志...");
return result;
} catch (Throwable t) {
System.out.println("环绕通知-异常:记录日志...");
throw new RuntimeException(t);
} finally {
System.out.println("环绕通知-最终:记录日志...");
}
}
}
- 开启aop
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.lizza")
public class SpringConfig {
}
3.4 注意问题
- spring aop注解配置时,最终通知会在异常通知或后置通知之前执行,故实际应用时建议使用环绕通知
- spring aop环绕通知使用
ProceedingJoinPoint
来获取切入点的方法,参数