虽然加入程序员这个大家庭已有段时间了,但是对于很多原理性的东西一直理解不够深,自己也在慢慢的努力中,今天在温习spring面向切面编程的时候,看到两篇对于aop解释的比较通俗易懂的博客,记录下:感谢两位博主的分享
1、https://blog.csdn.net/qq_32317661/article/details/82878679
2、https://blog.csdn.net/yanquan345/article/details/19760027
AOP:即Aspect orientied program,就是面向切面的编程,
- 切入点(Pointcut)
在哪些类,哪些方法上切入(where) - 通知(Advice)
在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能) - 切面(Aspect)
切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强! - 织入(Weaving)
把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)
aop切面的面指的是:
切:切入系统的一个切面。
面:贯穿到系统的各个模块中的系统一个功能就是一个方面。比如,记录日志,统一异常处理,事务处理,权限检查,这些都是软件系统的一个面,而不是一点,在各个模块中都存在。
什么是面向方面编程:把系统的一个方面的功能封装成对象的形式来处理,是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。采用代理技术,代理会调用目标,同时把切面功能的代码(对象)加入进来,
下面先说AOP是什么样的思想,我们一步一步慢慢来,先看一下传统程序的流程,比如银行系统会有一个取款,查询流程
有没有发现,这个两者有一个相同的验证流程,我们先把它们圈起来再说下一步:
有没有想过可以把这个验证用户的代码是提取出来,不放到主流程里去呢,这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另我一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程。,举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。
现在大概明白了AOP了吗,我们来理一下头绪,上面那个方框像不像个平面,你可以把它当块板子,这块板子插入一些控制流程,这块板子就可以当成是AOP中的一个切面。所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面,这句话应该好理解吧,我们把纵向流程画成一条直线,然把相同的部分以绿色突出,如下图左,而AOP相当于把相同的地方连一条横线,如下图右,这个图没画好,大家明白意思就行。
这个验证用户这个子流程就成了一个条线,也可以理解成一个切面,aspect的意思我认为是方面,你用什么实物去类比,只要你能理解都可以。这里的切面只插了两三个流程,如果其它流程也需要这个子流程,也可以插到其它地方去。
讲了这么多,那到AOP该如何使用呢? 下面我分享一个自己写的小例子:需求是,调用每个方法时都打印出方法名、参数等
第一步:加入依赖
<!-- AOP依赖模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--导入 @Aspect所依赖的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
相关注解
@Before 前置通知,在方法执行之前执行
@After 后置通知,在方法执行之后执行
@AfterRuturning 返回通知,在方法返回结果之后执行,也就是return之后
@Around 环绕通知,围绕着方法执行
执行顺序:
第二步:创建切面类
示例: 这里放出我项目的结构,以便大家根据自己的实际情况填写自己的切入点
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@Aspect
@Component
public class ControllerAspect {
// 在controller包或者子包里的任意连接点
private final String POINT_CUT = "execution(* com.fristapp.*.controller.*.*(..)) ";
// 在controller包或者子包里以Controller结尾连接点
//private final String POINT_CUT ="execution(*com.fristapp.*.controller.*controller.*(..)) ";
// 将切入点设置在service层
// private final String POINT_CUT = "execution(* com.fristapp.*.service.*.*(..)) ";
@Pointcut(POINT_CUT)
public void pointCut(){}
@Before("pointCut()")
// 请求method前打印内容
public void doBeforeAdvice(JoinPoint joinPoint){
//ServletRequestAttributes可以获取request信息 session信息等
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//获取连接点参数
Object[] args = joinPoint.getArgs();
// 打印请求内容
log.info("===============Before前置通知请求内容开始===============");
log.info("请求地址:" + request.getRequestURL().toString());
log.info("ajax请求方式:" + request.getMethod());
log.info("请求接口方法:" + joinPoint.getSignature());
log.info("接收的参数:"+ JSON.toJSONString(joinPoint.getArgs()));
log.info("===============Before前置通知请求内容结束===============");
}
@After("pointCut()")
public void after(JoinPoint point) {
log.info("===============After后置通知开始===============");
log.info("===============After后置通知结束===============");
}
@AfterReturning(returning="object",pointcut="pointCut()")
public void afgerRturning(JoinPoint jp,Object object) {
log.info("===============AfterReturning返回开始===============");
log.info("response返回的数据:{}",object.toString());
log.info("===============AfterReturning返回结束===============");
}
@AfterThrowing(throwing="ex",pointcut="pointCut()")
public void afterThrowing(Throwable ex) {
log.info("===============AfterThrowing异常通知开始===============");
log.info("异常信息:"+ex);
log.info("===============AfterThrowing异常通知结束===============");
}
}
第三步、创建一个测试接口类
import com.fristapp.book.entity.BookEntity;
import com.fristapp.book.service.BookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RequestMapping("book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@PostMapping("get")
public> String getAllBook(@RequestBody String requestString){
// 方法体
return "success";
}
}