springboot一定要注意1.5.17.RELEASE才可以打断点在注解类上
2.X后的版本都无法打断点。(搞了很久也不知道原因)
springboot的启动过程,要看看面试题,重新整理不然太乱
@SpringBootApplication
//这是实现注解需要的配置
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//这是复合了其他注解的功能
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
实际上就是下方三个注解的整合:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
@SpringBootConfiguration
实际上就是复用了@Configuration,表示带有该注释的类是一个Spring的配置类。
//这是实现注解需要的配置
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//这是复合了其他注解的功能
@Configuration
public @interface SpringBootConfiguration {
}
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
//import表示导入某个类
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
@EnableAutoConfiguration 注解启用自动配置,其可以帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前 IoC 容器之中
@import的三种使用方式
@AutoConfigurationPackage
@Import({Registrar.class})
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//获取到元信息的包名传入注册器
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
- metadata 该注解所在的启动类信息
- registry 用作注册的Bean注册器
new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
获取到该启动类所在路径的包名,根据传入的register和包名packageName注册该包名下的所有需要注册并实例化的Bean(包括@Component @Service @Mapper @Repository等)。
@Import(AutoConfigurationImportSelector.class)
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
//获取META-INF/spring-autoconfigure-metadata.properties 键值对,配置类的加载条件(需要依赖的class之类的条件)
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//获取@EnableAutoConfiguration 中的属性值,exclude/excludeName,用于过滤自动配置类
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//获取META-INF/spring.factories 键值对,"EnableAutoConfiguration"对应的值,也就是所有自动装配类名
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//移除list中的重复自动配置类名
configurations = this.removeDuplicates(configurations);
//通过读取@AutoConfigureOrder、@link AutoConfigureBefore、@AutoConfigureAfter注解对自动配置类名进行优先级排序
configurations = this.sort(configurations, autoConfigurationMetadata);
//获得排除项,该值为@EnableAutoConfiguration 中的属性值,目前为空,因此exclusions为空
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
//移除候选配置类当中的的所有排除项(不需要自动配置的类);
configurations.removeAll(exclusions);
//根据自动装配类和配置类加载条件进行条件判断,从而排除掉不满足的配置类
configurations = this.filter(configurations, autoConfigurationMetadata);
//通过AutoConfigurationImportListener监听器触发自动配置导入事件;
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
@ComponentScan
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
没有配置basePackages,默认扫描该类所在的包下所有的配置类
这个注解咱们都是比较熟悉的,无非就是自动扫描并加载符合条件的Bean到容器中,这个注解会默认扫描声明类所在的包开始扫描,例如:类cn.shiyujun.Demo
类上标注了@ComponentScan
注解,则cn.shiyujun.controller
、cn.shiyujun.service
等等包下的类都可以被扫描到
这个注解一共包含以下几个属性:
basePackages:指定多个包名进行扫描
basePackageClasses:对指定的类和接口所属的包进行扫
excludeFilters:指定不扫描的过滤器
includeFilters:指定扫描的过滤器
lazyInit:是否对注册扫描的bean设置为懒加载
nameGenerator:为扫描到的bean自动命名
resourcePattern:控制可用于扫描的类文件
scopedProxy:指定代理是否应该被扫描
scopeResolver:指定扫描bean的范围
useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行检测
?它与@AutoConfigurationPackage 不是重复了吗:
it will be used when scanning for code @Entity classes. It is generally recommended that you place EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.
@ComponentScan是先于@AutoConfigurationPackage 进行检索。
@Controller/@Service/@Component/@Repository这些注解是由ComponentScan来扫描并加载的。
除此之外,还有一些Spring Data注解 比如说,你用了Spring Data JPA,可能会在实体类上写@Entity
注解。这个@Entity
注解由@AutoConfigurationPackage
扫描并加载。
简单理解:这二者扫描的对象是不一样的。
总结
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
因为自动装配的存在,所以才体现了"约定大于配置"
AutoConfigurationImportSelector.class 详细说明如下
AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader)
这一步就是将META-INF/spring-autoconfigure-metadata.properties文件中的键值对放入AutoConfigurationMetadata的Properties对象中,共有485个元素。
org.springframework.boot:spring-boot-autoconfigure:1.5.17RELEASE/META-INF下
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
AnnotationAttributes attributes =this.getAttributes(annotationMetadata);
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = this.getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?");
return attributes;
}
- 注意的是这里的name并没有使用annotationMetadata,而是使用了this
- 因此它的作用是获取@EnableAutoConfiguration 中的属性值
因为我们并没有设置值,所以它使用默认值为空
List<String> configurations =this.getCandidateConfigurations(annotationMetadata, attributes);
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
核心在于
springFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(),this.getBeanClassLoader());
- this.getSpringFactoriesLoaderFactoryClass():EnableAutoConfiguration.class;
- this.getBeanClassLoader():AppClassLoader
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
- classLoader.getResources("META-INF/spring.factories"):通过类加载器获取jar包中所有META-INF/spring.factories并获取其中的内容
会把当前加载器和父类加载器都去扫描
?但是我没懂这些变量并没有找到文件所在的jar包路径
- while(urls.hasMoreElements())
- 遍历文件路径url=jar:file:/C:/Users/Alan/.m2/repository/org/springframework/boot/spring-boot/1.5.17.RELEASE/spring-boot-1.5.17.RELEASE.jar!/META-INF/spring.factories
- 获得文件内容键值对properties
- 获得"org.springframework.boot.autoconfigure.EnableAutoConfiguration"对应的值
- 该值以','作为连接符,因此需要进行逗号切割
- 至此获得所有自动装配的类名
configurations = this.filter(configurations, autoConfigurationMetadata);
- configurations:读取MEAT-INF/spring.factories文件得到的经过排除得到的自动配置类名的list集合,96个值
- autoConfigurationMetadata:读取META-INF/spring-autoconfigure-metadata.properties文件得到的485个键值对。
该方法,就是判断configurattiaions是否满足autoConfigurationMetadata的类加载条件,比如说
匹配失败原因:@ConditionalOnClass did not find required classes 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice'
由于项目没有引入Aop的相关依赖,导致类路径中没有Aspect和Advice类,因此AopAutoConfiguration这个自动配置类匹配失败,无法进行自动配置。
(1) META-INF/spring-autoconfigure-metadata.properties文件中的Aop内容:
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.ConditionalOnClass=
org.springframework.context.annotation.EnableAspectJAutoProxy,org.aspectj.lang.annotation.Aspect,org.aspectj.lang.reflect.Advice
(2) META-INF/spring.factories中的Aop内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=...,
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,...
能否成功配置,关键还要看是否已经被排除以及是否满足对于这个@ConditionXXX,也就是说是否导入了XXXstarter依赖,否则就会缺少一些类,就不满足条件。
若不满足,则该类是会从自动配置类列表中排除,这样能加快springboot的启动速度
总结
导入所有的自动配置类,然后根据当前不同的条件判断,觉得这个配置类是否生效,一旦配置类生效,这个配置类就会给容器中添加各自组件,这些组件的属性是从对应的properties类中获取,这些类里面的每一个属性又是与配置文件绑定的。
挖坑
一定要弄懂一下@Configuration 为什么就可以注入自定义属性了!
https://juejin.im/post/5e689b49e51d4527143e5e2f
源码分析来源集合
https://www.jianshu.com/p/9dc7a385d19e
https://juejin.im/post/5dce352351882510337e27a6
https://juejin.im/post/5d84269d5188257ae63ebbe6