1. Java注解
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java语言中的类、方法、变量、参数和包等都可以被标注。
在编译器生成类文件时,标注可以被嵌入到字节码中
因此Java虚拟机可以在运行时可以通过反射获取到标注内容。
2. 元注解
JDK中提供了一些基础注解,称为元注解。最常见的几种比如:
作用在代码上的:
- @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
作用在其他注解的:
- @Retention - 标识这个注解怎么保存,是只在代码中(SOURCE),还是编译入class文件中(CLASS),或者是在运行时可以通过反射访问(RUNTIME)。
- @Documented - 标记这个注解是否包含在JavaDoc中。
- @Target - 标记这个注解应该修饰哪种 Java 成员。
- @Inherited - 标记如果一个类具有继承注解,那么他所有的子类也拥有该注解
后续新加入的:
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
这里不对元注解进行详细的介绍,有需要的可以去查阅相关的文档。
3. 自定义注解
Java允许我们实现自定义注解来完成某些功能。
总的来说,自定义注解需要完成3步:定义注解、标注注解、处理注解
(1) 定义注解
使用@interface来定义一个注解,下面定义了一个名为MyAnnotation的注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value() default "MyAnnotation";
}
@Retention(RetentionPolicy.RUNTIME)表示该注解在运行时也有效,这样就可以使用反射的方式来获取到。
@Target(ElementType.TYPE)表示该注解是用在类或者接口上的
(2) 标注注解
@MyAnnotation("test")
public class MyClass1 { }
@MyAnnotation
public class MyClass2 { }
这里实现了两个类,分别使用MyAnnotation进行注解,MyClass1的注解中配置了属性值test,MyClass2没有配置属性值。
(3) 处理注解
public class MyClassTest {
@Test
public void test() {
new ApplicationContextRunner().withBean(MyClass1.class).withBean(MyClass2.class).run(context -> {
// 获取所有被MyAnnotation标注的类
Map beansWithAnnotation = context.getBeansWithAnnotation(MyAnnotation.class);
for (Map.Entry entry : beansWithAnnotation.entrySet()) {
System.out.println("BeanName is " + entry.getKey());
// 获取类上的MyAnnotation标注
MyAnnotation annotation = entry.getValue().getClass().getAnnotation(MyAnnotation.class);
System.out.println("the value of annotation is " + Objects.requireNonNull(annotation).value());
}
});
}
}
这里实现了一个测试类,根据注解的属性值的不同,打印不同的内容。
运行结果:
可以看到MyClass1打印出了设置的test,MyClass2打印出了默认的MyAnnotation。
4. 自定义条件注解
@Conditional是一个很常用的注解,用来根据一定的条件判断,是否给容器注入Bean的。
而@ConditionalOnProperty、@ConditionalOnClass等都是通过组合@Conditional,提供了更强大的功能。
接下来我们通过组合@Conditional,实现一个当属性name的值只要属于配置数组中的时候,就实现Bean的注入。
(1) 定义注解
定义一个ConditionalOnContains注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ContainsCondition.class)
public @interface ConditionalOnContains {
int[] value();
}
@Target(ElementType.METHOD)表示该注解是用在方法上的
(2) 标注注解
public class ContainsConfiguration {
@Bean
@ConditionalOnContains(value = {0, 1})
public ContainsTest1 containsTest1() {
return new ContainsTest1();
}
@Bean
@ConditionalOnContains(value = {1, 2})
public ContainsTest2 containsTest2() {
return new ContainsTest2();
}
@Bean
@ConditionalOnContains(value = {2, 3})
public ContainsTest3 containsTest3() {
return new ContainsTest3();
}
}
这里实现了三个类,分别使用ConditionalOnContains进行注解,ContainsTest1的注解中配置了value = {0,1}, ContainsTest2的注解中配置了value = {1,2}, ContainsTest3的注解中配置了value = {2,3}。
(3) 处理注解
public class ContainsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map annotationAttributes = metadata.getAnnotationAttributes(
ConditionalOnContains.class.getName());
int[] values = (int[]) Objects.requireNonNull(annotationAttributes).get("value");
for (int value : values) {
if (value == 1) {
return true;
}
}
return false;
}
}
这里对注解的属性值进行处理,如果包含1,则返回true,否则返回false.
运行结果:
可以看到打印出了containsTest1和containsTest2,而没有打印containsTest3,因为其value属性值不包含1.