SpringBoot项目中框架给我们提供了很多内置的注解,比如@Component,@RestController,@Service等等,这些注解可以简化配置以及程序,大大的提高了开发的效率。项目中很多场景,我们也可以使用自定义注解方式,将某一场景提取出来通过自定义注解方式实现,来解耦业务并提高开发效率。
本文基于验证用户角色这一场景通过Demo示例来展示如果通过自定义注解来实现,介绍两种实现方式,拦截器的方式以及AOP的方式。
目录
定义自定义注解
首先定义自定义注解,注解中我们定义一个接收参数value用户传递允许的用户角色。这里为了方便演示,为拦截器和AOP分别定义了两个不同的自定义注解,其实可以只定义一个注解。
基于拦截器实现的自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HeaderValidInterceptor {
String value() default "";
}
基于AOP实现的自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HeaderValidAspect {
String value() default "";
}
自定义注解中用到的元注解@Documented,@Target,@Retention简介如下:
元注解释义:
java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
@Inherited – 是否允许子类继承该注解
实现自定义注解逻辑
示例中我们通过自定义请求头"CUSTOM_CLIENT"传递的角色信息来验证是否可以访问,这里为了演示只做简单的验证逻辑,实际项目中需要根据具体的业务要求来实现。
拦截器方式:
拦截器逻辑实现:
package com.flyduck.annotation.interceptor;
import com.flyduck.annotation.annotation.HeaderValidInterceptor;
import com.flyduck.annotation.util.CommonUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器处理注解逻辑
*
* @author flyduck 2020/10/22 21:40
* @version V1.0
* @modify by user: LiuHui 2020/10/22
* @modify by reason:{原因}
**/
@Component
public class HeaderInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 判断handler是否为方法声明类型
if (handler instanceof HandlerMethod) {
// 获取自定义注解
HeaderValidInterceptor headerValid = ((HandlerMethod) handler).getMethodAnnotation(HeaderValidInterceptor.class);
if (headerValid != null) {
// 判断自定义注解验证逻辑
String allowRole = headerValid.value();
String requestRole = request.getHeader(CommonUtils.HEADER_CLIENT);
if (!allowRole.equals(requestRole)) {
return false;
}
}
}
return super.preHandle(request, response, handler);
}
}
拦截器的方式我们需要将自定义的拦截器注册进registry中:
package com.flyduck.annotation.config;
import com.flyduck.annotation.interceptor.HeaderInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* <p></p>
*
* @author flyduck 2020/10/22 21:53
* @version V1.0
* @modify by user: LiuHui 2020/10/22
* @modify by reason:{原因}
**/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private HeaderInterceptor headerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册自定义拦截器
registry.addInterceptor(headerInterceptor).addPathPatterns("/**");
}
}
AOP方式:
使用AOP的方式需要在pom.xml中加入aop的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP逻辑实现:
package com.flyduck.annotation.aspect;
import com.flyduck.annotation.annotation.HeaderValidAspect;
import com.flyduck.annotation.util.CommonUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
public class HeaderAspect {
/**
* 将自定义注解作为切入点
*
* @author flyduck
* @date 2020/11/1 15:26
* @param []
* @return void
*/
@Pointcut("@annotation(com.flyduck.annotation.annotation.HeaderValidAspect)")
public void annotation() {
}
/**
* 环绕切入点
*
* @author flyduck
* @date 2020/11/1 15:26
* @param [pjp]
* @return java.lang.Object
*/
@Around("annotation()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 从请求中获取Header和参数
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HeaderValidAspect headerValidAspect = ((MethodSignature)pjp.getSignature()).getMethod().getAnnotation(HeaderValidAspect.class);
// 判断自定义注解验证逻辑
String allowRole = headerValidAspect.value();
String requestRole = request.getHeader(CommonUtils.HEADER_CLIENT);
if (!allowRole.equals(requestRole)) {
return null;
}
return pjp.proceed();
}
}
使用自定义注解
自定义注解的使用与SpringBoot提供的注解使用方式一致,这里我们主要使用在接口方法上加上@HeaderValidInterceptor或者@HeaderValidAspect即可,自定义注解中带上允许的角色名即可只允许带了指定角色名信息的请求访问接口。代码如下:
package com.flyduck.annotation.controller;
import com.flyduck.annotation.annotation.HeaderValidAspect;
import com.flyduck.annotation.annotation.HeaderValidInterceptor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p></p>
*
* @author flyduck 2020/10/22 21:49
* @version V1.0
* @modify by user: LiuHui 2020/10/22
* @modify by reason:{原因}
**/
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("validInterceptor")
@HeaderValidInterceptor("ADMIN")
public String validInterceptor() {
return "pass";
}
@GetMapping("noNeedValid")
public String noNeedValid() {
return "pass";
}
@GetMapping("validAspect")
@HeaderValidAspect("ADMIN")
public String validAspect() {
return "pass";
}
}
测试自定义注解
到此自定义注解拦截器和AOP实现的两种方式就介绍完了。通过curl测试接口,如下图所示,在加了自定义注解的接口上我们需要带上允许的自定义请求头才能够正常的访问,否则会被拦截掉。
总结
本文基于角色验证这一业务场景,使用自定义注解通过拦截器和AOP的方式分别对请求进行角色验证,示例源码地址:https://gitee.com/flyduck128/springboot-demo/tree/master/flyduck-annotation。本文仅仅介绍了SpringBoot中自定义注解的使用层面,源码层面未做深入的研究。希望本问可以给其他小伙伴提供一些帮助,如果有描述错误的地方欢迎指正。