springboot中常用的注解

1:@Inherited

该注解的用法是:如果某个类使用了被@Inherited标注的注解,则该类的子类会自动继承该注解,即@Inherited的作用就是标记注解是否是可被继承
分析如下:
该注解源码如下:

java.lang.annotation.Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
    
    
}

@Target(ElementType.ANNOTATION_TYPE)可以看出该注解是用在注解上的注解,因此是元注解

1.1:标注了@Inherited

如果是某个注解使用了@Inherited注解进行标注,如下:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedTest {
    
    
    String value();
}

这里我们定义了注解InheritedTest,然后我们将该注解使用在类上,如下:

@InheritedTest("我是被@Inherited标注的注解")
public class Parent {
    
    
}

然后我们直接看Parent的注解信息:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, NoSuchFieldException {
    
    
	Class<Parent> parentClass = Parent.class;
	boolean annotationPresent = parentClass.isAnnotationPresent(InheritedTest.class);
	if (annotationPresent) {
    
    
		String value = parentClass.getAnnotation(InheritedTest.class).value();
		System.out.println(value);
	}
}

输出:

我是被@Inherited标注的注解

可以看到,是有@InheritedTest("我是被@Inherited标注的注解")注解信息的(有点废话,直接获取被标注的类,肯定能获取,不过也是为了说明问题),接下来我们定义一个子类:

public class Son extends Parent {
    
    
}

然后获取Son的注解:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, NoSuchFieldException {
    
    
	Class<Son> sonClass = Son.class;
	boolean annotationPresent = sonClass.isAnnotationPresent(InheritedTest.class);
	if (annotationPresent) {
    
    
		String value = sonClass.getAnnotation(InheritedTest.class).value();
		System.out.println(value);
	}
}

输出:

我是被@Inherited标注的注解

同样也输出了,说明Son继承了Parent的@InheritedTest的注解。接下来我们再测试下没有被标注的情况。

1.2:没有标注@Inherited

定义一个没有使用@Inherited注解的注解:

public @interface NoInheriedAnnotation {
    
    
    String value();
}

将注解是用在类上:

@NoInheriedAnnotation("我是没有被@Inherited标注的注解")
public class NoInheriedAnnotationParent {
    
    
}

定义子类:

public class NoInheriedAnnotationSon extends NoInheriedAnnotationParent {
    
    
}

查看子类是否继承了注解:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, NoSuchFieldException {
    
    
	Class<NoInheriedAnnotationSon> noInheriedAnnotationSonClass = NoInheriedAnnotationSon.class;
	boolean annotationPresent = noInheriedAnnotationSonClass.isAnnotationPresent(NoInheriedAnnotation.class);
	if (annotationPresent) {
    
    
		String value = noInheriedAnnotationSonClass.getAnnotation(NoInheriedAnnotation.class).value();
		System.out.println(value);
	} else {
    
    
		System.out.println("没有继承注解NoInheriedAnnotation");
	}
}

输出:

没有继承注解NoInheriedAnnotation

可以看到注解没有被继承。

2:@Repeatable

该注解是jdk定义的一个注解,源码如下:

java.lang.annotation.Repeatable
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    
    
   
    Class<? extends Annotation> value();
}

被该注解标注的注解可以在类上被重复的使用,下面看个实例。从源码中可以看到其value是一个Class<? extends Annotation>,实际上Class<? extends Annotation>是最终用来容纳被使用了多次的注解的容器,因此我们先来定义这个注解容器,我们假定这个可以重复定义的注解是RepeatableAnnotation,后续会定义:

@Target(ElementType.TYPE)
// 存放可重复注解的容器的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableAnnotationContainer {
    
    
    // 存放被重复使用了n次的可重复注解
    RepeatableAnnotation[] value();
}

接着我们来定义可重复定义注解,即被@Repeatable注解的注解,并指定存放自己的重复定义的容器注解为前面定义的@RepeatableAnnotationContainer:

// 定义可重复定义的注解,并指定其重复定义的注解容器为RepeatableAnnotationContainer.class
@Repeatable(RepeatableAnnotationContainer.class)
public @interface RepeatableAnnotation {
    
    
    String yourName() default "";
}

接着我们定义一个类来使用我们自定义的可重复注解:

@RepeatableAnnotation(yourName = "张三")
@RepeatableAnnotation(yourName = "李四")
@RepeatableAnnotation(yourName = "王五")
@RepeatableAnnotation(yourName = "赵六")
public class RepeatableAnnotationUse {
    
    
}

可以看到我们重复定义了4次,接下来我们来获取重复定义的注解:

public static void main(String[] args) {
    
    
    Annotation[] annotations = RepeatableAnnotationUse.class.getAnnotations();
    System.out.println(annotations.length);
}

输出结果:
1,并不是我们期望的4
我们来通过debug看下具体类型:
在这里插入图片描述
可以看到是我们定义的容器注解RepeatableAnnotationContainer,我们来通过容器注解获取注解的内容:

public static void main(String[] args) {
    
    
    Annotation[] annotations = RepeatableAnnotationUse.class.getAnnotations();
    RepeatableAnnotationContainer annotationContainer = (RepeatableAnnotationContainer) annotations[0];
    for (RepeatableAnnotation repeatableAnnotation : annotationContainer.value()) {
    
    
        System.out.println(repeatableAnnotation.yourName());
    }
}

输出如下:

李四
王五
赵六
张三

3:@Indexed

该注解是在spring5中定义的,其源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Indexed {
    
    
}

用来解决通过@ComponentScan扫描包过多时,导致spring容器初始化慢的问题,在编译后会在META-INF/spring.components文件中生成需要扫描的索引条目,但是注意需要引入spring-context-indexer依赖,下面测试一下。
定义一个类:

@Indexed
@Component
public class AService {
    
    

    public void sayHi() {
    
    
        System.out.println("AService hi");
    }
}

引入索引需要的依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-indexer</artifactId>
    <optional>true</optional>
</dependency>

编译打包:
在这里插入图片描述
内容如下不用深究

#
#Thu Jan 28 19:14:53 CST 2021
dongshi.daddy.service.AService=org.springframework.stereotype.Component,dongshi.daddy.service.AService
dongshi.daddy.HelloWorldMainApplication=org.springframework.stereotype.Component
dongshi.daddy=package-info
dongshi.daddy.controller.HelloController=org.springframework.stereotype.Component

之后在运行阶段,spring就可以直接读取这个文件来初始化spring容器了,就不需要再扫描文件了,从而提高容器初始化的速度。
在spring项目中,该注解会用在@Component注解上,该注解是一个用来标记是一个spring bean的注解,其实就是使用了@Component注解就会自动在编译时进行索引,如下源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    
    
	String value() default "";
}

4:@Component

该注解时spring定义的用来标记成为spring bean的注解,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    
    
	String value() default "";
}

其中被@Indexed注解标记,代表会在编译时期构建索引。

5:@Configuration

该注解的作用时标记类为java config类,即使用java类来代替配置文件的一种机制,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    
    

	@AliasFor(annotation = Component.class)
	String value() default "";

}

从源码看到该注解被@Component标注,因此使用了该注解的bean,在是一个java config类的同时,也是一个spring bean。

6:@Import

该注解是在spring的context包中定义的,是和spring bean的上下文相关的一个注解,其作用是导入java config的配置类,即使用了@Configuration的类,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    
    
	Class<?>[] value();
}

可以看到其value是一个Class的数组,一般就是我们使用@Configuration配置的类,下面我们定义一个这样的类:

@Configuration
public class OutConfiguration {
    
    

    @Bean
    public Student student() {
    
    
        Student student = new Student();
        student.setName("张三小姐");
        return student;
    }
}

上面的类有一个问题需要注意,那就是不要放到springboot能够扫描到的包路径下,否则自动扫描机制会自动引入该类,影响我们的测试,基本上放在和springboot启动类所在包的平级包或者是更上级包里就可以了。
接着我们来定义Student类:

public class Student {
    
    
    private String name;

    ...getter setter tostring...
}

然后定义springboot启动类如下:

@SpringBootApplication
//@Import(OutConfiguration.class)
public class SpringbootHelloworldApplication {
    
    

    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext cac = SpringApplication.run(SpringbootHelloworldApplication.class, args);
        Student student = cac.getBean(Student.class);
        System.out.println(student);
    }

}

注意此时我们的@Import(OutConfiguration.class)是注释掉的,输出如下:
在这里插入图片描述
可以看到配置类并没有被引入,这时我们放开@Import(OutConfiguration.class)再重新启动测试:

2021-01-30 09:52:43.808  INFO 50409 --- [           main] d.d.s.SpringbootHelloworldApplication    : Started SpringbootHelloworldApplication in 4.718 seconds (JVM running for 6.951)
Student{
    
    name='张三小姐'}

引入配置类除了直接指定配置类的class外,还可以指定org.springframework.context.annotation.ImportSelector接口的实现类,该接口定义如下:

public interface ImportSelector {
    
    
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	@Nullable
	default Predicate<String> getExclusionFilter() {
    
    
		return null;
	}
}

方法selectImports(importingClassMetadata)就是我们需要实现的方法,该方法的入参是在启动类上定义的注解信息,返回的参数是一个数组,是我们自定义的需要引入的java config配置类的全限定名的数组,同样使用上面自定义的javaconfig类实现该接口:

public class MyImportSelector implements ImportSelector {
    
    

    /**
     * 获取需要import的配置类数组
     * @param importingClassMetadata 在启动类上定义的注解信息
     * @return 配置类权限定名称数组
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
        importingClassMetadata.getAnnotationTypes().forEach(System.out::println);
        return new String[] {
    
     OutConfiguration.class.getName() };
    }

    @Override
    public Predicate<String> getExclusionFilter() {
    
    
        return null;
    }
}

然后修改@Import(OutConfiguration.class)@Import(MyImportSelector.class)然后再进行测试,可以看到效果完全相同:

...
org.springframework.boot.autoconfigure.SpringBootApplication
org.springframework.context.annotation.Import
...
Student{
    
    name='张三小姐'}

不管在@Import中设置的值是java config的class还是ImportSelector接口的实现类的class,最终都是通过java config的@Bean注解定义的方法返回需要放到IOC容器中的bean对象,这里其实还支持使用BeanDefinition的方式,这种方式我们需要实现org.springframework.context.annotation.ImportBeanDefinitionRegistrar接口,该接口源码如下:

public interface ImportBeanDefinitionRegistrar {
    
    

	
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {
    
    

		registerBeanDefinitions(importingClassMetadata, registry);
	}

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
	}

}

我们只需要实现registerBeanDefinitions的两个参数的方法,然后定义自己的BeanDefinition,之后注册到注册机中就可以了,如下我们的定义:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
        // 定义Student的bean定义
        BeanDefinition studentBeanDefinition = new GenericBeanDefinition();
        // 设置class
        studentBeanDefinition.setBeanClassName(Student.class.getName());

        // 设置构造函数的值
        /**
         * public Student(String name) {
         *    this.name = name;
         * }
         */
        ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
        // 设置构造函数第一个参数的值
        constructorArgumentValues.addIndexedArgumentValue(0, "李四的大姑");
        // 设置参数定义到bean定义中
        ((GenericBeanDefinition) studentBeanDefinition)
                .setConstructorArgumentValues(constructorArgumentValues);
        // 注册到注册机中
        registry.registerBeanDefinition("student", studentBeanDefinition);
    }
}

然后修改的@Import注解为@Import(MyImportBeanDefinitionRegistrar.class),启动测试:

2021-01-30 10:59:30.705  INFO 53382 --- [           main] d.d.s.SpringbootHelloworldApplication    : Started SpringbootHelloworldApplication in 2.629 seconds (JVM running for 3.803)
Student{
    
    name='李四的大姑'}

我们注意到,以上的方式组中都是将某个对象引入作为IOC容器的bean,殊途同归,那么我们能不能直接将某个想要引入的bean直接作为@Import注解的值呢?也是可以的,我们用这种方式改造启动类:

@SpringBootApplication
@Import(Student.class)
public class SpringbootHelloworldApplication {
    
    

    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext cac = SpringApplication.run(SpringbootHelloworldApplication.class, args);
        Student student = cac.getBean(Student.class);
        System.out.println(student);
    }

}

为了更方便的看到效果,我们改造下Student类:

public class Student {
    
    
    private String name;

    public Student() {
    
    
        this.name = "王五的媳妇";
    }
    ...snip...
}

启动测试:

2021-01-30 17:35:45.709  INFO 59451 --- [           main] d.d.s.SpringbootHelloworldApplication    : Started SpringbootHelloworldApplication in 2.591 seconds (JVM running for 3.861)
Student{
    
    name='王五的媳妇'}

7:@SpringBootConfiguration

该注解源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    
    

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

可以看到该注解仅仅是继承了@Configuration,因此只是更加明确了是使用在springboot中 java config配置类的注解,可以认为和@Configuration功能是完全相同的,所以该注解的含义就是这是一个springboot的java config的配置类

8:@ComponentScan

用来扫描被@Component以及其子注解,如@Controller,@Service,@Reporitory等,源码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    
    
}

另外从源码中可以看到,继承了注解@Repeatable代表该注解在类上是可以重复定义的,因为该注解是定义扫描路径的,因此可能会定义多次,其中重复定义的ComponentScan注解会放到注解org.springframework.context.annotation.ComponentScans中,源码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {
    
    

	ComponentScan[] value();

}

9:@EnableAutoConfiguration

该注解是springboot定义的注解,用于开启自动配置功能,是spring-boot-autoconfigure项目最核心的注解,其源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    
    
}

其自动配置功能,是通过其父注解来辅助实现的来分别看下这些注解。
@AutoConfigurationPackage,该注解源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    
    

}

其中@Import(AutoConfigurationPackages.Registrar.class)中的AutoConfigurationPackages.Registrar源码如下:

org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
    

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    
    
		register(registry, new PackageImport(metadata).getPackageName());
	}

	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
    
    
		return Collections.singleton(new PackageImport(metadata));
	}

}

实现了ImportBeanDefinitionRegistrar因此可以将bean注入到springIOC容器中的功能,完成注册的源码如下:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    
    
    // 正常进else,因此这里只看else
	if (registry.containsBeanDefinition(BEAN)) {
    
    
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
		constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
	}
	else {
    
    
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(BasePackages.class);
		// 将要扫描的包路径注册进来,后续springboot就可以对该路径进行扫描了
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}

debug如下:
在这里插入图片描述
这里的包路径就是我的启动类所在的包路径,如下:
在这里插入图片描述
这样,springboot就有了拥有需要扫描的路径信息的spring bean了,后续的工作也就可以从这里展开了。
@Import(AutoConfigurationImportSelector.class),源码如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    
    
}

其中DeferredImportSelector实现了接口org.springframework.context.annotation.ImportSelector,因此AutoConfigurationImportSelector就是一个importselector,用来引入标注了@Configuration注解的java config类。接下来我们再重点看下这个类。
该类是用在org.springframework.boot.autoconfigure.EnableAutoConfiguration定义,真正完成资源的导入,下面我们来看下。
首先启动后会执行到方法org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process,源码如下:

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    
    
	Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
			() -> String.format("Only %s implementations are supported, got %s",
					AutoConfigurationImportSelector.class.getSimpleName(),
					deferredImportSelector.getClass().getName()));
	// 获取配置
	AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
			.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
	this.autoConfigurationEntries.add(autoConfigurationEntry);
	for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    
    
		this.entries.putIfAbsent(importClassName, annotationMetadata);
	}
}

继续看方法getAutoConfigurationEntry,源码如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
    
    
	if (!isEnabled(annotationMetadata)) {
    
    
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// <getAutoConfigurationEntry_1>获取候选的配置
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	configurations = removeDuplicates(configurations);
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	// <getAutoConfigurationEntry_2>
	configurations = filter(configurations, autoConfigurationMetadata);
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}

我们先来看<getAutoConfigurationEntry_2>,因为并非所有的自动配置类都需要进行自动配置,因此就需要过滤掉那些不需要自动配置的类,使用的是AutoConfigurationImportSelector相关的API,详细可以参考这里
继续看方法<getAutoConfigurationEntry_1>:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    
	// getSpringFactoriesLoaderFactoryClass() -> interface org.springframework.boot.autoconfigure.EnableAutoConfiguration
	// 该代码就是获取META-INF/spring.factories文件夹下获取key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有的接口的实现类,其实这些类就是springboot已经实现的自动配置的实现类了
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
			getBeanClassLoader());
	Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
			+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

结合注释查看后,我们先来看下META-INF/spring.factories文件的对应的配置比较多,这里就截取部分

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
...

可以看到这里定义里所有的自动配置的类,其中的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,就是web框架springmvc的自动配置类,我们看下List<String> configurations的结果:
在这里插入图片描述
我们可以进一步看下WebMvcAutoConfiguration代码,自动配置类位置,debug信息对比看下,如图:
在这里插入图片描述
其实到这里基于java config的自动配置类都已经成功获取了,但是,我们继续来看下程序到底是怎么执行到这里的,我们来看下方法的调用链:
在这里插入图片描述
其中1处的代码可以从springboot的启动过程详细分析中找到,从而也就是可以找到我们通过SpringApplication.run(SpringbootHelloworldApplication.class, args);启动springboot程序如何最终调用到这里,完成自动配置了。

猜你喜欢

转载自blog.csdn.net/wang0907/article/details/113131549
今日推荐