AOP(面向切面编程):就是把公共逻辑抽出来,让开发者更好的专注于业务逻辑的开发,AOP是OOP的补充,OOP是面向类和对象的,但是AOP是面向不同切面的,一个切面可以横跨多个类和对象去操作,极大提高开发效率。
例如一个订单的创建,可能需要下列四个步骤
1、权限校验
2、事务管理
3、创建订单
4、日志打印
如果使用AOP的思想,其实就可以把四个步骤抽象成四个切面,让开发者专注于第3个切面,其他的切面都用基础通用的逻辑(就是抽象出来的公共逻辑),统计交给AOP封装管理。
实现原理:
通过代理模式实现,实现方式有两种,一种事基于Java原生的动态代理,一种是基于cglib的动态代理。
Spring AOP默认使用JDK的原生代理,可以代理任何接口,但是不能代理没有接口的类,所以使用cglib来实现动态代理没有接口的类。
AOP的业务场景:
1、参数校验
当使用AOP思想实现参数校验功能时,你可以创建一个AOP切面来拦截需要校验的方法,并从HttpServletRequest
中获取JWT参数进行校验。以下是一个基本示例:
- 创建自定义注解: 创建一个自定义注解,用于标记需要进行JWT校验的方法。
1 2 3 4 5 6 7 8 9
| import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ValidateJWT { }
|
- 编写AOP切面: 创建一个AOP切面,用于拦截带有
@ValidateJWT
注解的方法,并从HttpServletRequest
中获取JWT参数进行校验。
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 40
| import javax.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;
@Aspect @Component public class JWTValidationAspect {
@Autowired private HttpServletRequest request;
private final String secretKey = "your-secret-key";
@Before("@annotation(com.example.ValidateJWT)") public void validateJWT(JoinPoint joinPoint) { String jwtToken = extractTokenFromRequest(request); if (jwtToken == null) { throw new IllegalArgumentException("JWT token is missing"); }
try { Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken).getBody(); } catch (Exception e) { throw new IllegalArgumentException("Invalid JWT token"); } }
private String extractTokenFromRequest(HttpServletRequest request) { String authorizationHeader = request.getHeader("Authorization"); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { return authorizationHeader.substring(7); } return null; } }
|
在这个示例中,我们使用了@Autowired
来注入HttpServletRequest
,然后在AOP切面中使用extractTokenFromRequest
方法从请求头中提取JWT token。然后,我们在校验逻辑中解析JWT token,并根据需要处理JWT的内容。这个切面会在带有@ValidateJWT
注解的方法执行前进行JWT校验。
- 在需要校验的方法上添加注解: 在需要校验JWT的方法上添加
@ValidateJWT
注解。
1 2 3 4 5 6 7 8 9 10
| import org.springframework.stereotype.Service;
@Service public class YourService {
@ValidateJWT public void processRequest() { } }
|
这个示例中,processRequest
方法会在执行前进行JWT校验,确保JWT的有效性。同时,你可以将类似的校验逻辑应用于其他带有JWT的方法,以提高代码的安全性和可维护性。请注意,实际项目中可能需要根据业务需求进行更复杂的校验逻辑。
2、缓存逻辑
当使用AOP思想来实现Redis的缓存逻辑时,你可以按照以下步骤来操作:
引入依赖: 在你的项目中引入Spring Data Redis依赖,确保你能够使用Redis作为缓存。
创建缓存切面: 创建一个AOP切面,用于拦截带有@CacheableResult
注解的方法,并将方法的返回值缓存到Redis。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component;
@Aspect @Component public class CacheAspect {
@Autowired private RedisTemplate<String, Object> redisTemplate;
@AfterReturning(pointcut = "@annotation(com.example.CacheableResult)", returning = "result") public void cacheMethodResult(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().toShortString(); redisTemplate.opsForValue().set(methodName, result); } }
|
在这个切面中,我们使用了@Autowired
注入了RedisTemplate
,这是Spring提供的与Redis交互的工具类。cacheMethodResult
方法会在带有@CacheableResult
注解的方法执行后执行,将方法返回的结果存储到Redis缓存中,使用方法的名称作为缓存的键。
- 创建自定义注解: 创建一个自定义注解,用于标记需要进行缓存的方法。
1 2 3 4 5 6 7 8 9
| import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CacheableResult { }
|
- 在需要缓存的方法上添加注解: 在需要进行缓存的方法上添加
@CacheableResult
注解。
1 2 3 4 5 6 7 8 9 10 11
| import org.springframework.stereotype.Service;
@Service public class YourService {
@CacheableResult public String getFromDatabase() { return "Data from database"; } }
|
在这个示例中,getFromDatabase
方法被@CacheableResult
注解标记,当这个方法被调用并返回结果时,切面会拦截这个方法的执行,将返回的数据存储到Redis缓存中。
- 启用AOP和组件扫描: 在Spring Boot的配置类上添加
@EnableAspectJAutoProxy
注解,以启用AOP功能,同时确保你的切面和服务类被正确地扫描到。
1 2 3 4 5 6 7 8 9 10
| import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration @EnableAspectJAutoProxy @ComponentScan(basePackages = "com.example") public class AppConfig { }
|
通过以上步骤,你就可以使用AOP思想实现基本的Redis缓存逻辑。当使用带有@CacheableResult
注解的方法时,切面会自动将方法的结果存储到Redis缓存中。在实际项目中,你可能还需要考虑缓存的失效策略、数据更新时的缓存更新等细节。同时,Spring框架也提供了更丰富的缓存管理功能,你可以根据具体的需求进行选择和配置。
3、日志打印
实际上在日志打印的场景中,不一定需要创建自定义注解。通常情况下,日志打印是一种通用的操作,因此你可以直接在切面中定义切点,而不必引入自定义注解。
以下是一个更准确的示例,展示如何使用AOP来实现日志打印功能:
- 创建日志切面: 创建一个AOP切面,用于拦截方法的执行并记录日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;
@Aspect @Component public class LoggingAspect {
private final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@AfterReturning("execution(* com.example.*.*(..))") public void logMethodExecution(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().toShortString(); logger.info("Method {} executed", methodName); } }
|
在这个切面中,LoggingAspect
会拦截com.example
包下所有方法的执行,在方法执行后使用logger
记录方法的名称。
- 启用AOP和组件扫描: 在Spring Boot的配置类上添加
@EnableAspectJAutoProxy
注解,以启用AOP功能,同时确保你的切面被正确地扫描到。
1 2 3 4 5 6 7 8 9 10
| import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration @EnableAspectJAutoProxy @ComponentScan(basePackages = "com.example") public class AppConfig { }
|
确保basePackages
中的包路径是你的切面和服务类所在的包。
- 调用方法: 在业务逻辑中调用方法,切面会在方法执行后记录日志。
1 2 3 4 5 6 7 8 9 10
| import org.springframework.stereotype.Service;
@Service public class YourService {
public String process() { return "Processed result"; } }
|
在这个示例中,当调用process
方法时,切面会在方法执行后使用日志记录相关信息。
- 配置日志输出: 在
src/main/resources
目录下创建logback.xml
文件,根据你的需求配置日志的输出格式、级别等。
1 2 3 4 5 6 7 8 9 10 11
| <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>
|
通过以上步骤,你就可以使用AOP思想实现基本的日志打印功能。当执行方法时,切面会在方法执行后记录相关的日志信息。