一.前言
1.1 什么是微服务?
单服务场景
开发简单,测试简单,部署简单
下面举个例子,一种单一的服务场景:所有的模块都是打包成一个war包的形式,然后部署到tomcat中。
缺点:
1) 只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块;
2) 系统耦合性强,一旦其中一个模块有问题,整个系统就瘫痪了;一旦升级其中一个模块,整个系统就停机了;
3) 集群只能是复制整个系统,即使只是其中一个模块压力大。(可能整个订单处理,仅仅是支付模块压力过大, 按道理只需要升级支付模块,但是在单一场景里面是不能的)
1.2 微服务概念
下面是微服务(Micro-Service)架构,不同模块放到不同的进程**/**服务器上,模块之间通过网络通讯进行协作。
各个模块都部署在不同的服务器上面,模块之间可以通过http请求进行协作。例如web服务器收到请求,那么可以根据请求的业务吧请求分配到响应的处理服务器,例如短信消息服务器。
优点:
1) 可以用不同的语言或者语言不同版本开发不同模块;
2) 系统耦合性弱,其中一个模块有问题,可以通过“降级熔断”等手段来保证不停机;
3) 可以对不同模块用不同的集群策略,哪里慢集群哪里。
缺点:
1) 开发难度大,系统结构更复杂;
2) 运行效率低; (模块之间相互请求时间长等等)
https://martinfowler.com/articles/microservices.html#MicroservicesAndSoa
所以就衍生出了 springboot和springcloud两个框架,当然springboot并不能代表微服务的概念,springcloud才是。
因为springboot只是简化了单一web服务开发的流程,摒弃了大量的xml配置,提供了大量的自动化配置。
那么springboot跟springcloud是什么关系呢? SpringBoot专注于快速方便的开发单个个体微服务。SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系.
SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
1.3 spring的演化
Spring1.x 时代
在Spring1.x时代,都是通过xml文件配置bean,随着项目的不断扩大,需要将xml配置分放到不同的配置文件中,需要频繁的在java类和xml配置文件中切换。
Spring2.x时代
随着JDK 1.5带来的注解支持,Spring2.x可以使用注解对Bean进行申明和注入,大大的减少了xml配置文件,同时也大大简化了项目的开发。
那么,问题来了,究竟是应该使用xml还是注解呢?
最佳实践:
1、 应用的基本配置用xml,比如:数据源、资源文件等;
2、 业务开发用注解,比如:Service中注入bean等;
Spring3.x到Spring4.x
从Spring3.x开始提供了Java配置方式,使用Java配置方式可以更好的理解你配置的Bean,现在我们就处于这个时代,并且Spring4.x和Spring boot都推荐使用java配置的方式。
1.4为什么要学习SpringBoot
java一直被人诟病的一点就是臃肿、麻烦。当我们还在辛苦的搭建项目时,可能Python程序员已经把功能写好了,究其原因主要是两点:
· 复杂的配置
项目各种配置其实是开发时的损耗, 因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以写配置挤占了写应用程序逻辑的时间。
· 混乱的依赖管理
项目的依赖管理也是件吃力不讨好的事情。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本和其他库不会有冲突,这也是件棘手的问题。并且,依赖管理也是一种损耗,添加依赖不是写应用程序代码。一旦选错了依赖的版本,随之而来的不兼容问题毫无疑问会是生产力杀手。
而SpringBoot让这一切成为过去!
二. 什么是springboot
三.Springboot使用
这里开发工具使用的是sts和idea,他们两者开发springboot 的方式没有什么区别,所以下面两种方式我都会嵌套使用。下面的工程采用springboot版本是:1.5.9。
3.1 使用maven方式手动搭建sb项目
Idea maven环境配置
编码设置:
需求:浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串;
1、创建一个maven工程
2、导入spring boot相关的依赖
|
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
3、 编写一个主程序;启动Spring Boot应用
/* @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用 */ @SpringBootApplication public class HelloWorldMainApplication { public static void main(String[] args) { // Spring应用启动起来 SpringApplication.run(HelloWorldMainApplication.class,args); } }
|
4、编写相关的Controller、Service
|
@Controller public class HelloController { @ResponseBody @RequestMapping(“/hello”) public String hello(){ return “Hello World!”; } }
5、运行主程序测试
直接运行HelloWorldMainApplication的main方法,启动sb程序。
在浏览器输入 127.0.0.1:8080/hello 可看到输出 Hello World!
6、打包成嵌入式web的可执行jar包
|
运行maven的 mvn package 打包命令,可以看到生成的jar包。
直接使用java -jar的命令进行执行,效果同 5 。
3.2 使用Spring Initializer快速创建Spring Boot项目-推荐
3.2.1 sts编译器方式
STS工具下创建CRUD的步骤(IDEA相同的步骤)
1.主菜单:File→New→Spring Starter Project。
在Type中选Maven,Package选War
下一步中搜索勾选Web。
点击【Finish】会创建项目,第一次创建完成后会进行maven包的下载等,需要几分钟。项目创建成功后,他会生成一个springboot的启动类。后面就是靠他来启动springboot
2.新建一个Controller
3.新建一个index.html
需要注意的是,界面要放置在 resouces – templates 目录下(不是放在我们以前的webroot下面了)
4.启动程序,访问。
可以看到,他非常的简单。不需要配置一大堆文件
3.2.2 IDEA编译器方式
方式同上
IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目;
选择我们需要的模块;向导会联网创建Spring Boot项目;
默认生成的Spring Boot项目;
- 主程序已经生成好了,我们只需要我们自己的逻辑
- resources文件夹中目录结构
- static:保存所有的静态资源; js css images;
- templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf);
- application.properties:Spring Boot应用的配置文件;可以修改一些默认设置;例如修改服务器端口号
四. Springboot核心
下面分析根据helloworld 源码。
4.1、POM文件
|
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
单击进去查看,发现他的父项目是:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.5.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>
|
再单击进去发现
它里面配置了很多properties,定义了各种依赖的版本。也就是说他是用来真正管理Spring Boot应用里面的所有依赖版本,Spring Boot的版本仲裁中心;以后我们导入依赖默认是不需要写版本;(没有在dependencies里面管理的依赖自然需要声明版本号-)
4.2、web启动器
|
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
spring-boot-starter-web:
spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器
4.2、主程序类,主入口类
/* @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用 */ @SpringBootApplication public class HelloWorldMainApplication { public static void main(String[] args) { // Spring应用启动起来 SpringApplication.run(HelloWorldMainApplication.class,args); } }
|
1 注解分析-@SpringBootApplication
@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
1.@SpringBootConfiguration
@SpringBootConfiguration:Spring Boot的配置类,sb自定义的一个注解,标注在某个类上,表示这是一个Spring Boot的配置类。
点进去发现实际上他是使用spring的@Configuration来注解的,而@Configuration的上一层是@Component。
在Spring Boot项目中推荐使用@ SpringBootConfiguration替代@Configuration
**总的来说:这个注解的作用是把当前启动类加入到spring容器中,同时当做一个配置类来使用。**
2. @EnableAutoConfiguration
@EnableAutoConfiguration:开启自动配置功能,以前我们需要配置的东西,Spring Boot帮我们自动配置;例如:我们添加了spring-boot-starter-web的依赖,项目中也就会引入SpringMVC的依赖,Spring Boot就会自动配置tomcat和SpringMVC,下面我们查看这个注解具体的作用。
@AutoConfigurationPackage:自动配置包
利用Spring的底层注解@Import,给IOC容器中导入组件,导入的组件由AutoConfigurationPackages.Registrar.class来控制。
额外提示:
可以参见在网站上发布的《spring注解》文章,关于@import注解导入bean的三种方式
(1)源码分析
查看AutoConfigurationPackages的内部类Registrar,他实现了ImportBeanDefinitionRegistrar接口。通过这个接口的重载方法registerBeanDefinitions(),控制导入IOC容器的bean。
|
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { 1. register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }
会去调用registerBeanDefinitions方法注册bean,紧接着registerBeanDefinitions方法调用register方法。
该方法会首先判断,IOC容器中是否存在
org.springframework.boot.autoconfigure.AutoConfigurationPackages 类,不存在则把该类注册到容器中,不存在则创建
在1处打个断点
发现获取的metadata元数据信息是HelloWorldMainApplication的。计算new PackageImport(metadata).getPackageName()的值得出的是HelloWorldMainApplication所在包的包名。Registry是IOC容器实例
总结:
@AutoConfigurationPackage注解的作用是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器,也就是说,如果controller,service等等类不在这个包下的话,是不会注册到spring容器中的,所以需要注意。
尖叫提示:
我们知道spring启动的时候会默认扫描启动类所在的包下面的所有类,注入springioc容器中,但是如果我们需要注入ioc容器的类不在启动类包下,那么我们可以通过这个@ImportResource(locations = {“classpath:beans.xml”})
注解进行注入额外的类(注解加在 启动类上)
注解进行注入额外的类(注解加在 启动类上)
@Import(AutoConfigurationImportSelector.class)
(1)整体结构分析
还是利用了@import注解导入了AutoConfigurationImportSelector.class 类,
查看他的继承结构。
题外话:
可以看到很多组件都实现了Aware接口,关于实现Aware的作用是,我们可以在自定义组件中使用Spring底层的一些组件,例如创建一个bean时候,我们想查看IOC容器的信息或者查看当前系统的信息,那么这个时候就需要用到Aware接口,注入这些底层组件,供自定义组件调用。
可以参见在网站上发布的《spring注解》文章,关于Aware接口
关键代码
selectImports方法,返回的是需要导入到IOC容器的bean的全类名。类似
(2)详细分析
- 查看selectImport(),可以看到最终返回的是自动配置实体类autoConfigurationEntry. getConfigurations()方法的返回值,就是一个List
那么很明显获取自动配置实体类的代码是接下来我们所要关注的重点。
2.进入AutoConfigurationImportSelector.getAutoConfigurationEntry()方法
同理可得,关键代码是:List
3.接着看getCandidateConfigurations方法里面的关键代码段。
关键代码:loadFactoryNames()方法,他的第一个参数通过debug我们可以得知,也就是getSpringFactoriesLoaderFactoryClass()方法的值是:
EnableAutoConfiguration.class –> org.springframework.boot.autoconfigure.EnableAutoConfiguration
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader)**;**
深入查看loadFactoryNames方法可知。
他是通过
最终loadFactoryNames()方法的返回值是:
(3)总结:
@Import(AutoConfigurationImportSelector.class)注解的作用是,通过以key为
org.springframework.boot.autoconfigure.EnableAutoConfiguration,然后在META-INF/spring.factories文件中,获取key对应的value值,然后打包成一个值是全类名的List
META-INF/spring.factories文件的位置。
J2EE**的整体整合解决方案和自动配置都在**spring-boot-autoconfigure-2.0.5.RELEASE.jar
关于自动配置:下面的5.12章节也会阐述到。
3. @ComponentScan
4.3 关闭自动配置
通过上述,我们得知,Spring Boot会根据项目中的jar包依赖,自动做出配置,Spring Boot支持的自动配置如下(非常多):
如果我们不需要Spring Boot自动配置,想关闭某一项的自动配置,该如何设置呢?
比如:我们不想自动配置Redis,想手动配置。
当然了,其他的配置就类似了。
4.4 自定义Banner-了解即可-没什么用
启动Spring Boot项目后会看到这样的图案:
这个图片其实是可以自定义的:
\1. 打开网站:
http://patorjk.com/software/taag/#p=display&h=3&v=3&f=4Max&t=itcast%20Spring%20Boot
拷贝生成的字符到一个文本文件中,并且将该文件命名为banner.txt
\2. 将banner.txt拷贝到项目的resources目录中:
\3. 重新启动程序,查看效果:
如果不想看到任何的banner,也是可以将其关闭的:
4.5全局配置文件
Spring Boot项目使用一个全局的配置文件application.properties或者是application.yml,在resources目录下或者类路径下的/config下,一般我们放到resources下。
1、 修改tomcat的端口为8088
重新启动应用,查看效果:
2、 修改进入DispatcherServlet的规则为:*.html
测试:
五. 配置文件
SpringBoot使用一个全局的配置文件,配置文件名是固定的(名字不能更改否则无效)
•application.properties (默认生成)
•application.yml
而且通过观察发现 application.properties 的优先级别比 application.yml 高,同样的属性配置,**properties会覆盖yml**的配置
配置文件的作用:修改SpringBoot自动配置的默认值,SpringBoot在底层都给我们自动配置好。
5.1 YAML
他是一种标记语言
5.2YAML语法
5.3配置文件值注入
5.4 @Value获取值和@ConfigurationProperties获取值比较
5.5、配置文件注入值数据校验
5.6、@PropertySource&@ImportResource&@Bean
1. @PropertySource:加载指定的配置文件;
应用场景,我们知道application.properties是项目的整体配置文件,但是如果存在大量跟项目关系不是那么密切的配置信息,我们是可以配置到其他的配置的文件中去,避免造成application.peoperties文件的大小过大。这个时候就需要PropertySource注解进行指定。
2. @ImportResource**:**
导入Spring的配置文件,让配置文件里面的内容生效,Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别,想让Spring的配置文件生效,加载进来,@ImportResource标注在一个配置类上。
3.@bean
Bean注解一般是配合着@Configure注解使用
5.7比较application.properties和application.yml和自定义properti的优先级别
同样的属性配置 : application.properties > application.yml > 自定义properties
前者会覆盖后者
5.8、配置文件占位符
5.9、Profile
使用场景,针对于配置文件。例如我们在开发过程中使用的是开发环境的properties,那么生产使用的是生产的properties。那么我们怎么指定呢? spring提供了profile功能
5.10、配置文件加载位置(Important)
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
–file:./config/ (在项目的的根目录下新建config)
–file:./ (在项目的的根目录下)
–classpath:/config/ (项目的resource目录下新建config)
–classpath:/ (默认生成的application.properties是在类路径下面的)
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
5.11、外部配置加载顺序
5.11 bookstrap.yml
其实还有一个系统级别的配置文件,这个是springcloud的在使用configserver组件的时候使用的,详情可查看后续的springcloud文章-springcloud的配置中心。
5.12、自动配置原理
配置文件到底能写什么?怎么写?自动配置原理;
1、自动配置原理
1)、SpringBoot启动的时候加载主配置类,开启了自动配置功能通过这个注解@EnableAutoConfiguration
2)、@EnableAutoConfiguration作用:
· 利用EnableAutoConfigurationImportSelector给容器中导入一些组件?
· 可以查看selectImports()方法的内容;
· List
|
将 类路径下 META-INF/spring.factories 里面配置的所有**EnableAutoConfiguration**的值加入到了容器中
|
每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
3)、每一个自动配置类进行自动配置功能;
4)、以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
|
根据当前不同的条件判断,决定这个配置类是否生效?
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
5)、所有在配置文件中能配置的属性都是在xxxxProperties类中封装者。配置文件能配置什么就可以参照某个功能对应的这个属性类
|
精髓:
1**)、SpringBoot启动会加载大量的自动配置类**
2**)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;**
3**)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)**
4**)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;**
xxxxAutoConfigurartion:自动配置类;
给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
2、细节
1、@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
@Conditional**扩展注解** | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
自动配置类必须在一定的条件下才能生效;
我们怎么知道哪些自动配置类生效;
==**我们可以通过在application.properties中启用 debug=true属性;来让控制台打印自动配置报告==**,这样我们就可以很方便的知道哪些自动配置类生效;
|
六.日志
七 springboot的web开发
7.1 引言
使用SpringBoot;
1**)、创建SpringBoot应用,选中我们需要的模块;**
2**)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来**
3**)、自己编写业务代码;**
我们知道sb框架已经给我们自动配置了很多相关的配置,我们只需要着重于业务代码的编写,但是有些细则还是需要了解的,例如我们打包的sb程序成jar包的形式,那么我们网站的css js等等资源是放置在那个目录呢?换言说,静态资源的访问是个怎么流程呢?
7.2、SpringBoot对静态资源的映射规则
1.Web**开发的自动配置类**
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration
查看关键代码
7.2.1 查看第一层映射关系
这段代码的意思是:所有/webjars/**的请求 ,都去classpath:/META-INF/resources/webjars/ 找资源。
例如请求jauery的请求 localhost:8080/webjars/jquery/3.3.1/jquery.js
1.**什么是webjars?**
以jar包的方式引入静态资源,可登陆这个网址了解http://www.webjars.org/
也就是说,网站需要的jq等等资源你可以通过maven的方式导入到项目中。
在访问的时候只需要写webjars下面资源的名称即可
|
导入到sb项目后查看依赖
启动项目请求:localhost:8080/webjars/jquery/3.3.1/jquery.js。 输出jquery内容。
总结:
也就是说,如果我们需要引用网上相关的资源可以使用webjars的方式导入相关的组件,但是有个问题,那就是如果我们项目中已经存在某些**css和js,那么怎么引用到sb**项目中呢(也就是请求不满足第一层映射关系)?
例如:
7.2.2 第二层映射关系
查看staticPathPattern的值:staticPathPattern = “/“。也就是说改代码块匹配访问当前项目任何资源的请求**
那么他是去哪里寻找资源回应请求呢?
查看getStaticLocations()方法,通过查看发现:
|
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
也就是说,请求会去类路径的根路径下的这个几个目录寻找相应的资源响应。
类路径是指:
例如sbp项目中,java和resources都是属于类路径的根路径。静态资源,我们可以在resources下创建 resources目录存放,或者创建public目录存放,static**目录默认已经创建**
2.案例
我们把一些样式文件放置到static目录下
启动sb项目,访问静态资源。http://127.0.0.1:8080/asserts/img/4D74191A.jpg
访问成功。很明显不满足第一层映射关系,走的是第二层映射关系的逻辑
7.2.3 欢迎页配置
通过查看他也是从7.2.2章节中静态资源存放目录下寻找index.html。
localhost:8080/ 找index页面
7.2.4 网站图标
所有的 **/favicon.ico 都是在静态资源文件下找;==(也是从7.2.2章节中静态资源存放目录下)
7.2.5 修改静态资源文件夹路径
7.3、模板引擎
常用的模板引擎有JSP、Velocity、Freemarker、Thymeleaf。核心理念是,通过传入模板代码和需要替换的数据到模板引擎中,模板引擎自动转化成静态的界面显示
SpringBoot推荐的Thymeleaf,语法更简单,功能更强大;
1、引入thymeleaf
|
我这里默认导入的是3.011版本。
如果你们想要修改导入版本,那么也是可以的:
Pom.xml文件中添加版本覆盖即可
2、Thymeleaf使用
我们通过查看他的自动配置源码来了解一下他的加载结构:
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
的ThymeleafProperties 属性
发下它默认加载类路径下的templates目录下的后缀为html的文件。
只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染;
2.1 案例1(常规使用)
(1) 在templates目录下新建ok.html界面
(2) 在controller中添加请求处理,最终是跳转到ok.html界面
(3)启动sb项目,并访问http://127.0.0.1:8080/ok
2.2 动态赋值
1.修改ok.html界面
2.修改请求处理方法 ok()
3.启动sb项目,并访问http://127.0.0.1:8080/ok
总结:
这里建议给html界面加上命名空间,这样在使用thyeleaf语法时会有提示
4. Thymeleaf语法
https://www.thymeleaf.org/ 官方地址
1)、th:text;改变当前元素里面的文本内容;
th:任意html属性;来替换原生属性的值
2)、表达式?
7.4、SpringMVC自动配置
1. Spring MVC auto-configuration
通过查看官方文档查看springboot为springmvc自动配置了那些工作
逐句分析:
Spring Boot 自动配置好了SpringMVC
以下是SpringBoot对SpringMVC的默认配置:关键类WebMvcAutoConfiguration
1 Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
- 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发或者重定向))
- ContentNegotiatingViewResolver:组合所有的视图解析器的;
- 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来
2.Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars
3.Static index.html support. 静态首页访问
4.Custom Favicon support (see below). favicon.ico
5.自动注册了 of Converter, GenericConverter, Formatter beans.
- Converter:转换器; public String hello(User user):类型转换使用Converter
- Formatter 格式化器; 2017.12.17===Date;
关键代码:
|
@Bean @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")//在文件中配置日期格式化的规则 public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件 }
自己添加的格式化器转换器,我们只需要放在容器中即可
6**.Support for HttpMessageConverters (see below).**
7**. Automatic registration of MessageCodesResolver (see below).**定义错误代码生成规则
8**. Automatic use of a ConfigurableWebBindingInitializer bean (see below).**
2、扩展SpringMVC
虽然springboot框架给我们自动配置了很多组件,但是在真实的应用场景中,肯定还需要自己实现一些组件,来扩展我们的springboot程序,例如我们需要定义一个拦截器和一个特定功能的视图解析器。那么就需要扩展了。
官方文档有段话,给我们阐述了如果扩展:
总的来说:编写一个配置类(@Configuration),是WebMvcConfigurer类型,不能标注@EnableWebMvc,即可实现扩展功能。
**尖叫提示:有些文档还是使用WebMvcConfigurerAdapter。**
需要注意的是:在springboot2.0版本以上,WebMvcConfigurerAdapter类已经过时,需要使用WebMvcConfigurer 接口或者WebMvcConfigurationSupport
过时原因:原因是springboot2.0以后,引用的是spring5.0,而spring5.0取消了WebMvcConfigurerAdapter
2.1 案例
实现一个视图解析器,将请求是/no时,直接重定向到success界面(也就是说不用编写controller**方法**)。
重要总结
总结:建议以后所有关于**mvc扩展的自定义的功能组件(视图解析器,国际化,拦截器等等),都放在某一个自实现的mvcConfig中(例如上面的MyViewConfig),方便管理,也可以实现多个扩展的webMvcConfig,**按照功能放置自定义的组件。
2.2 扩展mvc原理
查看WebMvcAutoConfiguration**代码的适配器代码,可以知道关键代码是**@Import(EnableWebMvcConfiguration.class)
点进去 EnableWebMvcConfiguration.class**,发现他还是WebMvcAutoConfiguration的一个内部类。**
查看 DelegatingWebMvcConfiguration 类。
可以看到他是加载了容器中所有**WebMvcConfigurer类型的配置类,然后逐个调用。这里也可以证明了,我们自定义的扩展类仅仅只是扩展了mvc**的功能而已,并没有让其他自动配置功能失效。
3、全面接管SpringMVC
全面接管也就意味着:SpringBoot**对SpringMVC的自动配置不需要了,所有都是我们自己配置,所有的SpringMVC的自动配置都会失效。**
我们需要在配置类中添加**@EnableWebMvc**即可
访问:发现提示界面找不到了。
加上注解,启动springboot项目,我们通过控制台输出的日志中我们发现少了一些filter。然后我们对于静态资源的默认访问路径等等都失效了。也就是说,访问html界面等等都行不通了。
这些代码都会失效,所以请求访问不到静态界面
3.1 原理
1**)@EnableWebMvc的核心**
可以看到他的核心是导入一个类 DelegatingWebMvcConfiguration
2**)DelegatingWebMvcConfiguration类**
这个类就是我们上面2.2节查看注册各种适配器的关键代码,那么这里并没有什么代码控制 mvc**自动配置失效**
发现它继承 WebMvcConfigurationSupport ,这个类我们好像在那里看见过
2**)查看**WebMvcAutoConfiguration
让我们回过头查看mvc自动配置类
注意到一段代码:@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
意思是:当WebMvcConfigurationSupport类在容器中找不到时,执行自动配置类。
那么我们加上了@EnableWebMvc**注解,就相当于把WebMvcConfigurationSupport类注入到了容器中,所以WebMvcAutoConfiguration自动配置类失效。**
总结
3**)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;**
4**)、导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;**
7.5 默认访问首页
通过查看WebMVCAutoConfiguration源代码可以知道,默认的”/”请求是会定向到类路径
下的index.html界面,那么我们可不可以手动控制他呢?
很明显是可以的。
1.实现方式1
实现一个controller,映射”/”请求
2.实现方式2
7.6 国际化
1**)、编写国际化配置文件;**
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容
步骤:
1)、编写国际化配置文件,抽取页面需要显示的国际化消息
第一个properties表示是在没有指定语言的情况下,默认的显示值
2)、SpringBoot自动配置好了管理国际化资源文件的组件;
3)、配置国际化基础名
在application.properties中设置
3)、去页面获取国际化的值;
效果:根据浏览器语言设置的信息切换了国际化;
原理
国际化的核心是:
国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);
查看默认的区域信息解析器:
在WebMvcAutoConfiguration中
初始化了一个区域信息解析器,查看获取区域信息相关带代码
AcceptHeaderLocaleResolver ,看到这个类,我们可以猜测他是通过请求头中获取区域编码
看到 Accept-Language 说明上面我们的猜测是正确的。
也就是说,国际化的区域信息,默认是通过请求头中获取。而且需要注意的是当**spring容器中如果缺失LocaleResolver这个bean**实例,那么才会去加载默认的,区域化信息解析器。(下面的点击链接切换国际化就是很好的利用了这个注解)
点击链接切换国际化
知道了上的原理,我们就可以手动的控制,切换语言环境。
1.实现一个区域信息解析器,通过获取请求中l的值,设定响应的语言环境。
|
那么springboot默认的区域化解析器就会失效。
\2. 实例化到spring容器中
上面的两个步骤可以整合在一起,最终实现:
在自定义的LocaleResolver中直接注入到spring容器中。注意需要指明**bean的id是localeResolver**
3.通过链接实现
http://127.0.0.1:8080/login.html?i=en_US
http://127.0.0.1:8080/login.html?i=en_US
7.7 themleaf关闭缓存
Thymeleaf会在第一次对模板解析之后进行缓存,极大的提高了并发处理能力,但是在开发期间模板引擎页面修改以后,要实时生效,所以我们开发阶段可以关掉缓存使用
1)、禁用模板引擎的缓存
\# 禁用缓存
spring.thymeleaf.cache=false
2)、页面修改完成以后ctrl+f9:重新编译;
7.8 自定义拦截器
1. 第一种实现方式
继承WebMvcConfigurerAdapter
1.定义一个拦截器实现HandlerInterceptor接口
2.注册到容器中
我们把扩展mvc的组件都放在自定义mvc扩展类中:MyMvcConfig
2. 第二种方式(常见)
1.
\2. 然后定义配置类,注册拦截器
3.运行输出
接下来运行并查看日志:
你会发现日志中只有这些打印信息,springMVC的日志信息都没有,因为springMVC记录的log级别是debug,springboot默认是显示info以上,我们需要进行配置。
SpringBoot通过logging.level.=debug来配置日志级别,填写包名
# 设置org.springframework包的日志级别为debug
logging.level.org.springframework=debug
设置完后,访问请求,查看输出
相比springmvc,springboot已经帮我们做好了静态资源的映射访问,所以不需要额外处理。
7.9 springmvc默认日期类型转化
例如从前台传回一个时间字符串,后台通过date日期类型进行映射获取。那么如果前台日期格式是:2017/12/12 ,那么mvc会自动转化为date类型,因为他的默认时间转换器是以/ 进行分割的。
如果是2017-12-12、2017.12.12 这样的日期格式,那么就需要我们自己实现日期转换器
通过查看WebMvcAutoConfiguration的源码得知:
默认的日期格式是使用反斜杠来分割。
–修改默认的日期格式化:
只需要在applicatio.proeprties文件中,覆盖默认的日期格式即可
7.10 post/get请求转化为put或其他
我们知道html的form表单只支持get/post两种请求,那么我们使用restful url进行请求数据的时候,那么需要使用到put请求,那么怎么转化呢?
7.11错误处理机制
1)、SpringBoot默认的错误处理机制
当我们访问一个不存在的资源时,那么springboot默认帮我们跳转到一个错误界面
1.使用pc浏览器访问
2.使用其他工具访问(postman)
我们不难发现,他返回错误的形式都是不一样的,前者返回一个html界面,后者返回的是一个json字符串。为什么会有两种错误请求返回方式呢?接下来看源码就知道了。
原理:
可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;
给容器中添加了以下四个组件
1、DefaultErrorAttributes:
2、BasicErrorController:他是一个基本的错误控制类。处理默认/error请求
是一个controller,处理/error请求
这里就解释了为什么会出现,两种错误请求响应方式(**html和josn**)
那么会产生另一个问题,他是怎么知道客户端是**pc还是其他访问工具呢?究竟怎么选择返回哪种格式的错误消息??????????**
很明显是通过请求头进行区分的。
1.通过postman发出的请求,请求头是:
2.通过浏览器发出的请求,请求头是:
3、ErrorPageCustomizer:
查看getpath(),
总的来说:系统出现错误以后来到error请求进行处理;(类似我们在web.xml注册的错误页面规则-根据不同的错误码,响应不同的错误界面:使用
4、DefaultErrorViewResolver:
主要解析代码:
总结
步骤:
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;
2)、如果定制错误响应:
1)、如何定制错误的页面;
1**)、有模板引擎的情况下**;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到对应的页面;
那么就会存在一个缺点,那就是如果我想把所有4开头的错误码都定向到一个错误界面怎么办呢?
我们回过头查看DefaultErrorViewResolver 源代码发现,他默认注册了两个规则4xx和5xx。
我们可以使用**4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html**);
页面能获取的信息(我们可以在自定义的错误界面获取到这些信息,参见DefaultErrorAttributes源码)
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹(static)下找
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面
2)、如何定制错误的json数据;
1)、自定义异常处理&返回定制json数据;
实现一个异常处理controller
只要是UserNotExistEception的异常都交由此方法处理,并返回自己自己定制json信息。
缺点: 永远返回json格式信息,没有html界面(7.11.1.1)。
2)、转发到/error进行自适应响应效果处理
缺点:自定义的map错误数据,没有携带过去。
3)、将我们的定制数据携带出去;
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中(这种方式太过麻烦不推荐)
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到(推荐这种方法)
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
自定义ErrorAttributes
八. 默认嵌套的Servlet服务器
打开springboot项目的pom文件,查看依赖。
8.1如何定制和修改Servlet容器的相关配置
1、修改和server有关的配置(ServerProperties也是EmbeddedServletContainerCustomizer的实现)-第一种方式
就是我们直接在application.properties文件中的修改操作
server.port=8081 server.context-path=/crud server.tomcat.uri-encoding=UTF-8
2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置 – 第二种方式
8.2 替换为其他嵌入式Servlet容器
Springboot 完美的集成了下面这三款servlet容器,只需要做一些小改动即可切换使用。
具体实现的容器工厂类。
1. 默认使用tomcat
2. Jetty
做法其实很简单,那就是一出web模块自动装配的tomcat 组件,然后引入jetty组件即可。Jetty适合在开发长连接的项目中使用(例如聊天类的项目)
3. Undertow
Undertow不支持jsp界面
8.3 嵌入式Servlet容器自动配置原理
1. 源码分析
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置
可以看到容器的切换,是通过注解来进行控制。通过容器工厂进行创建相应的容器
1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)
通过 getEmbeddedServletContainer()获得相应的servlet容器。
2)、EmbeddedServletContainer:(嵌入式的Servlet容器)
总的来说:通过servlet容器工厂类来创建相应的servlet容器
2.以tomcat容器工厂为例,分析流程
1.查看TomcatEmbeddedServletContainerFactory
**重点是:****getEmbeddedServletContainer****方法**
3. ServerProperties、EmbeddedServletContainerCustomizer
我们知道我们修改application.properties都是映射成ServerProperties
他实现了EmbeddedServletContainerCustomizer接口,所以衍生出了,第二种修改servlet容器的配置的方法:
自定义实现EmbeddedServletContainerCustomizer:
那么这些改动是怎么样生效的呢?
容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor
步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法
8.4 嵌入式Servlet容器启动原理
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;
获取嵌入式的Servlet容器工厂:
1)、SpringBoot应用启动运行run方法
2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
3)、refresh(context);刷新刚才创建好的**ioc**容器;
4)、 onRefresh(); web的ioc容器重写了onRefresh方法
5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();
6**)、获取嵌入式的Servlet容器工厂:**
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
7)、使用容器工厂获取嵌入式的**Servlet**容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());
8)、嵌入式的Servlet容器创建对象并启动Servlet容器;
先启动嵌入式的**Servlet容器,再将ioc**容器中剩下没有创建出的对象获取出来;
==IOC**容器启动创建嵌入式的Servlet容器**==
九. springboot注册三大组件
三大组件:servlet、filter、listenner
传统的web项目,我们可以在webroot/WEB_INFO/web.xml 中配置我们三大组件,但是springboot打包方式采用jar的方式,内嵌servlet容器,那么我们应该在哪里注册这三大组件呢?
9.1 注册servle
1.首先自定义一个servler
2.通过ServletRegistrationBean注册自定义servle
注意:如果存在一个**controller同时映射了/myServlet 请求,那么就会失效(被自定义servlet所覆盖)。**
9.2 注册filter
1.首先自定义一个filter
2.FilterRegistrationBean
9.3 注册listenner
1.首先自定义一个listenner
2.ServletListenerRegistrationBean
总结
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
DispatcherServletAutoConfiguration中:
10 使用外置的Servlet容器
嵌入式Servlet容器:应用打成可执行的jar
优点:简单、便携
缺点:默认不支持JSP、优化定制比较复杂(虽然可以使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】等方式修改配置,但是还是在需要通读代码的情况下才能够修改)
外置的Servlet容器:外面安装Tomcat—应用war包的方式打包
步骤
1)、必须创建一个项目打包类型为war的项目;(利用idea创建好目录结构,因为默认生成的springboot项目是没有webapp和web.xml等文件,需要手动创建)
下一步
完成
很明显没有生成相应的**webapp目录,同时tomcat的作用域修改为了运行时使用,打包不使用,这个也就是为我们使用外置servlet**容器打下基础。
项目最终目录结构
2)、将嵌入式的Tomcat指定为provided;
Springboot**默认已经帮我们完成**
3)、必须编写一个SpringBootServletInitializer的子类,并调用configure方法
**Springboot****默认已经帮我们完成**
4)、启动服务器就可以使用
可以编写一个jsp界面,访问测试
原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器
实现方式:Servlet3.0是一次Java EE规范
https://blog.csdn.net/f641385712/article/details/87474907 相关连接
规则:
1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例
2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类
流程:
1)、启动Tomcat
2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:
Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set
4)、每一个WebApplicationInitializer都调用自己的onStartup;
5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
7)、Spring的应用就启动并且创建IOC容器
==**启动Servlet容器,再启动SpringBoot应用**==
十一、Springboot整合其他技术
11.1 整合Mybatis
1.Idea创建一个springboot项目并选择以下依赖
点击下一步完成项目创建,查看项目的pom文件
2.完善项目的目录结构(MVC结构)
在资源文件夹中新建**application.yml**文件
3.在application.yml文件中书写数据库配置
spring: profiles: active: dev mybatis: type-aliases-package: com.kingge.entity mapper-locations: classpath:mapper/*.xml — spring: profiles: dev datasource: url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123 #com.mysql.jdbc.Driver 注意这个已经过时,推荐使用上面那个
4.编写entity、dao、service、serviceimpl、xml、conrtoller
4.1 User
4.2 dao
需要注意,配置文件没有配置mapper接口扫描包,因此我们需要给每一个Mapper/dao接口添加@Mapper注解,才能被识别。
4.3 service
4.4 serviceimpl
4.5 mapper.xml
4.6 controller
完整目录结构展示
5. 运行项目访问请求
访问成功!!!!!!
6.通过junit方式测试
11.2.整合Spring Data JPA
1 添加Spring Data JPA的起步依赖
<!-- springBoot JPA的起步依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
2 添加数据库驱动依赖
<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3 在application.properties中配置数据库和jpa的相关属性
DB Configuration: spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=root #JPA Configuration: spring.jpa.database=MySQL spring.jpa.show-sql=true spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
4 创建实体配置实体
@Entity public class User { // 主键 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 用户名 private String username; // 密码 private String password; // 姓名 private String name; //此处省略setter和getter方法… … }
5 编写UserRepository
public interface UserRepository extends JpaRepository<User,Long>{
public List<User> findAll();
}
6 编写测试类
@RunWith(SpringRunner.class) @SpringBootTest(classes=MySpringBootApplication.class) public class JpaTest { @Autowired private UserRepository userRepository; @Test public void test(){ List
7 控制台打印信息
注意:如果是jdk9,执行报错如下:
原因:jdk缺少相应的jar
解决方案:手动导入对应的maven坐标,如下:
<!--jdk9需要导入如下坐标-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
11.3 整合redis
1 添加redis的起步依赖
2 配置redis的连接信息
#Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
3 注入RedisTemplate测试redis操作
@RunWith(SpringRunner.class) @SpringBootTest(classes = SpringbootJpaApplication.class) public class RedisTest { @Autowired private UserRepository userRepository; @Autowired private RedisTemplate
十二、springboot运行流程回顾
1.启动柜springboot程序
断点进入-step into 发现
停在了这里,也就是说他会首先创建SpringApplication对象
2. 创建SpringApplication对象
创建SpringApplication对象运行run方法
继续进入方法