跳到主要内容
Java 自定义注解详解 | 极客日志
Java java
Java 自定义注解详解 综述由AI生成 系统介绍了 Java 注解机制,涵盖内置注解、元注解定义、自定义注解语法及数据类型限制。通过反射 API 演示了运行时读取注解的方法,并结合 Spring AOP 与 IOC 展示了实际应用场景。最后补充了 Java 8 新增的重复注解与类型注解特性及其使用规范。
黑客 发布于 2026/3/25 更新于 2026/6/3 29 浏览1 理解 Java 注解
实际上 Java 注解与普通修饰符(public、static、void 等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {
@Test
public static void A () {
System.out.println("Test....." );
}
@Deprecated
@SuppressWarnings("unchecked")
public static void B () {}
}
@Test 实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。
而对于@Deprecated和@SuppressWarnings("unchecked"),则是 Java 本身内置的注解,在代码中,可以经常看见它们。
2 Java 内置标准注解
JavaSE 中内置三个标准注解,定义在 java.lang 中:
@Override :用于修饰此方法覆盖了父类的方法;
@Deprecated :用于修饰已经过时的方法;
@SuppressWarnings :用于通知 java 编译器禁止特定的编译警告。
2.1 @Override
用于标明此方法覆盖了父类的方法。
2.2 @Deprecated
用于标明已经过时的方法或类。用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。
2.3 @SuppressWarnings
用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。SuppressWarnings annotation 类型只定义了一个单一的成员,所以只有一个简单的 value={…}作为 name=value 对。又由于成员值是一个数组,故使用大括号来声明数组值。
其数组的值可以为下列枚举:
deprecation :使用了不赞成使用的类或方法时的警告;
unchecked :执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough :当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path :在类路径、源文件路径等中有不存在的路径时的警告;
serial :当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally :任何 finally 子句不能正常完成时的警告;
all :关于以上所有情况的警告。注意 :我们可以在下面的情况中缩写 annotation:当 annotation 只有单一成员,并成员命名为"value="。这时可以省去"value="。如:
@SuppressWarnings({"unchecked","deprecation"})
public void test1 () {}
3 注解基础
3.1 注解分类 根据注解参数的个数,注解分为标记注解、单值注解、完整注解三类。
(1)标记注解:一个没有成员定义的 Annotation 类型被称为标记注解。如:@Test,@Inherited,@Documented 等
(2)单值注解:只有一个值
(3)完整注解:拥有多个值。
根据注解使用方法和用途:
(1)JDK 内置系统注解
(2)元注解
(3)自定义注解
3.2 元注解 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
3.2.1 @Target 表示该注解用于什么地方,可能的值在枚举类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 新加入)
示例 1: @Target(ElementType.TYPE)
public @interface Table {
public String tableName () default "className" ;
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {}
@Table 可以用于注解类、接口 (包括注解类型) 或 enum 声明,而@NoDBColumn 仅可用于注解类的成员变量。
3.2.2 @Retention 用来约束注解的生命周期。可选的参数值在枚举类型RetentionPolicy 中,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime)。
类型枚举 说明 RetentionPolicy.SOURCE 注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的 class 文件里,如@Override) RetentionPolicy.CLASS 注解在 class 文件中可用,但会被 VM 丢弃(该类型的注解信息会保留在源码里和 class 文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义 Retention 值时,默认值是 CLASS RetentionPolicy.RUNTIME 注解信息将在运行期 (JVM) 也保留,因此可以通过反射机制 读取注解的信息(源码、class 文件和执行的时候都有注解的信息),如 SpringMvc 中的@Controller、@Autowired、@RequestMapping 等。这也是我们常用 的。
示例 1: @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,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。
3.2.3 @Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解 ,没有成员。
3.2.4 @Inherited @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 类型被发现,或者到达类继承结构的顶层。
3.3 注解支持的数据类型
所有基本类型(int,float,boolean,byte,double,char,long,short)
String
Class
enum
Annotation
上述类型的数组
注意 :倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解(SpringBoot 中多为嵌套注解)。
@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 name () default "" ;
Class<?> testCase() default Void.class;
Reference reference () default @Reference(next=true) ;
long [] value();
}
3.4 编译器对默认值的限制 编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以 null 作为值,这就是限制,没有什么利用可言,但造成一个元素的存在或缺失状态,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
static class None extends Throwable {
private static final long serialVersionUID = 1L ;
private None () {}
}
Class<? extends Throwable > expected() default None.class;
long timeout () default 0L ;
}
3.5 注解不支持继承 注解是不支持继承的,因此不能使用关键字 extends 来继承某个@interface,但注解在编译后,编译器会自动继承 java.lang.annotation.Annotation 接口。
3.6 快捷方式 所谓的快捷方式就是注解中定义了名为 value 的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用 key=value 的语法,而只需在括号内给出 value 元素所需的值即可。这可以应用于任何合法类型的元素,记住,这限制了元素名必须为 value。
如:2.3 节的示例中。
4 自定义注解 使用@interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
4.1 注解示例 先看一个 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 提供的元注解。
4.2 定义注解格式 @interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过 default 来声明参数的默认值。
public @interface 注解名 {定义体}
4.3 注解参数 (即方法) 注解里面的每一个方法实际上就是声明了一个配置参数,其规则如下:
①修饰符
只能用 public 或默认 (default) 这两个访问权修饰,默认为 default
基本数据类型(int,float,boolean,byte,double,char,long,short);
String 类型;
Class 类型;
enum 类型;
Annotation 类型;
以上所有类型的数组
③命名
对取名没有要求,如果只有一个参数成员,最好把参数名称设为"value",后加小括号。
⑤默认值
可以包含默认值,使用 default 来声明默认值。
4.4 示例 以下举两个例子:
LogThreadAnnotation 使用 Log4j2 时,控制多线程中线程日志数据的注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogThreadAnnotation {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceEnum value () default DataSourceEnum.MYSQL;
}
5 注解处理器 如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建与使用注解处理器。
5.1 反射机制 Retention.RUNTIME 时,Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。除此之外,Java 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
Class:类定义
Constructor:构造器定义
Field:类的成员变量定义
Method:类的方法定义
Package:类的包定义
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};
Color fruitColor () default Color.GREEN;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
public int id () default -1 ;
public String name () default "" ;
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 {
public static void main (String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
5.2 SpringAOP 除了通过反射工具自定义注解解释器外,在日常开发中用的最多的就是注解与 Spring AOP 结合完成特定的工作。
示例 下面以 4.4 节中的日志注解为例介绍。
LogThreadAnnotation 主要的通途就是,当标注@LogThreadAnnotation方法执行时,会根据方法参数判断,如果参数不一致,则整个方法及后续方法的日志都输出在一个 log 文件中,即每次标注@LogThreadAnnotation方法的日志都会在不同的日志文件中。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogThreadAnnotation {}
@Component
@Aspect
@ComponentScan
@EnableAspectJAutoProxy
public class LogThreadRecordAspect {
@Pointcut("@annotation(com.zznode.gum.task.core.aop.LogThreadAnnotation)")
public void addLogThread () {}
@Before("addLogThread()")
public void beforeAdvide () {
}
@After("addLogThread()")
public void afterAdvide () {
}
@Around("addLogThread()")
public void aroundAvide (ProceedingJoinPoint pjp) throws Throwable {
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();
LogUtils.logThreadEnd();
}
}
public class LogUtils {
public static void logThreadBegin (String key) {
ThreadContext.put("JobUUID" , key);
}
public static void logThreadEnd () {
ThreadContext.remove("JobUUID" );
}
}
@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());
}
}
<?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 >
<loggers >
<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 >
5.3 Spring IOC 除了配置 AOP 之外,其次就是结合 IOC,直接从 IOC 容器中拿取标注了指定注解的 Bean。
5.3.1 注解 @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ColumnCommandOrder {
int valueOrder () default 99 ;
int checkOrder () default 99 ;
String[] telexType() default {AnalyseConstants.TELEX_TYPE_NOTAMNCR};
}
5.3.2 注解解析 通过 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);
}
});
}
}
5.3.3 注解标注 @Slf4j
@Component
@ColumnCommandOrder(valueOrder = 12, checkOrder = 12)
public class ItemBParserCommand extends AbstractColunmParserCommand {
...
}
6 Java 8 中注解增强 对于元注解,Java 8 主要有两点改进:类型注解和重复注解。
6.1 重复注解 元注解@Repeatable 是 JDK1.8 新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的。
@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 注解后就可以采用如下的方式定义并使用了
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class)
public @interface FilterPath {
String value () ;
}
@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的注解时,可以通过这两个方法获取到注解相关信息。
旧版 API 中的getDeclaredAnnotation()和 getAnnotation()是不对@Repeatable注解的处理的 (除非该注解没有在同一个声明上重复出现)。
getDeclaredAnnotationsByType方法获取到的注解不包括父类,其实当 getAnnotationsByType()方法调用时,其内部先执行了getDeclaredAnnotationsByType方法,只有当前类不存在指定注解时,getAnnotationsByType()才会继续从其父类寻找,但请注意如果@FilterPath和@FilterPaths没有使用了@Inherited的话,仍然无法获取。
6.2 类型注解
6.2.1 注解使用的范围。 在 java 8 之前,注解只能是在声明的地方所使用,java8 开始,注解可以应用在任何地方。
TYPE_USE 则可以用于标注任意类型 (不包括 class)
@Interned MyObject();
myString = (@NonNull String) str;
if (input instanceof @NonNull String)
class Image implements @RectangularShape {}
void monitorTemperature () throws @CriticalTemperatureException {...}
在 Java 8 里面,当类型转化甚至分配新对象的时候,都可以在声明变量或者参数的时候使用注解。
Java 注解可以支持任意类型。
类型注解只是语法而不是语义,并不会影响 java 的编译时间,加载时间,以及运行时间,也就是说,编译成 class 文件的时候并不包含类型注解。
由上面的注解使用范围的变更,引出 ElementType 新增的两个类型。
6.2.2 新增的两种 ElementType 新增的两个注释的程序元素类型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER用来描述注解的新场合。
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(eg:声明语句、泛型和强制转换语句中的类型)。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
6.2.3 类型注解的作用 类型注解被用来支持在 Java 的程序中做强类型检查。配合第三方插件工具Checker Framework,可以在编译的时候检测出 runtime error(eg:UnsupportedOperationException; NumberFormatException;NullPointerException 异常等都是 runtime error),以提高代码质量。这就是类型注解的作用。
注意:
使用 Checker Framework 可以找到类型注解出现的地方并检查。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online