1.注解的概念
- 注解是一种元数据模式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
- 注解不会对所修饰的代码产生直接的影响
2.注解的使用范围
- 类、接口、枚举:@Controller 标记controller层、@Service 标记service层、@Mapper 标记DAO,自动映射
- 属性 @Autowired 自动装配
- 方法 @Override 表示重写
- 方法形式参数 @Param 设置传递参数的名称
- 构造方法
- 局部变量
- 注解类型 @Target 标记此注解类的使用范围
- 包
3.注解的基本语法
- 注解类型的声明:注解在Java中,与类、接口、枚举类似,因此声明语法基本一致,只是所使用的关键字有所不同,其使用@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
public @interface TestAnnotation{
}
- 注解类型的实现部分:其实现部分只能定义注解类型元素(annotation type element)。
//class的一维数组类型
Class<? extends Payload>[] payload() default { };
//基本数据类型
int ret() default 7;
//String类型
String message() default "message params error";
-
定义注解类型的特点:
1.访问修饰符必须为public,不写默认为public 。
2.该元素类型只能是基本数据类型,String、Class、枚举类型、注解类型(体现了注解的嵌套效果),以及上述类型的一维数组。
3.()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊语法
4.default代表默认的值,值必须和第2点定义的类型一致
5.如果没有默认值,代表后续使用注解时必须给该类型元素赋值
4.自定义注解所使用到的元注解
- @Target注解,专门用来限定某个自定义注解能被应用到哪些Java元素上面
//只适用于属性
@Target(ElementType.FIELD)
//属性和方法都适用
@Target({ElementType.METHOD, ElementType.TYPE})
其中ElementType支持的类别为
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
-
@Retention,用来修饰自定义注解的生命力,有下面三种方式
1.Java源文件阶段
@Retention(RetentionPolicy.SOURCE)
如果一个注解被定义为SOURCE,则它会被限定在Java源文件中。也就是说这个注解既不会参与编译也不会在运行期起任何作用,这个注解就和注释一样,只能被阅读Java文件的人看到。
2.编译class文件阶段
@Retention(RetentionPolicy.CLASS)
如果一个注解被定义为CLASS,那么它将被编译到Class文件中,编译器可以在编译时根据注解做一些处理动作,但是运行JVM时会忽略它,我们在运行期也不能读取到。在默认情况下,自定义注解会使用此属性
3.运行期阶段
@Retention(RetentionPolicy.RUNTIME)
如果一个注解被定义为RUNTIME,那么这个注解可以用在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发红的自定义RUNTIME
- @Document,用来指定自定义注解能否随着被定义的java文件生成到JavaDoc文档中。
- @Inherited,是指某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。此注解只对那些@Target被定义为ElementType.的自定义注解起作用
5.使用场景
1.可以和Validator配合使用来进行请求字段的非空校验
2.可以和aop配合通过反射来进行日志管理
3.可以和aop配合通过反射来控制某些特定接口的权限管理
6.使用样例
样例使用自定义注解和Validator来配合进行请求字段的非空校验
1.添加pom依赖
如果是单纯使用自定义注解,则不需要添加pom依赖,如果要配合validator使用,则需要添加validator的依赖
<!-- custom validator start -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.7.Final</version>
</dependency>
<!-- custom validator end -->
2.生成自定义注解类
//仅仅用于请求字段的非空校验
@Target(ElementType.FIELD)
//需要jvm运行时使用
@Retention(RetentionPolicy.RUNTIME)
//使用validator时必须添加,指定与NotNullValidator自定义校验器配合使用
@Constraint(validatedBy = NotNullValidator.class)
//@interface 代表此类是注解类
public @interface CheckParamNullAnnotation {
//使用validator时必须添加,主要是将validator进行分类,不同的group中会执行不同的validator操作
Class<?>[] groups() default { };
//使用validator时必须添加,主要针对bean的
Class<? extends Payload>[] payload() default { };
//使用validator时必须添加,定制化的提示信息
String message() default "message params error";
//自定义注解类型,如果不适用validator,只需要写这一个注解类型即可。
int ret() default 7;
}
如果groups、payload和message没有添加的话会报下面的异常
javax.validation.ConstraintDefinitionException: HV000074: com.ding.platform.common.annotation.CheckParamNullAnnotation contains Constraint annotation, but does not contain a message parameter.
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.assertMessageParameterExists(ConstraintHelper.java:1060) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.lambda$isConstraintAnnotation$3(ConstraintHelper.java:1006) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660) ~[na:1.8.0_131]
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.isConstraintAnnotation(ConstraintHelper.java:1005) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraintAnnotations(AnnotationMetaDataProvider.java:534) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraints(AnnotationMetaDataProvider.java:479) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraints(AnnotationMetaDataProvider.java:461) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findPropertyMetaData(AnnotationMetaDataProvider.java:236) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getFieldMetaData(AnnotationMetaDataProvider.java:229) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.retrieveBeanConfiguration(AnnotationMetaDataProvider.java:129) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getBeanConfiguration(AnnotationMetaDataProvider.java:120) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanConfigurationForHierarchy(BeanMetaDataManagerImpl.java:234) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.createBeanMetaData(BeanMetaDataManagerImpl.java:201) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanMetaData(BeanMetaDataManagerImpl.java:165) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:156) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
3.生成自定义校验器
自定义校验器需要实现ConstraintValidator接口,其中两个参数,一个是指定的自定义注解CheckParamNullAnnotation,一个是传入的值Object。
public class NotNullValidator implements ConstraintValidator<CheckParamNullAnnotation, Object> {
@Override
public boolean isValid(Objecto, ConstraintValidatorContext constraintValidatorContext) {
//此时name是当前非空校验的字段名
String name = String.valueOf(((ConstraintValidatorContextImpl) constraintValidatorContext).getConstraintViolationCreationContexts().get(0).getPath());
System.out.println(name + " value is " + String.valueOf(o));
//o是字段的值,我们需要校验是否为空
if(null == o){
//返回false代表校验失败
return false;
}
//返回true代表校验成功
return true;
}
}
4.生成请求类
public class TestReq {
//在需要校验的字段上打上自定义注解
@CheckParamNullAnnotation
private String param1;
public String getParam1() {
return param1;
}
public void setParam1(String param1) {
this.param1 = param1;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("TestReq{");
sb.append("param1='").append(param1).append('\'');
sb.append('}');
return sb.toString();
}
}
5.生成controller类
注意,在controller层需要校验的请求类前面一定要加 @Valid
@Controller
@ResponseBody
public class TestController {
@GetMapping(value = "/value")
//这边必须要加@Valid
public String testController(@Valid TestReq req){
return "123";
}
}
6.验证结果
启动项目,当param1值为空时,结果如下
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9kkaOFzY65UMNRUT6FlMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZwpmL2EzN0AzMzgTM2AzMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
当param1值不为空时,结果如下
7.自定义响应
如果说不想要系统带的400响应,那么在controller类中的req参数后面添加BindingResult入参,它会自动接收校验器的校验结果,此时校验器不会直接返回400。特别要注意的是,BindingResult入参一定要紧跟在校验请求体的请求后面,也就是一定要在@Valid修饰的对象后面,中间只要有间隔,都会失败。
@Controller
@ResponseBody
public class TestController {
@GetMapping(value = "/value")
//这边必须要加@Valid
public String testController(@Valid TestReq req, BindingResult binRes){
return "123";
}
}
当param1值为空时。其中errorCount代表请求消息体中有多少个字段校验失败。如果说req有两个参数,param1和param2,都打上非空注解。如果两个都为空,那么errorCount就是2,allError数组长度就是2,分别是param1校验失败和param2校验失败的信息。
当param1值不为空时。这边特别要注意,当errorCount为0的时候,allError是空的,所以不能取值。只有errorcount不为空时,才能取值allError。