如何优雅的实现自定义注解?两个常见场景轻松拿捏

自定义注解想必大家都不陌生吧,尤其是使用springboot的同学应该会大量使用spring的注解,什么@RestController 、@RequestBody 之类的,那他们都是怎么实现的呢,我们该如何在自已的项目中编写注解呢?

下面通过这两个场景来看该如何实现自定义注解

1、如何通过自定义注解获取登录用户信息?

下面这种注解方式大家是不是经常见到:
img
这个注解的用处就是在参数前标记这个注解就可以将当前用户信息绑定到当前的参数中

接下来我们看下这个注解是如何实现的

1.1 编写注解类
/**
 * 当前用户
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentLocalUser {
}

对 Target、Retention、Documented 注解做下解释:

Target:用于指定自定义注解可以应用的元素类型

  • ElementType.METHOD 表示该注解可以应用于方法
  • ElementType.FIELD 表示可以应用于字段
  • ElementType.TYPE(类、接口或枚举
  • ElementType.PARAMETER(参数)

Retention:用于指定注解的保留策略

  • RetentionPolicy.RUNTIME 表示注解在运行时保留,这样可以通过反射机制在运行时获取注解信息
  • RetentionPolicy.CLASS 表示注解在类文件中保留,但在运行时不可用,是默认的保留策略
  • RetentionPolicy.SOURCE 表示注解仅在源代码中保留,不会出现在编译后的字节码中,常用于代码生成工具

Documented:是一个标记注解,没有成员变量,用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档

1.2 编写参数解析器
/**
 * 当前用户参数解析器
 */
@Component
public class LoginManagerHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final Logger LOG = LoggerFactory.getLogger(LoginManagerHandlerMethodArgumentResolver.class);

    public static final String CURRENT_LOGIN_USER = "user";


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    	// 检查参数是否被 @CurrentLocalUser 注解修饰
        if (parameter.hasParameterAnnotation(CurrentLocalUser.class)) {
            return true;
        }
        return false;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request == null) {
            return null;
        }
        //Object user = request.getAttribute(CURRENT_LOGIN_USER);
        // 下面我直接创建了一个用户类,模拟从认证中提取用户信息,大家这里可以从拦截器或者过滤器中拿
        CurrentUser currentUser = new CurrentUser();
        currentUser.setUsername("admin");
        LOG.info("获取到当前用户 = {}", currentUser);
        return currentUser;
    }
}

对以上代码做下解释:
这是一个实现了 HandlerMethodArgumentResolver 的类,用于在 Spring 框架的处理器方法中解析自定义的方法参数。它的主要作用是为带有 @CurrentLocalUser 注解的参数提供值

supportsParameter方法的目的是判断处理器方法中的参数是否需要由这个解析器来解析,如果参数前定义了@CurrentLocalUser这个注解,那就会调用resolveArgument方法执行解析

resolveArgument方法负责实际的参数解析工作,我这里为了方便演示直接创建了一个用户对象,这个信息可以是在拦截器或者是其他解析用户token的地方注册或者添加的,最后,将获取到的对象作为解析结果返回

1.3 编写web配置类

配置WebAppConfig,注册参数解析器

@Configuration
public class WebAppConfig implements WebMvcConfigurer, ApplicationContextAware {

    @Autowired
    LoginManagerHandlerMethodArgumentResolver loginManagerHandlerMethodArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        // 添加登录用户方法参数拦截器,用于获取当前登录用户信息
        resolvers.add(loginManagerHandlerMethodArgumentResolver);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

    }
}
1.4 编写测试类
/**
     * 测试接口
     * @param currentUser
     * @param commodityCode
     * @return
     */
    @PostMapping("createOrder")
    public CommonResponse<String> createOrder(@CurrentLocalUser CurrentUser currentUser, String commodityCode){
        LOG.info("当前用户信息:{}",currentUser);
        return CommonResponse.success();
    }

调用接口,可以看到标记注解的参数已经拿到了结果
在这里插入图片描述

2、如何通过自定义注解对方法进行干预或者通用处理?

来看第二个场景,在某个标识了自定义注解的方法执行前输出固定日志
直接看代码实现吧:
代码解释直接放到注释里了

2.1 定义自定义注解
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 LogMethod {
}
2.2 编写切面类

使用Spring Aop来实现切面类,确保你在项目的依赖中包含了 Spring AOP 库。

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

切面类实现:

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.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class LogMethodAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogMethodAspect.class);

    // 定义切点,使用 @LogMethod 注解的方法
    @Pointcut("@annotation(com.mdx.order.annotation.LogMethod)")
    public void logMethodPointcut() {
    }

    // 在方法执行前进行日志记录
    @Before("logMethodPointcut()")
    public void logMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        logger.info("开始执行方法: {}", method.getName());
    }
}

解释:
@Pointcut(“@annotation(com.mdx.order.annotation.LogMethod)”):定义了切点,该切点会匹配所有使用了 @LogMethod 注解的方法
@Before(“logMethodPointcut()”):表示在切点所匹配的方法执行之前执行 logMethod 方法

2.3 编写测试

在方法上增加LogMethod注解
在这里插入图片描述
预期效果:

  1. 先输出 LOG.info(“开始执行方法 = {}”, method.getName());
  2. 再输出 LOG.info(“测试日志”);

调用方法,按照预期输出成功
在这里插入图片描述

One more thing
原来时间真的不是一条横跨在你面前的河,有着此岸和彼岸,而是一条挂在悬崖上的瀑布,奔流直下,一去无回。

有想获取源码的同学,点击下方👇👇👇 🔗,主页获取项目:mdx-shop(TestController)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最后一支迷迭香

您的赞赏将给作者加杯☕️

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值