Springboot AOP 使用小结

本文详细介绍了如何在SpringBoot项目中使用AOP,包括添加依赖、配置切面、自定义注解和注解及包路径匹配的实现,通过实例展示如何确保AOP正确地应用到业务接口上。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

为了简化应用层开发人员的工作复杂性,我一般会把一些通用的,复杂的,跟具体业务没有特别关系的逻辑封装在框架层。AOP用的就会比较多。有一段时间,总是会出现自己设计的AOP不能准确匹配的问题。特意做了一点研究,为了增强记忆,方便日后应用,做个小结。

搭建工程

搭建一个普通的Springboot工程即可。为了看到效果,我们使用Logger打印日志,配置好日志打印级别。

logging:
    level:
        com.jiezhi.aspect.demo: debug

POM依赖

默认的工程没有aop模块,要单独添加。

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

添加配置

可以在启动类添加,也可以在专用的配置文件上面添加。

@EnableAsync

编写两个用于测试的接口文件

com.jiezhi.aspect.demo.business.user.web.controller.UserController

package com.jiezhi.aspect.demo.business.user.web.controller;

import com.jiezhi.aspect.demo.annotations.OnMethod;
import com.jiezhi.aspect.demo.annotations.OnType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Chris Chan
 * Create on 2021/8/29 17:18
 * Use for: 测试接口
 * Explain:
 */
@OnType
@RestController
@RequestMapping("api/user")
public class UserController {
    @OnMethod
    @GetMapping("test")
    public String test() {
        return "Call api/user/test success.";
    }

    @GetMapping("test1")
    public String test1() {
        return "Call api/user/test1 success.";
    }
}

com.jiezhi.aspect.demo.business.worker.web.controller.WorkerController

package com.jiezhi.aspect.demo.business.worker.web.controller;

import com.jiezhi.aspect.demo.annotations.OnMethod;
import com.jiezhi.aspect.demo.annotations.OnType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Chris Chan
 * Create on 2021/8/29 17:18
 * Use for: 测试接口
 * Explain:
 */
@RestController
@RequestMapping("api/worker")
public class WorkerController {
    @OnMethod
    @GetMapping("test")
    public String test() {
        return "Call api/worker/test success.";
    }

    @GetMapping("test1")
    public String test1() {
        return "Call api/worker/test1 success.";
    }
}

编写几个自定义注解

OnType 用于类上的注解
OnMethod 用于方法上的注解

package com.jiezhi.aspect.demo.annotations;

import java.lang.annotation.*;

/**
 * @author Chris Chan
 * Create on 2021/8/29 17:07
 * Use for:
 * Explain:
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OnMethod {
}

编写两个切面配置

1. 用于注解匹配的Aspect

com.jiezhi.aspect.demo.config.AnnotationAspect

package com.jiezhi.aspect.demo.config;

import com.jiezhi.aspect.demo.annotations.OnMethod;
import com.jiezhi.aspect.demo.annotations.OnType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author Chris Chan
 * Create on 2021/8/29 17:10
 * Use for: 注解匹配切面
 * Explain:
 */
@Component
@Aspect
@Order(0)
public class AnnotationAspect {
    private Logger logger = LoggerFactory.getLogger(AnnotationAspect.class);

    @Pointcut("@within(com.jiezhi.aspect.demo.annotations.OnType)")
    public void cutOnType() {
    }

    @Pointcut("@annotation(com.jiezhi.aspect.demo.annotations.OnMethod)")
    public void cutOnMethod() {
    }

    /**
     * 匹配方法上的注解
     *
     * @param joinPoint
     * @param onMethod
     */
    @Before("@annotation(onMethod)")
    public void before(JoinPoint joinPoint, OnMethod onMethod) {
        logger.debug("接口调用OnMethod: {}", joinPoint.getSignature().getName());
    }

    /**
     * 匹配类上的注解
     *
     * @param joinPoint
     * @param onType
     */
    @Before("@within(onType)")
    public void before(JoinPoint joinPoint, OnType onType) {
        logger.debug("接口调用OnType: {}", joinPoint.getSignature().getName());
    }

    /**
     * 匹配类和方法上的注解
     *
     * @param joinPoint
     */
    @Before("cutOnType() || cutOnMethod()")
    public void before(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        Class<?> targetClass = target.getClass();
        OnType onType = targetClass.getDeclaredAnnotation(OnType.class);

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        OnMethod onMethod = method.getDeclaredAnnotation(OnMethod.class);
        logger.debug("接口调用OnType&OnMethod: {} ,OnType: {},OnMethod: {}", ((MethodSignature) joinPoint.getSignature()).getMethod().getName(), onType, onMethod);
    }

}

关于注解匹配的语法

@annotation(注解名)是一种比较简便的使用模式,不用定义切点,还可以直接获取到注解。不过只能匹配方法上面的注解;
@within功能同上,匹配的是类上面的注解;
如果要同时匹配方法和类上面的注解,不写切点就不行了,需要参照第三种方式,先写切点,然后进行逻辑处理。最终需要的注解,要在内部用逻辑自己获取。

2. 用于包路径匹配的Aspect

com.jiezhi.aspect.demo.config.CommonAspect

package com.jiezhi.aspect.demo.config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author Chris Chan
 * Create on 2021/8/29 17:10
 * Use for: 包名匹配切面
 * Explain:
 */
@Component
@Aspect
@Order(1)
public class CommonAspect {
    private Logger logger = LoggerFactory.getLogger(CommonAspect.class);

    /**
     * 第一个参数是权限修饰符匹配
     * 第二个参数是返回值类型匹配
     * 第三部分(最后一段之前)是包路径匹配 *表示任意包
     * 第四部分(最后一段)是类匹配 *表示所有类
     * 第五部分(括号内)是参数列表匹配 ..表示所有参数
     * ..表示匹配多重
     */
    @Pointcut("execution(public * com.jiezhi.aspect.demo.business.*.web.controller.*Controller.*(..))")
    public void cutController() {
    }
    @Pointcut("execution(public * com.jiezhi.aspect.demo..*Control*.*(..))")
    public void cutController1() {
    }
    /**
     * 匹配Controller
     *
     * @param joinPoint
     */
    @Before("cutController1()")
    public void before(JoinPoint joinPoint) {
        logger.debug("接口调用 路径匹配1: {}", joinPoint.getSignature().getName());
    }

}

关于包路径匹配的语法简记

包路径最后一节是类名,*匹配任何类,同时*也可以在类名中匹配任何字符串;
..匹配任意多段路径;
括号内是参数匹配,..匹配任意参数表,具体细节所用较少,暂时没有过细研究。

测试结果

各种情况都测试过,符合预期效果。达成此种结果,基本上就可以灵活运用了。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值