springboot-aop应用

AOP工作重点:

1.如何通过切点(Pointcut)和增强(Advice)定位到连接点(Jointpoint)上;
2.如何在增强(Advice)中编写切面的代码。

aop

面向切面编程

几个重要概念搞清楚就行

  • 执行点(Executepoint)-类初始化,方法调用。
  • 连接点(Joinpoint)-执行点+方位的组合,可确定Joinpoint,比如类开始初始化前,类初始化后,方法调用前,方法调用后。
  • 切点(Pointcut)-在众多执行点中,定位感兴趣的执行点。Executepoint相当于数据库表中的记录,而Pointcut相当于查询条件。
  • 增强(Advice)-织入到目标类连接点上的一段程序代码。除了一段程序代码外,还拥有执行点的方位信息。
  • 目标对象(Target)-增强逻辑的织入目标类
  • 引介(Introduction)-一种特殊的增强(advice),它为类添加一些额外的属性和方法,动态为业务类添加其他接口的实现逻辑,让业务类成为这个接口的实现类。
  • 代理(Proxy)-一个类被AOP织入后,产生一个结果类,它便是融合了原类和增强逻辑的代理类。
  • 切面(Aspect)-切面由切点(Pointcut)和增强(Advice/Introduction)组成,既包括横切逻辑定义,也包括连接点定义。

引入依赖pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

配置文件application.yml

server:
  port: 8092

spring:
  profiles:
    active: dev

---
spring:
  profiles: dev

logging:
  level:
    root: INFO
    com.example: DEBUG
  path: D:/logs/springboot-aop

控制器

package com.example.aop.controller;

import com.example.aop.anno.UserAccess;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @RequestMapping("/first")
    public Object first() {
        return "first controller";
    }

    @RequestMapping("/doError")
    public Object error() {
        return 1 / 0;
    }

    @RequestMapping("/second")
    @UserAccess(desc = "second")
    public Object second() {
        int i = 1 / 0;
        return "second controller";
    }

}

定义切面

package com.example.aop.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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;
import java.util.Arrays;

/**
 * 日志切面
 */
@Aspect
@Component
public class LogAspect {

    //切点
    @Pointcut("execution(public * com.example.aop.controller.*.*(..))")
    public void webLog() {
    }

    //连接点
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("2、方法before.....");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("IP : " + request.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    }

    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("1、最先执行,方法环绕start.....");
        try {
            Object o = pjp.proceed();
            System.out.println("3、方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            throw e;
        }
    }

    //后置异常通知
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp) {
        System.out.println("5、方法异常时执行.....");
    }

    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp) {
        System.out.println("4、方法最后执行.....");
    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {
        // 处理完请求,返回内容
        System.out.println("5、方法的返回值 : " + ret);
    }

}

请求和输出

请求地址:http://127.0.0.1:8092/first
打印信息:

1、最先执行,方法环绕start.....
2、方法before.....
URL : http://127.0.0.1:8092/first
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.example.aop.controller.UserController.first
ARGS : []
3、方法环绕proceed,结果是 :first controller
4、方法最后执行.....
5、方法的返回值 : first controller

请求地址:http://127.0.0.1:8092/doError
打印信息:

1、最先执行,方法环绕start.....
2、方法before.....
URL : http://127.0.0.1:8092/doError
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.example.aop.controller.UserController.error
ARGS : []
4、方法最后执行.....
5、方法异常时执行.....
2022-06-13 23:29:20.749 ERROR 8100 --- [nio-8092-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

java.lang.ArithmeticException: / by zero

自定义注解

package com.example.aop.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
    String desc() default "无信息";
}

注解切面

package com.example.aop.aspect;

import com.example.aop.anno.UserAccess;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 切面
 */
@Component
@Aspect
public class UserAccessAspect {
    //切点
    @Pointcut(value = "@annotation(com.example.aop.anno.UserAccess)")
    public void access() {
    }

    @Before("access()")
    public void deBefore(JoinPoint joinPoint) {
        System.out.println("second before");
    }

    @Around("@annotation(userAccess)")
    public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
        //获取注解里的值
        System.out.println("second around:" + userAccess.desc());
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

请求地址:http://127.0.0.1:8092/second
打印信息:

1、最先执行,方法环绕start.....
2、方法before.....
URL : http://127.0.0.1:8092/second
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.example.aop.controller.UserController.second
ARGS : []
second around:second
second before
3、方法环绕proceed,结果是 :null
4、方法最后执行.....
5、方法的返回值 : null
java.lang.ArithmeticException: / by zero

aop执行顺序

aop执行流程

AOP切面的优先级Order属性

如果有两个切面,那么谁先谁后怎么判断?
那如果我们要指定切面的执行顺序呢?

可以使用@Order注解指定切面的优先级,值越小优先级越高。

如果不指定事务切面和缓存切面的 Order,它们的 Order 都将是默认值 —— Integer.MAX_VALUE,即最小优先级。如果两个切面 Order 相同,那么是按照切面的字母顺序来执行的切面。所以如果一个方法上同时存在 @Transactional(对应切面为 TransactionInterceptor)和 @Cacheable (对应切面为 CacheInterceptor),且如果没有指定事务切面和缓存切面的 Order,因为 CacheInterceptor 的字母顺序在 TransactionInterceptor 之前,所以先执行 @Cacheable 对应的切面,再执行 @Transactional 对应的切面。

那么如何指定缓存切面或者事务切面的 Order ?
使用注解:
@EnableCaching(order = 1)
@EnableTransactionManagement(order = 2)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值