原文地址:https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486938&idx=1&sn=c99ef0233f39a5ffc1b98c81e02dfcd4&chksm=cea24211f9d5cb07fa901183ba4d96187820713a72387788408040822ffb2ed575d28e953ce7&token=1736772241&lang=zh_CN#rd
什么是AOP
AOP(Aspect Oriented Programming 面向切面编程),AOP是一种设计思想,它本质是为了解耦。AOP是OOP(Object Oriented Programming 面
向对象编程)的一种延续。为什么这么说呢?
先看一个OOP的例子:
例如:现有三个类,Horse
、Pig
、Dog
,这三个类中都有 eat()
和 run()
两个方法。
通过 OOP 思想中的继承,我们可以提取出一个 Animal
的父类,然后将 eat()
和 run()
方法放入父类中,Horse
、Pig
、Dog
通过继承Animal类即可自动获得 eat() 和 run() 方法。这样将会少些很多重复的代码。
OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了
/**
* 动物父类
*/
public class Animal {
/** 身高 */
private String height;
/** 体重 */
private double weight;
public void eat() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can eat...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
public void run() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can run...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
}
这部分重复的代码,一般统称为 横切逻辑代码。
横切逻辑代码存在的问题:
- 代码重复问题
- 横切逻辑代码和业务代码混杂在一起,代码臃肿,不便维护
AOP 就是用来解决这些问题的
AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离,不再是侵入式的
AOP 解决了什么问题
通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
比如想在多个业务都有个需求,在每个业务的最初阶段添加日志,如果在每个业务代码里都写日志记录的代码,这样代码重复太严重,也就是代码冗余,使用SpringAOP将重复的日志记录的代码提取为日志切面
spring里的aop
基于注解的aop
- 引入依赖
使用spring6会报错类文件具有错误的版本 61.0, 应为 52.0
解决办法:
(1)使用spring5
(2)升级jdk1.8到17
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.27</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.27</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.27</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
- bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.example.aopdemo"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 新建测试接口及其实现类,这里以登录业务为例
public interface UserService {
void login();
}
@Component
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("登录");
}
}
- 新建aop包,创建切面类LogAspect
- @Aspect标记为切面
注意切面表达式的写法,这里的切面表达式表示将此段 横切逻辑代码 应用在com.example.aopdemo.service.impl
包下的UserServiceImpl
类里的修饰符为public
、返回值类型为void
的没有参数
的名为login
的方法
@Component
@Aspect
public class LogAspect {
@Before(value = "execution(public void com.example.aopdemo.service.impl.UserServiceImpl.login())")
public void beforeLogin() {
System.out.println("登陆前");
}
}
以上代码还可以写成如下
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(public void com.example.aopdemo.service.impl.UserServiceImpl.login())")
public void serviceLayerMethods() { }
@Before("serviceLayerMethods()")
public void beforeLogin() {
System.out.println("登陆前");
}
}
下面是一些切面表达式写法示例
1
2
3
4
5
6
- 测试
public class AOPTest {
@Test
public void testAOP() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
UserService service = context.getBean(UserService.class);
service.login();
}
}
每个注解都可以获取到方法的名称,参数等信息,只需要在方法参数加入JointPoint jointPoint
,以上面的例子为例
@Before(value = "execution(public void com.example.aopdemo.service.impl.UserServiceImpl.login())")
public void beforeLogin(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("方法名: " + methodName + " 参数 " + Arrays.toString(args));
System.out.println("登陆前");
}
除了@before
外,还有@After,@AfterReturning,@AfterThrowing,@Around
@AfterReturning可以得到返回值
@AfterReturning(value = "", returning = "result")
public void xxx(Object result) {
System.out.println("返回结果: " + result);
}
@AfterThrowing:目标方法出现异常,此通知执行
@AfterThrowing(value = "", throwing = "exception")
public void xxx(Throwable exception) {
System.out.println("异常信息: " + exception);
}
@Around
基于xml的AOP
与基于注解的aop差不多,就是把原来切面类里的注解@Aspect和@Before
换成了bean.xml
里面的配置(切面类还在,切面类里的方法不能删),原本bean.xml
里的<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
删掉,删不删其实都ok
有注解肯定用注解,正常谁用这个啊😅
<aop:config>
<aop:aspect ref="logAspect"> <!--指定切面类,因为切面类类名为LogAspect,ioc默认取类名首字母小写-->
<aop:pointcut id="pointcut"
expression="execution(public void com.example.aopdemo.service.impl.UserServiceImpl.login())"/>
<aop:before method="beforeLogin" pointcut-ref="pointcut"></aop:before> <!--指定-->
</aop:aspect>
</aop:config>
相关术语说明
- 连接点:我们要在哪个方法周围添加横切逻辑,哪个方法就是连接点,此处的连接点是UserService中的login()方法
- 切入点:横切逻辑代码就是切入点,这里的切入点是LogAspect类中的beforeLogin()方法