(2021.9.25)面向切面编程简述

说到面向切面编程,想必大家都不会陌生。不就是AOP——Aspect Orient Programming

但是说了那么久,你所理解的面向切面编程,用一句话概括到底是什么呢?

目录

1.什么是AOP(面向切面编程)

2.AOP相关术语

1.连接点(Joinpoint)

2.切点(Pointcut)

3.增强(Advice)

4.目标对象(Target)

5.引介(Introduction):

6.织入(Weaving):

7.代理(Proxy)

9.切面(Aspect)

3.实践

3.1 简单的自定义注解增强

3.2 通过指定类/方法进行增强


1.什么是AOP(面向切面编程)

在我的理解来说:

在运行时,动态的 将代码 切入到 类的 指定方法。  这种编程思想就称为面向切面编程

 

来举个例子吧,你写了A方法之后,老板想让你看看这段代码的执行时间。ok,那你就在A方法的前后加上代码,查看时间。。。

过了几天,你又完成了B方法之后,老板又想让看你记录这段代码的日志信息。没问题,加上

又过了几天,老板又要让你每个功能都加上日志功能。。。没过几天你就。。。

久而久之,你就发现,你的代码中会出现越来越多与业务无关的东西。后来你发现它们都是有共同点的,于是你将这些公共的代码整体抽成common方法进行调用,想办法降低耦合。但是由于是在方法中进行调用,该污染代码还是会污染,如果严格说来还是有很强的关联系的。比如说一旦涉及到部分参数的传递,那么耦合度还是很高的。

在你想解耦合且方便维护的时候,AOP出现了。

spring提供这个工具,让你能解决上述老板给你提出的这些要求,避免你修改次数太多删库跑路~

2.AOP相关术语

1.连接点(Joinpoint)

程序执行的某个特定位置:如类开始初始化前,类初始化后,类某个方法调用前。一个类或一段代码拥有一些边界性质的特定点,这些代码中的特定点就被称为“连接点”。Spring仅支持方法的连接点,既仅能在方法调用前,方法调用后,方法抛出异常时等这些程序执行点进行织入增强。

连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置。Spring使用切点对执行点进行定位,而方位则在增强类型中定义。

2.切点(Pointcut)

每个程序类都拥有多个连结点,如一个拥有两个方法的类,这两个方法都是连接点,既连接点是程序中客观存在的事务。当某个连接点满足指定要求时,就可以被添加Advice,成为切点,比如:

pointcut xxxPorintcut()

:execution(void H*.say*())

每个方法被调用,都只是连接点,如果某个方法属于H开头的类,而且方法一say开头,则该方法就变成了切入点。如何使用表达式定义切入点,是AOP的核心,Spring默认使用AspectJ切入点语法。

3.增强(Advice)

连接上一条,你可以在切点这里做一些事情,有“around”,“before”,“after”等类型。

4.目标对象(Target)

被AOP框架进行增强处理的对象。如果是动态AOP的框架,则对象是一个被代理的对象。

5.引介(Introduction):

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态的为该事务添加接口的实现逻辑,让业务类成为这个接口的实现类。

6.织入(Weaving):

织入是将增强添加对目标类具体连接点上的过程,AOP象一台织布机,将目标类增强或引介AOP这台织布机天衣无缝的编织在一起。

7.代理(Proxy)

一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增前逻辑的代理类。根据不同的代理方式,代理类及可能是和原类具有相同的接口的类,也可能是原类的子类,所以我们可以采用调用原类得相同方式调用代理类。

9.切面(Aspect)

切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的链接点中。

以上内容其实一搜一大堆,个人认为需要认识理解的切点、增强、切面即可不需要去死记硬背这些点。使用的多了,会自然的熟悉这些知识点。实践出真理

简单的介绍一下,方便大家来记忆:

有一个方法a,但是这个方法只支持进行A操作。

增强这我想在这个方法a前/后添加一个B动作,那么这个增加的B动作就叫做增强。

切点,你针对这个a方法做了增强操作,那么这个a方法就称为切点

切面,切点和增点组成切面,它是一个大的概念,一个统称

3.实践

先给出控制层代码

@RestController
@RequestMapping(value = "/a")
public class AController {

    @GetMapping(value = "/test1")
    @PermissionAnnotation
    public String test1(){
       return "a.test1";
    }

    @GetMapping(value = "/test2")
    public String test2(){
        return "a.test2";
    }

}
@RestController
@RequestMapping(value = "/b")
public class BController {

    @GetMapping(value = "/test1")
    public String test1(){
        return "b.test1";
    }

    @GetMapping(value = "/test2")
    public String test2(){
        return "b.test2";
    }

}

3.1 简单的自定义注解增强

引入pom

   
        
            org.springframework.boot
            spring-boot-starter-aop
        

创建一个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation {


}

创建对应的切面

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TestAspect {

    @Pointcut("@annotation(com.example.aspectdemo.annotation.PermissionAnnotation)")
    private void permissionCheck() {

    }

    @Before("permissionCheck()")
    public void doBefore() {
        System.out.println("doBefore");
    }


    @Around("permissionCheck()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("do something。。。before");
        return joinPoint.proceed();
    }

    @After("permissionCheck()")
    public void doAfter() {
        System.out.println("doAfter");
    }
}

调用方法: 直接调用方法,http://localhost:8888/a/test1

打印结果:

(2021.9.25)面向切面编程简述_第1张图片

 让我们加断点进去看看

(2021.9.25)面向切面编程简述_第2张图片

(2021.9.25)面向切面编程简述_第3张图片

(2021.9.25)面向切面编程简述_第4张图片

(2021.9.25)面向切面编程简述_第5张图片

 从上面的几张图,我们可以看到一个较为详细的执行流程,为了加深印象大家可以自己创建之后跟进去试试看。这样会有更深的印象。

下面用一段代码来总结一下执行的流程。但是注意不管是前置通知还是后置通知,一定是要进入proceed()这个方法之后才会执行的,下面这样写只是为了方便去理解执行的流程

 try {
        //前置通知@Before
        System.out.println("环绕前置通知");
        //目标方法执行
        result = proceedingJoinPoint.proceed(args);
        //环绕返回通知@AfterReturning
        System.out.println("环绕返回通知");
    } catch (Throwable throwable) {
        //环绕异常通知@AfterThrowing
        System.out.println("环绕异常通知");
        throw new RuntimeException(throwable);
    } finally {
        //最终通知@After
        System.out.println("环绕最终通知");
    }

其实也可以有多个@Before、@After联合使用。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TestAspect {

    @Before("permissionCheck()")
    public void doBeforeY() {
        System.out.println("doBeforeY");
    }

    @Before("permissionCheck()")
    public void doBeforeX() {
        System.out.println("doBeforeX");
    }

    @Before("permissionCheck()")
    public void doBeforey() {
        System.out.println("doBeforey");
    }

    @Before("permissionCheck()")
    public void doBeforex() {
        System.out.println("doBeforex");
    }

    @Before("permissionCheck()")
    public void doBefore2() {
        System.out.println("doBefore2");
    }

    @Pointcut("@annotation(com.example.aspectdemo.annotation.PermissionAnnotation)")
    private void permissionCheck() {

    }

    @Before("permissionCheck()")
    public void doBefore1() {
        System.out.println("doBefore1");
    }


    @Around("permissionCheck()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("do something。。。before");
        return joinPoint.proceed();
    }

    @After("permissionCheck()")
    public void doAfter() {
        System.out.println("doAfter");
    }
}

这段代码,我们就加入了很多的前置处理和后置处理。那它的运行结果是啥呢?大家可以猜猜看,是按照代码的顺序来执行呢?还是怎么来执行呢?

(2021.9.25)面向切面编程简述_第6张图片

 可以明显的看一些端倪,当前执行的优先级:数字>大写字母>小写字母

其实执行顺序还有很多有趣的地方,这里就不再做过多的描述了。有兴趣的可以去看看aop从加载到执行的spring源码。

3.2 通过指定类/方法进行增强

不了解表达式的同学可以参考下这位大佬写的博客:spring AOP切面表达式详解_人有匪气,代码才能飞起-CSDN博客_切面表达式

下面列出一些常见的,方便大家进行对比实践

execution(public * *(..))  // 匹配任意公共方法
execution(* set*(..))      // 匹配任意名称为 set 开头的方法
execution(* com.xyz.service.AccountService.*(..))    // 匹配 AccountService 类中的任意方法
execution(* com.xyz.service.*.*(..))    // 匹配 com.xyz.service 包下的任意类的任意方法
execution(* com.xyz.service..*.*(..))    // 匹配 com.xyz.service 包及子包下的任意类的任意方法
​
within(com.xyz.service.*)    // 匹配 com.xyz.service 包下的任意类的任意方法
within(com.xyz.service..*)   // 匹配 com.xyz.service 包及子包下的任意类的任意方法

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TestAspect {

    @Pointcut("execution(* com.example.aspectdemo.controller.BController.*(..))")
    private void pointCut() {

    }

 
    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("do something。。。before");
        return joinPoint.proceed();
    }

    @Before("pointCut()")
    public void doBefore1() {
        System.out.println("doBefore1");
    }

    @After("pointCut()")
    public void doAfter() {
        System.out.println("doAfter");
    }
}

直接调用方法,http://localhost:8888/b/test1

得到结果:(2021.9.25)面向切面编程简述_第7张图片

你可能感兴趣的:(Java,spring,java)