问题
日志、验证、事务、权限等逻辑贯穿项目各处。
如果让每个类各自实现这些逻辑:
- 重复:每个方法都要写一遍,代码大量重复。
- 耦合:业务代码和通用逻辑混在一起,职责不清。
- 难维护:改一处日志格式,要改几十个类。
这些逻辑叫横切关注点——它们横跨多个模块,却不属于任何一个模块的核心职责。
解决
把这些横切逻辑从业务代码中抽出来,集中定义,再在需要的地方“织入”。
业务类只写业务,日志/验证等由切面统一处理。
基础概念
| 概念 | 含义 |
|---|---|
| Aspect(切面) | 横切逻辑的模块化,如日志切面 |
| Advice(通知) | 切点处执行的动作,如前置、后置、环绕 |
通知类型
@Before:方法执行前@After:方法执行后@AfterReturning:正常返回后@AfterThrowing:抛异常后@Around:包围方法---唯一可控制执行与返回值的通知
小结
AOP 让业务逻辑和横切逻辑各归其位,是对 OOP 的补充。
例子
逻辑
@PreDoSth注解:标记在类或方法上,表示“这个地方需要切入”。MyLog切面:切点匹配“带@PreDoSth的地方”,通知类型@Before,输出一行日志。UserService:业务类,createUser方法标注@PreDoSth。CommandLineRunner:启动后调用userService.createUser("Alice")。
1. 注解文件 PreDoSth.java
package com.example.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD}) // 可标在类或方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时可被读取
public @interface PreDoSth {
}
2. 切面文件 MyLog.java
package com.example.demo.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyLog {
// 匹配所有标注了 @PreDoSth 的方法
@Before("@annotation(com.example.demo.annotation.PreDoSth)")
public void Logging() {
System.out.println("进行业务代码之前,可以在这里记录日志");
}
}
3. 业务类 UserService.java
package com.example.demo.service;
import com.example.demo.annotation.PreDoSth;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@PreDoSth
public String createUser(String name) {
System.out.println("【业务】正在创建用户:" + name);
return "创建成功: " + name;
}
}
4. 调用 DemoApplication.java
package com.example.demo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private UserService userService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) {
String msg = userService.createUser("Alice");
System.out.println("【调用者拿到】" + msg);
}
}
5. pom.xml 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
运行结果:
进行业务代码之前,可以在这里记录日志
【业务】正在创建用户:Alice
【调用者拿到】创建成功: Alice
说明
@PreDoSth只是标签,本身不执行任何逻辑。MyLog里的@Before才是真正执行日志的地方。- 切点
@annotation(...)表示“匹配标了这个注解的方法”。
如果想改成标在类上,把 UserService 的 @PreDoSth 从方法移到类上,切点表达式从@annotation(com.example.demo.annotation.PreDoSth)换成 @within(com.example.demo.annotation.PreDoSth) 即可。也可以用@Before("@within(com.example.demo.annotation.PreDoSth) || " +"@annotation(com.example.demo.annotation.PreDoSth)")让它在类和方法上都生效。
切片控制业务(@Around)
-
需求:创建用户的时候,需要对用户名长度进行判断。长度小于5,则不创建该用户。日常判断权限,就是这个道理
-
代码
package com.example.demo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyLog {
// // 匹配所有标注了 @PreDoSth 的方法
// @Before("@annotation(com.example.demo.annotation.PreDoSth)")
// public void Logging() {
// System.out.println("进行业务代码之前,可以在这里记录日志");
// }
@Around("@annotation(com.example.demo.annotation.PreDoSth)")
public Object aroundCreateUser(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
String name = (String) args[0];
if (name == null || name.length() < 5) {
return "创建失败:用户名长度必须大于等于5";
}
return pjp.proceed();
}
}
运行效果自行查看。
- 说明:
-
@Around可以通过参数获取要执行的业务方法(这里是pjp)。在切片方法内部,调用
pjp.proceed()运行业务。业务方法返回值,必须通过切片return才能被调用方拿到。 -
正是通过上面的流程,@Around切片可以实现对切入业务的任意控制。
含ai生成、作者整理的内容。
浙公网安备 33010602011771号