赞
踩
java注解,网上的实例千篇一律,感觉大多都没有抓到重点。最糟糕的是,示例不全,不能完全让别人把示例做出来。
起因:希望通过自定义一个字段校验的注解,来理解注解。
意如其名,其本来的意思就是用来做标注用:可以在类、字段变量、方法、接口等位置进行一个特殊的标记,为后续做一些诸如:代码生成、数据校验、资源整合等工作做铺垫。
注解一旦对代码标注完成,后续我们就可以结合Java强大的反射机制,在运行时动态地获取到注解的标注信息,从而可以执行很多其他逻辑,完成我们想要的自动化工作。
总的来说就是先标记,然后定义有这个标记的需要做操作,以及什么时候触发这个操作。
但其实注解只是标记,标记的动作和标记的触发是单独的内容。并不需要一定存在,你可以只定义个标记,而不去对这个标记做任何操作。
目标:手动创建一个length注解,校验字段的长度。
一般我们使用注解有两种方式,一种是验证器配合着使用,另外一种就是利用 AOP 结合使用。
@Target({ElementType.FIELD})//用来说明该自定义注解可以用在什么位置
@Retention(RetentionPolicy.RUNTIME)//用来说明你自定义注解的生命周期
@Documented
@Constraint(validatedBy = {LengthValidator.class}) // 与约束注解关联的验证器
public @interface Length {
int min(); //允许字符串长度的最小值
int max();//允许字符串长度的最大值
String message() default "长度超过限制";//自定义的错误提示信息
// 约束注解在验证时所属的组别
Class<?>[] groups() default {};
// 约束注解的有效负载
Class<? extends Payload>[] payload() default {};
}
用来说明该自定义注解可以用在什么位置
ElementType.FIELD:说明自定义的注解可以用于类的变量
ElementType.METHOD:说明自定义的注解可以用于类的方法
ElementType.TYPE:说明自定义的注解可以用于类本身、接口或 enum类型
等等… 还有很多,如果记不住,建议现用现查。
保留的意思,用来说明你自定义注解的生命周期,比如:
@Retention(RetentionPolicy.RUNTIME):表示注解可以一直保留到运行时,因此可以通过反射获取注解信息
@Retention(RetentionPolicy.CLASS):表示注解被编译器编译进 class文件,但运行时会忽略
@Retention(RetentionPolicy.SOURCE):表示注解仅在源文件中有效,编译时就会被忽略
它的作用是能够将注解中的元素包含到 Javadoc 中去。
与约束注解关联的验证器,LengthValidator.class中是验证器的内容。
使用验证器方式时需要配置这个注解,AOP则不需要
成员变量没有使用 default 定义默认值,在使用注解时,就必须要对它进行赋值,有默认值则可以不必赋值;
String message() default "长度超过限制"
使用的地方可以不写message,因为上面有默认值了(这个编译时会检查出来的不用特意去记):
@Length(min= 1, max= 3)
private String username;
还有就是必须是message,不能改名称,应该是验证器写死了。我改了之后报错:
javax.validation.ConstraintDefinitionException: HV000074: com.xxxxx.annotation.Length contains Constraint annotation, but does not contain a message parameter
固定加上下面的(详细是做什么的,我也不清楚):
// 约束注解在验证时所属的组别
Class<?>[] groups() default {};
// 约束注解的有效负载
Class<? extends Payload>[] payload() default {};
如果没有上面的话,会报错:
but does not contain a groups parameter 注解需要定义成员变量 groups
xxx contains Constraint annotation, but does not contain a payload parameter. 注解需要定义成员变量 Payload
还有就是
/** * @author aliyu * @create 2020-07-07 18:41 * 类描述:Length-是哪个注解的校验, Object注解校验的参数是什么类型 */ public class LengthValidator implements ConstraintValidator<Length, Object> { private Integer min; private Integer max; @Override public void initialize(Length constraintAnnotation) { this.min = constraintAnnotation.min(); this.max = constraintAnnotation.max(); } /** * 验证方法 * @param object * @param context * @return */ @Override public boolean isValid(Object object, ConstraintValidatorContext context) { if (object == null) { return true; } if (((String)object).length() >= min && ((String)object).length() <= max) { return true; } return false; } }
验证器LengthValidator 实现 ConstraintValidator<Length, Object>接口,并重写initialize和isValid。Length-是哪个注解的校验, Object注解校验的参数是什么类型(说明校验的参数可以是任意类型,当然你也可以指定)。
我们的验证内容写在isValid。
当触发isValid方法时,object 直接获取到了标注了@Length的字段的值。不知名的内部通过反射找到标注了@Length注解的字段以及它的值
context的basePath 就是注解的字段 username
dto 为字段加上注解:
public class AdminDTO {
private Integer id;
@Length(min= 1, max= 3)
private String username;
private String password;
调用Controller时,对传入的dto进行校验,需要加 @Validated触发 校验,如果不加的话,不会进行校验。
@RequestMapping("/login")
public R login(@RequestBody @Validated AdminDTO adminDTO)throws Exception{
System.out.println("A");
return null;
}
@Target({ElementType.FIELD})//用来说明该自定义注解可以用在什么位置
@Retention(RetentionPolicy.RUNTIME)//用来说明你自定义注解的生命周期
@Documented
//@Constraint(validatedBy = {LengthValidator.class}) // 与约束注解关联的验证器(AOP方式不需要)
public @interface LengthAop {
int min(); //允许字符串长度的最小值
int max();//允许字符串长度的最大值
String errorMsg();//自定义的错误提示信息(AOP方式随便命名,相对于应验证器模式)
//String message();//自定义的错误提示信息
//AOP方式不需要下面的
// Class<?>[] groups() default {}; // 约束注解在验证时所属的组别
// Class<? extends Payload>[] payload() default {};// 约束注解的有效负载
}
仔细看一下与验证器方式的不同。
AOP的方式,就是标注了@length注解的字段校验,触发由自己定义,一般都是自己定义拦截器或者过滤器。让请求在进入congtroller之前对dto的内容进行校验。这个拦截器,我就不写了,可以自行百度。
/** * 验证方法 * @param object 要验证的数据的类型 * @return */ public String isValid(Object object) throws IllegalAccessException{ // 首先通过反射获取object对象的类有哪些字段 Field[] fields = object.getClass().getDeclaredFields(); //for循环逐个字段校验,看哪个字段上标了注解 for (Field field : fields){ // if判断:检查该字段上有没有标注了@Length注解 if (field.isAnnotationPresent(Length.class)){ // 通过反射获取到该字段上标注的@Length注解的详细信息 Length annotation = field.getAnnotation(Length.class); field.setAccessible(true);// 让我们在反射时能访问到私有变量 int value = ((String)field.get(object)).length(); if(value<annotation.min() || value>annotation.max()){ return annotation.message(); } } } return null; }
可以看出查找注解的字段,已及注解字段的值,都需要自己去编写代码。通过反射可以得到注解的信息,并根据这些信息写自己的验证内容。
aop方式不需要controller 写什么@Validated ,因为它触发的地方是自己写的拦截器。
我感觉把,验证器方式算是比较常用的,但是它应该是写死了触发的时机。而AOP方式较为灵活,但是代码需要自己写。
参考自:
听说你只会用注解,不会自己写注解?那有点危险了
https://mp.weixin.qq.com/s/Z7nL1hNF74CdJGMORyFWzQ
Java 注解的初步学习
https://www.cnblogs.com/michael-xiang/p/12369898.html
下面说的注解原理应该就是实际spring注解实现的原理:
https://www.jianshu.com/p/104984eacf41
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。