什么是Spring AOP(面向切面编程)
AOP(Aspect Orient Programming),也就是 面向切面编程,AOP 是一种编程思想,是OOP(面向对象编程)的一种补充,面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
为什么需要AOP
场景:当你手上有一个毕设项目,一开始开发的时候并没有验证用户权限的功能以及打印详细操作日志的功能,现在被甲方巴巴看上了,需要大部分的方法上加上这两个功能来完善系统,现在我有30多个方法需要这两个功能,难道需要在这些方法上都加上这两个功能吗?答案是不可能的,就算这次能给你肝完,下次呢?甲方巴巴还需要你加一些公共的功能,我相信得分分钟脱离此行业去搬砖了
所以就出现了AOP来代替重复造轮子的工作,也就是说对于多个地方且需要做出相同修改的问题我们就可以使用AOP的思想来解决这个问题
使用AOP的目的
我们使用AOP来编程的目的,本质为了保证开发者在尽可能少的修改源码的情况下,去为系统的多个地方去添加某个通用功能,例如方法执行日志、权限验证等通用功能
对于AOP就是我们23种设计模式之代理模式的一个典型应用,如果对于代理模式不了解的可以先去看看我这篇文章 23种设计模式之代理模式,里面涉及到了静态代理、JDK的动态代理以及CGLIB的动态代理的应用以及源码分析
AOP各术语的含义
术语名称 | 术语含义 |
---|---|
Advice (增强处理) | 表示切面何时执行以及如何进行增强处理,它通过(@Before 、@After 和@Around )来表示该joint point 切点是在方法执行前、执行后、还是在方法执行前后交替执行 |
Joint point (连接点) | 表示应用执行过程中能够插入切面的一个点,一般为方法的调用,少部分为异常抛出 |
PointCut (切点) | 表示可以插入增强处理的Joint point ,表示增强的效果是在哪个方法(或者是哪一组)上调用的,和正则表达式搭配使用可以指定多个类方法 |
Aspect (切面) | 一般称为切面类,内部包含了Advice 、Joint point 、PointCut 等元素 |
Target (目标对象) | 需要增强处理的目标对象 |
Weaving (织入) | 为目标对象创建一个增强对象,该增强对象包括了我们切面所定义的增强方法,也就是我们所说的代理对象 |
Introduction (引入) | 允许我们向目标对象添加新的方法或者属性 |
各术语在代码中的表现
这里我们直接用Spring boot整合AOP实现日志切面的例子给大家扩展各术语的知识,在实践中学习是效率最高的,先定义一个ALiangXLog
注解,关于此注解上面的元注解,不理解的可以看看我这篇文章 Java基础——注解的艺术
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ALiangXLog {
public String value();
}
首先我们看看Advice
的5种增强处理以及正则表达式的应用,再去看看切面类
注解 | 含义 |
---|---|
@Before (前置增强) | 表示在目标方法执行前,执行增强方法 |
@After (后置增强) | 表示在目标方法返回后或异常返回后,执行增强方法 |
@Around (环绕增强) | 将目标方法封装,可以在增强方法内调用目标方法 |
@AfterReturning (返回) | 目标方法的返回后调用该增强方法 |
@AfterThrowing (抛出异常) | 目标方法抛出异常则调用该增强方法 |
还有execution表达式在切点的应用,* execution(com.hecl.zhenghe.Controller..*.*(..)
各参数解释如下表格
参数 | 含义 |
---|---|
execution(...) | 表达式主体内容 |
第一个"* " | 表示返回值为任意类型 |
com.hecl.zhenghe.Controller | 需要切入的包名 |
包名后面的.. | 表示当前包与子包 |
第二个"*" | 表示类名,* 表示任何类,也可自定义 |
.*(..) | 表示方法(arg[0]…),* 表示任何方法,括号内为参数,.. 表示任何参数 |
了解了上面Advice
的增强处理与execution表达式,我们来写一个切面类
然后我们定义切面类
@Aspect
@Component
@Slf4j
public class ALiangXLogAspect {
//配置切入点
//也就是说使用了@ALiangXLog注解的目标方法,增强方法将会织入目标方法中
@Pointcut(value = "@annotation(com.hecl.zhenghe.annotation.ALiangXLog)")
//在Pointcut我们还可以限制对哪个bean进行增强以及传入参数的限制
//@Pointcut("execution()")
public void LogAspect(){
// 此方法无方法体,目的是为了让同类中其他方法使用此切入点
}
//在Pointcut我们还可以限制对哪个bean进行增强以及传入参数的限制
//还可以搭配正则表达式使用,可定位到特定包下的某些方法,示例
//多个匹配之间我们可以使用链接符 &&(且),||(或),!(非)来表示
//下面这个切点的意思就是满足com.hecl.zhenghe.Controller.UserController.getUserInfo(String)
//且参数名为name
//且注入的bean为ALiangX
//该切点才会增强目标方法
@Pointcut("* execution(com.hecl.zhenghe.Controller.UserController.getUserInfo(String) && args(name) && bean(ALiangX))")
public void LogAspect2(String name){
// 此方法无方法体,目的是为了让同类中其他方法使用此切入点
}
@Before("LogAspect()")
public void doBefore(JoinPoint joinPoint){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
log.info("请求方法地址-----------" + request.getRequestURI());
log.info("请求方法参数-----------" + Arrays.toString(joinPoint.getArgs()));
log.info("请求类方法名称-----------" + joinPoint.getSignature());
}
@After("LogAspect()")
public void doAfter(JoinPoint joinPoint){
log.info("该方法已执行完毕");
//...这下面还可以执行其它的行为
}
@AfterReturning(pointcut = "LogAspect()",returning = "object")
public void doAfterReturning(Object object){
log.info("返回参数内容-----------" + object);
}
//在使用时,我们传入了 ProceedingJoinPoint 类型的参数,
//这个对象是必须要有的,并且需要调用 ProceedingJoinPoint 的 proceed() 方法
//如果没有调用joinPoint.proceed()则会阻塞目标方法
@Around("LogAspect()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
log.info("调用方法前先验证权限是否足够");
Object result = joinPoint.proceed();
log.info("方法结束时间-----------" + System.currentTimeMillis());
return result;
}
@AfterThrowing(pointcut = "LogAspect()",throwing = "e")
public void doAfterThrowing(Exception e){
System.out.println("错误行为-----------" + e);
}
}
目标方法,具体的这个方法的内部调用可以看看我这篇文章 Springboot整合数据库 +JpaRepository实现简单数据查询,这里只是展示切面的使用方法
//直接在目标方法上加上该注解即可完成增强
@ALiangXLog("userInfoLog")
@GetMapping(value = "/getInfo")
public User getUserInfo(String name){
return userService.getUserInfo(name);
}
从PostMan调用该接口,或者浏览器都可以直接调用
返回结果
我们来看看打印的顺序
@Around
——>@Before
——>@Around
——>@After
——>@AfterReturning
看着上面输出顺序我们就知道了各个Advice
的执行顺序了
本文简单介绍了Spring AOP的实现思想以及用日志切面的实例来带大家了解怎样使用切面,希望大家能上手练练,毕竟熟能生巧
这篇文章对你有帮助的话就请帮忙点个赞赞赞赞赞吧,谢谢各位看官老爷了
完成:2021/4/05 00:37 ALiangXLogic
创作不易,转载请标注原作者,谢谢你们的支持