一、理论
AOP面向切面编程
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术.
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,
从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP的作用(特点)是什么
AOP代表的是一个横向的关 系,将“对象”比作一个空心的圆柱体,其中封装的是对象的属性和行为;则面向方面编程的方法,就是将这个圆柱体以切面形式剖开,选择性的提供业务逻辑。而 剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹,但完成了效果。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式, 引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
比较专业的概念定义
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Advice object 的过程
Advice 的类型
- before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行,
但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码) - after return advice, 在一个 join point 正常返回后执行的 advice
- after throwing advice, 当一个 join point 抛出异常后执行的 advice
- after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
- around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
- introduction,introduction可以为原有的对象增加新的属性和方法。
二、实操
1、pom依赖
并不是全都需要
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- cglib依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
2、定义一个实体类、传参需要
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
3、定义一个注解类
/**
* 自定义的日志注解
*/
/**
* @Target(ElementType.TYPE)——接口、类、枚举、注解
* @Target(ElementType.FIELD)——字段、枚举的常量
* @Target(ElementType.METHOD)——方法
* @Target(ElementType.PARAMETER)——方法参数
* @Target(ElementType.CONSTRUCTOR)——构造函数
* @Target(ElementType.LOCAL_VARIABLE)——局部变量
* @Target(ElementType.ANNOTATION_TYPE)——注解
* @Target(ElementType.PACKAGE)——包
*/
@Target({ElementType.METHOD}) //元注解、使用地方 METHOD表示方法上使用
/**
* @Retention(RetentionPolicy.SOURCE)——只保存到源文件、加载成class就消失
* @Retention(RetentionPolicy.CLASS)——只保存到class、加载到jvm就消失
* @Retention(RetentionPolicy.RUNTIME)——运行的时候还存在
*/
@Retention(RetentionPolicy.RUNTIME) //元注解、生命周期 RUNTIME表示运行时候也存在
public @interface MyLog {
}
4、service类
/**
* 要被增强方法的类
*/
@Service
public class AopService {
@MyLog
public User save(User user) {
System.out.println(user);
user.setId(1000001);
return user;
}
@MyLog
public void del(int id) {
System.out.println("删除了用户:" + id);
}
}
5、定义切面
@Aspect //切面注解
@Component
public class MyLogAspect {
//以注解标记作为切入点
@Pointcut("@annotation(com.lm.web_module_study.aop.samples5.MyLog)")
public void pointcut() {}
/**
* 环绕通知
* @param proceedingJoinPoint
* @throws Throwable
*/
@SneakyThrows //为代码生成一个try…catch块,并把异常向上抛出来
@Around("pointcut()") //Around 环绕增强
//@Around("@annotation(com.lm.web_module_study.aop.samples5.MyLog)") 也可以这么写
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
// 获取入参
Object[] args = Optional.ofNullable(proceedingJoinPoint.getArgs()).orElse(new Object[]{});
for (Object arg : args) {
System.out.println("------------->入参为arg=" + arg);
}
//proceedingJoinPoint.proceed() 这个可以理解为在执行方法
// 获取出参
Object result = Optional.ofNullable(proceedingJoinPoint.proceed()).orElse(new Object());
System.out.println("-------------->出参result=" + result);
return result;
}
}
6、单元测试
//classes 指向一个ioc容器,这个容器得有上方所定义的类
//一般情况下指向你这个工程的启动类,也就是标记@SpringBootApplication注解的类就可以了
@SpringBootTest(classes = RunMain.class)
class MyLogServiceTest {
@Resource
private AopService aopService;
@Test
void test1() {
//添加用户
User user = new User();
user.setName("小黑");
user.setAge(18);
user = aopService.save(user);
//删除用户
if (user != null) {
int id = user.getId();
aopService.del(id);
}
}
}
7、执行结果
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.2.RELEASE)
2022-11-29 15:27:08.794 INFO 14416 --- [ main] MyLogServiceTest : Starting MyLogServiceTest on LAPTOP-9UNHRCRU with PID 14416 (started by lm720 in F:\JavaProject\springcloud_study20220927\web_module_study)
2022-11-29 15:27:08.796 INFO 14416 --- [ main] MyLogServiceTest : No active profile set, falling back to default profiles: default
2022-11-29 15:27:10.681 INFO 14416 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2022-11-29 15:27:10.809 INFO 14416 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2022-11-29 15:27:11.048 INFO 14416 --- [ main] MyLogServiceTest : Started MyLogServiceTest in 2.695 seconds (JVM running for 3.756)
------------->入参为arg=User(id=0, name=小黑, age=18)
User(id=0, name=小黑, age=18)
-------------->出参result=User(id=1000001, name=小黑, age=18)
------------->入参为arg=1000001
删除了用户:1000001
-------------->出参result=java.lang.Object@737d100a
2022-11-29 15:27:11.255 INFO 14416 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 0