依赖管理
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.5version>
<relativePath/>
parent>
点进去后发现还有一个父依赖,这里的父依赖为包的版本管理
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.4.5version>
parent>
修改默认默认版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43mysql.version>
properties>
启动器 spring-boot-starter
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
spring-boot-starter-xxx:就是spring-boot的场景启动器,xxx就是某种场景
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;
SpringBoot将所有的功能场景都抽取出来,做成一个个的spring-boot-starter-xxx(启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;
见到***-springboot-boot-starter:第三方为我们提供的简化开发的场景启动器
查看IOC容器里面的所有组件
@SpringBootApplication
public class HellobootApplication {
public static void main(String[] args) {
//1、返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(HellobootApplication.class, args);
//2、查看容器里面的组件
String[] names=run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
@SpringBootApplication其实就是下面三个注解的集合,依次分析
@SpringBootConfiguration//springboot的配置
@EnableAutoConfiguration//自动配置,开启配置功能
//自动扫描
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
@SpringBootConfiguration
作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
@Configuration //代表spring配置类,配置类就是对应Spring的xml 配置文件;
public @interface SpringBootConfiguration {
}
@Component//说明启动类本身也是Spring中的一个组件而已,负责启动应用!
public @interface Configuration {
}
@EnableAutoConfiguration
@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效
@AutoConfigurationPackage //自动配置包
//AutoConfigurationImportSelector:自动配置导入选择
@Import(AutoConfigurationImportSelector.class)//给容器导入组件,导入选择器
public @interface EnableAutoConfiguration {
}
@AutoConfigurationPackage注解源码解析
//@import :Spring底层注解@import,给容器中导入一个组件
//Registrar.class 作用:利用Registrar给容器批量导入一系列组件,MainApplication所在包下
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//把注解元信息拿来,得到所有包名,封装到数组里面,然后批量注册进去
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
@Import(AutoConfigurationImportSelector.class)注解源码解析
流程
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
AutoConfigurationImportSelector:自动配置导入选择器,那么它会导入哪些组件的选择器
public class AutoConfigurationImportSelector implements xxxxx {
//获得所有自动配置的实体
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);
}
// 获得候选的配置
protected List<String> getCandidateConfigurations(
AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里加载的就是标注了EnableAutoConfiguration注解的类,说白了就是主启动类SpringBootApplication
//返回的就是启动自动导入配置文件的注解类:EnableAutoConfiguration
List<String> configurations =SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
//没找到META-INF/spring.factories文件,此文件为自动配置的核心文件
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;
}
//返回的就是启动自动导入配置文件的注解类;EnableAutoConfiguration
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
}
这个方法又调用了SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//获取所有的配置
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//这里它又调用了loadSpringFactories 方法
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
//********************************************************
//加载"META-INF/spring.factories"配置文件,所有的自动配置类都在这里了
//???思考:这么多自动配置为什么有的没有生效,需要导入对应的start才能有作用;核心注解:@ConditionalOnClass(),如果这里面的条件都成立,才会生效
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//获得classLoader,得到的就是EnableAutoConfiguration标注的类本身
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
//以前的是去获取一个资源 "META-INF/spring.factories"
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
//将读取到的资源"判断有没有更多元素"==遍历,封装成为一个Properties
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//资源加载到properties里面
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;
}
}
发现一个多次出现的文件:spring.factories,
//所有的spring.factories资源加载到properties类中
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
}
按需开启自动项配置
思考:一开始加载了这么多自动配置,但为什么有的没有生效,需要导入对应的start才能有作用;
答:核心注解:@ConditionalOnClass(),如果这里面的条件都成立,才会生效, 比如RabbitAutoConfiguration配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })//需要使里面的条件都成立
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}
步骤总结:
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
生效的配置类就会给容器中装配很多组件
只要容器中有这些组件,相当于这些功能就有了
定制化配置
用户直接自己@Bean替换底层的组件
用户去看这个组件是获取的配置文件什么值就去修改。
如果用户自己配置了以用户的优先
总流程
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
@ComponentScan
作用:扫描当前主启动类同级的包
方式:@SpringBootApplication(scanBasePackages=“com.atguigu”)或者@ComponentScan指定扫描路径
结论:SpringBoot所有的自动配置都是在启动的时候扫描并加载:META-INF/spring.factories,所有的自动配置都在这里面了,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,自动转配就会生效,然后就配置成功了;
所有自动配置的东西,都在spring-boot-autoconfigure-2.4.5.jar里面
HttpEncodingAutoConfiguration类的源码解析:
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration(proxyBeanMethods = false)
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@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;
}
。。。
}
与配置文件的关联关系:
WebMvcAutoConfiguration关联
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
SpringApplication.run(HellobootApplication.class, args);
这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、自动推断并设置main方法的定义类,找到运行的主类
查看它的构造方法
public class SpringApplication {
//构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1、推断应用类型是否为WEB
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2、加载所有可用初始化器
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//3、设置所有可用程序监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4、推断并设置main方法的定义类
this.mainApplicationClass = deduceMainApplicationClass();
}
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
//计时器实例化并启动,应用监听器开始监听
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//1、Headless系统熟悉设置
configureHeadlessProperty();
//2、初始化监听器:getRunListeners(args)
SpringApplicationRunListeners listeners = getRunListeners(args);
//3、启动已准备好的监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//4、装配环境参数:DefaultApplicationArguments(args)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//5、打印banner图案
Banner printedBanner = printBanner(environment);
//6、上下文区域
context = createApplicationContext();
//7、准备上下文异常报告器
context.setApplicationStartup(this.applicationStartup);
//8、上下文前置处理:prepareContext()
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//9、上下文刷新:refreshContext-->bean工厂加载,通过工厂生成bean,刷新生命周期
refreshContext(context);
//10、上下文后置结束处理:afterRefresh
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
//11、执行Runner运行器,发布应用上下文
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
//12、返回应用上下文
return context;
}
}