Spring Boot 框架学习笔记(一)
一. 回顾:Spring、SpringMVC
1.1 Spring框架
Spring框架最核心特性IoC(控制反转)或者DI(依赖注入),以及AOP的容器框架。
它使得开发的耦合度降低,扩展性增强,从而简化企业开发,可以跟很多很多的第三方框架进行整合,例如MyBatis等等
(1)IOC是什么?
控制反转,帮我们管理Bean,创建和初始化(依赖)
(2)AOP是什么?
面向切面编程,在不改变原来代码的基础增加新的功能,底层实现动态代理(Java动态代理或者CGLIB的动态代理),
增强代码的位置:前置、后置、返回、异常、环绕
1.2 Spring MVC框架
Spring MVC提供另一条友好的开发WEB应用程序(类似于Struts2框架)
(1)模板引擎是什么
模板引擎为了使用户页面和业务数据相互分离而产生,它将从后台返回的数据生成特定格式的文档,用于网站的模板引擎就是生成HTML文档。
(2)未来发展
在未来发展过程,基本上不会在使用模板引擎(JSP、Freemark,Thymeleaf)。使用模板引擎的目的是为了动态生成HTML页面
技术发展到现在,早已做到了动静分离(涉及到数据的传输格式,大部分都使用JSON格式,没有了动态生成html页面的过程),静态资源(HTML/JS/CSS/图片)静态资源会在一个单独的服务器(Nginx)
二. Spring Boot
2.1 Spring Boot 诞生
在Spring Boot诞生之前,我们自己搭建SSM框架整合的过程中**,使用Spring框架需要配置很很多的文件(XML文件或者Java配置类)**,为了向Spring Boot转变的过程,我们特意忽略XML配置,直接使用Spring注解方式(即使使用注解,依旧要配置很多东西)。
随着WEB项目应用的需求的变化,我们大家环境之间的框架会越来越多(比如MongDB,Redis等框架),XML配置文件也会不断增加,容易造成配置文件难以理解并且“容易出错”。并且我们会发现很多的配置文件之间的配置内容总是不断重复,一般 我们会直接使用粘贴复制大法。
项目重复性太多、相同的配置太多、增加了工作量,所以我们可以把重复部分提取出来(构建一个应用场景,提供了最基本的配置方案(默认配置))
2.2 Spring Boot 的理解
(1)自己接电线和标准化插座
Spring Boot的配置方式,就是像标准化的插头插座,都是有提供“统一标准(约定)”。
当使用第三方开源框架的时候,想进入Spring Boot生态圈,必须实现Spring
Boot提供标准,才能使用(需要按照标准制作一个starter
场景适配),提供最基本的使用配置,我们也可以去改变默认的配置。
单独使用Spring MVC就像手动接线,很灵活,但是由于每个人不同的使用方式,接出来的效果会千差万别,而且容易出错,造成接手人员不适应。
SpringBoot虽然灵活性不如单独Spring MVC的方式,但是我们使用SpringBoot之后,不用清楚出内部是如何设计,我们只是会使用而已(但是对于我们自己,还需要知道内部是如何使用),话说回来,当我们习惯使用插头插座的时候,你不会期望自己成为电工。
Spring Boot的目标不在于为已解决的问题提供新的解决方案(SSM自己搭建),而是为平台带来一种新的开发体验(内部还是SSM搭建方案),换汤不换药,Spring Boot只是简化技术使用(配置和处理):
- 使配置变简单
- 使监控变简单
- 使部署变简单(内置WEB服务器,不需要我们自己配置)
- 使开发变简单(定制也不会太简单)
springboot核心就是自动装配!这点就和spring核心ioc和aop一样,牢牢记住!springboot为什么能简化开发,因为它可以自动装配,那为什么自动装配(这个时候我们就要去了解自动装配的原理)
2.3 主要特性
- 遵循“约定优于配置”的原则,简化配置(最重要)
- 可以完全脱离XML配置文 件,采用注解配置
- 内嵌Servlet容器,应用可以使用jar方式执行:
java -jar
- 开发完成项目搭建(Maven或者Gradle),整合第三方类库,方便使用(实现标准)
- 提供
starter
,能够非常方便的进行包的管理,简化包管理配置(基本不要我们管理包的版本) - 最大优势:
微服务
可以与Spring Cloud天然无缝集成,Spring Boot目前Java体系中实现微服务的最佳方案之一
2.4 集成第三方框架步骤
(1)使用Maven引入springboot-XXX-starter
资源(资源一定实现Spring Boot标准,提供默认的配置项,我们可以自定义)
(2)自定义:可以修改Spring Boot平台的默认配置文件application.properties
或者application.yaml
(3)加入一个Java Config 配置类,属于个性化配置,如果采用默认配置,就不需要建立配置类
三. 缘起Hello World
-
创建Spring Boot项目,项目Maven的配置
-
Spring MVC的场景:提供默认的配置项,直接可以使用(不需要你做任何事情)
-
创建完成
-
新建控制器,设置映射路径
package com.yue.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloWorldController {
@ResponseBody
@GetMapping("/hello")
public String test01(){
return "你好,世界!"+Math.random();
}
}
- 启动主程序,访问映射路径(不需要写任何配置,不需要自己配置服务器)
运行成功,由此我们知道不用配置SpringMVC依旧可以访问。那么SpringBoot默认引用了什么配置呢?我们去该项目下的pom文件中查看:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
通过此项依赖,即可引入SpringMVC。
并且不需要自己配置版本
Spring MVC的场景适配,提供了针对于Spring Boot版本对应的Spring MVC的版本,并且提供了默认的配置项
- 注意:
默认情况下,扫描的是主程序操作的包以及子包
所以如果建立控制器的位置在其以外会扫描不到
那么当我们的控制器不在其扫描范围的时候,解决方法如下:
改变主程序中
@SpringBootApplication
的属性scanBasePackages
的值:
默认为空,按照当前目录找
经查看其源码发现它就是个数组,所以此时添加控制器所属路径的字符串即可
如@SpringBootApplication(scanBasePackages ={“com.yue”,"org.os"})
四. SpringBoot自动装配原理简述
简述
核心依赖
spring-boot-dependencies
:核心依赖在父工程中,路径:pom==>spring-boot-starter-parent==>spring-boot-dependencies
pom
中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
spring-boot-starter-parent
中还有一个父依赖,这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
在写入一些SpringBoot依赖的时候,不需要指定版本,就是因为有这些版本仓库,但是如果导入的包没有在依赖中管理,就需要手动配置版本了;
资源过滤
- 路径:
pom==>spring-boot-starter-parent
- filtering用于扩大范围:maven默认只会替换pom文件中的占位符属性,不会触碰resources下相关文件.但如果filtering=true了,就可以触碰了.触碰的效果就是能替换resources下指定文件的占位符属性.
- 只想让指定的几个文件被打包,那就将这几个文件放在includes标签下处理
- 不想让这几个文件被打包,那就将这几个文件放在excludes标签下处理.
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/application*.yaml</exclude>
<exclude>**/application*.properties</exclude>
</excludes>
</resource>
</resources>
启动器
SprinfBoot将所有的功能场景都变成一个个启动器(
springboot-boot-starter-xxx
:就是spring-boot的场景启动器)。
具体有哪些功能场景,我们可以去核心依赖库也就是spring-boot-dependencies
中通过jar包来查看有哪些功能场景
启动器说白了就是SprinfBoot的启动场景。
它的作用就是自动导入该启动器涉及环境所需的所有依赖
比如将启动器名换成<artifactId>spring-boot-starter-web</artifactId>
后,会帮我们自动导入web环境所需所有依赖
我们需要什么功能,只需找到相应启动器就行(start),可以在官网中查看最新的SpringBoot中有哪些启动器,有哪些依赖
<!--启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
主启动类
//@SpringBootApplication标注这个类是一个springboot的应用
@SpringBootApplication
public class Boot03Application {
public static void main(String[] args) {
//将springboot启动
SpringApplication.run(Boot03Application.class, args);
}
}
主启动类注解:@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}
)}
)
前四个为元注解,详细点这里
@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@SpringBootConfiguration
SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;继续查看源码:
@Configuration
@Indexed
@Configuration
":说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件
@Indexed
:为Spring的模式注解添加索引,以提升应用启动性能。
点击@Configuration
继续查看源码:
@Component
public @interface Configuration
这里有 @Component
注解:说明其实质是个spring的组件
@EnableAutoConfiguration
(开启自动导入配置功能)
顾名思义,自动导入配置,查看源码:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
@AutoConfigurationPackage
:自动配置包
@Import({Registrar.class})
public @interface AutoConfigurationPackage
@Import({Registrar.class})
:包注册,Spring底层注解@import , 给容器中导入一个组件
Registrar.class
作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器;
@Import({AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector :自动配置导入选择器
AutoConfigurationImportSelector
:它会导入哪些组件的选择器?
进入查看源码:
- 这个类中有一个方法,作用是获取候选配置:
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;
}
点击getSpringFactoriesLoaderFactoryClass()
:发现返回的是EnableAutoConfiguration
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
这个方法返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
- 这个方法又调用了
SpringFactoriesLoader
类的静态方法.点击进入SpringFactoriesLoader类的loadFactoryNames() 方法:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
//这里它又调用了 loadSpringFactories 方法
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
- 继续点击查看 loadSpringFactories 方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//获得classLoader , 我们通过前面的步骤可以知道这里得到的就是EnableAutoConfiguration标注的类本身
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
//去获取一个资源 "META-INF/spring.factories"
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//将读取到的资源遍历,封装成为一个Properties
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
发现其从
META-INF/spring.factories
文件中获取资源,然后遍历获取到的资源并封装到一个个properties中:Properties properties = PropertiesLoaderUtils.loadProperties(resource)
- 发现一个多次出现的文件:spring.factories,全局搜索
- 打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
META-INF/spring.factories
:自动配置的核心文件
- 我们在上面的自动配置类随便找一个打开看看,比如
WebMvcAutoConfiguration
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean
总结:
所以,自动配置**真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。**
思维导图:
这么多的引用类只有导入相应start,相应的类才生效。通过
@ConditionalOnXXX
l来判断条件是否成立,从而控制类是否生效
结论:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
- 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
SpringApplication.run
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
SpringApplication.run
分析
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
SpringApplication
这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
run方法启动会独占一个端口
1.推断应用的类型是普通的项目还是Web项目
2.推断并设置main方法的定义类,找到运行的主类然后加载
3.run方法中的监听器获取上下文,处理bean
深入
分析自动配置原理
我们以
spring.factories
中的HttpEncodingAutoConfiguration
(Http编码自动配置)为例解释自动配置原理;
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration
//启动指定类的ConfigurationProperties功能;
//使使用 @ConfigurationProperties 注解的类生效,也就是使HttpProperties类生效
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来,并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//它已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//。。。。。。。
}
注解解释
@Configuration
:表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(指定类.class)
:自动配置指定类的属性
在上面的代码中发现,此配置类中私有属性及构造方法都会传入httpproperties,那么我们点击HttpProperties
进入源码查看:
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties{}
可以看到该注解中有前缀
此时我们在配置中写前缀spring.http
可以看到提示出来的语法全部都是httpproperties
类中的属性
配置文件和spring.factories
文件关联的xxxProperties
类的联系
ConfigurationProperties
注解有一个prefix属性,通过指定的前缀,绑定配置文件中的配置,该注解可以放在类上,也可以放在方法上.此时
HttpProperties
类绑定了配置文件下spring.http
的所有东西,
所以在配置文件中写的spring.http.xxx
都会与其绑定(但是写的属性必须和该类的属性对应,如果写该类中没有的属性,也就没地绑定)
@ConditionalOnWebApplication
/ @ConditionalOnClass
/ @ConditionalOnProperty
:根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效
自动配置原理总结
- SpringBoot启动会加载大量的自动配置类 ,根据不同的条件判断类是否生效,一但生效;这个配置类就会给容器中添加各种组件
- 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
- 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
- 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类。给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion
:自动配置类;给容器中自动添加组件
xxxxProperties
:封装配置文件中相关属性;自动装配类,装配配置文件中自定义的内容
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix ="spring.http")
public class HttpProperties {...}
去配置文件中试试绑定:
以上,就是自动装配原理!
重要!@Conditional
是Spring的底层注解:根据当前不同的条件判断,决定当前配置类是否生效
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多配置类,如何知道哪些自动配置类生效?
可以通过在配置类中启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
控制台输出的配置类日志中有三类:
- Positive matches:自动配置类启用的:正匹配
- Negative matches:没有启动,没有匹配成功的自动配置类:负匹配
- Unconditional classes: 没有条件的类
五. 项目说明
4.1 目录说明
src/main/java
:存储Java的主程序,初始化包含主程序入口XXXApplication.java
,可以通过直接运行该启动类,启动Spring Boot应用src/main/resources
:可以存储静态资源以及存储WEB页面的模版(动态生成),个人不推荐将静态资源放置到这个目录下src/test
:单元测试目录.gitgnore
:GIT管理项目版本的排除文件target
:代码构建打包结构文件的位置,不需要我们维护pom.xml
:Maven项目配置文件application.properties
或者application.yaml
(更灵活,但是需要学习相应语法):用于存储程序的各种依赖模糊的配置信息(定制个性化的配置),不配置就使用提供的默认配置
关于静态资源存储目录说明:
src/main/resources/static
:主要存放静态资源文件(CSS/JS/图片等文件)src/main/resources/public
:主要存放静态资源文件(可以自己访问HTML文件)src/main/resources/template
:用来存储WEB开发的模版文件(动态生成HTML文件)
4.2 核心配置文件
在 application.properties
定制化配置
##定制个性化的配置文件
server.port=8001
server.servlet.context-path=/boot01
结果:
4.3 修改Banner
在src/main/resources
新建banner.txt文件,可以设置启动图形https://www.bootschool.net/ascii
六. JSP模版引擎
JSP在Spring Boot平台淘汰了,如果可能,应该避免jsp,默认不支持JSP,除了REST
web服务之外,您还可以使用Spring MVC提供动态HTML内容。Spring
MVC支持多种模板技术,包括Thymeleaf、FreeMarker和jsp。而且,许多其他模板引擎都包含它们自己的Spring
MVC集成。
(1)打包方式变成war包
<groupId>com.yue</groupId>
<artifactId>boot01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot01</name>
<packaging>war</packaging>
(2)支持JSP依赖
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
(3)修改application.properties
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp
(4)webapp/WEB-INF目录
@Controller
public class IndexController {
@GetMapping("/student")
public String test02(Model model){
model.addAttribute("userName","夜雨");
return "welcome";//页面
}
}
<body>
你好,${requestScope.userName}!
</body>