Spring 笔记:(二)AOP面向切面编程 (0.9w字讲解)
(二)AOP面向切面编程
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.10</version>
</dependency>Spring整合了AspectJ框架的一部分。
核心概念
切面:AOP中的核心模块化单元。它是一个类,这个类封装了一个或多个横切关注点(即通知)和确定这些通知执行位置的规则(即切点)。比如创建一个 LoggingAspect 类,专门负责所有与日志相关的逻辑。
连接点:在应用程序执行过程中,可以插入切面代码的特定点。这是AOP框架提供的一个“钩子”机会。如:
- 方法调用
- 方法执行
- 异常抛出
- 字段访问(部分AOP框架支持)
切点:一个谓词或表达式,用于匹配和筛选出那些需要应用通知的特定连接点。它定义了“在哪里”。execution(* com.example.service.*.*(..)) 这个表达式匹配 com.example.service 包下所有类的所有方法执行连接点。
通知:切面在特定切点上要执行的动作。它是包含横切逻辑(如写日志)的代码块。它定义了“做什么”以及“何时做”。
类型(根据执行时机划分):
- 前置通知:在目标方法执行前执行。
- 后置通知:在目标方法成功执行后执行(无论返回什么,只要不抛异常)。
- 返回通知:在目标方法成功执行并返回结果后执行(可以访问返回值)。
- 异常通知:在目标方法抛出异常后执行(可以访问异常对象)。
- 环绕通知:最强大的通知,包裹了目标方法的整个执行过程。可以在调用目标方法之前和之后执行自定义行为,甚至可以决定是否调用目标方法。
编织(织入):将切面应用到目标对象(被代理的对象)以创建代理对象的过程。这是实现AOP效果的关键步骤。
编织时机:
- 编译时编织:在源代码编译成字节码时进行(如AspectJ编译器
ajc)。 - 类加载时编织:在Java类被JVM加载时进行(需借助特殊的类加载器)。
- 运行时编织:在应用程序运行时,通过动态代理技术实现(Spring AOP默认采用的方式)。
- 编译时编织:在源代码编译成字节码时进行(如AspectJ编译器
引入:一种特殊的通知,允许为现有的类动态地添加新的方法或属性(以及实现新的接口)。这改变了类的结构,而不仅仅是行为。例如,可以通过引入,让一个普通的业务类实现 Auditable 接口,并拥有对应的 setLastModifiedBy 方法,而无需修改其源代码。
一、使用配置实现AOP
目标类:
public class UserServiceImpl {
public void saveUser() {
System.out.println("正在保存用户信息...");
}
}切面类:
public class MyLogger {
// 前置通知
public void beforeLog() {
System.out.println("[日志] 方法开始执行...");
}
// 后置通知
public void afterLog() {
System.out.println("[日志] 方法执行完毕。");
}
}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="userService" class="com.example.UserServiceImpl" />
<bean id="myLogger" class="com.example.MyLogger" />
<aop:config>
<!--切入点-->
<aop:pointcut id="logPointcut" expression="execution(* com.example.UserServiceImpl.saveUser(..))" />
<!--切面-->
<aop:aspect ref="myLogger">
<aop:before method="beforeLog" pointcut-ref="logPointcut" />
<aop:after method="afterLog" pointcut-ref="logPointcut" />
</aop:aspect>
</aop:config>
</beans>在 <aop:aspect> 内部,我们可以根据需要选择不同的通知类型:
| 标签 | 类型 | 执行时机 |
|---|---|---|
<aop:before> | 前置通知 | 目标方法运行之前 |
<aop:after-returning> | 后置通知 | 目标方法正常结束之后 |
<aop:after-throwing> | 异常通知 | 目标方法抛出异常时执行 |
<aop:after> | 最终通知 | 无论是否异常,方法结束后都执行 |
<aop:around> | 环绕通知 | 手动控制方法何时执行(功能最强) |
不同切面之间的重合,使用 优先级(Order)控制顺序,数值越小,优先级越高。
<aop:aspect ref="securityAspect" order="1">
<aop:before method="check" pointcut-ref="commonPointcut" />
</aop:aspect>
<aop:aspect ref="logAspect" order="2">
<aop:before method="log" pointcut-ref="commonPointcut" />
</aop:aspect>二、使用接口实现AOP
切面类实现MethodBeforeAdvice,
public class Student {
public String study(){
System.out.println("学习!");
return "study";
}
}例如:aop:before、aop:after-returning
public class StudentAOP implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("通过Advice实现AOP");
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("我是方法执行之后的结果,方法返回值为:"+returnValue);
}
}或者 aop:around
public class StudentAOP implements MethodInterceptor { //实现MethodInterceptor接口
@Override
public Object invoke(MethodInvocation invocation) throws Throwable { //invoke方法就是代理方法
Object value = invocation.proceed(); //需要手动proceed()才能调用原方法
return value+"增强";
}
}xml 配置:
<bean id="student" class="org.example.entity.Student"/>
<bean id="studentAOP" class="org.example.entity.StudentAOP"/>
<aop:config>
<aop:pointcut id="test" expression="execution(* org.example.entity.Student.study())"/>
<aop:advisor advice-ref="studentAOP" pointcut-ref="test"/>
</aop:config>三、使用注解实现AOP
在主类添加@EnableAspectJAutoProxy注解,开启AOP注解支持:
@EnableAspectJAutoProxy
@ComponentScan("org.example.entity")
@Configuration
public class MainConfiguration {
}目标类:
@Component
public class Student {
public void study(){
System.out.println("我是学习方法!");
}
}使用 @Aspect注解实现切面
aop:before
@Aspect
@Component
public class StudentAOP {
@Before("execution(* org.example.entity.Student.study())")
public void before(){
System.out.println("我是之前执行的内容!");
}
}@Aspect
@Component
public class StudentAOP {
@Before("execution(* org.example.entity.Student.study())")
public void before(JoinPoint point){
System.out.println("参数列表:"+ Arrays.toString(point.getArgs()));
System.out.println("我是之前执行的内容!");
}
}命名绑定模式
@Aspect
@Component
public class StudentAOP {
@Before(value = "execution(* org.example.entity.Student.study(..)) && args(str)", argNames = "str")
//命名绑定模式就是根据下面的方法参数列表进行匹配
//这里args指明参数,注意需要跟原方法保持一致,然后在argNames中指明
public void before(String str){
System.out.println(str); //可以快速得到传入的参数
System.out.println("我是之前执行的内容!");
}
}aop:after-returning
使用returning指定接收目标方法返回值的参数returnVal,以在切面方法内使用
@Aspect
@Component
public class StudentAOP {
@AfterReturning(value = "execution(* org.example.entity.Student.study())", argNames = "returnVal", returning = "returnVal")
//使用returning指定接收目标方法返回值的参数returnVal,以在切面方法内使用
public void afterReturn(Object returnVal){
System.out.println("返回值是:"+returnVal);
}
}aop:around
@Aspect
@Component
public class StudentAOP {
@Around("execution(* com.test.bean.Student.test(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("方法执行之前!");
Object val = point.proceed();
System.out.println("方法执行之后!");
return val;
}
}附:execution表达式的书写方法
基本语法结构
execution([修饰符] 返回类型 [包名.类名.]方法名(参数列表) [throws 异常类型])注意:[] 表示可选部分。实际书写时不包含 []。
各部分详解
- 修饰符(可选)
- 指定方法的访问权限:
public、private、protected等 - 通常省略,表示匹配所有访问权限
- 示例:
execution(public * *(..))- 匹配所有 public 方法
- 返回类型(必需)
- 指定方法的返回类型
*表示匹配任何返回类型示例:
execution(String *(..))- 匹配返回 String 的方法execution(void *(..))- 匹配返回 void 的方法
- 包名.类名.(可选)
- 指定完全限定类名
- 支持通配符
示例:
execution(* com.example.service.*.*(..))-service包下的所有类execution(* com.example.service..*.*(..))-service包及其子包下的所有类
- 方法名(必需)
- 指定方法名称
- 支持通配符
示例:
execution(* save*(..))- 匹配所有以save开头的方法execution(* *Service.*(..))- 匹配所有以Service结尾的类中的所有方法
- 参数列表(必需)
()- 匹配无参数方法(..)- 匹配任意数量、任意类型的参数(0个或多个)(*)- 匹配一个任意类型的参数(String, *)- 匹配两个参数,第一个是 String,第二个任意(String, ..)- 匹配至少一个参数,第一个是 String
- throws 异常类型(可选)
- 指定方法抛出的异常类型
- 较少使用
- 示例:
execution(* *(..) throws IOException)
常用通配符
| 通配符 | 含义 | 示例 |
|---|---|---|
* | 匹配任意数量字符(除了.) | com.*.service |
.. | 1)匹配任意数量子包 2)匹配任意数量参数 | com..service (..) |
+ | 匹配指定类型及其子类 | com.example.BaseService+ |
本文系作者 @xiin 原创发布在To Future$站点。未经许可,禁止转载。
暂无评论数据