AOP 可以做什么?
- 1. 日志记录
- 自动记录方法的调用、参数、返回值和异常等信息,减少了在每个方法中添加日志代码的需要。
- 2. 管理事务
- 在方法执行前后自动处理事务的开启、提交和回滚,确保数据的一致性和完整性。
- 3. 权限控制
- 实现方法的访问控制,检查用户权限,确保只有授权用户才能执行特定操作。
- 4. 性能监控
- 自动收集方法的执行时间、调用次数等性能指标,帮助开发者进行性能分析和优化。
- 5. 缓存管理
- 实现方法结果的缓存,减少重复计算,提高系统性能。
- 6. 异常处理
- 统一处理方法执行中的异常,简化异常捕获和处理逻辑。
- 7. 输入验证
- 在方法执行前对输入参数进行验证,保证数据的有效性和完整性。
- 8. 动态代理
- 创建代理对象,在不修改原有类的情况下添加额外的行为。
应用场景
- Web开发:用于请求处理、事务管理、安全控制等。
- 企业应用:在服务层实现统一的日志和异常处理。
- 微服务:在服务之间进行统一的监控和熔断处理。
日志记录
1.准备工作
首先需要导入AOP依赖
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-aop</ artifactId >
</ dependency >
< dependency >
< groupId >com.alibaba.fastjson2</ groupId >
< artifactId >fastjson2</ artifactId >
< version >2.0.50</ version >
</ dependency >
< dependency >
< groupId >io.jsonwebtoken</ groupId >
< artifactId >jjwt</ artifactId >
< version >0.9.1</ version >
</ dependency >
|
然后需要创建日志记录数据库和实体类还有在Mapper层实现插入数据方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//操作日志实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //主键ID
private Integer operateUser; //操作人ID
private LocalDateTime operateTime; //操作时间
private String className; //操作类名
private String methodName; //操作方法名
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
private Long costTime; //操作耗时
}
|
1 2 3 4 5 6 7 8 9 |
@Mapper
public interface OperateLogMapper {
//插入日志数据
@Insert ( "insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});" )
public void insert(OperateLog log);
}
|
2.创建自定义注解和切面类
因为我们要记录的操作日志是对于数据的增,删,改所以采用@annotation来指定要为连接点的方法。
而且在涉及到消耗时间的字段,所以需要采用@Around通知类型
1 2 3 4 |
@Retention (RetentionPolicy.RUNTIME) //运行时有效
@Target (ElementType.METHOD) //作用在方法上
public @interface Log {
}
|
3.实现日志记录(Around)
首先需要需要记录的操作接口方法前加上@Log注解
接下来在切面类中实现操作日志记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
@Aspect
@Component
public class LogAsper {
@Autowired
private OperateLogMapper operateLogMapper;
@Autowired
private HttpServletRequest request;
@Around ( "@annotation(com.ly.springbootdemotlias.anno.Log)" ) //匹配加了Log注解的方法
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
OperateLog operateLog = new OperateLog();
//获取操作人id,通过获取并解析jwt令牌
String jwt = request.getHeader( "token" );
Claims claims = JwtUtils.parseJwt((jwt));
Integer operateUser =(Integer) claims.get( "id" );
operateLog.setOperateUser(operateUser);
//获取操作时间
operateLog.setOperateTime(LocalDateTime.now());
//获取操作类名
operateLog.setClassName(joinPoint.getTarget().getClass().getName());
//获取操作方法名
operateLog.setMethodName(joinPoint.getSignature().getName());
//获取操作方法参数
operateLog.setMethodParams(Arrays.toString(joinPoint.getArgs()));
//获取开始时间
long begin = System.currentTimeMillis();
//获取操作方法返回值
Object result = joinPoint.proceed();
String returnValue = JSONObject.toJSONString(result);
operateLog.setReturnValue(returnValue.toString());
//获取操作结束时间
long end = System.currentTimeMillis();
//获取操作耗时
operateLog.setCostTime((end-begin));
operateLogMapper.insert(operateLog);
return result;
}
}
|
管理事务
1. 添加依赖
在 pom.xml 中添加 MyBatis-Plus 和 AOP 的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-aop</ artifactId >
</ dependency >
< dependency >
< groupId >com.baomidou</ groupId >
< artifactId >mybatis-plus-boot-starter</ artifactId >
< version >3.5.0</ version > <!-- 根据需要选择版本 -->
</ dependency >
< dependency >
< groupId >mysql</ groupId >
< artifactId >mysql-connector-java</ artifactId >
< scope >runtime</ scope >
</ dependency >
|
2. 配置 MyBatis-Plus
在 application.yml 中配置 MyBatis-Plus 和数据源:
1 2 3 4 5 6 7 8 |
spring:
datasource:
url: jdbc : mysql : //localhost : 3306/your_database
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath* : /mappers/**/*.xml
|
3. 创建事务切面
创建一个事务切面类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Aspect
@Component
public class TransactionAspect {
@Transactional
@Around ( "execution(* com.yourpackage.service..*(..))" ) // 指定需要事务的业务逻辑包
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
try {
result = joinPoint.proceed(); // 执行目标方法
return result;
} catch (Exception e) {
throw e; // 抛出异常以触发回滚
}
}
}
|
4. 示例业务逻辑类
创建一个服务类,使用 MyBatis-Plus 进行数据库操作:
1 2 3 4 5 6 7 8 9 10 |
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
public void createUser(User user) {
this .save(user); // 调用 MyBatis-Plus 的 save 方法
}
}
|
5. 用户实体和 Mapper
1 2 3 4 5 6 7 8 9 10 11 |
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName ( "users" ) // 数据库表名
public class User {
@TableId
private Long id;
private String name;
// getters and setters
}
|
1 2 3 4 5 |
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
// 可以自定义额外的方法
}
|
6. 创建控制器
在控制器中调用 UserService 的 createUser 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping ( "/users" )
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public String createUser( @RequestBody User user) {
userService.createUser(user); // 调用 UserService 的 createUser 方法
return "User created successfully" ; // 返回成功消息
}
}
|
7. 启动类
1 2 3 4 5 6 7 8 9 |
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application. class , args);
}
}
|
心得
- 通过以上步骤,你可以在 Spring Boot 项目中整合 AOP 和 MyBatis-Plus,实现简单而有效的事务管理。
- 这样,无论是在正常情况下提交事务,还是在出现异常时回滚事务,都能得到很好的支持。
为什么做事务,做事务有啥用
- 事务确保一组操作要么全部成功,要么全部失败,从而保持数据的一致性和完整性。
- 它们在处理银行转账、订单处理等场景时尤其重要,可以防止数据不一致、部分更新或系统故障导致的数据丢失。
- 通过事务,确保即使在异常情况下,数据状态依然可靠。
那为什么要结合aop去做事务
- 结合 AOP(面向切面编程)可以将事务管理的逻辑与业务逻辑分离,提高代码的可维护性和可读性。
- 通过 AOP,可以在不修改业务代码的情况下,集中管理事务,简化代码结构,并实现事务的自动化管理,减少重复代码,提高开发效率。
权限控制
1. 添加依赖
在 pom.xml 中添加 AOP 相关依赖:
1 2 3 4 |
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-aop</ artifactId >
</ dependency >
|
2. 创建权限注解
定义一个自定义注解 @RequiresPermission:
1 2 3 4 5 6 7 8 9 10 |
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target (ElementType.METHOD)
@Retention (RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value();
}
|
3. 创建切面类
创建切面类,使用 @Aspect 注解来定义切面,处理权限控制逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class PermissionAspect {
@Autowired
private UserService userService; // 获取用户服务
@Around ( "@annotation(requiresPermission)" ) // 拦截使用 @RequiresPermission 注解的方法
public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermission requiresPermission) throws Throwable {
String requiredPermission = requiresPermission.value(); // 获取所需权限
// 权限检查逻辑
if (!hasPermission(requiredPermission)) {
throw new SecurityException( "Permission denied: " + requiredPermission);
}
return joinPoint.proceed(); // 执行目标方法
}
private boolean hasPermission(String permission) {
User currentUser = userService.getCurrentUser(); // 获取当前用户
if (currentUser == null ) {
return false ; // 用户未登录
}
return currentUser.getPermissions().contains(permission); // 检查权限
}
}
|
在使用 AOP 时,@RequiresPermission 注解是一个标记,它本身并不直接传递参数。Spring AOP 在拦截带有该注解的方法时,会自动创建该注解的实例,并将其作为参数传递给
checkPermission 方法。这是通过 AOP 框架的代理机制实现的,而不是通过显式调用。具体来说,当被注解的方法被调用时,AOP 会拦截这个调用并注入相应的注解信息。
permission 参数来源于 @RequiresPermission 注解的 value() 方法。在 checkPermission 方法中,当该方法拦截到一个使用 @RequiresPermission 注解的方法时,Spring AOP 会将该注解的实例作为第二个参数传递给 checkPermission 方法。因此,你可以通过调用 requiresPermission.value() 来获取注解中定义的权限字符串。
4. 使用权限注解
在需要进行权限控制的方法上使用自定义注解
1 2 3 4 5 6 7 8 9 10 11 12 |
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@GetMapping ( "/admin" )
@RequiresPermission ( "ADMIN_ACCESS" )
public String adminAccess() {
return "Welcome, admin!" ;
}
}
|
5. 配置 Spring Boot
确保 Spring Boot 应用程序能够扫描到切面和自定义注解。通常情况下,只需在主类中添加 @EnableAspectJAutoProxy 注解:
1 2 3 4 5 6 7 8 9 10 11 |
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication. class , args);
}
}
|
@EnableAspectJAutoProxy 注解的作用是启用 Spring 的 AOP(面向切面编程)支持,允许使用 AspectJ 的代理机制来实现切面。具体来说,它有以下几个功能:
1.启用代理
它告诉 Spring 创建代理对象,这些代理对象可以拦截方法调用并执行切面逻辑。
2.支持注解
当你在代码中使用注解(如 @Around、@Before 等)时,@EnableAspectJAutoProxy 会使这些注解生效,从而实现方法拦截和切面逻辑的执行。
3.简化配置
通过添加这个注解,开发者不需要额外的 XML 配置,只需通过注解来定义切面和增强,减少了配置的复杂性。
在主类中添加这个注解后,Spring Boot 应用会自动扫描到切面类并将其注册到应用上下文中,从而实现所需的 AOP 功能。
AOP的五种通知类型包括
- 前置通知(@Before)
- 后置通知(@After)
- 返回通知(@AfterReturning)
- 异常通知(@AfterThrowing)
- 环绕通知(@Around)
每种通知类型都有其特定的用途和场景,可以根据实际需求选择使用。