Spring技术总结03-Aop

一、什么是Aop

AOP:Aspect Oriented Programming面向切面编程。

AOP利用的是一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP技术包含了切点表达式和增强方法的知识点。

二、切点表达式和增强方法

增强方法用于获取调用切面类的类信息,可以根据其信息进行添加独特的功能。

1.增强方法

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* 1.定义增强方法
* 2.使用注解指定对应的方法 @After等
* 3.配置切点表达式中的方法 在增强方法的注解上配置
* 4.切面和ioc配置 在类前面添加相应的注解@Component @Aspect
* 5.开启aspectj注解的支持 在配置类或者配置文件中开启
* TODO:实践获取目标类的信息
* 1.全部增强方法中,获取目标方法的信息(方法名,参数,访问修饰符,所属类的信息)
* 在增强方法的参数中添加JoinPoint形参,该参数包含了目标类的信息
* 2.返回的结果
* 增强方法的参数列表中添加(Object result)
* 在@AfterReturning(value = "execution()", returning = "result")中添加returing="形参名"接收返回结果
* 3.异常的信息
* 增强方法的参数列表中添加(Throwable throwable)
* 在@AfterThrowing(value = "execution()", throwing = "throwable")中添加throwing="形参名"接收返回结果
*
* */
@Component
@Aspect
@Order(15)
public class LogAdivce {

@Before("execution(* com.ldy.service.impl.*.*(..))")
public void before(JoinPoint joinPoint){
//获取方法参数列表
Object[] args = joinPoint.getArgs();
System.out.println("方法参数列表:"+ Arrays.toString(args));
//获取方法的名称
String name = joinPoint.getSignature().getName();
System.out.println("方法名称:"+ name);
//获取方法的访问修饰符
int modifiers = joinPoint.getSignature().getModifiers();
String modifer = Modifier.toString(modifiers);
System.out.println("访问修饰符:"+ modifer);
//获取方法所属类的信息
String className = joinPoint.getTarget().getClass().getName();
System.out.println("方法所属类:"+className);

}
@After("execution(* com.ldy.service.impl.*.*(..))")
public void after(){

}
@AfterReturning(value = "execution(* com.ldy.service.impl.*.*(..))", returning = "result")
public void afterReturning(Object result){
System.out.println("方法返回值:"+result);
}

@AfterThrowing(value = "execution(* com.ldy.service.impl.*.*(..))",throwing = "throwable")
public void afterThrowing(Throwable throwable){
System.out.println("方法异常对象:"+ throwable);
}

// @Around("execution(* com.ldy.service.impl.*.*(..))")
// public void around(){
//
// }
}

测试类

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
41
42
//使用注解来创建ioc容器对象
// 要求依赖: spring-text
//@SpringJUnitConfig(value= , location= )
// value: 指定配置类
// location: 指定配置文件
@SpringJUnitConfig(value = Config.class)
public class MyTest2 {

//使用注解自动获取ioc容器中的对象
@Autowired
//若多个类实现某个接口,代理时可以加@Qualifier指定代理的对象
//@Qualifier("demo2")
//若不指定,则根据变量名来选对应的代理对象,即demo选Demo,demo2选Demo2
public DemoService demo;
@Autowired
public Calculate calculate;
@Test
public void test01(){
/*
方法参数列表:[1, 2]
方法名称:div
访问修饰符:public abstract
方法所属类:com.ldy.service.impl.Demo
方法返回值:-1
div=-1
*/
int div = demo.div(1, 2);
System.out.println("div=" + div);

/*
方法参数列表:[1, 0]
方法名称:chu
访问修饰符:public abstract
方法所属类:com.ldy.service.impl.Demo
方法异常对象:java.lang.ArithmeticException: / by zero
*/
// int chu = demo.chu(1, 0);
// System.out.println("chu=" + chu);
int add = calculate.add(1, 2);
System.out.println("add="+add);
}
}

2.切点表达式

切点表达式语法如下

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
/*
* TODO:切点表达式
* 固定语法: execution("1 2 3 4 5 6")
* 1: 访问修饰符
* public/private
* 2: 返回参数类型
* int/void/...
* 细节: * 代表任意修饰符和返回参数类型,但不能独立表示其中一个,即不能 * int
* 3: 包名
* 具体: com.ldy.service.impl
* 单层模糊: com.ldy.service.* *单层模糊
* 多层模糊: com..impl ..多层模糊
* 细节: 不能..开头
* 查询所有包下的impl包 *..impl
* 全部包: *..
* 4: 类名
* 具体: DemoService
* 部分模糊: Demo* 表示以Demo开头的所有类
* 模糊: *
* 5: 方法名 与类名一致
* 6: 方法参数
* 具体: (String, int)
* 部分模糊: (String..) String后面有没有都无所谓
* 模糊: (..)
* 实战:
* 1.全部包下的impl包的所有参数为int的方法
* * *..impl.*.*(int)
* 2.某包某类下,访问修饰符公有的,返回值类型是int的全部方法,
* public int xx.xx.jj.*(..)
*
* */
/*
* TODO:
* 切点表达式的提取和复用
* 法1.当前类中定义空方法
* 添加注解@Pointcut()里面传要复用的切点表达式 @Pointcut("execution(* com.ldy..*.*(..))")
* 法2.创建一个存储切点的类
* 增强方法的注解里写存储切点类的 全类名.方法名()
* */

三、依赖和注解介绍

aspectj依赖

在新的子类工程的pom文件里添加aspectj依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.1.12</version>
</dependency>

注解介绍

1.@Aspect

作用于类上,声明该类为切面类

2.@Component

作用于类上,使得该切面类能放到ioc容器

3.@Order

作用于类上,用于声明切面类的优先级,数字越小级别越优先

1
@Order(10)

4.@Pointcut

@Pointcut作用于空方法上,为其他方法提供切点表达式。

4.切面类的方法上的注解

1
2
3
4
5
6
*       使用注解配置
* 前置 @Before
* 后置 @AfterReturning
* 最后 @After
* 异常 @AfterThrowing
* 环绕 @Around
1
2
@Pointcut("execution(* com.ldy..*.*(..))")
public void pc(){}

这些注解可以指定切点表达式,也可以引用本类或其他类的切点表达式。

直接指定

1
@After("execution(* com.ldy.service.impl.*.*(..))")

引用本类的切点

1
2
3
4
5
6
7
@Pointcut("execution(* com.ldy..*.*(..))")
public void pc(){}
//该类中的表达式提取
@Before("pc()")
public void before(){
System.out.println("事务开启");
}

引用其他类中的切点表达式

1
2
3
4
5
//其他类中切点表达式提取
@After("com.ldy.pointcut.MyPointcut.all())")
public void after(){
System.out.println("事务结束");
}

被引用的切点类

1
2
3
4
5
6
7
8
@Component
@Aspect
public class MyPointcut {
@Pointcut("execution(* *..impl.*.*(..))")
public void impl(){}
@Pointcut("execution(* com.ldy..*.*(..))")
public void all(){}
}

5.@EnableAspectJAutoProxy

作用于配置类上面,用于开启Aspectj注解支持

四、实操

简单实操如下

1.配置类

1
2
3
4
5
6
7
@Configuration
@ComponentScan(value={"com.ldy.service", "com.ldy.advice","com.ldy.pointcut"})
//开启支持Aspectj的注解配置
@EnableAspectJAutoProxy
public class Config {

}

@EnableAspectAutoProxy用于开启Aspectj的注解支持

2.切面类

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
/*
* 增强类的内部存储增强代码
*
* 1.定义方法存储增强代理
* 具体定义几个根据实际需要
* 2.使用注解配置
* 前置 @Before
* 后置 @AfterReturning
* 最后 @After
* 异常 @AfterThrowing
* 环绕 @Around
* */
//@Component
//@Aspect
public class Myaop {
@Before("execution(* com.ldy.service.impl.*.*(..))")
public void start(){
System.out.println("方法开始了");
}

@After("execution(* com.ldy.service.impl.*.*(..))")
public void end(){
System.out.println("方法结束了");
}
@AfterThrowing("execution(* com.ldy.service.impl.*.*(..))")
public void error(){
System.out.println("方法报错了");
}

@AfterReturning("execution(* com.ldy.service.impl.*.*(..))")
public void afterReturning(){

}
}

3.Service类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.ldy.service.impl;

import com.ldy.service.DemoService;
import org.springframework.stereotype.Service;

@Service
public class Demo implements DemoService {

@Override
public int div(int a, int b) {
return a - b;
}

@Override
public int chu(int a, int b) {
return a/b;
}
}

4.测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyTest {
@Test
public void demo01(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
DemoService demo = applicationContext.getBean("demo", DemoService.class);
int div = demo.div(1, 2);
System.out.println("div=" + div);
// int chu = demo.chu(2, 0);
// System.out.println("chu=" + chu);

}

public void demo02(){

}

}

五、Aop的实现原理

2024/12/6 突然想起aop的实现原理

静态代理和动态代理可参考以下文章,有空再用自己的语言整理整理

AOP的实现原理 —— 静态代理 和 动态代理( Spring AOP)_aspectj 静态代理-CSDN博客