虽然加入程序员这个大家庭已有段时间了,但是对于很多原理性的东西一直理解不够深,自己也在慢慢的努力中,今天在温习spring面向切面编程的时候,看到两篇对于aop解释的比较通俗易懂的博客,记录下:感谢两位博主的分享


AOP:即Aspect orientied program,就是面向切面的编程,

  • 切入点(Pointcut)
    在哪些类,哪些方法上切入(where
  • 通知(Advice)
    在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能)
  • 切面(Aspect)
    切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
  • 织入(Weaving)
    把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)

aop切面的面指的是:

  切:切入系统的一个切面。

面:贯穿到系统的各个模块中的系统一个功能就是一个方面。比如,记录日志,统一异常处理,事务处理,权限检查,这些都是软件系统的一个面,而不是一点,在各个模块中都存在。

什么是面向方面编程:把系统的一个方面的功能封装成对象的形式来处理,是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。采用代理技术,代理会调用目标,同时把切面功能的代码(对象)加入进来,

 

下面先说AOP是什么样的思想,我们一步一步慢慢来,先看一下传统程序的流程,比如银行系统会有一个取款,查询流程

spring 切面到所有controller spring切面使用_连接点

有没有发现,这个两者有一个相同的验证流程,我们先把它们圈起来再说下一步:

spring 切面到所有controller spring切面使用_AOP_02

有没有想过可以把这个验证用户的代码是提取出来,不放到主流程里去呢,这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另我一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程。,举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。

现在大概明白了AOP了吗,我们来理一下头绪,上面那个方框像不像个平面,你可以把它当块板子,这块板子插入一些控制流程,这块板子就可以当成是AOP中的一个切面。所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面,这句话应该好理解吧,我们把纵向流程画成一条直线,然把相同的部分以绿色突出,如下图左,而AOP相当于把相同的地方连一条横线,如下图右,这个图没画好,大家明白意思就行。

 

spring 切面到所有controller spring切面使用_aop_03

spring 切面到所有controller spring切面使用_spring_04

这个验证用户这个子流程就成了一个条线,也可以理解成一个切面,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 环绕通知,围绕着方法执行

执行顺序:

spring 切面到所有controller spring切面使用_spring_05

第二步:创建切面类

示例: 这里放出我项目的结构,以便大家根据自己的实际情况填写自己的切入点

spring 切面到所有controller spring切面使用_spring_06

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";
    }

}

执行结果:(异常通知只在有异常的时候才会打印)

spring 切面到所有controller spring切面使用_aop_07