在 Java 体系中(截止到 17 ),元注解只有 5 个
JDK中的5个元注解
@Target
指定一个注解的使用范围,表示被描述的注解可以用在什么地方,想要让一个注解可以被使用,就必须使用 @Target 来标注它的使用范围
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
ElementType 是一个枚举类
其中列举了可以使用注解的元素类型:
名称 | 说明 |
---|---|
TYPE | 用于类/接口/枚举 |
FIELD | 用于成员变量(包含枚举常量) |
METHOD | 用于方法 |
PARAMETER | 用于形式参数 |
CONSTRUCTOR | 用于构造函数 |
LOCAL_VARIABLE | 用于局部变量 |
ANNOTATION_TYPE | 用于注解类型 |
PACKAGE | 用于包 |
TYPE_PARAMETER | 用于类型参数(jdk8新增) |
TYPE_USE | 用于类型使用(jdk8新增) |
MODULE | 用于模块(jdk9新增) |
@Documented
会被 JavaDoc 工具提取成文档
javadoc -c mydoc xxx.java
如果类里面使用的注解上没有 @Documented 那么生成的文档也不会。如果有,生成的文档也会保留。
@Retention
用于描述注解的保留策略,表示在什么级别保存该注解信息
注意:如果我们定义的一个注解需要在运行期间通过反射读取,那么就需要把 RetentionPolicy 设置成 RUNTIME。
@Inherited
指定该注解可以被继承
它只会影响类上面的注解,而方法和属性等上面的注解的继承性是不受 @Inherited 影响的
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
名称 | 说明 |
---|---|
SOURCE | 注解会被编译器丢弃,即只在原文件中保留 |
CLASS | 编译器将注解记录在 Class 文件中,但不需要在运行时由虚拟机保留。这是所有注解默认保留策略 |
RUNTIME | 编译器将注解记录在 Class 文件中,并在运行时由虚拟机保留,因此可以以反射方式读取他们 |
@Repeatable
是 Java8 新增的一个元注解,使用该注解来标识允许一个注解在一个元素上使用多次
注解的继承与组合
注解类是不能继承其他类也不能实现其他接口的。但是,注解之间是可以建立组合关系的
类似于 @SpringBootApplication 注解,就是多个注解的组合;
注解的组合层数没有限制,可以无限组合;但是当我们通过反射获取一个类的注解时,只能获取组合注解,无法获取被组合的注解,需要通过组合注解的二次解析才能得到
如果使用 Spring 那么直接使用 Spring 中的 AnnontatedElementUtils 的 getMergedAnnotation 方法可以获取被组合的注解。
日常开发中常用的注解
- 使用自定义注解做日志记录
- 使用自定义注解,在接口方法做缓存
- 参数校验
等等
不要过度依赖注解
注解使用比较简单,但是其部分内部逻辑很容易被忽略
比如:
在 Spring 体系中,关于事物的管理有两种模式,分别是编程式事务和声明式事务,其中声明式事务就是注解实现,但是不建议过度依赖声明式事务
编程式事务
基于底层的 API ,如 PlatformTransactionManager/TransactionDefinition/TransactionTemplate等核心接口,开发者完全可以通过编程的方式进行事务的管理
需要开发者在代码中手动管理事务的开启/提交/和回滚等操作,可以通过Api自己控制事务
public void test(){
TransactionDefinition def=new DefaultTransactionDefinition();
TransactionStatus status=transactionManager.getTransaction(def);
try{
//事务操作
//事务提交
transactionManager.commit(status);
}catch(Exception e){
//事务回滚
transactionManager.rollback(status);
throw e;
}
}
声明式事务
允许开发者在配置的帮助下来管理事务,而不需要依赖底层API进行硬编码
比如 @Transactional 注解
它会自动进行事务的开启/提交/回滚等操作,使用 AOP 实现的,本质上就是在目标方法执行前后进行拦截,使用这种方式,对代码没有侵入性,在方法内只需要编写业务逻辑即可
声明式事务粒度问题
声明式事务的最小粒度要作用在方法上。
如果一个方法是被事务嵌套的,那么就可能在方法中加入一些如 RPC 远程调用/消息发送/缓存更新和文件写入等操作。
声明式事务失效场景
- 修饰在非 public 方法上
- 注解属性 propagation 设置错误
- 注解属性rollbackFor 设置错误
- 同一个类中调用方法
- 异常被 catch 捕获
- 数据库引擎不支持