Spring-AOP

AOP面向切面编程

切面的逻辑和业务逻辑进行隔离,降低耦合度,提高代码可重用性

1. 动态代理

AOP通过动态代理来实现,分为两种代理方式,(1)JDK动态代理,(2)CGLIB动态代理

1.1 JDK动态代理

有接口通过JDK动态代理,通过实现接口来创建代理对象

1.2 demo

  • 定义一个接口

    1
    2
    3
    4
    public interface TestService {
    Integer add(String str);
    Boolean delete(String str);
    }
  • 创建实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class TestServiceImpl implements TestService {
    @Override
    public Integer add(String str) {
    System.out.println("执行add方法:" + str);
    return 1;
    }

    @Override
    public Boolean delete(String str) {
    System.out.println("执行删除方法:" + str);
    return true;
    }
    }
  • 创建代理类并执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) {
    TestService testService = new TestServiceImpl();
    TestService testServiceProxy = (TestService) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class[]{TestService.class}, (proxy, method, args1) -> {
    System.out.println("方法执行前执行:" + method.getName());
    Object result = method.invoke(testService, args1);
    System.out.println("方法执行后执行:" + result);
    return result;
    });
    testServiceProxy.add("hello");
    System.out.println("---------------------------");
    testServiceProxy.delete("world");
    }
    • 通过Proxy.newProxyInstance来创建代理类,第一个参数为类加载器,第二个参数为需要代理的接口,第三个参数为代理方法,需要自己实现InvocationHandler接口。
  • 执行结果

    1
    2
    3
    4
    5
    6
    7
    方法执行前执行:add
    执行add方法:hello
    方法执行后执行:1
    ---------------------------
    方法执行前执行:delete
    执行删除方法:world
    方法执行后执行:true

1.3 CGLIB动态代理

没有接口时,需要使用CGLIB动态代理,通过创建被代理类的子类作为代理对象

2. 基本概念

  • 连接点

    • 可以被增强的方法
  • 切入点

    • 实际被增强的方法
  • 通知

    • 增强的逻辑就是通知
    • 通知的类型
      • 前置通知
      • 后置通知,正常执行完通知
      • 异常通知
      • 环绕通知
      • 方法执行完的最终通知,不管有没有异常
  • 切面

    • 把通知应用到切入点的动作叫切面

3. 切面表达式

表达式模板:execution([权限修饰符][返回值][全类名].[方法名]([参数]))

例如:execution(public void cn.aacopy.learn.spring.aop.UserService.add())

  • 通配符可以用*表示
  • 修饰符可以省略
  • 方法参数可以用..表示

4. demo

  • 引入依赖
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.22</version>
</dependency>
  • 启用aop注解
1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "cn.aacopy")
@EnableAspectJAutoProxy
public class MyConfig {
}
  • 编写业务类
1
2
3
4
5
6
7
@Component
public class UserService {
public String add(String str) {
System.out.println("执行add方法" + str);
return str + " @aacopy";
}
}
  • 编写切面类
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
@Component
@Aspect
public class AopTest {

@Before("execution(public String cn.aacopy.learn.spring.aop.UserService.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知执行..." + joinPoint.getSignature().toShortString());
}

@Around("execution(* cn.aacopy.learn.spring.aop.UserService.*(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知前...");
Object result = proceedingJoinPoint.proceed();
System.out.println("环绕通知后..." + result);
}

@AfterReturning("execution(* cn.aacopy.learn.spring.aop.UserService.*(..))")
public void afterReturning() {
System.out.println("后置通知执行...");
}

@After("execution(* cn.aacopy.learn.spring.aop.UserService.*(..))")
public void after() {
System.out.println("最终通知执行...");
}

@AfterThrowing("execution(* cn.aacopy.learn.spring.aop.UserService.*(..))")
public void afterThrowing() {
System.out.println("异常通知执行...");
}
}
  • 执行代码
1
2
3
4
5
public static void main( String[] args ) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.add("hello");
}

4.1 提取公共模板

  • 编写一个方法,上面使用@Pointcut注解,其他方法直接使用方法名即可,方便统一管理
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
@Component
@Aspect
public class AopTest {

@Pointcut("execution(* cn.aacopy.learn.spring.aop.UserService.*(..))")
public void pointCutTemplate() {

}

@Before("execution(public String cn.aacopy.learn.spring.aop.UserService.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知执行..." + joinPoint.getSignature().toShortString());
}

@Around("pointCutTemplate()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知前...");
Object result = proceedingJoinPoint.proceed();
System.out.println("环绕通知后..." + result);
}

@AfterReturning("pointCutTemplate()")
public void afterReturning() {
System.out.println("后置通知执行...");
}

@After("execution(* cn.aacopy.learn.spring.aop.UserService.*(..))")
public void after() {
System.out.println("最终通知执行...");
}

@AfterThrowing("execution(* cn.aacopy.learn.spring.aop.UserService.*(..))")
@Async
public void afterThrowing() {
System.out.println("异常通知执行...");
}
}
  • 如果需要异步执行,只需要在启动类上开启异步@EnableAsync,并在方法上添加@Async注解

5. 优先级

多个AOP切面,设置优先级,可以在切面类上添加@Order注解,值小的,优先执行

1
2
3
4
@Component
@Aspect
@Order(-1)
public class AopTest {

6. 通过注解方式实现切面

  • 定义注解
1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RefreshCache {
}
  • 添加注解到需要执行切面的方法上
1
2
3
4
@RefreshCache
public Result<RoleVo> add(@RequestBody RoleAddDto roleAddDto) {
return this.success(roleService.add(roleAddDto));
}
  • 切面逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
@Aspect
@Slf4j
public class RefreshCacheAspect {

@Resource
private ResourceCacheService resourceCacheService;

/**
* 方法成功返回后执行刷新缓存
*/
@AfterReturning("@annotation(com.xxx.cache.RefreshCache)")
@Async
public void refreshCache(JoinPoint joinPoint) {
log.info("{}执行完成,开始异步刷新缓存...", joinPoint.getSignature().toShortString());
long l1 = System.currentTimeMillis();
resourceCacheService.doCacheAll();
long l2 = System.currentTimeMillis();
log.info("异步刷新Resource缓存成功,耗时:{}ms", (l2-l1));
}
}