当前位置: 首页 > news >正文

@test注解_Spring 自定义注解你了解过吗?

12436572addf809ddb9d54d80974b35e.png   Java大联盟

  帮助万千Java学习者持续成长

关注

5cc3bf5d7081a4f8692626716ef2127f.png

作者|何甜甜在吗

https://juejin.im/post/5cf376e16fb9a07eee5eb6eb

B 站搜索:楠哥教你学Java

获取更多优质视频教程

在业务开发过程中我们会遇到形形色色的注解,但是框架自有的注解并不是总能满足复杂的业务需求,我们可以自定义注解来满足我们的需求。根据注解使用的位置,文章将分成字段注解、方法、类注解来介绍自定义注解。

字段注解

字段注解一般是用于校验字段是否满足要求,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 {};
}

@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 {
    /**
     * 合法的参数值,从注解中获取
     * */
    private List 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;
    }
}

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

使用方式

定义一个实体类

@Data
public class User {
    /**
     * 姓名
     * */
    private String name;

    /**
     * 性别 man or women
     * */
    @Check(paramValues = {"man", "woman"})
    private String sex;
}

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

测试

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

注意需要在User对象上加上@Validated注解,这里也可以使用@Valid注解,@Validated 和 @Valid 的区别,这篇建议看下。

方法、类注解

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

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

权限注解

自定义注解

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

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

拦截器类

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;
    }
}

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

测试

 @GetMapping("/api/test")
 @PermissionCheck(resourceKey = "test")
 public Object testPermissionCheck() {
     return "hello world";
 }

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

缓存注解

自定义注解

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

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

切面

@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;
    }
}

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

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

测试

@GetMapping("/api/cache")
@CustomCache(key = "test")
public Object testCustomCache() {
    return "don't hit cache";
}
推荐阅读

1、Spring Boot+Vue项目实战

2、B站:4小时上手MyBatis Plus

3、一文搞懂前后端分离

4、快速上手Spring Boot+Vue前后端分离

楠哥简介

资深 Java 工程师,微信号 southwindss

《Java零基础实战》一书作者

腾讯课程官方 Java 面试官,今日头条认证大V

GitChat认证作者,B站认证UP主(楠哥教你学Java)

致力于帮助万千 Java 学习者持续成长。

6d7fc950a59e98ae8a38a2e7428fa159.png

cf1909fb52060c0f59cba58ccb2c0cba.png

有收获,就 在看   cd60b7cdd3fc741fe760fe02371c3ff8.png

相关文章:

  • docker查看容器详细信息_Docker学习
  • 域用户频繁被锁定怎么解决_最新,三星手机系统崩溃,已有网友找到了解决方法,历史不会重现...
  • python读取excel送到网页_PYTHON读取EXCEL内容再转变成HTML添加到OUTLOOK中
  • hbuilder简单网页模板_网页制作软件教程:怎么制作网页?
  • a标签禁止点击_禁止a标签跳转的几个方案
  • python flask web_Python Flask Web 项目实战
  • python类的专有方法_python类专有
  • linux sleeping进程多_掌握了这几个Linux命令可以让你工作效率提高一倍
  • cmakelist .so_ROS 机器人技术 解析 CMakeList.txt 文件
  • python爬虫搭建环境_python爬虫起步...开发环境搭建,最简单的方式
  • if test 多条件_1分钟让你读懂if __name__==#x27;__main__#x27;的含义,面试不用愁
  • python打不开txt文件苹果怎么打开_如何在Mac上的Python中从TextEdit中打开文本文......
  • 如何使用python编程抢京东优惠券 知乎_小猿圈Python之实现京东秒杀功能代码
  • 预警系统一键自动升级程序_带有L2级自动驾驶技术,本田思域的起售价为11.99万...
  • python删除列表中的偶数_从奇数/偶数Python列表中删除偶数/奇数
  • 深入了解以太坊
  • python3.6+scrapy+mysql 爬虫实战
  • 【知识碎片】第三方登录弹窗效果
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • Computed property XXX was assigned to but it has no setter
  • Django 博客开发教程 16 - 统计文章阅读量
  • iOS编译提示和导航提示
  • Linux下的乱码问题
  • V4L2视频输入框架概述
  • 汉诺塔算法
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 那些年我们用过的显示性能指标
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 我看到的前端
  • 小程序测试方案初探
  • 正则表达式-基础知识Review
  • #mysql 8.0 踩坑日记
  • #大学#套接字
  • (20050108)又读《平凡的世界》
  • (6)设计一个TimeMap
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (Redis使用系列) Springboot 实现Redis消息的订阅与分布 四
  • (zt)最盛行的警世狂言(爆笑)
  • (笔试题)合法字符串
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (六)Hibernate的二级缓存
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)EOS中账户、钱包和密钥的关系
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .NET导入Excel数据
  • .Net调用Java编写的WebServices返回值为Null的解决方法(SoapUI工具测试有返回值)
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • ;号自动换行
  • @开发者,一文搞懂什么是 C# 计时器!
  • []FET-430SIM508 研究日志 11.3.31
  • [AAuto]给百宝箱增加娱乐功能
  • [ASP.NET MVC]如何定制Numeric属性/字段验证消息