目录
面向切面编程AOP
AOP的目标:让我们可以“专心做事”
专心做事
- 我们作为开发系统可以分为两大部分:业务功能和辅助业务的隐性功能。
- 例如开发一个商城系统,商品模块的商品添加业务就是业务功能;
- 而保证商品添加不出问题,如程序在执行商品添加时的入参/出参记录、事务处理等就属于辅助业务的隐性功能
- 通常这些隐性的辅助功能一来都比较通用,二来跟业务平没有什么联系。
- 因此就需要考虑将这些通用功能集中处理,来简化编码、专心做事。
专心做事解决方案1.0
解决方案:把公共代码抽取到一个父类的BaseService,让具体的模块Service继承BaseService,然后调用父类的功能,这样做即:通过继承的方式实现对业务方法的功能的增强。如:
通用功能类
public class BaseService {
// 新增事务
public void transaction(){
System.out.println("事务功能");
}
// 入参日志
public void before(){
System.out.println("记录入参日志功能");
}
// 出参日志
public void afterReturnLog(){
System.out.println("记录出参日志功能");
}
// 资源最终关闭
public void afterLog(){
System.out.println("资源close功能...");
}
}
业务类
public class UserServiceImpl extends BaseService{
public void save(User user){
super.before();
super.transaction();
// 调用Dao新增用户
super.afterReturnLog();
super.after();
}
}
public class GoodsServiceImpl extends BaseService{
public void save(Goods goods){
super.before();
super.transaction();
// 调用Dao新增商品
super.afterReturnLog();
super.after();
}
}
但是这样做还有一个问题,虽然我们其他模块也需要此功能时,可以采用继承的方式来做,但是一个很严重的问题是:我们的业务功能在去调用的时候,对业务功能的增强实际上还是硬编码了。还是没有解决方便维护的问题,那我们期望能够解决的问题是:能否“神不知鬼不觉”的在不改变源代码的情况下去扩展功能呢? 答案肯定是可以的,那这就是AOP,同时,这个也是我们学习AOP的原因所在,也是AOP的作用所在。
专心做事解决方案2.0
蓝图
如果从系统的横向角度来看,我们的日志功能,事务功能、资源关闭功能是把系统的各个模块中的各个业务方法在执行之前从前面拦截了,好像拿了一把刀把一个完整的苹果切成一半,形成了一个切面。这个也就是 中切面的含义
AOP应用场景
日志记录、性能统计、安全控制、事务处理、异常处理
AOP原理
- 将复杂的需求分解出不同方面,将散布在系统中的公共功能集中解决
- 采用代理机制组装起来运行,在不改变原程序的基础上对代码段进行增强处理,增加新的功能
- 核心:动态代理
AOP相关术语
- 增强处理/通知(Advice)
所谓通知/增强是指拦截到 Joinpoint 之后所要做的事情就是通知。说白了,就是一段功能性的代码。- 前置增强
- 后置增强
- 环绕增强、异常抛出增强、最终增强等类型
- 切入点(Pointcut):
切入点就是对连接点中哪些点进行拦截的定义,对连接点(一般所有方法都可做连接点)进行条件筛选。 - 连接点(Join Point):
连接点就是可以被拦截的点,在程序中,通常指的是方法,在Spring中只支持方法类型的连接点。在其他的地方可能支持类,这里记住方法就行了。 - 切面(Aspect):
是切入点和通知/增强的结合。 - 目标对象(Target object)
- AOP代理(AOP proxy)
- 织入(Weaving):
指的是在把增强的方法加入到目标对象(切点方法的拥有者)来创建新的对象的过程,spring采用的是动态代理织入(jdk动态代理和子类动态代理都有),AspectJ采用编译期织入和类装载织入。
术语理解
用LOL中远古龙BUFF来理解吧:比如你现在在蓝色方,蓝色方刚刚跟对面打了一波5V5团战,1换5,蓝色方辅助挂了,这个时候已经30分钟后了,你们开始去打远古龙,打掉远古龙的时候辅助还没有复活,因此只有你们四个人获得了远古龙BUFF。
- Joint point(连接点): 在上面的故事里,你们四个有了远古龙BUFF,就是都能被增强,可以看到远古龙BUFF是可以作用在上面所有人的身上,因为如果辅助不挂他也会有BUFF。在Spring中,这些人就等于可以被增强的方法。
- Pointcut(切点): 在上面的故事里,只有你们四个有远古龙BUFF,但辅助没有。可以看出来Pointcut(切点)是有条件的Joint point(连接点),活着的人被增强了。
- Advice(通知/增强): 你们四个在打掉远古龙的时候活着,那么你们四个就被增强了。
- Aspect(切面): 切点和通知的结合,上面的切点就是活着的你们四个(四个点),通知是打掉远古龙后获得的BUFF,切点有很多,连载一起就像一个面一样。
AOP案例实现
前置/后置/异常/最终增强的配置实现
1.依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<!-- aop依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
2.业务类
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public Object saveUser(String userName) {
//log.info("调用org.aop.service.impl.UserServiceImpl的saveUser(),入参是{}",user);
boolean result = userMapper.insertUser(userName) > 0;
//log.info("调用org.aop.service.impl.UserServiceImpl的saveUser()完毕,返回值是{}",result);
return result;
}
@Override
public boolean updateUser(User user) {
//log.info("调用org.aop.service.impl.UserServiceImpl的updateUser(),入参是{}",user);
boolean result = userMapper.updateUser(user) > 0;
//log.info("调用org.aop.service.impl.UserServiceImpl的updateUser()完毕,返回值是{}",result);
return result;
}
}
3.日志类
package org.aop.log;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author: zjl
* @datetime: 2024/3/30
* @desc:
*/
@Slf4j
public class MyServiceLogger {
//前置增强
public void before(JoinPoint jp) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
}
//后置增强
public void afterReturning(JoinPoint jp, Object result) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法返回值:" + result);
}
//异常处理增强
public void afterThrowing(JoinPoint jp, Throwable e) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法抛出异常:" + e);
}
//最终增强
public void after(JoinPoint jp) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法执行完毕。模拟关闭资源...");
}
4.配置
<?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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!-- 配置相关的AOP -->
<bean id="myServiceLog" class="org.aop.log.MyServiceLogger"/>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* org.aop.service..*.*(..)))"/>
<aop:aspect ref="myServiceLog">
<aop:before method="before"
pointcut-ref="pointcut"></aop:before>
<aop:after-returning method="afterReturning"
pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
切入点表达式匹配规则举例
public * addNewUser(entity.User): “*”表示匹配所有类型的返回值。
public void *(entity.User): “*”表示匹配所有方法名。
public void addNewUser(..): “..”表示匹配所有参数个数和类型。
* com.zjl.service.*.*(..):匹配com.zjl.service包下所有类的所有方法。
* com.zjl.service..*.*(..):匹配com.zjl.service包及其子包下所有类的所有方法
环绕增强的配置实现(1替4)
1.service类不变
2.日志类
注释掉前面四种增强的方法,加入这个环绕增强的方法
public Object around(ProceedingJoinPoint jp) throws Throwable {
Object result = null;
try {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
result = jp.proceed();
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法返回值是:" + result);
}catch (Exception e){
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。发生了异常,异常信息为:" + e);
}finally {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法,进行最终处理,比如模拟资源关闭");
}
return result;
}
3.配置
<!-- 配置相关的AOP -->
<bean id="myServiceLog" class="org.aop.log.MyServiceLogger"/>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* org.aop.service..*.*(..)))"/>
<aop:aspect ref="myServiceLog">
<!--<aop:before method="before"
pointcut-ref="pointcut"></aop:before>
<aop:after-returning method="afterReturning"
pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<aop:after method="after" pointcut-ref="pointcut"/>-->
<aop:around method="around" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
Spring AOP配置元素
注解实现AOP
1.service类不变
2.日志类
package org.aop.log;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author: zjl
* @datetime: 2024/3/30
* @desc:
*/
@Slf4j
@Aspect
@Component
public class MyServiceLogger {
//1.针对service实现类中所有方法,记录某个方法在被调用时的入参信息
//@Before("execution(* org.aop.service..*.*(..))")
public void before(JoinPoint jp) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
}
//2.针对service实现类中所有方法,记录某个方法被调用后的返回值信息
//@AfterReturning(returning = "result", pointcut = "execution(* org.aop.service..*.*(..))")
public void afterReturning(JoinPoint jp, Object result) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法返回值:" + result);
}
//@AfterThrowing(throwing = "e", pointcut = "execution(* org.aop.service..*.*(..))")
public void afterThrowing(JoinPoint jp, Throwable e) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法抛出异常:" + e);
}
// @After("execution(* org.aop.service..*.*(..))")
public void after(JoinPoint jp) {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法执行完毕。模拟关闭资源...");
}
@Around("execution(* org.aop.service..*.*(..))")
public Object around(ProceedingJoinPoint jp) throws Throwable {
Object result = null;
try {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
result = jp.proceed();
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。方法返回值是:" + result);
}catch (Exception e){
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法。发生了异常,异常信息为:" + e);
}finally {
log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
getName() + " 方法,进行最终处理,比如模拟资源关闭");
}
return result;
}
}
3.配置
<?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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<context:component-scan base-package="org.aop"/>
<aop:aspectj-autoproxy />
</beans>