自定义注解想必大家都不陌生吧,尤其是使用springboot的同学应该会大量使用spring的注解,什么@RestController 、@RequestBody 之类的,那他们都是怎么实现的呢,我们该如何在自已的项目中编写注解呢?
下面通过这两个场景来看该如何实现自定义注解
1、如何通过自定义注解获取登录用户信息?
下面这种注解方式大家是不是经常见到:
这个注解的用处就是在参数前标记这个注解就可以将当前用户信息绑定到当前的参数中
接下来我们看下这个注解是如何实现的
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注解
预期效果:
- 先输出 LOG.info(“开始执行方法 = {}”, method.getName());
- 再输出 LOG.info(“测试日志”);
调用方法,按照预期输出成功
One more thing
原来时间真的不是一条横跨在你面前的河,有着此岸和彼岸,而是一条挂在悬崖上的瀑布,奔流直下,一去无回。
有想获取源码的同学,点击下方👇👇👇 🔗,主页获取项目:mdx-shop(TestController)