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程序如何最终调用到这里,完成自动配置了。