Spring AOP,老鸟们是这样用的

211 阅读5分钟

什么是AOP?

AOP:Aspect Oriented Programming,中文翻译为”面向切面编程“。面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码

Join point:连接点,例如:servlet中的longin()就是连接点;所以连接点在spring中它永远是一个方法。也可以说'目标对象中的方法就是一个连接点。Join point:连接点,例如:servlet中的longin()就是连接点;所以连接点在spring中它永远是一个方法。也可以说'目标对象中的方法就是一个连接点

pointcut:切点,就是连接点的集合

Weaving:织入

Advice:通知,就字面意思,但是有2个部分组成,通知内容和通知到哪里去

Aspect:切面!包括连接点,切点,通知的一个载体。(如果用AspectJ它就是一个类,如果用springXML的时候它就是一个标签)并且交给spring管理

Target object:目标对象,原始对象

AOP proxy:代理对象, 包含了原始对象的代码和增强后的代码的那个对象

Introduction:引入,个人理解为实现接口

使用场景

日志记录,权限验证,效率检查,事务管理......

通知类型:

Before :在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常

After :在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容

AfterReturning:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行

AfterThrowing:在连接点抛出异常后执行

Around:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行

实现方式

方法拦截式实现AOP

导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1、用javaconfig启动AOP首先需要在Spring配置类中添加@EnableAspectJAutoProxy来使工程支持处理带有AspectJ的注解的组件
@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfig {
}
2.定义切面,对符合方法规则的类进行拦截处理
@Aspect
@Component
public class TestAspect {
    private final static Logger log = LoggerFactory.getLogger(TestAspect.class);

    @Pointcut("execution(* com.example.demo3.service.*.*(..))")
    public void logPointCut() {
    }

    @Before("logPointCut()")
    public void beforeLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println((method.getName() + "前置增强"));
    }

    @After("logPointCut()")
    public void afterLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "后置增强");
    }

    @AfterThrowing("logPointCut()")
    public void exceptionLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "异常增强");
    }

    @Around("logPointCut()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "环绕增强");
        return joinPoint.proceed();
    }

    @AfterReturning("logPointCut()")
    public void afterRunningLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "执行正常的后置增强");
    }
}

使用注解式配置切面

定义拦截规则的注解

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

    // 拦截规则类型
    String type() default "";
}
定义关于此注解的切面
@Aspect
@Component
public class TestAspect2 {
    private final static Logger log = LoggerFactory.getLogger(TestAspect2.class);

    @Pointcut("@annotation(com.example.demo3.annotation.TestAnnotation")
    public void logPointCut() {
    }

    @Before("logPointCut()")
    public void beforeLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println((method.getName() + "前置增强"));
    }

    @After("logPointCut()")
    public void afterLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "后置增强");
    }

    @AfterThrowing("logPointCut()")
    public void exceptionLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "异常增强");
    }

    @Around("logPointCut()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "环绕增强");
        return joinPoint.proceed();
    }

    @AfterReturning("logPointCut()")
    public void afterRunningLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println(method.getName() + "执行正常的后置增强");
    }
}

image.png

可以看到由于程序没有发生异常,所以异常增强没有执行,我们接着看

image.png

此时程序报错,我们可以看到执行正常的后置增强没有执行,异常增强在异常情况下执行

方法拦截式与注解式的比较

方法规则式拦截

优点

  • 方法规则式侵入性低,在分工开发的环境中,编写业务方法代码的人并不需要知道他编写的业务方法的AOP处理逻辑,只要按照统一的命名规则对方法命名即可,然后由编写AOP的人根据统一的方法命名来选择对哪些方法实施拦截。
  • 适用于业务代码开发时不考虑AOP处理,业务代码开发完成后再开发AOP的,可以实现无缝接入。

缺点

  • 方法规则式灵活性稍差一点,在设计切面时,如果要实现精确的方法拦截处理或者对不同的方法实现不同的处理,execution表达式会写的比较多。
注解式拦截

优点

  • 注解式AOP灵活性高,只要定义好注解,想对哪个业务方法做拦截,只需要加上注解就可以了,A方法用A注解,B方法用B注解,不同的业务逻辑使用不同的拦截处理逻辑,这些工作只需要在切面中声明即可。

缺点

  • 正因为较高的侵入性,需要在业务代码中加上AOP注解,需要在设计阶段就考虑清楚AOP的拦截逻辑。

在开发当中小编比较推荐注解式拦截,用法灵活方便,特别是接手他人代码时。

AOP切点表达式

1、execution 表达式

execution(modifiers-pattern?ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

image.png   语法格式: execution(返回类型.包名.类名.方法名(参数表))

  exection(.com.xxx.AService.(..))

  com.xxx.AService 类型中的任意方法,任意类型返回结果,参数表不限定,都增加切面

  应用:最常用,也是相对最通用。根据方法执行的标准,定义切点,如事务日志

2.@annotation 表达式 语法格式: @annotation(注解的完全限定名)

@annotation(com.example.demo3.annotation.TestAnnotation)

表示匹配使用@annotation指定注解标注的方法将会被环绕

小编列举了两种比较常用的,还有其他的切点表达式,有兴趣的同学可以自行百度了解。

今天的分享就到这里啦,希望能给您带来帮助!!!