​ 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;

// 替换为你的JWT密钥
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();
// 在这里可以根据需要进一步处理claims,比如判断过期时间、验证issuer等
} 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() {
// 在这里处理请求,前置通知会在此方法执行前进行JWT校验
}
}

这个示例中,processRequest方法会在执行前进行JWT校验,确保JWT的有效性。同时,你可以将类似的校验逻辑应用于其他带有JWT的方法,以提高代码的安全性和可维护性。请注意,实际项目中可能需要根据业务需求进行更复杂的校验逻辑。

2、缓存逻辑

当使用AOP思想来实现Redis的缓存逻辑时,你可以按照以下步骤来操作:

  1. 引入依赖: 在你的项目中引入Spring Data Redis依赖,确保你能够使用Redis作为缓存。

  2. 创建缓存切面: 创建一个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. 创建自定义注解: 创建一个自定义注解,用于标记需要进行缓存的方法。
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 {
}
  1. 在需要缓存的方法上添加注解: 在需要进行缓存的方法上添加@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缓存中。

  1. 启用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来实现日志打印功能:

  1. 创建日志切面: 创建一个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记录方法的名称。

  1. 启用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. 调用方法: 在业务逻辑中调用方法,切面会在方法执行后记录日志。
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方法时,切面会在方法执行后使用日志记录相关信息。

  1. 配置日志输出: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思想实现基本的日志打印功能。当执行方法时,切面会在方法执行后记录相关的日志信息。