一、什么是注解
- Java 注解是在 JDK5 時(shí)引入的新特性,注解(也被稱為元數(shù)據(jù))為我們?cè)诖a中添加信息提供了一種形式化的方法,使我們可以在稍后某個(gè)階段方便的使用這些數(shù)據(jù)。
二、JDK常用注解、元注解
常用注解如下
- @Override: 表示注解修飾的方法必須滿足重寫(xiě)的規(guī)則
- @Deprecated: 表示成員過(guò)時(shí),編譯器可以在程序運(yùn)行的時(shí)候獲取到該注解
- @SupressWarnings: 表示忽略編譯器的警告
常見(jiàn)的元注解: - @Target 可聲明在哪些目標(biāo)元素之前
ElementType.PARAMETER 聲明在參數(shù)上
ElementType.METHOD 聲明在方法上
ElementType.FIELD 聲明在字段上
ElementType.TYPE 聲明在類,接口,枚舉
ElementType.CONSTRUCTOR 聲明在構(gòu)造函數(shù)
ElementType.LOCAL_VARIABLE 局部變量
ElementType.ANNOTATION_TYPE 注釋類型聲明
ElementType.PACKAGE 包聲明
- @Retention 注解類的生命周期,及作用在哪個(gè)階段
RetentionPolicy.RUNTIME 程序運(yùn)行時(shí)期起作用
RetentionPolicy.SOURCE 在原文件中有效,被編譯器丟棄
RetentionPolicy.CLASS 程序編譯時(shí)期起作用
- @Documented 生成文檔的時(shí)候,會(huì)被寫(xiě)入到文檔中
三、如何使用注解
那么注解是怎么在某個(gè)階段被方便的使用呢,比如我們自定義一個(gè)注解,程序是如何使用的呢?其實(shí)注解不能自己起作用,需要程序員自身去針對(duì)注解寫(xiě)專門(mén)的邏輯,比如:日志切面針對(duì)日志注解進(jìn)行掃描處理;登錄注解使用攔截器去判斷是否包含再處理登錄邏輯。
四、注解實(shí)戰(zhàn)
接下來(lái)介紹下注解在實(shí)際中的使用,本次介紹的demo是一個(gè)利用注解來(lái)實(shí)現(xiàn)動(dòng)態(tài)參數(shù)校驗(yàn)功能。
1、首先新建一個(gè)驗(yàn)證注解,用于切入點(diǎn)進(jìn)行參數(shù)校驗(yàn)。
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidParam {
}
2、其次編寫(xiě)實(shí)現(xiàn)此注解的掃描,對(duì)含有此注解的方法參數(shù)進(jìn)行校驗(yàn)。這里我們用到了SpringMVC中的參數(shù)解析器,RequestBody注解的操作類繼承了AbstractMessageConverterMethodArgumentResolver,因此我們也使用此類實(shí)現(xiàn)RequestBody相同參數(shù)解析功能。
@Slf4j
public class ValidMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
private static Validator validator;
static {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public ValidMethodArgumentResolver(List<HttpMessageConverter<?>> converters, List<Object> requestResponseBodyAdvice){
super(converters, requestResponseBodyAdvice);
}
public ValidMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
super(converters);
}
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(ValidParam.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
final Object object = readWithMessageConverters(nativeWebRequest, methodParameter, methodParameter.getParameterType());
validateParam(object);
return object;
}
/**
* 參數(shù)校驗(yàn)
* @param object
*/
private void validateParam(Object object) {
Set<ConstraintViolation<Object>> constraintViolations;
if(object instanceof List){
List list = (List)object;
list.forEach(p -> validateParam(p));
}else {
constraintViolations = validator.validate(object);
if (!constraintViolations.isEmpty()) {
ConstraintViolation<Object> constraint = (ConstraintViolation<Object>)constraintViolations.iterator().next();
throw new ParamException(GloabEnums.PARAM_ERROR.getCode(), constraint.getMessage());
}
}
}
}
使用之后我們需要將此類配置到參數(shù)解析器中,及實(shí)現(xiàn)WebMvcConfigurer類中,具體代碼如下:
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
list.add(new ValidMethodArgumentResolver(converters));
}
首先是實(shí)現(xiàn)HandlerMethodArgumentResolver方法的supportsParameter方法,用于判斷是否包含自定義驗(yàn)證注解,其次包含則調(diào)用resolveArgument方法對(duì)參數(shù)進(jìn)行處理驗(yàn)證;這里我們使用了validator驗(yàn)證工具包進(jìn)行校驗(yàn)。
3、接下來(lái)我們對(duì)入?yún)?shí)體進(jìn)行校驗(yàn)驗(yàn)證,這里我們使用了NotB
@Data
public class UserDto {
@NotBlank(message = "名字不能為空")
private String name;
@NotBlank(message = "電話號(hào)碼為空")
@Pattern(regexp = "^1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\\d|9\\d)\\d{8}$", message = "電話號(hào)碼有誤")
private String phone;
private int age;
}
@Slf4j
@RestController
@RequestMapping("valid")
public class ValidController {
@PostMapping("user")
public R insertAddress(@ValidParam UserDto dto){
log.info("參數(shù)驗(yàn)證成功,執(zhí)行正常邏輯:{}", dto);
return R.ok();
}
}
調(diào)用該接口:

有人會(huì)問(wèn),當(dāng)我們使用增、刪、改需要使用同一個(gè)類,如用如上參數(shù)注解校驗(yàn),則會(huì)引起新增的方法判斷了修改方法的校驗(yàn),我們不同的方法是需要對(duì)此類不同的字段進(jìn)行校驗(yàn),這不是要寫(xiě)多個(gè)類才能進(jìn)行校驗(yàn)嗎?
既然有不同的需求,我們就有不同的方案來(lái)進(jìn)行處理,這里我們引入group組這個(gè)概念,將字段劃分組進(jìn)行區(qū)分校驗(yàn)。
public interface QueryGroup {}
public @interface ValidParam {
Class value() default QueryGroup.class;
}
@NotBlank(message = "名字不能為空", groups = QueryGroup.class)
private String name;
@PostMapping("user")
public R insertAddress(@ValidParam(value = QueryGroup.class) UserDto dto){
log.info("參數(shù)驗(yàn)證成功,執(zhí)行正常邏輯:{}", dto);
return R.ok();
}
/**
* 參數(shù)校驗(yàn)
* @param object
*/
private void validateParam(Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations;
if(object instanceof List){
List list = (List)object;
list.forEach(p -> validateParam(p, groups));
}else {
constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
ConstraintViolation<Object> constraint = (ConstraintViolation<Object>)constraintViolations.iterator().next();
throw new ParamException(GloabEnums.PARAM_ERROR.getCode(), constraint.getMessage());
}
}
}
四、總結(jié)
1、注解就是對(duì)代碼的解釋,可以把它類同于標(biāo)簽
2、注解的定義只是interface前加了個(gè)@
3、注解不會(huì)自己起作用,需要程序開(kāi)發(fā)人員去開(kāi)發(fā)對(duì)應(yīng)的功能