前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot Aspect 切面编程

SpringBoot Aspect 切面编程

原创
作者头像
高久峰
发布2023-09-27 17:50:01
4000
发布2023-09-27 17:50:01
举报

Spring Boot中的Aspect是用于实现面向切面编程(Aspect-Oriented Programming,AOP)的一种机制。AOP是一种编程范式,通过将横切关注点(如日志记录、性能统计、事务管理等)从业务逻辑中分离出来,以模块化的方式进行处理。

在Spring Boot中,Aspect使用注解方式实现。它通过定义切点(Pointcut)来选择横切关注点所在的连接点(Join Point),并在特定的连接点上织入(Weave)切面逻辑。切面逻辑可以在连接点之前(Before)、之后(After)、异常抛出时(AfterThrowing)或返回结果后(AfterReturning)执行。 使用Spring Boot的Aspect可以在不修改原始代码的情况下,对系统进行功能增强,例如添加日志、进行性能监控、实现事务管理等。通过将这些横切关注点从各个业务模块中抽离出来,可以提高代码的可维护性和可重用性

我们使用切面编程实现无侵入记录接口日志信息。

首先定义一个切面类:

Java

代码语言:javascript
复制
package com.learn.aspect;

@Aspect
@Component()
public class LogAnnotationAspect {

    /**
     * 日志类
     */
    private static final Logger logger = LoggerFactory.getLogger(LogAnnotationAspect.class);

    /**
     * 定义切点:(只要带有@SaveLog注解的方法都需要记录日志)
     */
    @Pointcut("@annotation(com.learn.annotation.SaveLog)")
    public void pointCut() {

    }

    /**
     * 定义环绕通知:(在目标方法的前后都植入额外的逻辑)
     *
     * @param joinPoint
     * @return Object
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 返回信息
        Object response = null;

        // 获取当前连接点处的方法签名。方法签名包括方法的访问修饰符、返回类型、方法名称以及方法参数类型等信息。
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        // 获取注解配置信息
        SaveLog saveLog = methodSignature.getMethod().getDeclaredAnnotation(SaveLog.class);

        // 情况一:未设置日志注解,直接调用目标方法并返回
        if (saveLog == null) {
            response = joinPoint.proceed();
            return response;
        }

        // 获取接口名称
        String apiName = saveLog.name();

        // 是否打印日志
        boolean isPrintLog = saveLog.isPrintLog();

        // 获取参数值
        Object[] argValues = joinPoint.getArgs();

        // 获取参数名
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Map<Object, Object> httpReqArgs = new HashMap<>();
        if (argValues != null) {
            for (int i = 0; i < argValues.length; i++) {
                httpReqArgs.put(argNames[i], argValues[i]);
            }
        }

        // 执行方法+输出日志
        try {
            // 开启打印日志
            if (isPrintLog) {
                RequestAttributes ra = RequestContextHolder.getRequestAttributes();
                ServletRequestAttributes sra = (ServletRequestAttributes) ra;
                HttpServletRequest httpServletRequest = null;
                if (sra != null) {
                    httpServletRequest = sra.getRequest();
                }
                if (httpServletRequest != null) {
                    logger.info("IP地址: {}", httpServletRequest.getRemoteAddr());
                    logger.info("请求地址: {}", httpServletRequest.getRequestURL().toString());
                    //logger.info("请求参数:{}", JSON.toJSONString(httpReqArgs, true));
                    logger.info("接口名称: {}", apiName);
                    String className = joinPoint.getTarget().getClass().getSimpleName();
                    logger.info("接口类名:{}", className);
                    String methodName = joinPoint.getSignature().getName();
                    logger.info("接口方法:{}", methodName);
                }
            }

            // 执行目标方法
            response = joinPoint.proceed();
        } catch (Exception e) {
            // 输出异常
            logger.info("接口异常:{}", e.getMessage());

            // 异常继续抛出
            throw e;
        } finally {
            // 执行完成记录数据 todo()
            logger.info("接口执行完成,假装我自己记录完成了");
        }

        // 返回执行目标方法的结果
        return response;
    }
    
}

首先定义了一个切点pointCut,通过注解@Pointcut标记该方法作为切点,其所匹配的连接点是所有带有@SaveLog注解的方法。 接下来定义了一个环绕通知around,用于在目标方法的前后都插入额外的逻辑。在around方法中,首先获取了当前连接点处的方法签名(Method Signature),并通过访问该方法的注解信息SaveLog获取了接口名称、是否打印日志等配置参数。然后,获取请求参数的值和参数名,并将其封装成一个Map对象httpReqArgs。接着,在执行目标方法前,如果需要打印日志,会获取请求的URL、IP地址、接口名称、接口类名和接口方法名等信息,并输出到日志里。然后,执行目标方法,并获取返回值。如果执行过程中发生了异常,则捕获异常并输出异常信息,最后假装记录了接口执行完成的数据。最后,返回执行目标方法的结果。

我把我定义的注解代码展示一下:

Java

代码语言:javascript
复制
// 设置注解的使用范围(类和方法)
@Target({ElementType.METHOD, ElementType.TYPE})
// 设置注解的生命周期(运行时)
@Retention(RetentionPolicy.RUNTIME)
public @interface SaveLog {

    /**
     * 接口名称(必填)
     */
    String name();

    /**
     * 是否打印日志
     */
    boolean isPrintLog() default true;

    /**
     * 是否保存传入参数(默认true)
     */
    boolean isSaveParam() default true;
}

然后我们定义下控制器的接口信息:

Java

代码语言:javascript
复制
@RestController
public class ApiController {

    /**
     * 首页
     */
    @SaveLog(name = "首页", isPrintLog = true)
    @GetMapping("/")
    public String home() {
        return "Welcome to our home, sit down wherever you want";
    }

    /**
     * 关于
     */
    @SaveLog(name = "关于", isPrintLog = true)
    @GetMapping("/about")
    public String about() {
        return "Can you come and hear our story?";
    }
}

访问 http://127.0.0.1:8080/   和  http://127.0.0.1:8080/about 输出信息如下:

Java

代码语言:javascript
复制
2023-09-27 17:38:07.626  INFO 23864 --- [0.1-8080-exec-4] com.learn.aspect.LogAnnotationAspect     : IP地址: 127.0.0.1
2023-09-27 17:38:07.626  INFO 23864 --- [0.1-8080-exec-4] com.learn.aspect.LogAnnotationAspect     : 请求地址: http://127.0.0.1:8080/
2023-09-27 17:38:07.626  INFO 23864 --- [0.1-8080-exec-4] com.learn.aspect.LogAnnotationAspect     : 接口名称: 首页
2023-09-27 17:38:07.626  INFO 23864 --- [0.1-8080-exec-4] com.learn.aspect.LogAnnotationAspect     : 接口类名:ApiController
2023-09-27 17:38:07.627  INFO 23864 --- [0.1-8080-exec-4] com.learn.aspect.LogAnnotationAspect     : 接口方法:home
2023-09-27 17:38:07.627  INFO 23864 --- [0.1-8080-exec-4] com.learn.aspect.LogAnnotationAspect     : 接口执行完成,假装我自己记录完成了
2023-09-27 17:39:34.926  INFO 23864 --- [0.1-8080-exec-7] com.learn.aspect.LogAnnotationAspect     : IP地址: 127.0.0.1
2023-09-27 17:39:34.927  INFO 23864 --- [0.1-8080-exec-7] com.learn.aspect.LogAnnotationAspect     : 请求地址: http://127.0.0.1:8080/about
2023-09-27 17:39:34.927  INFO 23864 --- [0.1-8080-exec-7] com.learn.aspect.LogAnnotationAspect     : 接口名称: 关于
2023-09-27 17:39:34.927  INFO 23864 --- [0.1-8080-exec-7] com.learn.aspect.LogAnnotationAspect     : 接口类名:ApiController
2023-09-27 17:39:34.927  INFO 23864 --- [0.1-8080-exec-7] com.learn.aspect.LogAnnotationAspect     : 接口方法:about
2023-09-27 17:39:34.927  INFO 23864 --- [0.1-8080-exec-7] com.learn.aspect.LogAnnotationAspect     : 接口执行完成,假装我自己记录完成了

切面编程的魅力到此体验结束,完全无侵入,太棒了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
应用性能监控
应用性能监控(Application Performance Management,APM)是一款应用性能管理平台,基于实时多语言应用探针全量采集技术,为您提供分布式性能分析和故障自检能力。APM 协助您在复杂的业务系统里快速定位性能问题,降低 MTTR(平均故障恢复时间),实时了解并追踪应用性能,提升用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档