随便给最近想逐步整理的文章起了个皮皮虾名:SpringBoot起飞之路
这是第二篇,关于前一篇入门涉及到的一些基本原理讲解,有兴趣的朋友可以去了解一下前一篇
SpringBoot起飞之路-HelloWorld (一)
在前一篇的入门程序编写时,我们自己通过 Maven 创建一个 SpringBoot 的项目的时候,我们需要在前面使用 <parent></parent>
标签对引入一个 spring-boot-starter-parent
这就是 SpringBoot 的父级依赖,它就是一个特殊的 starter ,可以用来提供我们所需 Maven 的一些依赖,同时管理项目的资源过滤
简单看一下,首先按下 ctrl 键点击 spring-boot-starter-parent,
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
进去之后,就是一些资源引入的内容,还有一些插件,接着又看到了一个父级标签,我们继续点进去看一看
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.7.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>
到此为止,就看不到别的父级标签了,来观察一下这个文件,纵观一下,这里就是管理 SpringBoot 应用里面所有依赖版本的地方
properties dependencys plugins
从前在 Spring 中的开发,我们总会考虑不同依赖版本之间的兼容冲突问题,而在这里,SpringBoot 就会帮我们根据我们选定的 SpringBoot 版本,选择出最适合的依赖们,这一点就舒服了~
<properties> ...... <activemq.version>5.15.12</activemq.version> <commons-pool.version>1.6</commons-pool.version> <jackson.version>2.10.4</jackson.version> <javax-mail.version>1.6.2</javax-mail.version> <log4j2.version>2.12.1</log4j2.version> <logback.version>1.2.3</logback.version> <mssql-jdbc.version>7.4.1.jre8</mssql-jdbc.version> <mysql.version>8.0.20</mysql.version> ...... </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> <version>2.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <version>2.2.7.RELEASE</version> </dependency> ...... <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>${commons-pool.version}</version> </dependency> ..... </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> ...... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.7.RELEASE</version> </plugin> ...... <plugin> <artifactId>maven-install-plugin</artifactId> <version>${maven-install-plugin.version}</version> </plugin> ...... <plugin> <artifactId>maven-jar-plugin</artifactId> <version>${maven-jar-plugin.version}</version> </plugin> ...... </plugins> </pluginManagement> </build>
接着我们再次回到 pom.xml 中,还有一个与我们过去开发不同的东西,那就是 spring-boot-starter-web,在前面第一篇的文章中初始化组件时,我们就选择了 Spring Web 组件,自然这个依赖就是用来实现 Web 功能的
进入 spring-boot-starter-web
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
下面节选了一部分 xml 配置(依赖是全的),看到这里,也就能解释为什么 spring-boot-starter-web 这个依赖可以实现 Web 的开发,本质上就是对支持 Web 开发的 spring-webmvc、spring-webmvc等坐标进行了 “整合” 也就是将依赖传递了
<?xml version="1.0" encoding="UTF-8"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starters</artifactId> <version>2.2.7.RELEASE</version> </parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.2.7.RELEASE</version> <name>Spring Boot Web Starter</name> ...... <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.2.7.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.2.7.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.2.7.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.2.7.RELEASE</version> <scope>compile</scope> <exclusions> <exclusion> <artifactId>tomcat-embed-el</artifactId> <groupId>org.apache.tomcat.embed</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.2.6.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.6.RELEASE</version> <scope>compile</scope> </dependency> </dependencies> </project>
我们从一个入门程序讨论到了 spring-boot-starter-web, 在 SpringBoot 中所有的功能场景都被抽取出来 ,定义成了很多很多 启动器 ,通过这些启动器,开发人员就能按照自己的需求, 选择不同的启动器进行配 合,极为 快速的就能搭建起一个想要的项目运行框架环境 ,启动器命名规则如下:
在举几个例子,体验一下,以后的文章,在用的时候,会给出具体的说明
spring-boot-starter-test:支持常规的测试依赖,包括JUnit、Hamcrest、Mockito以及spring-test模块
spring-boot-starter-web:支持全栈式 Web 开发,包括 Tomcat 和 spring-webmvc
spring-boot-starter-redis:支持 Redis 键值存储数据库
除了依赖,还有一个与我们从前 Spring 的开发不一样的点就是,SpringBoot 多了一个启动类,在入门程序的编写的时候,我只提到,这个启动类是必须存在的,看似简单的一个类,实则也很玄妙,下面我们来一起分析一下
package cn.ideal; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(Springboot02QuickstartApplication.class, args); } }
首先最显眼的也就是在类上的 @SpringbootApplication 注解了,我们 ctrl 点击进入
@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
为什么这么说呢,大家可以把这三个注解加到我们之前的启动类上,然后执行一下,简单的看呢,其实也是可以达到之前的效果的,下面,我们就主要讲解一下这三个注解
package cn.ideal; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(Springboot02QuickstartApplication.class, args); } }
点击进去,我们可以看到 @SpringBootConfiguration 注解本质上,还是用了 Spring 的 @Configuration 注解
@Configuration public @interface SpringBootConfiguration {...}
在点 @Configuration 进去还可以看到下述内容
@Component public @interface Configuration {...}
这两个组件大家应该还是很熟悉的
@ComponentScan的功能其实是自动扫描并加载符合条件的组件(比如 @Component 和 @Controller 等)或者bean定义,并把这些bean定义加载到IoC容器中,在 XML 中相当于:
<!--开启扫描--> <context:component-scan base-package="cn.ideal"></context:component-scan>
我们可以通过 basePackages 属性来指定 @ComponentScan 自动扫描的范围,且和这个注解中value属性的作用是一致
如果不指定, 则默认Spring框架实现会从声明 @ComponentScan 所在类的 package 进行扫描 ,由于默认是不指定的,所以这也是我们把启动类放在与 controller service 等包同级的位置下
我们还可以看到指定了两个 Filter ,简单提一下他们两个
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
这个注解是极其重要的,顾名思义可以看到,它的格式为 @EnableXxxxxx,还有一些类似的注解 @EnableMBeanExport、@EnableScheduling 等等,他们也是相同的开头,拿一个举例
@EnableAutoConfiguration 也是这样的,他借助 @Import , 就可以把所有符合自动配置要求的 bean 定义加载到 IOC 容器
所以:这个注解的作用就是自动配置,代替了我们很多的配置操作,在 @SpringBootApplication 上加这个注解,也就代表 SpringBoot 自动配置的功能开启了
我们点进去具体看一下
首先同样可以看到很多注解,我们聚焦到这两个
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
我们可以点进去 @AutoConfigurationPackag 这个注解看一下,其下还有一个注解很重要 @Import(AutoConfigurationPackages.Registrar.class)
进而看一下 Registrar ,其中有一个 registerBeanDefinitions 方法就是导入组件类的实现,作用就是将注解标注的元信息传进去,然后得到对应的包名
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); }
总结一下:AutoConfigurationPackage注解的作用是将 添加该注解的类所在的package 作为 自动配置package 进行管理
根据我们启动类的所在包位置可得,使用 @AutoConfigurationPackage 注解可以将主程序类所在包及所有子包下的组件到扫描到 Spring 容器中
回到 EnableAutoConfiguration 下,除了 @AutoConfigurationPackage,最重要的来了,@Import(AutoConfigurationImportSelector.class),@Import 就不说了,那么 AutoConfigurationImportSelector 究竟做了什么呢?我们进入 AutoConfigurationImportSelector
由于 AutoConfigurationImportSelector 字面意思叫做 自动配置导入选择器,所我们来找一下关于获得配置的相关代码
首先在 getAutoConfigurationEntry 方法中看到这么一行代码:
// 获取全部的配置 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
这些配置是通过 getCandidateConfigurations 获取到的,所我们继续跳转过去
找到 getCandidateConfigurations ,这个方法,其中又调用了 SpringFactoriesLoader 类的静态方法 loadFactoryNames(注意引入的第一个参数)
// 获得候选的配置 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; } // 在上面被当做参数调用了 // 返回的就是最开始启动自动导入配置文件的注解类;EnableAutoConfiguration protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
继续深入这个方法,我节选了一部分重要的,关键部分我都加了注释
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { // 前面传入的参数为EnableAutoConfiguration.class // 所以factoryClassName为org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryTypeName = factoryType.getName(); // 把 factoryClassName传入,返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径 return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 找到所有的 "META-INF/spring.factories" 定义在上方 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类似于HashMap,包含了属性的key和value Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { // 属性文件中用','分割多个value 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); } }
到这里了,再怎么找也该知道去找 spring.factories 了,那么这个 META-INF/spring.factories 文件从哪里找起呢?一般都是默认当前文件下的,所以顺着上面的代码分析找到了源头 package org.springframework.boot.autoconfigure 下
点进去一看,好多以 AutoConfiguration 结尾的配置内容,这就是自动配置的根本,挑选几个简单列出来
...... org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,/ 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,/ ......
随便找几个点进去看一下,可以看到他们每个都是JavaConfig配置类,同时注入了一些Bean,所以可以看出这个 spring.factories 文件就是我们配置的根源内容了
@EnableAutoConfiguration 注解总结:
在 启动类中的 @SpringbootApplication 注解 下引入 @EnableAutoConfiguration 注解,它提供了一种配置查找的功能支持,也就是说,@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为查找的Key,获取对应的一组@Configuration类
这些配置类具体是如何做的我们这里不深究,但是要补充一个点,我们首先进入下面这个 Configuration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,/
@Configuration(proxyBeanMethods = false) 自然代表这是一个配置类,这也与我们前面的分析一致
要说的是这个注解:@EnableConfigurationProperties(ServerProperties.class)
看名字来说,它是和 Properties 配置有关的,继续点入 ServerProperties
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties { /** * Server HTTP port. */ private Integer port; /** * Network address to which the server should bind. */ private InetAddress address ...... }
又看到这样一条注解:@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
可以看到,这里指定了一个前缀 server,下面还定义了很多东西,例如这里定义了端口还有地址等等
那么,为什么要说这个呢?有什么用呢?
这就与我准备下一篇写的文章有关了,也就是配置相关的,我们可以在这里先体验一下
在项目 resources 的 application.properties 下,我们可以添加这样一条配置:
server.port=9090
就这样一行配置,就把端口号修改好了,是不是很神奇
这就是我们刚才想说的,实际上这里配置的 server.port 其中 server 就是那个 @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) 配置的前缀,port 就是下面定义的端口, 也就是SpringBoot会将配置文件中以server开始的属性映射到该类的字段中
如果文章中有什么不足,欢迎大家留言交流,感谢朋友们的支持!
如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号
一个坚持推送原创开发技术文章的公众号:理想二旬不止