Spring自定义注解玩法大全,从入门到…

开发 后端
在业务开发过程中我们会遇到形形色色的注解,但是框架自有的注解并不是总能满足复杂的业务需求,我们可以自定义注解来满足我们的需求。

[[354020]]

 在业务开发过程中我们会遇到形形色色的注解,但是框架自有的注解并不是总能满足复杂的业务需求,我们可以自定义注解来满足我们的需求。

根据注解使用的位置,文章将分成字段注解、方法、类注解来介绍自定义注解。

字段注解

字段注解一般是用于校验字段是否满足要求,hibernate-validate依赖就提供了很多校验注解 ,如@NotNull、@Range等,但是这些注解并不是能够满足所有业务场景的。

比如我们希望传入的参数在指定的String集合中,那么已有的注解就不能满足需求了,需要自己实现。

自定义注解

定义一个@Check注解,通过@interface声明一个注解 

@Target({ ElementType.FIELD}) //只允许用在类的字段上  
@Retention(RetentionPolicy.RUNTIME) //注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解  
@Constraint(validatedBy = ParamConstraintValidated.class)  
public @interface Check {  
    /**  
     * 合法的参数值  
     * */  
    String[] paramValues();  
    /**  
     * 提示信息  
     * */  
    String message() default "参数不为指定值";  
    Class<?>[] groups() default {};  
    Class<? extends Payload>[] payload() default {};  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

@Target 定义注解的使用位置,用来说明该注解可以被声明在那些元素之前。

ElementType.TYPE:说明该注解只能被声明在一个类前。

ElementType.FIELD:说明该注解只能被声明在一个类的字段前。

ElementType.METHOD:说明该注解只能被声明在一个类的方法前。

ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。

ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。

ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。

ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。

ElementType.PACKAGE:说明该注解只能声明在一个包名前

@Constraint 通过使用validatedBy来指定与注解关联的验证器

@Retention 用来说明该注解类的生命周期。

RetentionPolicy.SOURCE: 注解只保留在源文件中 

RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃

RetentionPolicy.RUNTIME: 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。

验证器类

验证器类需要实现ConstraintValidator泛型接口 

public class ParamConstraintValidated implements ConstraintValidator<Check, Object> {  
    /**  
     * 合法的参数值,从注解中获取  
     * */  
    private List<String> paramValues;  
    @Override  
    public void initialize(Check constraintAnnotation) {  
        //初始化时获取注解上的值  
        paramValues = Arrays.asList(constraintAnnotation.paramValues());  
    }  
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {  
        if (paramValues.contains(o)) {  
            return true;  
        }  
        //不在指定的参数列表中  
        return false;  
    }  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

第一个泛型参数类型Check:注解,第二个泛型参数Object:校验字段类型。需要实现initialize和isValid方法,isValid方法为校验逻辑,initialize方法初始化工作

使用方式

定义一个实体类 

@Data  
public class User {  
    /**  
     * 姓名  
     * */  
    private String name;  
    /**  
     * 性别 man or women  
     * */  
    @Check(paramValues = {"man", "woman"})  
    private String sex;  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

对sex字段加校验,其值必须为woman或者man

测试 

@RestController("/api/test")  
public class TestController {  
    @PostMapping  
    public Object test(@Validated @RequestBody User user) {  
        return "hello world";  
    }  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

注意需要在User对象上加上@Validated注解,这里也可以使用@Valid注解

方法、类注解

在开发过程中遇到过这样的需求,如只有有权限的用户的才能访问这个类中的方法或某个具体的方法、查找数据的时候先不从数据库查找,先从guava cache中查找,在从redis查找,最后查找mysql(多级缓存)。

这时候我们可以自定义注解去完成这个要求,第一个场景就是定义一个权限校验的注解,第二个场景就是定义spring-data-redis包下类似@Cacheable的注解。

权限注解

自定义注解 

@Target({ ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface PermissionCheck { 
    /**  
     * 资源key  
     * */ 
    String resourceKey();  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

该注解的作用范围为类或者方法上

拦截器类 

public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {  
    /**  
     * 处理器处理之前调用  
     */  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,  
                             Object handler) throws Exception { 
         HandlerMethod handlerMethod = (HandlerMethod)handler;  
        PermissionCheck permission = findPermissionCheck(handlerMethod);  
        //如果没有添加权限注解则直接跳过允许访问  
        if (permission == null) {  
            return true;  
        }  
        //获取注解中的值  
        String resourceKey = permission.resourceKey();  
        //TODO 权限校验一般需要获取用户信息,通过查询数据库进行权限校验  
        //TODO 这里只进行简单演示,如果resourceKey为testKey则校验通过,否则不通过  
        if ("testKey".equals(resourceKey)) {  
            return true;  
        }  
        return false;  
    }  
    /**  
     * 根据handlerMethod返回注解信息  
     *  
     * @param handlerMethod 方法对象  
     * @return PermissionCheck注解  
     */  
    private PermissionCheck findPermissionCheck(HandlerMethod handlerMethod) { 
        //在方法上寻找注解  
        PermissionCheck permission = handlerMethod.getMethodAnnotation(PermissionCheck.class);  
        if (permission == null) {  
            //在类上寻找注解  
            permission = handlerMethod.getBeanType().getAnnotation(PermissionCheck.class);  
        }  
        return permission;  
    }  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

权限校验的逻辑就是你有权限你就可以访问,没有就不允许访问,本质其实就是一个拦截器。我们首先需要拿到注解,然后获取注解上的字段进行校验,校验通过返回true,否则返回false

测试 

@GetMapping("/api/test")  
 @PermissionCheck(resourceKey = "test" 
 public Object testPermissionCheck() {  
     return "hello world";  
 } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

该方法需要进行权限校验所以添加了PermissionCheck注解。

缓存注解

自定义注解 

@Target({ ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface CustomCache {  
    /**  
     * 缓存的key值  
     * */  
    String key();  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

注解可以用在方法或类上,但是缓存注解一般是使用在方法上的。

切面 

@Aspect  
@Component  
public class CustomCacheAspect {  
    /**  
     * 在方法执行之前对注解进行处理  
     *  
     * @param pjd  
     * @param customCache 注解  
     * @return 返回中的值  
     * */  
    @Around("@annotation(com.cqupt.annotation.CustomCache) && @annotation(customCache)")  
    public Object dealProcess(ProceedingJoinPoint pjd, CustomCache customCache) {  
        Object result = null 
        if (customCache.key() == null) {  
            //TODO throw error  
        }  
        //TODO 业务场景会比这个复杂的多,会涉及参数的解析如key可能是#{id}这些,数据查询  
        //TODO 这里做简单演示,如果key为testKey则返回hello world  
        if ("testKey".equals(customCache.key())) {  
            return "hello word";  
        }   
        //执行目标方法  
        try {  
            result = pjd.proceed();  
        } catch (Throwable throwable) {  
            throwable.printStackTrace();  
        }  
        return result;  
    }  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

因为缓存注解需要在方法执行之前有返回值,所以没有通过拦截器处理这个注解,而是通过使用切面在执行方法之前对注解进行处理。

如果注解没有返回值,将会返回方法中的值

测试 

@GetMapping("/api/cache")  
@CustomCache(key = "test" 
public Object testCustomCache() {  
    return "don't hit cache";  
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

 

责任编辑:庞桂玉 来源: 民工哥技术之路
相关推荐

2024-10-14 17:18:27

2017-08-03 17:00:54

Springmvc任务执行器

2023-10-11 07:57:23

springboot微服务

2023-10-24 13:48:50

自定义注解举值验证

2024-12-27 15:37:23

2020-11-12 08:41:35

Linux系统

2011-12-26 10:11:08

JSP

2023-10-23 08:18:50

扫描SpringBean

2021-12-07 18:23:50

自定义进度条分段式

2021-12-30 12:30:01

Java注解编译器

2021-05-19 08:20:59

ViewGrouplayout作用

2023-10-09 07:37:01

2022-11-10 07:53:54

Spring参数校验

2009-08-05 17:03:37

C#自定义控件

2022-02-17 07:10:39

Nest自定义注解

2024-10-09 10:46:41

springboot缓存redis

2021-02-20 11:40:35

SpringBoot占位符开发技术

2024-07-02 11:42:53

SpringRedis自定义

2022-11-01 11:15:56

接口策略模式

2023-03-30 16:16:00

Java自定义注解开发
点赞
收藏

51CTO技术栈公众号