1 理解 Java 注解
实际上 Java 注解与普通修饰符(public、static、void 等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {
{
System.out.println();
}
{}
}
本文系统介绍了 Java 注解机制,涵盖内置注解、元注解定义、自定义注解语法及数据类型限制。通过反射 API 演示了运行时读取注解的方法,并结合 Spring AOP 与 IOC 展示了实际应用场景。最后补充了 Java 8 新增的重复注解与类型注解特性及其使用规范。

实际上 Java 注解与普通修饰符(public、static、void 等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {
{
System.out.println();
}
{}
}
@Test 实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。 而对于@Deprecated和@SuppressWarnings("unchecked"),则是 Java 本身内置的注解,在代码中,可以经常看见它们。
JavaSE 中内置三个标准注解,定义在 java.lang 中:
@Override:用于修饰此方法覆盖了父类的方法;
@Deprecated:用于修饰已经过时的方法;
@SuppressWarnings:用于通知 java 编译器禁止特定的编译警告。
@Override用于标明此方法覆盖了父类的方法。
@Deprecated用于标明已经过时的方法或类。用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。
@SuppressWarnings用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。SuppressWarnings annotation 类型只定义了一个单一的成员,所以只有一个简单的 value={…}作为 name=value 对。又由于成员值是一个数组,故使用大括号来声明数组值。
其数组的值可以为下列枚举:
注意:我们可以在下面的情况中缩写 annotation:当 annotation 只有单一成员,并成员命名为"value="。这时可以省去"value="。如:
@SuppressWarnings({"unchecked","deprecation"})
public void test1() {}
根据注解参数的个数,注解分为标记注解、单值注解、完整注解三类。
(1)标记注解:一个没有成员定义的 Annotation 类型被称为标记注解。如:@Test,@Inherited,@Documented 等
(2)单值注解:只有一个值
(3)完整注解:拥有多个值。
根据注解使用方法和用途: (1)JDK 内置系统注解 (2)元注解 (3)自定义注解
所谓元注解就是标记其他注解的注解。
Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
表示该注解用于什么地方,可能的值在枚举类ElementType。
| 类型枚举 | 说明 |
|---|---|
| ElementType.CONSTRUCTOR | 标明注解可以用于构造函数声明 |
| ElementType.FIELD | 标明该注解可以用于字段 (域) 声明,包括 enum 实例 |
| ElementType.LOCAL_VARIABLE | 标明注解可以用于局部变量声明 |
| ElementType.METHOD | 标明注解可以用于方法声明 |
| ElementType.PACKAGE | 标明注解可以用于包声明 |
| ElementType.PARAMETER | 标明注解可以用于参数声明 |
| ElementType.TYPE | 标明注解可以用于类、接口(包括注解类型)或 enum 声明 |
| ElementType.ANNOTATION_TYPE | 标明注解可以用于注解声明 (应用于另一个注解上) |
| ElementType.TYPE_PARAMETER | 标明注解可以用于类型参数声明(1.8 新加入) |
| ElementType.TYPE_USE | 类型使用声明(1.8 新加入) |
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {}
@Table 可以用于注解类、接口 (包括注解类型) 或 enum 声明,而@NoDBColumn 仅可用于注解类的成员变量。
用来约束注解的生命周期。可选的参数值在枚举类型RetentionPolicy中,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime)。
| 类型枚举 | 说明 |
|---|---|
| RetentionPolicy.SOURCE | 注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的 class 文件里,如@Override) |
| RetentionPolicy.CLASS | 注解在 class 文件中可用,但会被 VM 丢弃(该类型的注解信息会保留在源码里和 class 文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义 Retention 值时,默认值是 CLASS |
| RetentionPolicy.RUNTIME | 注解信息将在运行期 (JVM) 也保留,因此可以通过反射机制读取注解的信息(源码、class 文件和执行的时候都有注解的信息),如 SpringMvc 中的@Controller、@Autowired、@RequestMapping 等。这也是我们常用的。 |
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
Column 注解的的 RetentionPolicy 的属性值是 RUNTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。
用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。
@Inherited元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。
注意:@Inherited annotation 类型是被标注过的 class 的子类所继承。类并不从它所实现的接口继承 annotation,方法并不从它所重载的方法继承 annotation。
当@Inherited annotation 类型标注的 annotation 的 Retention 是 RetentionPolicy.RUNTIME,则反射 API 增强了这种继承性。如果我们使用 java.lang.reflect 去查询一个@Inherited annotation 类型的 annotation 时,反射代码检查将展开工作:检查 class 和其父类,直到发现指定的 annotation 类型被发现,或者到达类继承结构的顶层。
注解支持的元素数据有:
注意:倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解(SpringBoot 中多为嵌套注解)。
示例 1:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference {
boolean next() default false;
}
public @interface AnnotationElementDemo {
// 枚举类型
enum Status {FIXED,NORMAL};
// 声明枚举
Status status() default Status.FIXED;
// 布尔类型
boolean showSupport() default false;
//String 类型
String name() default "";
//class 类型
Class<?> testCase() default Void.class;
// 注解嵌套
Reference reference() default @Reference(next=true);
// 数组类型
long[] value();
}
编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以 null 作为值,这就是限制,没有什么利用可言,但造成一个元素的存在或缺失状态,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。
如:@Test
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
/**
* Default empty exception
*/
static class None extends Throwable {
private static final long serialVersionUID = 1L;
private None() {}
}
Class<? extends Throwable> expected() default None.class;
long timeout() default 0L;
}
注解是不支持继承的,因此不能使用关键字 extends 来继承某个@interface,但注解在编译后,编译器会自动继承 java.lang.annotation.Annotation 接口。
所谓的快捷方式就是注解中定义了名为 value 的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用 key=value 的语法,而只需在括号内给出 value 元素所需的值即可。这可以应用于任何合法类型的元素,记住,这限制了元素名必须为 value。 如:2.3 节的示例中。
使用@interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
先看一个 Java 的注解类@Deprecated的源码:
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE})
public @interface Deprecated {}
(1)首先,使用@interface声明了 Deprecated 注解
(2)其次,使用@Target注解传入{CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}参数,来标明@Deprecated可以用在构造器,字段,局部变量,方法,包,参数,类或接口上。
(3)再者,使用@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时。
(4)最后,使用@Documented则用来表明,当前注解在生成javadoc时需要展示,否则不予显示。
从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成 Deprecated.class 文件。对于@Target和@Retention,@Documented是由 Java 提供的元注解。
@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过 default 来声明参数的默认值。
public @interface 注解名 {定义体}
注解里面的每一个方法实际上就是声明了一个配置参数,其规则如下: ①修饰符 只能用 public 或默认 (default) 这两个访问权修饰,默认为 default
②类型 注解参数只支持以下数据类型:
③命名 对取名没有要求,如果只有一个参数成员,最好把参数名称设为"value",后加小括号。
④参数 注解中的方法不能存在参数
⑤默认值 可以包含默认值,使用 default 来声明默认值。
以下举两个例子: LogThreadAnnotation 使用 Log4j2 时,控制多线程中线程日志数据的注解。
/**
* @Description log4j 多线程日志输出注解
* 使用该注解的方法,其运行日志除了在正常的 log 文件中输出外。
* 还会在 thread 目录下 job:uuid-YYYYMMDD.log 文件中输出。
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogThreadAnnotation {}
DataSource 多数据源切换注解
/**
* @Description 用于 aop 类中当作切入点来选择数据源
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceEnum value() default DataSourceEnum.MYSQL;
}
如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建与使用注解处理器。
Retention.RUNTIME 时,Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。除此之外,Java 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射 API 扩充了读取运行时 Annotation 信息的能力。当一个 Annotation 类型被定义为运行时的 Annotation 后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的 Annotation 才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象之后,程序就可以调用该对象的如下四个个方法来访问 Annotation 信息(以上 5 个类都实现以下的方法):
| 返回值 | 方法名称 | 说明 |
|---|---|---|
| Annotation | getAnnotation(Class annotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 |
| Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的 |
| boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
| Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为 0 的数组 |
| Annotation[] | getAnnotationsByType(Class annotationClass) | JDK1.8 新增 |
| Annotation[] | getDeclaredAnnotationsByType(Class annotationClass) | JDK1.8 新增 |
/***********注解声明***************/
/**
* 水果名称注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果颜色注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*/
public enum Color {BLUE,RED,GREEN};
/**
* 颜色属性
* @return
*/
Color fruitColor() default Color.GREEN;
}
/**
* 水果供应者注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
* @return
*/
public int id() default -1;
/**
* 供应商名称
* @return
*/
public String name() default "";
/**
* 供应商地址
* @return
*/
public String address() default "";
}
/***********注解使用***************/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
@FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路 89 号红富士大厦")
private String appleProvider;
}
/***********注解处理器***************/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz) {
String strFruitName = " 水果名称:";
String strFruitColor = " 水果颜色:";
String strFruitProvicer = "供应商信息:";
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(FruitName.class)) {
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName = strFruitName + fruitName.value();
System.out.println(strFruitName);
} else if (field.isAnnotationPresent(FruitColor.class)) {
FruitColor fruitColor = (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor = strFruitColor + fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
} else if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:" + fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
/***********输出结果***************/
public class FruitRun {
/**
* @param args
*/
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
除了通过反射工具自定义注解解释器外,在日常开发中用的最多的就是注解与 Spring AOP 结合完成特定的工作。
下面以 4.4 节中的日志注解为例介绍。
LogThreadAnnotation 主要的通途就是,当标注@LogThreadAnnotation方法执行时,会根据方法参数判断,如果参数不一致,则整个方法及后续方法的日志都输出在一个 log 文件中,即每次标注@LogThreadAnnotation方法的日志都会在不同的日志文件中。
/**
* @Description log4j 多线程日志输出注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogThreadAnnotation {}
/**
* @Description log4j 多线程日志控制切面
*/
@Component
@Aspect
@ComponentScan
@EnableAspectJAutoProxy
public class LogThreadRecordAspect {
@Pointcut("@annotation(com.zznode.gum.task.core.aop.LogThreadAnnotation)")
public void addLogThread() {}
@Before("addLogThread()")
public void beforeAdvide() {
// TODO 操作日志@Before 方法执行前
}
@After("addLogThread()")
public void afterAdvide() {
// TODO 操作日志@After 方法执行后
}
@Around("addLogThread()")
public void aroundAvide(ProceedingJoinPoint pjp) throws Throwable {
// 操作日志@Around 方法执行前
Object[] args = pjp.getArgs();
if (args != null && args.length > 1) {
String jobId = args[0].toString();
String uuid = args[1].toString();
LogUtils.logThreadBegin(jobId + ":" + uuid);
}
// 方法执行
pjp.proceed();
// 操作日志@Around 方法执行后
LogUtils.logThreadEnd();
}
}
/**
* @Description Log4j2 多线程输出日志工具类
*/
public class LogUtils {
/**
* 开始日志输出到指定线程
* @param key
*/
public static void logThreadBegin(String key) {
ThreadContext.put("JobUUID", key);
}
/**
* 结束日志输出
*/
public static void logThreadEnd() {
ThreadContext.remove("JobUUID");
}
}
使用
/**
* @Description 数据汇聚计算 Controller
*/
@Slf4j
@RestController
@RequestMapping("/dataConverge")
@Api(value = "数据计算任务 RESTFUL")
public class DataConvergeController {
@Autowired
private DataConvergeService dataConvergeService;
@ApiOperation(value = "报表指标天汇聚计算任务", notes = "报表指标天汇聚计算任务")
@GetMapping("/rptIndexDayConverge")
@ApiImplicitParams({
@ApiImplicitParam(name = "jobId", value = "任务编号", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "uuid", value = "任务实例编号", required = true, dataType = "String", paramType = "query")
})
@ResponseBody
@LogThreadAnnotation
protected Result<String> rptIndexDayConverge(@RequestParam(name="jobId") String jobId, @RequestParam(name="uuid") String uuid) {
dataConvergeService.rptIndexDayConverge(jobId, uuid);
// 返回处理中
return new Result<>(EnumResult.EXECUTING.getIndex(), EnumResult.EXECUTING.getName());
}
}
另附 log4j2.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN" monitorInterval="60">
<Properties>
<Property name="PATTERN">%d{DEFAULT} [%t] %-5p %c{1}.%M %L - %msg%xEx%n</Property>
</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${PATTERN}"/>
</Console>
<RollingFile name="TRACE" fileName="${sys:app.log.home}/task-all.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-all-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${PATTERN}"/>
<SizeBasedTriggeringPolicy size="20MB"/>
</RollingFile>
<RollingFile name="DEBUG" fileName="${sys:app.log.home}/task-debug.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-debug-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${PATTERN}"/>
<SizeBasedTriggeringPolicy size="20MB"/>
</RollingFile>
<RollingFile name="INFO" fileName="${sys:app.log.home}/task-info.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-info-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${PATTERN}"/>
<SizeBasedTriggeringPolicy size="20MB"/>
</RollingFile>
<RollingFile name="ERROR" fileName="${sys:app.log.home}/task-error.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-error-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${PATTERN}"/>
<SizeBasedTriggeringPolicy size="20MB"/>
</RollingFile>
<Routing name="thread">
<Routes pattern="$${ctx:JobUUID}">
<Route>
<Filename>File-${ctx:JobUUID}</Filename>
<fileName>${sys:app.log.home}/threads/${ctx:JobUUID}.log</fileName>
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${PATTERN}"/>
</Route>
</Routes>
</Routing>
</appenders>
<!--然后定义 logger,只有定义了 logger 并引入的 appender,appender 才会生效 -->
<loggers>
<!--过滤掉 spring 和 mybatis 的一些无用的 DEBUG 信息 -->
<logger name="org.springframework" level="INFO"/>
<logger name="org.mybatis" level="debug"/>
<logger name="springfox.documentation" level="ERROR"/>
<logger name="io.netty" level="INFO"/>
<logger name="org.apache" level="info"/>
<logger name="reactor.util" level="info"/>
<logger name="org.flowable" level="info"/>
<logger name="com.test" level="debug"/>
<logger name="io.lettuce" level="info"/>
<logger name="org.quartz" level="info"/>
<logger name="org.hibernate.validator" level="info"/>
<root level="trace">
<appender-ref ref="Console"/>
<appender-ref ref="TRACE"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="INFO"/>
<appender-ref ref="ERROR"/>
<appender-ref ref="thread"/>
</root>
</loggers>
</configuration>
除了配置 AOP 之外,其次就是结合 IOC,直接从 IOC 容器中拿取标注了指定注解的 Bean。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ColumnCommandOrder {
/**
* 默认值处理顺序
* @return 序号
*/
int valueOrder() default 99;
/**
* 检查处理顺序
* @return 序号
*/
int checkOrder() default 99;
/**
* 指定报文类型。
* 只有指定的报文类型,才使用该 Command
* @return 报文类型数组
*/
String[] telexType() default {AnalyseConstants.TELEX_TYPE_NOTAMNCR};
}
通过 IOC 获取标注了该注解的类实例。 (1)ApplicationContext 的工具类
@Component
public class AnalyseServiceContext implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AnalyseServiceContext.applicationContext = applicationContext;
}
public static <T> T getBean(String beanName) {
return (T) applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static Map<String, Object> getBeanMap(Class<? extends Annotation> tClass) {
return applicationContext.getBeansWithAnnotation(tClass);
}
public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
return applicationContext.getBeansOfType(clazz);
}
}
注解解析
public class TelexBusinessCheckParserHandler implements ITelexParserHandler {
@Override
public void parserTelex(ITelexBusinessHandler businessHandler) {
if (businessHandler.isErrorOut()) {
return;
}
TreeMap<Integer, ITelexColumnParserCommand> commandTreeMap = Maps.newTreeMap();
Map<String, Object> maps = AnalyseServiceContext.getBeanMap(ColumnCommandOrder.class);
for (Map.Entry<String, Object> bean : maps.entrySet()) {
Object obj = bean.getValue();
ColumnCommandOrder annotation = obj.getClass().getAnnotation(ColumnCommandOrder.class);
if (null != annotation) {
int order = annotation.checkOrder();
commandTreeMap.put(order, (ITelexColumnParserCommand) obj);
}
}
commandTreeMap.forEach((k, v) -> {
v.setBusinessHandler(businessHandler);
if (log.isDebugEnabled()) {
log.debug("业务规则判断{},处理类{}", k, v.getClass().getSimpleName());
}
try {
ColumnCommandOrder annotation = v.getClass().getAnnotation(ColumnCommandOrder.class);
if (ArrayUtils.contains(annotation.telexType(), businessHandler.getOutputType().getApplicationCode())) {
v.columnCheckHandler();
}
} catch (Exception e) {
log.error("业务规则判断{},处理类{},异常{}", k, v.getClass().getSimpleName(), e);
}
});
}
}
@Slf4j
@Component
@ColumnCommandOrder(valueOrder = 12, checkOrder = 12)
public class ItemBParserCommand extends AbstractColunmParserCommand {
...
}
对于元注解,Java 8 主要有两点改进:类型注解和重复注解。
元注解@Repeatable 是 JDK1.8 新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的。
//Java8 前无法这样使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}
Java8 前如果是想实现类似的功能,我们需要在定义@FilterPath 注解时定义一个数组元素接收多个值如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
String[] value();
}
// 使用
@FilterPath({"/update","/add"})
public class A {}
但在 Java8 新增了@Repeatable 注解后就可以采用如下的方式定义并使用了
// 使用 Java8 新增@Repeatable 原注解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class)
public @interface FilterPath {
String value();
}
// 自定义一个包装类 FilterPaths 注解用来放置一组具体的 FilterPath 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
FilterPath[] value();
}
// 使用案例新方法
@FilterPath("/web/update")
@FilterPath("/web/add")
class AA {}
// 使用案例旧方法
@FilterPaths({@FilterPath("/web/update"), @FilterPath("/web/add")})
class AA {}
见 5.1 节,为了处理上述的新增注解,Java8 还在 AnnotatedElement 接口新增了getDeclaredAnnotationsByType() 和 getAnnotationsByType()两个方法并在接口给出了默认实现,在指定@Repeatable的注解时,可以通过这两个方法获取到注解相关信息。
注意:
getDeclaredAnnotation()和 getAnnotation()是不对@Repeatable注解的处理的 (除非该注解没有在同一个声明上重复出现)。getDeclaredAnnotationsByType方法获取到的注解不包括父类,其实当 getAnnotationsByType()方法调用时,其内部先执行了getDeclaredAnnotationsByType方法,只有当前类不存在指定注解时,getAnnotationsByType()才会继续从其父类寻找,但请注意如果@FilterPath和@FilterPaths没有使用了@Inherited的话,仍然无法获取。在 java 8 之前,注解只能是在声明的地方所使用,java8 开始,注解可以应用在任何地方。
// 用于构造函数,创建类实例 new
@Interned MyObject();
// 用于强制类型转换和 instanceof 检查,注意这些注解中用于外部工具,它们不会对类型转换或者 instanceof 的检查行为带来任何影响。
myString = (@NonNull String) str;
if (input instanceof @NonNull String)
// 用于父类或者接口
class Image implements @RectangularShape {}
// 用于指定异常
void monitorTemperature() throws @CriticalTemperatureException {...}
// 标注在类型参数上
class D<@ParameterT> {}
注意:
由上面的注解使用范围的变更,引出 ElementType 新增的两个类型。
新增的两个注释的程序元素类型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER用来描述注解的新场合。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
类型注解被用来支持在 Java 的程序中做强类型检查。配合第三方插件工具Checker Framework,可以在编译的时候检测出 runtime error(eg:UnsupportedOperationException; NumberFormatException;NullPointerException 异常等都是 runtime error),以提高代码质量。这就是类型注解的作用。
注意: 使用 Checker Framework 可以找到类型注解出现的地方并检查。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online