目录
3、@EnableAutoConfiguration(重点)
2、@Import(AutoConfigurationImportSelector.class)
背景
在我平时写项目时,都是业务逻辑为主,代码是抄了又抄,最后发现代码写不少,但是真遇到比较细节的,比如为什么Bean没有装配进去之类的问题,就直接百度找解决办法了,最后还是想自己研究一下。
如果在文章中有什么不对或者不理解的地方,直接私信或者留言,我看到就会及时回复,或者关注我的公众号:Java小白,私信我也可以哈。
自动装配干啥的
其实就是系统在项目启动,或者项目依赖包方面,Spring做了整合,由之前的导入jar包,变为现在的直接引入pom就可以直接使用了。
现在我们从@SpringBootApplication注解来一步步看一下
@SpringBootApplication
在项目启动类里面标记了@SpringBootApplication表示这个类为主启动类,并且带有main方法,main方法中执行了SpringApplication.run()方法
//我们这里主要看这个注解干了什么
@SpringBootApplication
public class Application {
public static void main(String[] args) {
//run方法同样也很重要,详情请看我的博客
SpringApplication.run(Application.class,args);
}
}
SpringApplication注解源码
//1、前四个就是元注解,系统原生注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//2、可以简单理解为@Configuration的替代注解,用来自动自动找到配置
@SpringBootConfiguration
//3、自动注入(重点)
@EnableAutoConfiguration
//4、指定扫描或者不扫描哪些包
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}
按照我上面代码块中注释的点,逐个分析一下:(如果想直接看自动注入,那就跳转到第三点)
1、元注解
注解 | 说明 |
@Retention | 是注解类,实现声明类 Class,声明类别 Category,声明扩展 Extension |
@Taget | 放在自定义注解的上边,表明该注解可以使用的范围 |
@Inherited | 允许子类继承父类的注解,在子类中可以获取使用父类注解 |
@Documented | 表明这个注释是由 Javadoc 记录的 |
@intertface | 用来自定义注释类型 |
@Retention
该注解用于说明自定义注解的生命周期,在注解中有三个生命周期。
- RetentionPolicy.RUNTIME:始终不会丢弃, 运行期也保留读注解,可以使用反射机制读取该注解的信息。 自定义的注解通常使用这种方式。
- RetentionPolicy.CLASS:类加载时丢弃,默认使用这种方式。
- RetentionPolicy.SOURCE:编译阶段丢弃,自定义注解在编译结束之后就不再有意义,所以它们不会写入字节码。@Overide、@SuppressWarnings 都属于这类注解。
@Taget
该注解的作用是告诉 Java 将自定义的注解放在什么地方,比如类、方法、构造器、变量上等,它的值是一个枚举类型,有一些属性:
- ElementType.CONSTRUCTOR:用于描述构造器。
- ElementType.FIELD:用于描述成员变量、对象、属性( 包括enum实例 )。
- ElementType.LOCAL_VARIABLE:用于描述局部变量。
- ElementType.METHOD:用于描述方法。
- ElementType.PACKAGE:用于描述包。
- ElementType.PARAMETER:用于描述参数。
- ElementType.TYPE:用于描述类、接口( 包括注解类型 )或 enum 声明。
@Inherited
该注解是一个标记注解,表明被标注的类型是可以被继承的。如果一个使用了 @Inherited 修饰的 Annotation 类型被用于一个Class,则这个 Annotation 将被用于该 Class 的子类。
@Documented
该注解表示是否将注解信息添加在 Java 文档中。
@intertface
该注解用来声明一个注解, 其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型( 返回值类型只能是基本类型、Class、String、enum )。可以通过 default 来声明参数的默认值。
定义注解格式的代码:
public @interface 注解名 {
......
}
2、@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
......
}
可以看到也是引用了@Configuration注解,相当于对@Configuration做了包装,所以称@SpringBootConfiguration可以是替代品,用了这个注解表示能够像xml配置文件,现在是用java配置文件。并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名,例如:
package com.zhanghao.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
@SpringBootConfiguration
public class Config {
@Bean
public Map createMap(){
Map map = new HashMap();
map.put("username","zhanghao");
map.put("age",27);
return map;
}
}
别的地方想用直接获取这个Bean名字就可以了。
3、@EnableAutoConfiguration(重点)
启用Spring应用程序上下文的自动配置,尝试猜测和配置可能需要的bean。自动配置类通常是根据类路径和您定义的bean来应用的。例如,如果您的类路径上有tomcat embedded.jar,那么你可能想要一个TomcatServletWebServerFactory,除非你写了自定义的ServletWebServerFactory的bean。
所以我们直接使用一个@SpringBootApplication放在主启动类上就行了,因为用了@SpringBootApplication之后,上下文的自动配置会自动启用(因为它里面就包含了@EnableAutoConfiguration)。
并且,这个注解是在@Configuration或者@SpringConfiguration注解加载完Bean之后才会加载(我不清楚到底是谁在控制注解加载顺序,但我猜肯定是框架默认的顺序)。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//1、注册包的
@AutoConfigurationPackage
//2、引入自动注入类(重点)
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
元注解就不看了啊
1、@AutoConfigurationPackage
注册程序包,如果未指定basePackages基包或basePackageClasses基包类,则会注册带注释类的包,简单而言,就是将主配置类(@SpringBootApplication标注的类)所在包以及子包里面的所有组件扫描并加载到spring的容器中,这也就是为什么我们在利用springboot进行开发的时候,无论是Controller还是Service的路径都是与主配置类同级或者次级的原因。
2、@Import(AutoConfigurationImportSelector.class)
@Import这个注解就是把AutoConfigurationImportSelector.class这个类注册为Bean,没啥好说的了,相当于手动注入了。
AutoConfigurationImportSelector.class这个类就有的说了:
public class AutoConfigurationImportSelector implements
DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
......
}
直接就可以看到,这类实现了很多接口:
BeanClassLoaderAware:Bean加载接口,用来通知@Bean的加载
ResourceLoaderAware:Resource加载接口,用来通知@Resource的加载
BeanFactoryAware:Bean工厂的加载
EnvironmentAware:获取系统环境信息
Ordered:类排序接口(具体咋用不知道,没研究过这个)
DeferredImportSelector:
{@ImportSelector}的变体,在处理完所有{@Configuration}bean后运行。实现还可以扩展{org.springframework.core.Ordered}接口,或者使用{@link.org.springframework.core.annotation.Order}注释来指示相对于其他{@link DeferredImportSelector DeferredImport Selector}的优先级<p> 实现还可以提供{@link getImportGroup()import group},它可以在不同的选择器之间提供额外的排序和过滤逻辑。
这是源码中的注释,简单来说就是继承了ImportSelector接口,处理完@Configuration或者@SpringConfiguration之后才会加载,也可以搭配@Order注解对某个类进行优先级的加载。
所以这个接口才是@EnableAutoConfiguration为什么会在@Configuration或者@SpringConfiguration之后才会加载的主要原因。
那么实现了这个接口,它的主要方法selectImports()是如何重写的?
3、重写selectImports()
现在回到AutoConfigurationImportSelector类中看一下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
这里进入方法首先判断了这么一个东西,我还纳闷这个参数AnnotationMetadata 是什么东西。。。。。
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
随后我看了一下isEnabled方法:
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
好像没有用到这个参数,最后还是判断了一下
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY
//它的值是:
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
enableautoconfiguration不就表示可以自动配置么,所以这个if判断就是判断一下是否开启了自动配置罢了,和那个Metadata参数没鸡毛关系。。。。。
随后来到了下面的方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}//还在判断,那肯定是开着的,除非你自己的配置文件写了关闭
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//这个方法就是重点了!!
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
继续进入这个方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
在断言表达式中我看到了:No auto configuration classes found in META-INF/spring.factories
表示断言上面的方法将会寻找这个配置文件
首先看一下,这个方法SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())的参数:
第一个getSpringFactoriesLoaderFactoryClass()是返回EnableAutoConfiguration用来加载候选配置的类,我理解就是加载配置文件里面记录的类;
第二个getBeanClassLoader()是返回一个Bean加载器,类加载器有很多种,这个可以自己去了解一下,或者看我的博客文章,这里先不说了
再进入方法loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
这里就印证了上面那句话“回EnableAutoConfiguration用来加载候选配置的类”
loadSpringFactories(classLoader)
进入方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//三目表达式来选择到底用哪个加载器,
//不为空用传进去的加载器,为空则用系统默认加载器
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
其中标红的全局变量看一下是什么?
public static final String FACTORIES_RESOURCE_LOCATION =
"META-INF/spring.factories";
到这里默认配置文件已经出来了 ,这时候你想去找一下这个文件在哪
去依赖包里面找一下
可以看到有两个,下面那个才是自动配置的文件
进去可以看到
EnableAutoConfiguration=a,b,c,d,e,f....
它们是以key=value键值对儿来存放的,值里面可以看到很多我们项目中用到的:jdbc,es,redis,datasourse,shiro,http等,
加载完这些值后,再继续看代码
最终把加载出来的值通过IO方式加载到Properties中,返回至AutoConfigurationImportSelector中的AutoConfigurationEntry中。
到这里自动装配就算完事儿了,那会有个疑问,这个selectImports()方法什么时候用的呢?后面文章再说!
我还整理汇总了⼀些 Java ⾯试相关的⾼质量 PDF 资料和免费Idea账号
微信公众号:Java小白
欢迎关注!!