Java注解(Annotation) Java
注解(Annotation
)又称Java
标注,是JDK1.5
引入的一种注释机制
Java语言的类、方法、变量、参数和包等都可以被标注,和JavaDoc
不同,Java
标注可以通过反射获取标注内容,在编译器生成类文件时,标注可以被嵌入到字节码中。Java
虚拟机可以保留标注内容,当然它也支持自定义Java
标注
内置的注解 Java
定义了一套注解,共有7个,3个在java.lang
中,剩下4个在java.lang.annotation
中
作用在代码的注解是 1 2 3 4 5 6 @Override 检查该方法是否是重写方法,如果发现其父类,或者是引用的接口中并没有该方法,会编译报错 @Deprecated 标记过时方法,如果使用该方法,会报编译警告 @SuppressWarnings 指示编译器去忽略注解中声明的警告
作用在其他注解的注解(或者说是元注解) 1 2 3 4 5 6 7 8 9 10 11 @Retention @Retention (RetentionPolicy.RUNTIME) 运行时通过反射访问@Retention (RetentionPolicy.SOURCE) @Retention (RetentionPolicy.CLASS) 标记这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问 @Documented 标记这些注解是否包含在用户文档中 @Target 标记这个注解应该是哪种Java成员 @Inherited 标记这个注解是继承于哪个注解类(默认 注解没有继承于任何子类)
从Java 7开始,额外添加了3个注解 1 2 3 4 5 6 @SafeVarargs Java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用的警告 @FuncationalInterface Java8开始支持,标识一个匿名函数或函数式接口 @Repeatable Java8开始支持,标识某注解可以在同一个声明上使用多次
自定义注解 示例 1 2 3 4 5 @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation1 {}
上面的作用是定义一个Annotation
,它的名字是MyAnnotation1
,定义了MyAnnotation1
之后,我们可以在代码中通过@MyAnnotation1
来使用它,其他的,@Documented
,@Traget
,@Retention
,@interface
都是来修饰MyAnnotation1
的,下面说说它们的含义:
@interface 1 2 3 使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。 定义 Annotation 时,@interface 是必须的。 注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
@Documented 1 2 3 类和方法的 Annotation 在缺省情况下是不出现在 javadoc 中的。如果使用 @ Documented 修饰该 Annotation ,则表示它可以出现在 javadoc 中。 定义 Annotation 时,@ Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc 中。
@Target(ElementType.TYPE) 1 2 3 4 5 前面我们说过,ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定 Annotation 的类型属性。 @Target (ElementType.TYPE ) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE 。这就意味着,MyAnnotation1 是来修饰"类、接口(包括注释类型)或枚举声明" 的注解。 定义 Annotation 时,@Target 可有可无。若有 @Target ,则该 Annotation 只能用于它所指定的地方;若没有 @Target ,则该 Annotation 可以用于任何地方。
@Retention(RetentionPolicy.RUNTIME) 1 2 3 4 5 前面我们说过,RetentionPolicy 是 Annotation 的策略属性,而 @Retention 的作用,就是指定 Annotation 的策略属性。 @Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被虚拟机读取。定义 Annotation 时,@Retention 可有可无。若没有 @Retention ,则默认是 RetentionPolicy.CLASS。
实例1:反射获取注解 1 2 3 4 5 6 7 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyField { String description () ; int length () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MyFieldTest { @MyField(description = "用户名", length = 12) private String username; @MyField(description = "密码", length = 13) private String password; @Test public void testMyField () { Class c = MyFieldTest.class; for (Field f : c.getDeclaredFields()) { if (f.isAnnotationPresent(MyField.class)) { MyField annotation = f.getAnnotation(MyField.class); System.out.println(f.getName() + " " + annotation.description() + " " + annotation.length()); } } } }
1 2 username 用户名 12 password 密码 13
应用场景1:自定义注解+拦截器 实现登录校验 1 2 3 4 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginRequired {}
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class IndexController { @GetMapping("/sourceA") public String sourceA () { return "sourceA" ; } @LoginRequired @GetMapping("/sourceB") public String sourceB () { return "sourceB" ; } }
没有使用@LoginRequired
直接可以访问,使用之后,可以在拦截器中获取这个注解
1 handlerMethod.getMethod().getAnnotation(LoginRequired.class);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class SourceAccessInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("进入拦截器了" ); HandlerMethod handlerMethod = (HandlerMethod) handler; LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class); if (loginRequired == null ) { return true ; } response.setContentType("application/json;charset=utf-8" ); response.getWriter().print("你访问的资源需要登录" ); return false ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
1 2 3 4 5 6 7 @Configuration public class InterceptorTrainConfigurer implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**" ); } }
应用场景2:自定义注解+AOP实现日志打印 先导入切面需要的依赖包
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
定义一个注解
1 2 3 4 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyLog {}
定义一个切面类,见如下代码注释理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Aspect @Component public class MyLogAspect { @Pointcut("@annotation(com.example.annotation.anno.MyLog)") public void logPointCut () { } @Around("logPointCut()") public Object logAround (ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); Object[] param = joinPoint.getArgs(); StringBuilder sb = new StringBuilder(); for (Object o : param) { sb.append(o + "; " ); } System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString()); return joinPoint.proceed(); } }
IndexController中写一个sourceC进行测试,加上我们的自定义注解
1 2 3 4 5 @MyLog @GetMapping("/sourceC/{source_name}") public String sourceC (@PathVariable("source_name") String sourceName) { return "你正在访问sourceC资源" ; }
自定义注解—参数校验 依赖 1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.12</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
声明参数注解 1 2 3 4 @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ValidCom {}
声明字段注解 1 2 3 4 5 6 7 8 @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Length { int maxLength () default 0 ; int minLength () default 0 ; String message () default "" ; }
1 2 3 4 5 @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface NotNull {}
1 2 3 4 5 6 7 8 @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Value { int maxValue () ; int minValue () ; String message () default "" ; }
AOP 切入 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 @Aspect @Component public class ValidComAspect { @Pointcut("within(com.example.annotation.controller.IndexController)") public void validComCut () { } @Around("validComCut()") public Object validComCutAround (ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); if (args.length == 0 ) { return joinPoint.proceed(args); } Signature signature = joinPoint.getSignature(); Method method = ((MethodSignature) signature).getMethod(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int i = 0 ; i < parameterAnnotations.length; i++) { Object object = args[i]; Annotation[] paramAnn = parameterAnnotations[i]; if (object == null || paramAnn.length == 0 ) { continue ; } for (Annotation annotation : parameterAnnotations[i]) { if (annotation.annotationType().equals(ValidCom.class)) { validParam(object); } } } return joinPoint.proceed(); } private void validParam (Object object) throws IllegalAccessException { Field[] declaredFields = object.getClass().getDeclaredFields(); for (Field field : declaredFields) { Annotation[] declaredAnnotations = field.getDeclaredAnnotations(); for (Annotation anno : declaredAnnotations) { if (anno.annotationType().equals(NotNull.class)) { field.setAccessible(true ); Object o = field.get(object); System.out.println(o); Assert.notNull(o, field.getName() + "不可为空" ); } if (anno.annotationType().equals(Length.class)) { Length length = field.getAnnotation(Length.class); field.setAccessible(true ); String o = (String) field.get(object); Assert.isTrue(o.length() <= length.maxLength(), length.message()); } if (anno.annotationType().equals(Value.class)) { Value value = field.getAnnotation(Value.class); field.setAccessible(true ); Integer o = (Integer) field.get(object); Assert.isTrue(o <= value.maxValue() && o >= value.minValue(), value.message()); } } } } }
实体类 1 2 3 4 5 6 7 8 9 10 11 @Data @AllArgsConstructor @NoArgsConstructor @Builder public class PersonVo { @NotNull @Length(maxLength = 10, minLength = 1, message = "名字长度不在范围") private String name; @Value(minValue = 18, maxValue = 28, message = "年龄长度不再范围") private int age; }
Controller 1 2 3 4 5 6 7 8 @RestController public class IndexController extends BasicController { @RequestMapping(value = "index", method = RequestMethod.POST) public String index (@ValidCom @RequestBody PersonVo personVo) { return personVo.getName() + " " + personVo.getAge(); } }
BasicController (不是必须,只是想增加异常处理的类,接口得到的返回结果更好看点)
1 2 3 4 5 6 7 8 public class BasicController { @ExceptionHandler({IllegalArgumentException.class}) public @ResponseBody Response<String > handleIllegalArgumentException (HttpServletRequest req, IllegalArgumentException exception ) { return Response.FAIL(500 , exception.getMessage()); } }
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class Response <E > { private String status; private E result; private Integer error_code; private String error_msg; private long current_time; public long getCurrent_time () { return this .current_time; } public void setCurrent_time (long current_time) { this .current_time = current_time; } private Response (String status, E result, Integer error_code, String error_msg) { this .status = status; this .result = result; this .error_code = error_code; this .error_msg = error_msg; } public String getStatus () { return this .status; } public void setStatus (String status) { this .status = status; } public E getResult () { return this .result; } public void setResult (E result) { this .result = result; } public Integer getError_code () { return this .error_code; } public void setError_code (Integer error_code) { this .error_code = error_code; } public String getError_msg () { return this .error_msg; } public void setError_msg (String error_msg) { this .error_msg = error_msg; } public static final <E> Response<E> SUCCESS () { return SUCCESS((E) null ); } public static final <E> Response<E> SUCCESS (E result) { Response response = new Response("success" , result, (Integer) null , (String) null ); response.setCurrent_time(System.currentTimeMillis()); return response; } public static final <E> Response<E> FAIL (Integer error_code, String error_msg) { return new Response("fail" , (Object) null , error_code, error_msg); } public String toString () { return "Response{status='" + this .status + '\'' + ", result=" + this .result + ", error_code=" + this .error_code + ", error_msg='" + this .error_msg + '\'' + '}' ; } }
常见注解 @PostConstruct 这是Java
自己的注解,Java
注解的说明:@PostConstruct
该注解被用来修饰一个非静态的void()
方法。被@PostConstruct
修饰的方法会在服务器加载Servlet
的时候运行,并且只会被服务器执行一次。PostConstruct
在构造函数之后执行,init()
方法前执行。
通常我们会是在Spring
框架中使用到@PostConstruct
注解,该注解的方法在整个Bean
初始化中的执行顺序:
Constructor
(构造方法)-> @Autowired
(依赖注入) -> @PostConstruct
(注释的方法)
@JsonUnwrapped
com.fasterxml.jackson.annotation
定义在字段上,展开字段
@JsonNaming
com.fasterxml.jackson.databind.annotation
1 @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class )
字段驼峰命名转为下划线
@JsonInclude
com.fasterxml.jackson.annotation
1 @JsonInclude(JsonInclude.Include.NON_NULL)
字段为空不转
@FieldDefaults 1 @FieldDefaults(level = AccessLevel.PRIVATE)