在实际的开发中,我们大量使用了注解,无论是spring、还是本身jdk提供的,注解都是围绕一个java程序员的开发生活,所以本篇主要介绍注解相关的概念、理论、实践。
定义注解
注解和异常非常相似,都可以自定义,但是我们自定义异常的场景比较多,但是注解就比较少。
overrider是本身jdk提供的,表示当前方法被覆盖的描述。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
我们自定义一个限流注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
public enum TimeUnit {SECOND,MINUTE,HOUR,DAY,MONTH}
String apiName();
int limitCount();
TimeUnit timeUnit() default TimeUnit.SECOND
}
target
target用来描述注解的使用范围,
public enum ElementType {
TYPE, // 类型 类,接口,枚举
FIELD, // 用于成员变量
METHOD, // 用于方法
PARAMETER, // 用于参数
CONSTRUCTOR, // 用于构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE,
PACKAGE, // 用于包
TYPE_PARAMETER,
TYPE_USE
}
一般主要常用的就是针对类、方法、成员变量。实际上注解只是一个标识作用。可以通过反射访问的代码元素,我们都可以通过注解标识。如果不使用target标识使用范围,默认就是做任何范围。
Retention
描述注解的可见范围,生命周期。
public enum RetentionPolicy {
SOURCE, // 源码中可见
CLASS,
RUNTIME
}
SOURCE代表仅在源码中可见,当编译器将源代码编译为字节码后,注解信息将被丢弃。不过编译器可以在可见范围内查找,比如override 查找其父类是否有对应的方法,没有就编译错误。class标识在字节码范围。runtime标识在运行时期。
源码->编译->运行时。
Documented
标识在java doc中进行输出
interface
class 代表类 interface 代表接口、enum代表枚举类、@interface代表注解。
标记注解
标记注解,其实就是使用不同的注解,放在方法或者类上。
@RateLimit(apiName = "/user/info",limitCount = 1000,
timeUnit = RateLimit.TimeUnit.SECOND)
public void getUserInfo() {
}
读取注解
定义注解、标记注解,还需要进行读取注解,也就是说需要通过响应逻辑的代码处理。对于java内建注解,编译器和JVM都可以对其进行读取和处理,比如override注解,编译器在编译代码时,会读取所有标记了@override的方法,并且检查父类中是否有同名方法。没有就编译报错。
对于自定义注解,需要我们开发相应的读取和处理逻辑,如何来读取代码中的注解信息。就需要使用上一节课中的反射语法。反射其实是作用于代码运行时。
注解应用
最常见的就是 替换配置文件,
spring中配置文件 一般通过xml进行定义,我们可以使用注解替代xml配置
那么spring容器时如何使用注解的?
在程序启动的时候,spring ioc容器利用反射获取到appConfig配置, 发现包含@configuration注解,便确定这个类时一个配置类。通过反射获取到对应方法的bean对象,并创建对应的对象,存储到一个大map中,key 为beanName value为对象,就可以通过getBean获取对象。
而平时常见的@service、@controller、@respository 都是同样的方式。
应用场景
自定义注解 csv文件
比如在实际的开发中,我们需要对一个对象的字段进行写入到csv文件中,但是有一些字段不想被写入。比如用户的基本信息(ID、name、age、phone、idcardnum、住址信息等),比如针对phone、和idcardnum 需要我们进行脱敏处理。或者忽略不写。 我们可以开发一个自定义注解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CsvIgnore {
}
@CsvIgnore
private String phone;
通过反射的方式在公共的csv文件操作逻辑进行忽略。这样其实就做到了透明化。使用者其实不用关心。
CsvIgnore ignoreProperty = field.getAnnotation(CsvIgnore.class);
if (!Objects.isNull(ignoreProperty)) {
csvIgnoreSet.add(field.getName());
}
自定义主从切换
在实际的开发中,我们可能有多个DB要切换操作,比如读取风控数据库、后台数据库、用户数据库等,如何在同一个请求中自定义获取数据源。我们可以通过注解的方式。通过AOP的方式
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceHolder {
DataSource value() default DataSource.SALVE;
}
@Around("@annotation(dataSourceHolder)")
public Object enhance(ProceedingJoinPoint joinPoint, DataSourceHolder dataSourceHolder) {
if (dataSourceHolder.value() == DataSource.MASTER) {
DynamicDataSourceHolder.setDataSourceTypeMaster();
} else {
DynamicDataSourceHolder.setDataSourceTypeSlave();
}
Object proceed = null;
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
} finally {
DynamicDataSourceHolder.clearDataSourceType();
}
return proceed;
}
小结
好了本篇主要详细介绍了java注解的相关使用,原理,关于如何获取注解,需要等下一篇的关于反射的讲解。以后写注解就知道为什么加一个@service就可以被扫描成bean对象使用,以及如何定义注解在 自己的项目中使用。