一、条件注解
1、@Conditional注解
@Conditional注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
由@Conditional注解的源码可知,由于元素value没有默认值,在使用@Conditional注解时,必须指定value的值,value的值是Class数组。
使用@Conditional注解的步骤:
1.自定义一个条件类,实现org.springframework.context.annotation.Condition接口,重写matches方法,根据实际需求判断返回true或false,spring容器会根据此处返回的true或false对使用@Conditional注解的代码进行处理。
2.在需要的地方使用@Conditional注解,该注解的value值是上面自定义的条件类。
@Conditional注解可以用在类上、方法上和其他注解上。
如果@Conditional注解用在类上,spring容器会根据自定义的条件类返回的结果来决定是否将当前类加载到容器中,如果自定义条件类返回的结果是true,则将当前类加载到spring容器中,否则不加载到spring容器中,自然就无法使用当前类。
如果@Conditional注解用在方法上,且自定义条件类返回的结果是true,则当前方法有效,可以被使用,否则当前方法不能被使用。
如果@Conditional注解用在其他注解上,说明其他注解也是一个条件注解,自定义条件注解就是通过@Conditional注解实现的。
自定义一个条件类DemoCondition:
package com.study.conditional;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ObjectUtils;
public class DemoCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//获取ioc容器使用的beanFactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
//获取当前环境(java运行环境所在的环境)
Environment environment = conditionContext.getEnvironment();//
//获取定义bean的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//获取资源加载器
ResourceLoader resourceLoader = conditionContext.getResourceLoader();
String property = environment.getProperty("os.name");
if (!ObjectUtils.isEmpty(property) && property.contains("Windows")){
System.out.println("输出信息:==>"+property);
return true;
}
/*if (!ObjectUtils.isEmpty(property) && property.contains("Linux")){
System.out.println("输出信息:==>"+property);
return true;
}*/
//默认返回false,前面通过获取到的信息进行if条件判断,符合则return true
return false;
}
}
通过@Conditional注解使用自定义的条件类:
package com.study.conditional;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
@Component
@Conditional(DemoCondition.class)
public class ConditionService {
public void add(){
System.out.println("新增数据......");
}
}
测试是否可以获取到ConditionService对象:
package com.study.conditional;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ConditionTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionService.class);
//如果DemoCondition#matches方法返回true,则此处可以获取到ConditionService对象,否则抛出异常
ConditionService service = context.getBean(ConditionService.class);
service.add();
}
}
输出结果:
如果自定义的条件类DemoCondition#matches方法返回false,则获取ConditionService的bean对象时就会报错:没有合格可用的ConditionService类型的bean对象。
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.study.conditional.ConditionService' available
把@Conditional用在方法上:
package com.study.conditional;
import com.study.annotation.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConditionService {
@Conditional(DemoCondition.class)
@Bean("person")
public Person person(){
System.out.println("创建对象......");
return new Person();
}
}
测试:
package com.study.conditional;
import com.study.annotation.Person;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ConditionTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionService.class);
ConditionService service = context.getBean(ConditionService.class);
//如果DemoCondition#matches方法返回true,则此处可以获取到Person对象,否则抛出异常
Person person = service.person();
System.out.println("person=========" + person);
}
}
输出结果:
如果DemoCondition#matches方法返回false,则抛出异常。
2、@ConditionalOnProperty注解
Spring Boot源码中大量使用@ConditionalOnProperty来控制Configuration是否生效。
下面看一下ConditionalOnProperty注解的源码,该注解使用了@Conditional注解,该注解生效的前提是先创建OnPropertyCondition的bean对象。
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
/**
* 数组,获取对应property名称的值,与name不可同时使用。
*
* Alias for {@link #name()}.
* @return the names
*/
String[] value() default {};
/**
* property名称的前缀,可有可无,根据实际情况进行配置。
* 前缀将会应用于每个属性的前缀。如果未指定,前缀将自动结束.
*
* A prefix that should be applied to each property. The prefix automatically ends
* with a dot if not specified.
* @return the prefix
*/
String prefix() default "";
/**
* 数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用。
*
* The name of the properties to test. If a prefix has been defined, it is applied to
* compute the full key of each property. For instance if the prefix is
* {@code app.config} and one value is {@code my-value}, the fully key would be
* {@code app.config.my-value}
* <p>
* Use the dashed notation to specify each property, that is all lower case with a "-"
* to separate words (e.g. {@code my-long-property}).
* @return the names
*/
String[] name() default {};
/**
* 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置。
*
* The string representation of the expected value for the properties. If not
* specified, the property must <strong>not</strong> be equals to {@code false}.
* @return the expected value
*/
String havingValue() default "";
/**
* 缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
*
* Specify if the condition should match if the property is not set. Defaults to
* {@code false}.
* @return if should match if the property is missing
*/
boolean matchIfMissing() default false;
}
下面对@ConditionalOnProperty注解进行简单测试:
配置文件application.yml:
cfg:
psn: pers
std: stud
配置类Configuration:
package com.study.conditionOnProperty;
import com.study.annotation.Person;
import com.study.annotation.Student;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConditionOnPropConfig {
//当配置文件中有cfg.psn属性,且该属性的值是pers时,才创建Person对象
@ConditionalOnProperty(value = "cfg.psn",havingValue = "pers")
@Bean("person01")
public Person person01(){
System.out.println("=========创建person01对象==========");
return new Person();
}
//由于matchIfMissing=true,所以无论配置文件中有没有cfg.pesn属性,都会创建Person对象
@ConditionalOnProperty(prefix ="cfg",name = "pesn",havingValue = "pers",matchIfMissing = true)
@Bean("person02")
public Person person02(){
System.out.println("=========创建person02对象==========");
return new Person();
}
//当配置文件中有cfg.std属性,且该属性的值是stu时,才创建Student对象
@ConditionalOnProperty(value = "cfg.std",havingValue = "stu")
@Bean("student01")
public Student student01(){
System.out.println("=========创建student01对象=========");
return new Student();
}
}
启动类:
package com.study.conditionOnProperty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConditionApplication {
public static void main(String[] args) {
SpringApplication.run(ConditionApplication.class,args);
}
}
输出结果:
=========创建person01对象==========
=========创建person02对象==========
由于配置文件中cfg.std属性的值是stud,与havingValue = "stu"指定的值不同,所以没有创建student01对象。
下面对@ConditionalOnProperty注解的源码进行分析。
由源码可知,@ConditionalOnProperty上使用了@Conditional({OnPropertyCondition.class})注解,根据前面对@Conditional注解的分析可知,OnPropertyCondition必然直接或间接的实现Condition接口,重写matches方法,根据matches方法的返回值true或false来决定使用@Conditional注解的类或方法是否可用。
OnPropertyCondition继承了SpringBootCondition,SpringBootCondition实现了Condition接口,重写了matches方法,所以OnPropertyCondition类就会继承父类的matches方法,下面是父类SpringBootCondition的matches方法源码。OnPropertyCondition类中有matches方法(继承父类的),在matches方法中调用了getMatchOutcome方法,实际上就是调用OnPropertyCondition类中的getMatchOutcome方法。ConditionOutcome的isMatch方法到最后再分析。
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
this.logOutcome(classOrMethodName, outcome);
this.recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
} catch (NoClassDefFoundError var5) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
} catch (RuntimeException var6) {
throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
}
}
getMatchOutcome方法调用determineOutcome方法。
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes = this.annotationAttributesFromMultiValueMap(metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList();
List<ConditionMessage> match = new ArrayList();
Iterator var6 = allAnnotationAttributes.iterator();
while(var6.hasNext()) {
AnnotationAttributes annotationAttributes = (AnnotationAttributes)var6.next();
ConditionOutcome outcome = this.determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
} else {
return ConditionOutcome.match(ConditionMessage.of(match));
}
}
determineOutcome方法调用OnPropertyCondition.Spec内部类的collectProperties方法。
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
OnPropertyCondition.Spec spec = new OnPropertyCondition.Spec(annotationAttributes);
List<String> missingProperties = new ArrayList();
List<String> nonMatchingProperties = new ArrayList();
spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
if (!missingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, new Object[]{spec}).didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
} else {
return !nonMatchingProperties.isEmpty() ? ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, new Object[]{spec}).found("different value in property", "different value in properties").items(Style.QUOTE, nonMatchingProperties)) : ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnProperty.class, new Object[]{spec}).because("matched"));
}
}
在OnPropertyCondition.Spec内部类的collectProperties方法中,会对@ConditionalOnProperty注解的类型元素value、prefix、name、havingValue、matchIfMissing进行判断处理。
1、如果name属性的值与havingValue指定的值不一致,则添加到nonMatching列表中;
2、如果matchIfMissing是false,则添加到missing列表中。如果某个属性上@ConditionalOnProperty注解的matchIfMissing=true,则说明即使配置文件中缺少该属性,服务也可以正常使用,不会报错。
private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
String[] var4 = this.names;
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
String name = var4[var6];
String key = this.prefix + name;
if (resolver.containsProperty(key)) {
if (!this.isMatch(resolver.getProperty(key), this.havingValue)) {
nonMatching.add(name);
}
} else if (!this.matchIfMissing) {
missing.add(name);
}
}
}
private boolean isMatch(String value, String requiredValue) {
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
} else {
return !"false".equalsIgnoreCase(value);
}
}
Spec内部类的collectProperties方法调用执行完之后,就会回到determineOutcome方法中,接着根据missing和nonMatching两个列表中是否有数据来调用下面的两个方法创建ConditionOutcome对象,ConditionOutcome对象的match属性的值就会被赋值为false或true。
public static ConditionOutcome match(ConditionMessage message) {
return new ConditionOutcome(true, message);
}
public static ConditionOutcome noMatch(ConditionMessage message) {
return new ConditionOutcome(false, message);
}
private final boolean match;
private final ConditionMessage message;
public ConditionOutcome(boolean match, String message) {
this(match, ConditionMessage.of(message, new Object[0]));
}
//构造方法
public ConditionOutcome(boolean match, ConditionMessage message) {
Assert.notNull(message, "ConditionMessage must not be null");
this.match = match;
this.message = message;
}
现在再回过头看父类SpringBootCondition的matches方法,在matches方法中调用ConditionOutcome的isMatch方法,该方法就是获取match的值,match的值就是上面创建ConditionOutcome对象时初始化的。所以matches方法返回的true或false是由match属性决定的,而match属性的值是由@ConditionalOnProperty注解的类型元素value、prefix、name、havingValue、matchIfMissing的取值决定的。
public boolean isMatch() {
return this.match;
}
结论:在某个属性上使用spring中定义的@ConditionalOnProperty注解时,会根据类型元素value、prefix、name、havingValue、matchIfMissing的取值来判断OnPropertyCondition#matches方法的返回值为true或false,从而决定该属性是否有效。
可以仿照spring中定义的@ConditionalOnProperty注解来自定义条件注解,主要是三步:
1、创建自定义条件注解,在注解上通过@Conditional注解使用下面创建的条件类。
2、解析自定义条件注解,创建一个条件类实现Condition接口,重写matches方法,根据实际需求,从自定义条件注解中获取属性值,根据属性值判断从而决定返回true或false。
3、使用自定义的条件注解。
3、@ConditionalOnMissingBean注解
@ConditionalOnMissingBean与@ConditionalOnBean的作用:根据当前环境或者容器情况来动态注入bean,要配合@Bean使用。
@ConditionalOnMissingBean作用:判断当前需要注入Spring容器中的bean的实现类是否已经含有,有的话不注入,没有就注入。
@ConditionalOnBean作用:判断当前需要注册的bean的实现类是否被spring管理,或者当前需要注册的bean所依赖的条件bean(@ConditionalOnBean注解中写的类)是否被spring容器管理,如果被管理则注入,反之不注入。
下面简单编写代码测试一下。
创建一个接口Animal和两个实现类Dog、Cat。
package com.study.conditionOnBean;
public interface Animal {
void say();
}
package com.study.conditionOnBean;
public class Dog implements Animal{
@Override
public void say() {
System.out.println("===========小狗汪汪汪==========");
}
}
package com.study.conditionOnBean;
public class Cat implements Animal{
@Override
public void say() {
System.out.println("===========小猫喵喵喵==========");
}
}
创建一个配置类:
package com.study.conditionOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfiguration {
@ConditionalOnMissingBean
@Bean
public Animal dog(){
System.out.println("===========创建小狗对象===========");
return new Dog();
}
//@ConditionalOnMissingBean
//@ConditionalOnBean
@Bean
public Animal cat(){
System.out.println("===========创建小猫对象===========");
return new Cat();
}
}
创建一个启动类:
package com.study.conditionOnBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConditionApplication {
public static void main(String[] args) {
SpringApplication.run(ConditionApplication.class,args);
}
}
在配置类AnimalConfiguration中,dog()方法在cat()方法之前,按照代码顺序执行,先创建Dog对象(类型是Animal),再创建Cat对象(类型也是Animal)。dog()方法上使用了注解@ConditionalOnMissingBean,由于此时spring容器中还没有Animal类型的bean对象,所以可以成功创建Animal类型的Dog对象。如果cat()方法上也使用@ConditionalOnMissingBean注解,由于此时spring容器中已经有了Animal类型的bean对象,所以不会执行cat()方法,就不会创建Animal类型的Cat对象。如果cat()方法上加上@ConditionalOnBean注解,由于此时spring容器中已经有了Animal类型的bean对象,满足条件,就会创建Animal类型的Cat对象。或者cat()方法上直接去掉@ConditionalOnMissingBean注解,也会创建Animal类型的Cat对象。
二、自定义注解
目录结构:
pom.xml文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
</dependencies>
</project>
如何自定义注解?
注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。因此可以得出自定义注解使用的基本流程:
第一步,定义注解——相当于定义标记;
第二步,配置注解——把标记打在需要用到的程序代码中;
第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。
在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
1、创建自定义注解
注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。
package com.study.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
String name();
int[] ids();
}
注解里面定义的是:注解类型元素!如上面的value、name和ids。
定义注解类型元素时需要注意如下几点:
1.访问修饰符必须为public,不写默认为public;
2.该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
3.该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
4.()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
5.default代表默认值,值必须和第2点定义的类型一致;
6.如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
可以看出,注解类型元素的语法非常奇怪,既有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在后面的章节中可以看到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。
拓展:常用的元注解
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。
1、@Target
@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下:
public enum ElementType {
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE,
/** 属性的声明 */
FIELD,
/** 方法的声明 */
METHOD,
/** 方法形式参数声明 */
PARAMETER,
/** 构造方法的声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包的声明 */
PACKAGE
}
例如:
//@MyAnnotation被限定只能使用在类、接口或方法上面
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
String name();
int age() default 18;
int[] array();
}
2、@Retention
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。同样使用了RetentionPolicy枚举类型定义了三个阶段:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* (注解将被编译器忽略掉)
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
1.如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
2.如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
3.如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;
4.在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。
3、@Documented
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
4、@Inherited
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
2、使用自定义注解
在具体的Java类上使用自定义注解。
例如创建两个Java类:Person和Student。此处Person和Student类上使用@Component注解是为了交给Spring IoC容器管理。
package com.study.annotation;
import org.springframework.stereotype.Component;
@Component
@MyAnnotation(value = "myAnnoPerson",name = "annoPsn",ids = {1,2})
public class Person {
private Integer id;
private String name;
private Integer age;
}
package com.study.annotation;
import org.springframework.stereotype.Component;
@MyAnnotation(value = "myAnn",name = "stu",ids = {1})
@Component
public class Student {
}
拓展:特殊语法
特殊语法一:
如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!例如:
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
//省略实现部分
}
特殊语法二:
如果注解本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)。例如:
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface SecondAnnotation {
String value();
}
//等效于@ SecondAnnotation(value = "this is second annotation")
@SecondAnnotation("this is second annotation")
public class JavaBean{
//省略实现部分
}
特殊用法三:
如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!例如:
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
//省略实现部分
}
特殊用法四:
如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。
3、解析自定义注解
这一步是使用注解的核心:如何在程序运行时检测到注解,并进行一系列特殊操作!
注解保持力的三个阶段:Java源文件阶段、编译到class文件阶段和运行期阶段。
只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。
package com.study.annotation;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@Component
public class AnnotationResolver implements ApplicationListener, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//从IoC容器中获取所有被自定义注解MyAnnotation标注的类,
//所以被自定义注解标注的类必须交给IoC容器管理,即在类上使用@Component等注解
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(MyAnnotation.class);
System.out.println("--------------" + beansWithAnnotation.size());
for (Map.Entry<String,Object> me : beansWithAnnotation.entrySet()){
System.out.println(me.getKey() + "==========" + me.getValue());
Class<?> clz = me.getValue().getClass();
//获取类上的MyAnnotation标注
MyAnnotation myAnnotation = Objects.requireNonNull(AnnotationUtils.findAnnotation(clz, MyAnnotation.class));
String value = myAnnotation.value();
String name = myAnnotation.name();
int[] ids = myAnnotation.ids();
System.out.println("name=======" + name + ",value=========" + value + ",ids=======" + Arrays.toString(ids));
}
}
}
输出结果:
--------------2
person==========com.study.annotation.Person@50b1f030
name=======annoPsn,value=========myAnnoPerson,ids=======[1, 2]
student==========com.study.annotation.Student@5fa05212
name=======stu,value=========myAnn,ids=======[1]
或者使用下面的方法解析自定义注解:
package com.study.annotation;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class MyApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
final String RESOURCE_PATTERN = "/**/*.class";
// 扫描的包名
final String BASE_PACKAGE = "com.study";
Map<String,Class<?>> classCache = new HashMap<>();
try {
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE)
+ RESOURCE_PATTERN;
Resource[] resources = resourcePatternResolver.getResources(pattern);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader reader = readerFactory.getMetadataReader(resource);
//扫描到的class
String className = reader.getClassMetadata().getClassName();
Class<?> clazz = Class.forName(className);
//判断是否有指定注解
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
if(annotation != null){
//将使用了自定义注解的类放到缓存中
classCache.put(className, clazz);
System.out.println(className + "使用了自定义注解" + MyAnnotation.class.getSimpleName());
}
}
}
} catch (IOException | ClassNotFoundException e) {
System.out.println("读取class失败:" + e.getMessage());
}
}
}
输出结果:
com.study.annotation.Person使用了自定义注解MyAnnotation
com.study.annotation.Student使用了自定义注解MyAnnotation
编写启动类进行测试:
package com.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class,args);
}
}
拓展:
1.AnnotationUtils.findAnnotation(clz, MyAnnotation.class)方法是获取clz类上的MyAnnotation注解。
2.isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是专门判断类、方法或属性上是否配置有某个指定的注解。
3.getAnnotation(Class<A> annotationClass)方法是获取类、方法或属性上指定的注解,之后再调用该注解的注解类型元素方法就可以获得配置时的值数据。
4.反射对象上还有一个方法getAnnotations(),该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。
AnnotationUtils.findAnnotation方法的原理:
如果利用反射检索该类的注解集合里有没有这个注解可能不够全面,因为类可能继承自某个父类或者接口,而其父类或者接口上存在该注解。另外,该注解可能是个复合注解,即该注解也是被其他注解所注解的。所以,判断某个类是否有某个注解的完善流程应该包含4个方面:
检索该类是否直接被该注解标记;
递归检索该类注解的注解中是否存在目标注解;
递归检索该类的接口中是否存在目标注解;
递归检索该类的父类是否存在目标注解。
Spring框架的AnnotationUtils.findAnnotation方法的大致原理就是基于上面流程实现的,下面是该方法的源码。AnnotationUtils会通过缓存来加快检索速度。
private static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType, boolean synthesize) {
Assert.notNull(clazz, "Class must not be null");
if (annotationType == null) {
return null;
}
AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
// 若缓存中存在,则直接返回
A result = (A) findAnnotationCache.get(cacheKey);
if (result == null) {
// 否则执行注解检索,即上面讲过的4个过程
result = findAnnotation(clazz, annotationType, new HashSet<>());
if (result != null && synthesize) {
result = synthesizeAnnotation(result, clazz);
// 将结果更新到缓存中
findAnnotationCache.put(cacheKey, result);
}
}
return result;
}
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
try {
// 判断该类是否被该注解直接标记,对应第①种情况
A annotation = clazz.getDeclaredAnnotation(annotationType);
if (annotation != null) {
return annotation;
}
// 递归检索该类注解的注解中是否存在目标注解,对应第②种情况
for (Annotation declaredAnn : clazz.getDeclaredAnnotations()) {
Class<? extends Annotation> declaredType = declaredAnn.annotationType();
if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) {
// 进入递归
annotation = findAnnotation(declaredType, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
catch (Throwable ex) {
handleIntrospectionFailure(clazz, ex);
return null;
}
// 递归检索该类的接口中是否存在目标注解,对应第③种情况
for (Class<?> ifc : clazz.getInterfaces()) {
A annotation = findAnnotation(ifc, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
// 递归检索该类的父类是否存在目标注解,对应第④种情况
Class<?> superclass = clazz.getSuperclass();
// 如果父类不存在或者父类为Object,则直接返回null
if (superclass == null || Object.class == superclass) {
return null;
}
return findAnnotation(superclass, annotationType, visited);
}
4、自定义条件注解
在前面已经分析了@ConditionalOnProperty注解的用法和原理,@ConditionalOnProperty注解其实就是spring中定义的一个条件注解,可以仿照该注解创建自己的条件注解。主要是三步:
1、创建自定义条件注解,在注解上通过@Conditional注解使用下面创建的条件类。
2、解析自定义条件注解,创建一个条件类实现Condition接口,重写matches方法,根据实际需求,从自定义条件注解中获取属性值,根据属性值判断从而决定返回true或false。
3、使用自定义的条件注解。
1、创建自定义条件注解
package com.study.conditional;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ContainsCondition.class)
public @interface ConditionalOnContains {
int[] value() default {};
}
2、解析自定义条件注解
package com.study.conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
import java.util.Objects;
public class ContainsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
Map<String, Object> 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;
}
}
3、使用自定义条件注解
package com.study.conditional;
import com.study.annotation.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ContainsConfiguration {
@Bean(value = "person1")
@ConditionalOnContains(value = {0, 1})
public Person containsTest1() {
System.out.println("=========创建person1对象=========");
return new Person();
}
@Bean(value = "person2")
@ConditionalOnContains(value = {1, 2})
public Person containsTest2() {
System.out.println("=========创建person2对象=========");
return new Person();
}
@Bean(value = "person3")
@ConditionalOnContains(value = {2, 3})
public Person containsTest3() {
System.out.println("=========创建person3对象=========");
return new Person();
}
}
创建启动类进行测试:
package com.study.conditional;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConditionApplication {
public static void main(String[] args) {
SpringApplication.run(ConditionApplication.class,args);
}
}
输出结果:
=========创建person1对象=========
=========创建person2对象=========
由结果可知,没有创建person3对象,这是因为它的@ConditionalOnContains(value = {2, 3})注解中的value不包含1。