思考:springboot是如何把bean放入到IOC容器中
- 1、核心方法
SpringApplication.run(ManagementCenterGateWayApplication.class, args);
- 2、调入方法如下
public static ConfigurableApplicationContext run(Class> primarySource, String... args) {
return run(new Class>[] { primarySource }, args);
}
- 3、调入方法如下(超重点,核心)
public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
- 3.1、初始化阶段(把项目中所有的符合SpringBoot机制的bean全部收集起来 map)
new SpringApplication(primarySources)
- 3.1.1、调用构造函数方法
public SpringApplication(Class>... primarySources) {
this(null, primarySources);
}
- 3.1.2、调用具体的初始化方法
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new linkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- 3.1.3、调用getSpringFactoriesInstances
- 核心方法一、
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
核心方法二、
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
1:为什么要传递类
触发类加载器
2:传递类的作用是干啥
触发类加载器,通过双亲委派模型,把项目中所有的类,包括jdk,jdkext,spring.jar,mybatis.jar全部把他们中编译好的字节文件class找到,放入map中
3:怎么找到项目所需要的bean
上边两个核心方法都调用了getSpringFactoriesInstances方法,触发类加载器
privateCollection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set names = new linkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
- 验证阶段、我们在ClassLoader classLoader = getClassLoader();打断点,启动项目
结果发现,我们在启动项目的时候,类加载器加载了项目中所需要的所有jar包
思考?
项目中引入jdk是怎么加载进去的,项目中引入的spring.jar里面的类为什么可以使用,项目中的String,List为什么就可以使用呢
答案:在项目启动的时候,项目会调用类加载器通过双亲委派模型把项目中所有的class全部找到,然后放入到jvm中去,只不过springboot把这些加载的classes进行过滤匹配把符合条件的bean放入到ioc容器中的过程
双亲委派模型:一句话,找项目所有的class字节文件,不论jdk,ext,项目写的classes,spring.jar通通全部找到
- 3.1.4、开始对类加载的加载类进行过滤
Setnames = new linkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
SpringFactoriesLoader点进去
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration 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); //这里就把spring.factories 中的对应起来了 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); } }
type:开始进行匹配过滤,作用其实就是一个加载缓存的目的,作用就是,他会把类加载中所有的jar文件存在:meta-INF/spring.factories文件中的内容全部找到,然后把这个meta-INF/spring.factories中的bean全部放入到map中
我们知道类加载,把项目所有的classes文件通通都加载到jvm中,n那么springboot怎么是如何把需要管理和初始化放入到ioc容器的classes找出来呢?springboot借鉴了java9的新特性:spi加载机制,这种加载机制就是把需要加载的类放入一个配置中,而springboot的命名规则就是meta-INF/spring.factories,而这个文件中就定义了springboot所需要的bean都放入到这个文件中
.run(args);三、主程序【自动装配原理】
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- @SpringBootApplication组合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@SpringBootConfiguration //springboot的配置
@EnableAutoConfiguration //自动装配
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
- @SpringBootConfiguration
- @EnableAutoConfiguration
- AutoConfigurationimportSelector :自动配置导入选择器
1、获得候选的配置
protected ListgetCandidateConfigurations(Annotationmetadata metadata, AnnotationAttributes attributes) { List 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; } protected Class> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
public static ListloadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
3、我们继续点击查看 loadSpringFactories 方法
private static Map> loadSpringFactories(ClassLoader classLoader) { Map > result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); 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(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
,
4、发现一个多次出现的文件:spring.factories,全局搜索它,
spring.factories 看到了很多自动配置的文件;这就是自动配置根源所在!
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean
所以,自动配置真正实现是从classpath中搜寻所有的meta-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
-
结论:
1、SpringBoot在启动的时候从类路径下的meta-INF/spring.factories中获取EnableAutoConfiguration指定的值
2、将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
3、整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
4、它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
5、有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
- 以HttpEncodingAutoConfiguration为例子
//表示这是一个配置类
@Configuration(proxyBeanMethods = false)
//自动配置属性
//启动指定类的ConfigurationProperties功能;
//进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来;
//并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解(判断是否满足当前指定条件)
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//@Conditional(OnWebApplicationCondition.class)这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
1、一但这个配置类生效;这个配置类就会给容器中添加各种组件;
2、这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
3、所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
4、配置文件能配置什么就可以参照某个功能对应的这个属性类
- 精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
- @Conditional注解的作用
自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
- 一部分是SpringApplication的实例化,二是run方法的执行;
- SpringApplication类的作用
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类



