Spring Boot 入门
简介
百度百科
优点
- 快速创建独立运行的Spring项目以及与主流框架集成
- 使用嵌入式的Servlet容器,应用无需打成WAR包
- starters自动依赖与版本控制
- 大量的自动配置,简化开发,也可修改默认值
- 无需配置XML,无代码生成,开箱即用
- 准生产环境的运行时应用监控
- 与云计算的天然集成
微服务
2014,martin fowler
微服务:架构风格(服务微化)
一个应用应该是一组小型服务;可以通过HTTP的方式进行互通;
单体应用:ALL IN ONE
微服务:每一个功能元素最终都是一个可独立替换和独立升级的软件单元;
环境约束
–jdk1.8:Spring Boot 推荐jdk1.7及以上;
–maven3.x:maven 3.3以上版本;
Maven设置
给maven 的settings.xml配置文件的profiles标签添加:(设置使用的jdk版本)
开发工具中的maven设置为自己配置的maven
1 | <profile> |
创建一个maven工程
导入spring boot相关的依赖
1
2
3
4
5
6
7
8
9
10
11
12
13<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>Copy to clipboardErrorCopied编写一个主程序;启动Spring Boot应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package cn.clboy.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Author cloudlandboy
* @Date 2019/11/13 下午2:58
* @Since 1.0.0
* springBootApplication:标注一个主程序类,表示这个是一个Springboot应用
*/
public class HelloWorldMainApplication {
public static void main(String[] args) {
//启动
SpringApplication.run(HelloWorldMainApplication.class, args);
}
}Copy to clipboardErrorCopied编写一个Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package cn.clboy.springboot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author cloudlandboy
* @Date 2019/11/13 下午3:05
* @Since 1.0.0
* RestController:是spring4里的新注解,是@ResponseBody和@Controller的缩写。
*/
public class HelloController {
"/hello") (
public String hello(){
return "hello SpringBoot,this is my first Application";
}
}Copy to clipboardErrorCopied运行主程序Main方法测试
简化部署
添加maven插件
1
2
3
4
5
6
7
8
9<!-- 这个插件,可以将应用打包成一个可执行的jar包;-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>Copy to clipboardErrorCopied使用mvn package进行打包
进入打包好的jar包所在目录
使用
java -jar jar包名称
运行
Hello World探究
依赖
1 | <!--Hello World项目的父工程是org.springframework.boot--> |
启动器
1 | <dependency> |
spring-boot-starter-web
:
spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器
主程序类,主入口类
1 |
|
@SpringBootApplication
: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
看一下@SpringBootApplication
这个注解类的源码
1 | //可以给一个类型进行注解,比如类、接口、枚举 ({ElementType.TYPE}) |
@SpringBootConfiguration
:Spring Boot的配置类;标注在某个类上,表示这是一个Spring Boot的配置类;1
2
3
4
5({ElementType.TYPE})
(RetentionPolicy.RUNTIME)
public SpringBootConfigurationCopy to clipboardErrorCopied@Configuration
:配置类上来标注这个注解;配置类 —– 配置文件;配置类也是容器中的一个组件;@Component
1
2
3
4
5({ElementType.TYPE})
(RetentionPolicy.RUNTIME)
public Configuration Copy to clipboardErrorCopied
@EnableAutoConfiguration
:开启自动配置功能;以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;
1
2
3
4
5
6
7@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfigurationCopy to clipboardErrorCopied@AutoConfigurationPackage
:自动配置包1
2
3
4
5
6({ElementType.TYPE})
(RetentionPolicy.RUNTIME)
.class}) ({Registrar
public @interface AutoConfigurationPackageCopy to clipboardErrorCopied@Import
:Spring的底层注解@Import,给容器中导入一个组件导入的组件由
org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar
将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;这里controller包是在主程序所在的包下,所以会被扫描到,我们在springboot包下创建一个test包,把主程序放在test包下,这样启动就只会去扫描test包下的内容而controller包就不会被扫描到,再访问开始的hello就是404
@Import({AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector.class
将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中;会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件;有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;以前我们需要自己配置的东西,自动配置类都帮我们完成了;
使用Spring Initializer快速创建Spring Boot项目
IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目;
选择我们需要的模块;向导会联网创建Spring Boot项目;
需要联网
IDEA
创建项目时选择Spring Initializr
完善项目信息
出现 Artifact contains illegal characters 是因为
Artifact
中使用了大写,只能是全小写,单词之间用-
分隔选择需要的starter
创建完成后 不要的文件可以删除
Eclipse
需要安装插件,或者使用STS版本
创建项目时选择Spring Starter Project
完善信息
选择需要的选择需要的starter
默认生成的Spring Boot项目
主程序已经生成好了,我们只需要完成我们自己的逻辑
resources
k: v1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
文件夹中目录结构
- `static`:保存所有的静态资源; js、css、images;
- `templates`:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,`默认`不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf);
- `application.properties`:Spring Boot应用的配置文件;可以修改一些默认设置;
# 配置文件
SpringBoot使用一个全局的配置文件,配置文件名`application`是固定的;
- application.properties
- application.yml
- application.yaml
配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;
## YAML
YAML(YAML Ain't Markup Language)
YAML A Markup Language:是一个标记语言
YAML isn't Markup Language:不是一个标记语言;
标记语言:
以前的配置文件;大多都使用的是 **xxxx.xml**文件;
YAML:**以数据为中心**,比json、xml等更适合做配置文件;
### YAML语法:
以`空格`的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的
次等级的前面是空格,不能使用制表符(tab)
冒号之后如果有值,那么冒号和值之间至少有一个空格,不能紧贴着
### 字面量:普通的值(数字,字符串,布尔)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
字符串默认不用加上单引号或者双引号;
`""`:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思
*eg:* name: "zhangsan \n lisi":输出;zhangsan 换行 lisi
`''`:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据
*eg:* name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
### 对象、Map(属性和值):
`k: v`在下一行来写对象的属性和值的关系;注意缩进
1. ```yaml
person:
name: 张三
gender: 男
age: 22Copy to clipboardErrorCopied
行内写法
1
person: {name: 张三,gender: 男,age: 22}Copy to clipboardErrorCopied
数组(List、Set)
fruits: - 苹果 - 桃子 - 香蕉Copy to clipboardErrorCopied
fruits: [苹果,桃子,香蕉]Copy to clipboardErrorCopied1
2
2. 行内写法1
2
3
4
5
6
7
8
9
10
11
## 配置文件值注入
<details style="-webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); text-size-adjust: none; box-sizing: border-box; font-size: 15px; color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"><summary style="-webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); text-size-adjust: none; box-sizing: border-box; font-weight: bold; color: green;">JavaBean:</summary></details>
```xml
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>Copy to clipboardErrorCopied
配置文件:
1 | person: |
测试
1 | package cn.clboy.helloworldquickstart; |
properties
上面yaml对应的properties配置文件写法
1 | 李四 = |
测试,发现中文会乱码,而且char类型还会抛出Failed to bind properties under ‘person.gender’ to java.lang.Character异常
中文乱码解决方法:
在设置中找到File Encodings
,将配置文件字符集改为UTF-8
,并勾选:
Transparent native-to-ascii conversion
yaml和properties配置文件同时存在,properties配置文件的内容会覆盖yaml配置文件的内容
配置文件值注入两种方式对比
配置文件值注入有两种方式,一个是Spring Boot的@ConfigurationProperties
注解,另一个是spring原先的@value
注解
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
松散绑定:例如Person中有lastName
属性,在配置文件中可以写成
lastName
或lastname
或last-name
或last_name
等等
SpEL:
1 | ## properties配置文件 |
JSR303数据校验
@ConfigurationProperties
支持校验,如果校验不通过,会抛出异常
@value
注解不支持数据校验
复杂类型封装
1 | @value`注解无法注入map等对象的复杂类型,但`list、数组可以 |
@PropertySource
@PropertySource
注解的作用是加载指定的配置文件,值可以是数组,也就是可以加载多个配置文件
springboot默认加载的配置文件名是application
,如果配置文件名不是这个是不会被容器加载的,所以这里Person并没有被注入任何属性值
使用@PropertySource({"classpath:person.properties"})
指定加载person.properties
配置文件
使用这个注解加载配置文件就需要配置类使用@component等注解而不是等待@EnableConfigurationProperties激活m,而且不支持yaml,只能是properties
@ImportResource
@ImportResource
注解用于导入Spring的配置文件,让配置文件里面的内容生效;(就是以前写的springmvc.xml、applicationContext.xml)
Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上
注意!这个注解是放在主入口函数的类上,而不是测试类上
@Configuration
SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式
配置类@Configuration —equals—> Spring配置文件
@Bean
使用@Bean给容器中添加组件
1 | package cn.clboy.helloworldquickstart.config; |
配置文件占位符
随机
1 | ${random.value} |
可以引用在配置文件中配置的其他属性的值,如果使用一个没有在配置文件中的属性,则会原样输出
可以使用:
指定默认值
Profile
Profile是Spring对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速切换环境
多profile文件形式
文件名格式:application-{profile}.properties/yml,例如:
- application-dev.properties
- application-prod.properties
程序启动时会默认加载application.properties
,启动的端口就是8080
可以在主配置文件中指定激活哪个配置文件
yml支持多文档块方式
每个文档块使用---
分割
1 | server: |
激活指定profile的三种方式
在配置文件中指定 spring.profiles.active=dev(如上)
项目打包后在命令行启动
1
java -jar xxx.jar --spring.profiles.active=dev;Copy to clipboardErrorCopied
虚拟机参数
1
-Dspring.profiles.active=devCopy to clipboardErrorCopied
配置文件加载位置
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
1 | file: ./config/ |
优先级由高到底,高优先级的配置会覆盖低优先级的配置(优先级低的先加载);
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
这里项目根路径下的配置文件maven编译时不会打包过去,需要修改pom
1 | <resources> |
我们还可以通过
spring.config.location
来改变默认的配置文件位置项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;
1 java -jar xxx.jar --spring.config.location=/home/cloudlandboy/application.yamlCopy to clipboardErrorCopied
外部配置加载顺序
SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置
命令行参数
所有的配置都可以在命令行上进行指定
1
java -jar xxx.jar --server.port=8087 --server.context-path=/abcCopy to clipboardErrorCopied
多个配置用空格分开; –配置项=值
来自java:comp/env的JNDI属性 ⤴️
Java系统属性(System.getProperties()) ⤴️
操作系统环境变量 ⤴️
RandomValuePropertySource配置的random.*属性值 ⤴️
由jar包外向jar包内进行寻找;
再来加载不带profile
- jar包外部的
application.properties
或application.yml
(不带spring.profile)配置文件 ⤴️ - **jar包内部的
application.properties
或application.yml
(不带spring.profile)配置文件 ⤴️
优先加载带profile
**jar包外部的
application-{profile}.properties
或application.yml
(带spring.profile)配置文件 ⤴️**jar包内部的
application-{profile}.properties
或application.yml
(带spring.profile)配置文件 ⤴️@Configuration注解类上的@PropertySource ⤴️
通过SpringApplication.setDefaultProperties指定的默认属性 ⤴️
所有支持的配置加载来源:
自动配置原理
配置文件到底能写什么?怎么写?自动配置原理;
SpringBoot启动的时候加载主配置类,开启了自动配置功能
1 | @SpringBootApplication |
@EnableAutoConfiguration 作用
利用EnableAutoConfigurationImportSelector给容器中导入一些组件
getAutoConfigurationEntry方法中
1
2//获取候选的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);Copy to clipboardErrorCopiedgetCandidateConfigurations方法中,SpringFactoriesLoader.loadFactoryNames(),扫描所有jar包类路径下
META-INF/spring.factories
,把扫描到的这些文件的内容包装成properties对象,从properties中获取到EnableAutoConfiguration.class(类名)对应的值,然后把它们添加在容器中每一个这样的
xxxAutoConfiguration
类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;每一个自动配置类进行自动配置功能;
以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理
1 | package org.springframework.boot.autoconfigure.web.servlet; |
- 根据当前不同的条件判断,决定这个配置类是否生效
- 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
所有在配置文件中能配置的属性都是在xxxxProperties
类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类
1 | ( |
总结
- SpringBoot启动会加载大量的自动配置类
- 我们看我们需要的功能有没有SpringBoot默认写好的自动配置类
- 再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值
xxxxAutoConfigurartion
:自动配置类;
xxxxProperties
:封装配置文件中相关属性;
@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存在指定项 |
查看那些自动配置类生效了
自动配置类必须在一定的条件下才能生效;
我们怎么知道哪些自动配置类生效了;
我们可以通过配置文件启用 debug=true
属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
Positive matches
:(自动配置类启用的)Negative matches
:(没有启动,没有匹配成功的自动配置类)
springboot日志配置
市面上的日志框架
JUL
、JCL
、Jboss-logging
、logback
、log4j
、log4j2
、slf4j
….
日志门面 (日志的抽象层) | 日志门面 (日志的抽象层) |
---|---|
JCL(Jakarta Commons Logging SLF4j(Simple Logging Facade for Java) jboss-loggi | JUL(java.util.logging) Log4j Log4j2 Logback |
左边选一个门面(抽象层)、右边来选一个实现;
例:SLF4j–>Logback
SpringBoot选用 SLF4j
和logback
SLF4j使用
如何在系统中使用SLF4j :https://www.slf4j.org
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统里面导入slf4j的jar和 logback的实现jar
1 | import org.slf4j.Logger; |
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
遗留问题
项目中依赖的框架可能使用不同的日志:
Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx
当项目是使用多种日志API时,可以统一适配到SLF4J,中间使用SLF4J或者第三方提供的日志适配器适配到SLF4J,SLF4J在底层用开发者想用的一个日志框架来进行日志系统的实现,从而达到了多种日志的统一实现。
如何让系统中所有的日志都统一到slf4j
- 将系统中其他日志框架先排除出去;
- 用中间包来替换原有的日志框架(适配器的类名和包名与替换的被日志框架一致);
- 我们导入slf4j其他的实现
SpringBoot日志关系
1 | <dependency> |
SpringBoot使用它来做日志功能;
1 | <dependency> |
底层依赖关系
总结:
- SpringBoot底层也是使用slf4j+logback的方式进行日志记录
- SpringBoot也把其他的日志都替换成了slf4j;
- 中间替换包?
1 | "rawtypes") ( |
如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉?
Spring框架用的是commons-logging;
1 | <dependency> |
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;
日志使用
默认配置
SpringBoot默认帮我们配置好了日志;
1 | //记录器 |
SpringBoot修改日志的默认配置
1 | # 也可以指定一个包路径 logging.level.com.xxx=error |
logging.file | logging.path | Example | Description |
---|---|---|---|
(none) | (none) | 只在控制台输出 | |
指定文件名 | (none) | my.log | 输出日志到my.log文件 |
(none) | 指定目录 | /var/log | 输出到指定目录的 spring.log 文件中 |
指定配置
给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了
Logging System | Customization |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
logback.xml:直接就被日志框架识别了;
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能
1 | <springProfile name="staging"> |
如:
1 | <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> |
如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误
1 | no applicable action for [springProfile] |
切换日志框架
可以按照slf4j的日志适配图,进行相关的切换;
slf4j+log4j的方式;
1 | <dependency> |
切换为log4j2
1 | <dependency> |
SpringBoot Web开发
- 创建SpringBoot应用,选中我们需要的模块
- SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
- 自己编写业务代码
web自动配置规则
- WebMvcAutoConfiguration
- WebMvcProperties
- ViewResolver自动配置
- 静态资源自动映射
- Formatter与Converter自动配置
- HttpMessageConverter自动配置
- 静态首页
- favicon
- 错误处理
SpringBoot对静态资源的映射规则
WebMvcAutoConfiguration
类的addResourceHandlers
方法:(添加资源映射)
1 | public void addResourceHandlers(ResourceHandlerRegistry registry) { |
所有 /webjars/**
,都去 classpath:/META-INF/resources/webjars/
找资源
webjars
:以jar包的方式引入静态资源;
例如:添加jquery的webjars
1 | <dependency> |
访问地址对应就是:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
非webjars,自己的静态资源怎么访问
资源配置类:
1 | //说明可以在配置文件中配置相关参数 ( |
上图中添加的映射访问路径staticPathPattern
值是/**
,对应的资源文件夹就是上面配置类ResourceProperties
中的CLASSPATH_RESOURCE_LOCATIONS
数组中的文件夹:
数组中的值 | 在项目中的位置 |
---|---|
classpath:/META-INF/resources/ | src/main/resources/META-INF/resources/ |
classpath:/resources/ | src/main/resources/resources/ |
classpath:/static/ | src/main/resources/static/ |
classpath:/public/ | src/main/resources/public/ |
localhost:8080/abc —> 去静态资源文件夹里面找abc
欢迎页映射
location
就是静态资源路径,所以欢迎页的页面就是上面静态资源下的index.html
,被/**
映射,因此直接访问项目就是访问欢迎页
网站图标映射(favicon.ico)
所有的 favicon.ico 都是在静态资源文件下找;
模板引擎
常见的模板引擎有JSP
、Velocity
、Freemarker
、Thymeleaf
SpringBoot推荐使用Thymeleaf;
引入thymeleaf
1 | <dependency> |
如需切换thymeleaf版本:
1 | <properties> |
Thymeleaf使用
1 | package org.springframework.boot.autoconfigure.thymeleaf; |
默认只要我们把HTML页面放在classpath:/templates/
,thymeleaf就能自动渲染;
创建模板文件
t1.html
,并导入thymeleaf的名称空间1
<html lang="en" xmlns:th="http://www.thymeleaf.org">Copy to clipboardErrorCopied
1
2
3
4
5
6
7
8
9
10
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>Copy to clipboardErrorCopied使用模板
1
2
3
4
5
6
7
8
9
10
11
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[[${title}]]</title>
</head>
<body>
<h1 th:text="${title}"></h1>
<div th:text="${info}">这里的文本之后将会被覆盖</div>
</body>
</html>Copy to clipboardErrorCopied在controller中准备数据
1
2
3
4
5
6
7
8
9
10
public class HelloT {
"/ht") (
public String ht(Model model) {
model.addAttribute("title","hello Thymeleaf")
.addAttribute("info","this is first thymeleaf test");
return "t1";
}
}Copy to clipboardErrorCopied
语法规则
th:text
–> 改变当前元素里面的文本内容;
th:任意html属性
–> 来替换原生属性的值
更多配置参考官方文档:https://www.thymeleaf.org/documentation.html
中文参考书册:https://www.lanzous.com/i7dzr2j
SpringMVC自动配置
Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。
以下是SpringBoot对SpringMVC的默认配置
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
自动配置在Spring的默认值之上添加了以下功能:
- 包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
。–> 视图解析器 - 支持服务静态资源,包括对WebJars的支持(官方文档中有介绍)。–> 静态资源文件夹路径
- 自动注册
Converter
,GenericConverter
和Formatter
beans。–> 转换器,格式化器 - 支持
HttpMessageConverters
(官方文档中有介绍)。–> SpringMVC用来转换Http请求和响应的;User—Json; - 自动注册
MessageCodesResolver
(官方文档中有介绍)。–> 定义错误代码生成规则 - 静态
index.html
支持。–> 静态首页访问 - 定制
Favicon
支持(官方文档中有介绍)。–> 网站图标 - 自动使用
ConfigurableWebBindingInitializer
bean(官方文档中有介绍)。
如果您想保留 Spring Boot MVC 的功能,并且需要添加其他 MVC 配置(拦截器,格式化程序和视图控制器等),可以添加自己的 WebMvcConfigurer
类型的 @Configuration
类,但不能带 @EnableWebMvc
注解。如果您想自定义 RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
或者 ExceptionHandlerExceptionResolver
实例,可以声明一个 WebMvcRegistrationsAdapter
实例来提供这些组件。
如果您想完全掌控 Spring MVC,可以添加自定义注解了 @EnableWebMvc
的 @Configuration 配置类。
视图解析器
视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?)
- 自动配置了ViewResolver
- ContentNegotiatingViewResolver:组合所有的视图解析器的;
视图解析器从哪里来的?
所以我们可以自己给容器中添加一个视图解析器;自动的将其组合进来
1 |
|
转换器、格式化器
Converter
:转换器; public String hello(User user):类型转换使用Converter(表单数据转为user)Formatter
格式化器; 2017.12.17===Date;
1 |
|
自己添加的格式化器转换器,我们只需要放在容器中即可
HttpMessageConverters
HttpMessageConverter
:SpringMVC用来转换Http请求和响应的;User—Json;HttpMessageConverters
是从容器中确定;获取所有的HttpMessageConverter;
自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)
MessageCodesResolver
我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)
扩展SpringMVC
以前的配置文件中的配置
1 | <mvc:view-controller path="/hello" view-name="success"/>Copy to clipboardErrorCopied |
现在,编写一个配置类(@Configuration),是WebMvcConfigurer类型;不能标注@EnableWebMvc
1 |
|
原理:
我们知道WebMvcAutoConfiguration
是SpringMVC的自动配置类
下面这个类是WebMvcAutoConfiguration
中的一个内部类
看一下@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
中的这个类,
这个类依旧是WebMvcAutoConfiguration
中的一个内部类
重点看一下这个类继承的父类DelegatingWebMvcConfiguration
1 | public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { |
容器中所有的WebMvcConfigurer都会一起起作用;
我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
全面接管SpringMVC
SpringBoot对SpringMVC的自动配置不需要了,所有都是由我们自己来配置;所有的SpringMVC的自动配置都失效了
我们只需要在配置类中添加@EnableWebMvc
即可;
1 |
|
原理:
为什么@EnableWebMvc自动配置就失效了;
我们看一下EnableWebMvc注解类
1 | (RetentionPolicy.RUNTIME) |
重点在于@Import({DelegatingWebMvcConfiguration.class})
DelegatingWebMvcConfiguration
是WebMvcConfigurationSupport
的子类
我们再来看一下springmvc的自动配置类WebMvcAutoConfiguration
1 | ( |
- @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
- 导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;
如何修改SpringBoot的默认配置
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
- 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
- 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
restful风格的增删改查
静态资源文件:https://www.lanzous.com/i7eenib
- 将静态资源(css,img,js)添加到项目中,放到springboot默认的静态资源文件夹下
- 将模板文件(html)放到template文件夹下
如果你的静态资源明明放到了静态资源文件夹下却无法访问,请检查一下是不是在自定义的配置类上加了@EnableWebMvc注解
默认访问首页
template文件加不是静态资源文件夹,默认是无法直接访问的,所以要添加视图映射
1 | package cn.clboy.hellospringbootweb.config; |
i18n国际化
编写国际化配置文件,抽取页面需要显示的国际化消息
SpringBoot自动配置好了管理国际化资源文件的组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45(
proxyBeanMethods = false
)
(
name = {"messageSource"},
search = SearchStrategy.CURRENT
)
2147483648) (-
.class}) ({MessageSourceAutoConfiguration.ResourceBundleCondition
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = new Resource[0];
public MessageSourceAutoConfiguration() {
}
(
prefix = "spring.messages"
)
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}Copy to clipboardErrorCopied创建i18n文件夹存放配置文件,文件名格式为
基础名(login)
+语言代码(zh)
+国家代码(CN)
在配置文件中添加国际化文件的位置和基础名
1
i18n.loginCopy to clipboardErrorCopied =
如果配置文件中没有配置基础名,就在类路径下找基础名为
message
的配置文件将页面文字改为获取国际化配置,格式
#{key}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label class="sr-only">Username</label>
<input type="text" class="form-control" th:placeholder="#{login.username}" placeholder="Username" required="" autofocus="">
<label class="sr-only">Password</label>
<input type="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
</form>
</body>Copy to clipboardErrorCopied然后就可以更改浏览器语言,页面就会使用对应的国际化配置文件
原理
国际化Locale(区域信息对象);
LocaleResolver(获取区域信息对象的组件);
在springmvc配置类
WebMvcAutoConfiguration
中注册了该组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
*前提是容器中不存在这个组件,
*所以使用自己的对象就要配置@Bean让这个条件不成立(实现LocaleResolver 即可)
*/
/**
* 如果在application.properties中有配置国际化就用配置文件的
* 没有配置就用AcceptHeaderLocaleResolver 默认request中获取
*/
(
prefix = "spring.mvc",
name = {"locale"}
)
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
} else {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
}Copy to clipboardErrorCopied默认的就是根据请求头带来的区域信息获取Locale进行国际化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}Copy to clipboardErrorCopied
点击连接切换语言
实现点击连接切换语言,而不是更改浏览器
修改页面,点击连接携带语言参数
1
2<a class="btn btn-sm" href="?l=zh_CN">中文</a>
<a class="btn btn-sm" href="?l=en_US">English</a>Copy to clipboardErrorCopied自己实现区域信息解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class MyLocaleResolver implements LocaleResolver {
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
//获取请求参数中的语言
String language = httpServletRequest.getParameter("l");
//没带区域信息参数就用系统默认的
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(language)) {
//提交的参数是zh_CN (语言代码_国家代码)
String[] s = language.split("_");
locale = new Locale(s[0], s[1]);
}
return locale;
}
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}Copy to clipboardErrorCopied在配置类中将其注册到容器中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyMvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}Copy to clipboardErrorCopied
如果没有生效,请检查@Bean
的那个方法的名称是否为localeResolver
实现登录功能
提供登录的controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserController {
"/user/login") (
public String login(@RequestParam String username, @RequestParam String password, HttpSession session, Model model) {
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
//登录成功,把用户信息方法哦session中,防止表单重复提交,重定向到后台页面
session.setAttribute("loginUser", username);
return "redirect:/main.html";
}
//登录失败,返回到登录页面
model.addAttribute("msg", "用户名或密码错误!");
return "login";
}
}Copy to clipboardErrorCopied修改表单提交地址,输入框添加name值与参数名称对应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label class="sr-only">Username</label>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" placeholder="Username" autofocus="">
<label class="sr-only">Password</label>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" href="?l=zh_CN">中文</a>
<a class="btn btn-sm" href="?l=en_US">English</a>
</form>Copy to clipboardErrorCopied由于登录失败是转发,所以页面的静态资源请求路径会不正确,使用模板引擎语法替换
1
2
3<link href="asserts/css/bootstrap.min.css" th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">Copy to clipboardErrorCopied添加登录失败页面显示
1
2
3<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--msg存在才显示该p标签-->
<p th:text="${msg}" th:if="${not #strings.isEmpty(msg)}" style="color: red"></p>Copy to clipboardErrorCopied
修改页面立即生效
1 | # 禁用缓存 |
在页面修改完成以后按快捷键ctrl+f9
,重新编译;
拦截器进行登陆检查
实现拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38package cn.clboy.hellospringbootweb.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Author cloudlandboy
* @Date 2019/11/17 上午11:44
* @Since 1.0.0
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
//未登录,拦截,并转发到登录页面
request.setAttribute("msg", "您还没有登录,请先登录!");
request.getRequestDispatcher("/index").forward(request, response);
return false;
}
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}Copy to clipboardErrorCopied注册拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package cn.clboy.hellospringbootweb.config;
import cn.clboy.hellospringbootweb.interceptor.LoginHandlerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author cloudlandboy
* @Date 2019/11/16 下午3:32
* @Since 1.0.0
*/
public class MyMvcConfig implements WebMvcConfigurer {
//定义不拦截路径
private static final String[] excludePaths = {"/", "/index", "/index.html", "/user/login", "/asserts/**"};
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
public void addInterceptors(InterceptorRegistry registry) {
//添加不拦截的路径,SpringBoot已经做好了静态资源映射,所以我们不用管
registry.addInterceptor(new LoginHandlerInterceptor())
.excludePathPatterns(excludePaths);
}
}Copy to clipboardErrorCopied在spring2.0+的版本中,只要用户自定义了拦截器,则静态资源会被拦截。但是在spring1.0+的版本中,是不会拦截静态资源的。
因此,在使用spring2.0+时,配置拦截器之后,我们要把静态资源的路径加入到不拦截的路径之中。
CRUD-员工列表
使用rest风格
实验功能 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/1 | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/1 | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
为了页面结构清晰,在template文件夹下新建emp文件夹,将list.html移动到emp文件夹下
将dao层和实体层java代码复制到项目中
dao
,entities
添加员工controller,实现查询员工列表的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EmpController {
private EmployeeDao employeeDao;
"/emps") (
public String emps(Model model) {
Collection<Employee> empList = employeeDao.getAll();
model.addAttribute("emps", empList);
return "emp/list";
}
}Copy to clipboardErrorCopied修改后台页面,更改左侧侧边栏,将
customer
改为员工列表
,并修改请求路径1
2
3
4
5
6
7
8<li class="nav-item">
<a class="nav-link" th:href="@{/emps}">
<svg .....>
......
</svg>
员工列表
</a>
</li>Copy to clipboardErrorCopied同样emp/list页面的左边侧边栏是和后台页面一模一样的,每个都要修改很麻烦,接下来,抽取公共片段
thymeleaf公共页面元素抽取
语法
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名
1 | /*公共代码片段*/ |
具体参考官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#including-template-fragments
三种引入公共片段的th属性:
th:insert
:将公共片段整个插入到声明引入的元素中th:replace
:将声明引入的元素替换为公共片段th:include
:将被引入的片段的内容包含进这个标签中
1 | /*公共片段*/ |
后台页面抽取
将后台主页中的顶部导航栏作为片段,在list页面引入
dashboard.html:
1
2
3
4
5
6
7
8
9<nav th:fragment="topbar" class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company name</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
</li>
</ul>
</nav>Copy to clipboardErrorCopiedlist.html:
1
2
3
4
5<body>
<div th:replace="dashboard::topbar"></div>
......Copy to clipboardErrorCopied使用选择器的方式 抽取左侧边栏代码
dashboard.html:
1
2
3<div class="container-fluid">
<div class="row">
<nav id="sidebar" class="col-md-2 d-none d-md-block bg-light sidebar" ......Copy to clipboardErrorCopiedlist.html:
1
2
3
4<div class="container-fluid">
<div class="row">
<div th:replace="dashboard::#sidebar"></div>
......Copy to clipboardErrorCopied
引入片段传递参数
实现点击当前项高亮
将dashboard.html
中的公共代码块抽出为单独的html文件,放到commos文件夹下
在引入代码片段的时候可以传递参数,然后在sidebar代码片段模板中判断当前点击的链接
语法:
1 | ~{templatename::selector(变量名=值)} |
topbar.html
1 |
|
sidebar.html
1 |
|
然后在dashboard.html
和list.html
中引入
1 | <body> |
显示员工数据,添加增删改按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2>
<button class="btn btn-sm btn-success">添加员工</button>
</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>员工号</th>
<th>姓名</th>
<th>邮箱</th>
<th>性别</th>
<th>部门</th>
<th>生日</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}==1?'男':'女'"></td>
<td th:text="${emp.department.departmentName}"></td>
<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd')}"></td>
<td>
<button class="btn btn-sm btn-primary">修改</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</main>Copy to clipboardErrorCopied
员工添加
创建员工添加页面
add.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44......
<body>
<div th:replace="commons/topbar::topbar"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/sidebar::#sidebar(currentURI='emps')"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form>
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select name="department.id" class="form-control">
<option th:each="dept:${departments}" th:text="${dept.departmentName}" th:value="${dept.id}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="zhangsan">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</main>
</div>
</div>
......Copy to clipboardErrorCopied点击链接跳转到添加页面
1
<a href="/emp" th:href="@{/emp}" class="btn btn-sm btn-success">添加员工</a>Copy to clipboardErrorCopied
EmpController
添加映射方法1
2
3
4
5
6
7
8
9
10
private DepartmentDao departmentDao;
"/emp") (
public String toAddPage(Model model) {
//准备部门下拉框数据
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}Copy to clipboardErrorCopied修改页面遍历添加下拉选项
1
2
3<select class="form-control">
<option th:each="dept:${departments}" th:text="${dept.departmentName}"></option>
</select>Copy to clipboardErrorCopied表单提交,添加员工
1
<form th:action="@{/emp}" method="post">Copy to clipboardErrorCopied
1
2
3
4
5
6
7
8"/emp") (
public String add(Employee employee) {
System.out.println(employee);
//模拟添加到数据库
employeeDao.save(employee);
//添加成功重定向到列表页面
return "redirect:/emps";
}Copy to clipboardErrorCopied
日期格式修改
表单提交的日期格式必须是yyyy/MM/dd
的格式,可以在配置文件中修改格式
1 | yyyy-MM-ddCopy to clipboardErrorCopied = |
员工修改
点击按钮跳转到编辑页面
1
<a th:href="@{/emp/}+${emp.id}" class="btn btn-sm btn-primary">修改</a>Copy to clipboardErrorCopied
添加编辑页面,表单的提交要为post方式,提供
_method
参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48<body>
<div th:replace="commons/topbar::topbar"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/sidebar::#sidebar(currentURI='emps')"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emp}" method="post">
<!--员工id-->
<input type="hidden" name="id" th:value="${emp.id}">
<!--http请求方式-->
<input type="hidden" name="_method" value="put">
<div class="form-group">
<label>LastName</label>
<input name="lastName" th:value="${emp.lastName}" type="text" class="form-control" placeholder="zhangsan">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" th:value="${emp.email}" type="email" class="form-control" placeholder="zhangsan@atguigu.com">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp.gender==1}">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp.gender==0}">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select name="department.id" class="form-control">
<option th:each="dept:${departments}" th:value="${dept.id}" th:selected="${dept.id}==${emp.department.id}" th:text="${dept.departmentName}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${#dates.format(emp.birth,'yyyy-MM-dd')}">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</main>
</div>
</div>
......Copy to clipboardErrorCopiedController转发到编辑页面,回显员工信息
1
2
3
4
5
6
7
8"/emp/{id}") (
public String toEditPage(@PathVariable Integer id, Model model) {
Employee employee = employeeDao.get(id);
//准备部门下拉框数据
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("emp", employee).addAttribute("departments", departments);
return "emp/edit";
}Copy to clipboardErrorCopied提交表单修改员工信息
1
2
3
4
5"/emp") (
public String update(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}Copy to clipboardErrorCopied
员工删除
点击删除提交发出delete请求
1
2
3
4
5@DeleteMapping("/emp/{id}")
public String delete(@PathVariable String id){
employeeDao.delete(id);
return "redirect:/emps";
}Copy to clipboardErrorCopied如果提示不支持POST请求,在确保代码无误的情况下查看是否配置启动
HiddenHttpMethodFilter
1
trueCopy to clipboardErrorCopied =
这个好像是2.0版本以后修改的
如果删除不掉,请修改
EmployeeDao
,把String转为Integer类型1
2
3public void delete(String id) {
employees.remove(Integer.parseInt(id));
}
SpringBoot默认的错误处理机制
当访问一个不存在的页面,或者程序抛出异常时
默认效果:
浏览器返回一个默认的错误页面, 注意看浏览器发送请求的
请求头
:其他客户端返回json数据,注意看
请求头
查看org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
源码,
这里是springboot错误处理的自动配置信息
主要给日容器中注册了以下组件:
- ErrorPageCustomizer 系统出现错误以后来到error请求进行处理;相当于(web.xml注册的错误页面规则)
- BasicErrorController 处理/error请求
- DefaultErrorViewResolver 默认的错误视图解析器
- DefaultErrorAttributes 错误信息
- defaultErrorView 默认错误视图
ErrorPageCustomizer
1 |
|
当请求出现错误后就会转发到/error
然后这个error请求就会被BasicErrorController处理;
BasicErrorController
1 |
|
处理/error
请求
1 |
|
这两个方法一个用于浏览器请求响应html页面,一个用于其他客户端请求响应json数据
处理浏览器请求的方法 中,modelAndView存储到哪个页面的页面地址和页面内容数据
看一下调用的resolveErrorView方法
1 | protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { |
ErrorViewResolver从哪里来的呢?
已经在容器中注册了一个DefaultErrorViewResolver
DefaultErrorViewResolver
1 | ( |
然后调用ErrorViewResolver的resolveErrorView()
方法
1 | public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { |
如果模板引擎不可用,就调用resolveResource方法获取视图
1 | private ModelAndView resolveResource(String viewName, Map<String, Object> model) { |
所以:
如何定制错误响应页面
有模板引擎的情况下;将错误页面命名为
错误状态码.html
放在模板引擎文件夹里面的 error文件夹下发生此状态码的错误就会来到这里找对应的页面;比如我们在template文件夹下创建error/404.html当浏览器请求是404错误,就会使用我们创建的404.html页面响应,如果是其他状态码错误,还是使用默认的视图,但是如果404.html没有找到就会替换成4XX.html再查找一次,看
DefaultErrorViewResolver
中的静态代码块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static {
Map<Series, String> views = new EnumMap(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
.....
//再看解析方法
//把状态码和model传过去过去视图
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
//上面没有获取到视图就把状态吗替换再找,以4开头的替换为4xx,5开头替换为5xx,见下文(如果定制错误响应)
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}Copy to clipboardErrorCopied页面可以获取哪些数据
DefaultErrorAttributes
再看一下BasicErrorController的errorHtml方法
1 | public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { |
看一下调用的this.getErrorAttributes()方法
1 | protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { |
再看 this.errorAttributes.getErrorAttributes()方法, this.errorAttributes是接口类型ErrorAttributes,实现类就一个DefaultErrorAttributes
,看一下DefaultErrorAttributes
的 getErrorAttributes()方法
1 | public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { |
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSR303数据校验的错误都在这里
2.0以后默认是不显示exception的,需要在配置文件中开启
1 | server.error.include-exception=trueCopy to clipboardErrorCopied |
原因:
在注册时
- 没有模板引擎(模板引擎找不到这个错误页面),就会在静态资源文件夹下找;
- 如果以上都没有找到错误页面,就是默认来到SpringBoot默认的错误提示页面;
defaultErrorView
如何定制JSON数据
springboot做了自适应效果,浏览器访问响应错误页面。客户端访问响应错误信息的json数据
第一种方法,定义全局异常处理器类注入到容器中,捕获到异常返回json格式的数据
1
2
3
4
5
6
7
8
9
10
11
12
public class MyExceptionHandler {
.class) (Exception
public Map<String, Object> handleException(Exception e) {
Map<String, Object> map = new HashMap(2);
map.put("code", "100011");
map.put("msg", e.getMessage());
return map;
}
}Copy to clipboardErrorCopied1
2
3
4
5
6
7
8
9
10
11
public class Hello {
"/hello") (
public String hello(String str) {
if ("hi".equals(str)) {
int i = 10 / 0;
}
return "hello world";
}
}Copy to clipboardErrorCopied这样的话,不管是浏览器访问还是客户端访问都是响应json数据,就没有了自适应效果
第二种方法,捕获到异常后转发到/error
1
2
3
4
5
6
7
8
9
10
11
public class MyExceptionHandler {
.class) (Exception
public String handleException(Exception e) {
Map<String, Object> map = new HashMap(2);
map.put("code", "100011");
map.put("msg", e.getMessage());
return "forward:/error";
}
}Copy to clipboardErrorCopied访问localhost:8080/hello?str=hi,但这样异常被我们捕获然后转发,显示的状态码就是200,所以在转发之前还要设置一下状态码
1
2
3
4
5
6
7
8
9
10.class) (Exception
public String handleException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap(2);
map.put("code", "100011");
map.put("msg", e.getMessage());
//设置状态码
request.setAttribute("javax.servlet.error.status_code", 500);
return "forward:/error";
}Copy to clipboardErrorCopied但是设置的数据就没有用了,只能使用默认的
由上面我们已经知道数据的来源是调用DefaultErrorAttributes的getErrorAttributes方法得到的,而这个DefaultErrorAttributes是在ErrorMvcAutoConfiguration配置类中注册的,并且注册之前会检查容器中是否已经拥有
1
2
3
4
5
6
7
8
(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}Copy to clipboardErrorCopied所以我们可以只要实现ErrorAttributes接口或者继承DefaultErrorAttributes类,然后注册到容器中就行了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyExceptionHandler {
.class) (Exception
public String handleException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap(2);
map.put("name", "hello");
map.put("password", "123456");
//设置状态码
request.setAttribute("javax.servlet.error.status_code", 500);
//把数据放到request域中
request.setAttribute("ext", map);
return "forward:/error";
}
}Copy to clipboardErrorCopied1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyMvcConfig implements WebMvcConfigurer {
public DefaultErrorAttributes errorAttributes() {
return new MyErrorAttributes();
}
class MyErrorAttributes extends DefaultErrorAttributes {
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//调用父类的方法获取默认的数据
Map<String, Object> map = new HashMap<>(super.getErrorAttributes(webRequest, includeStackTrace));
//从request域从获取到自定义数据
Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
map.putAll(ext);
return map;
}
}
......
配置嵌入式Servlet容器
如何定制和修改Servlet容器的相关配置
修改和server有关的配置
1
2
3
4
5
6
7
8
98081 =
/crud =
UTF-8 =
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
to clipboardErrorCopied编写一个
EmbeddedServletContainerCustomizer,2.0以后改为WebServerFactoryCustomizer
:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置1
2
3
4
5
6
7
8
9
10
11
12
public class MyMvcConfig implements WebMvcConfigurer {
public WebServerFactoryCustomizer webServerFactoryCustomizer() {
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8088);
}
};
}
......Copy to clipboardErrorCopied
代码方式的配置会覆盖配置文件的配置
小Tips: 如果使用的是360极速浏览器就不要用8082端口了
注册Servlet三大组件
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
Servlet
向容器中添加ServletRegistrationBean
1 |
|
MyServlet
向容器中添加FilterRegistrationBean
1 |
|
MyFilter
向容器中注入ServletListenerRegistrationBean
1 |
|
MyListener
SpringBoot默认使用的是Tomcat
如果要换成其他的就把Tomcat的依赖排除掉,然后引入其他嵌入式Servlet容器的以来,如Jetty
,Undertow
1 | <dependency> |
原理
查看web容器自动配置类
2.0以下是:EmbeddedServletContainerAutoConfiguration
ServletWebServerFactoryAutoConfiguration
:嵌入式的web服务器自动配置
1 | ( |
EmbeddedTomcat.class
:
1 | ( |
ServletWebServerFactory
:嵌入式的web服务器工厂
1 |
|
工厂实现类
WebServer
:嵌入式的web服务器实现
以TomcatServletWebServerFactory
为例,下面是TomcatServletWebServerFactory类
1 | public WebServer getWebServer(ServletContextInitializer... initializers) { |
我们对嵌入式容器的配置修改是怎么生效的?
配置修改原理
ServletWebServerFactoryAutoConfiguration
在向容器中添加web容器时还添加了一个组件
BeanPostProcessorsRegistrar
:后置处理器注册器(也是给容器注入一些组件)
1 | public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { |
关于配置文件是如何设置的,参考EmbeddedWebServerFactoryCustomizerAutoConfiguration
类,最后还是使用上面的方便
总结:
SpringBoot根据导入的依赖情况,给容器中添加相应的
XXX
ServletWebServerFactory容器中某个组件要创建对象就会惊动后置处理器
webServerFactoryCustomizerBeanPostProcessor
只要是嵌入式的是Servlet容器工厂,后置处理器就会工作;
后置处理器,从容器中获取所有的
WebServerFactoryCustomizer
,调用定制器的定制方法给工厂添加配置
嵌入式Servlet容器启动原理
SpringBoot应用启动运行run方法
153行,创建IOC容器对象,根据当前环境创建
156行,刷新IOC容器
刷新IOC容器中272行,onRefresh();web的ioc容器重写了onRefresh方法,查看
ServletWebServerApplicationContext
类的onRefresh方法,在方法中调用了this.createWebServer();方法创建web容器1
2
3
4
5
6
7
8
9protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}Copy to clipboardErrorCopied98行获取嵌入式的web容器工厂
接下来就是上面的上面的相关配置流程,在创建web容器工厂时会触发
webServerFactoryCustomizerBeanPostProcessor
然后99行使用容器工厂获取嵌入式的Servlet容器
嵌入式的Servlet容器创建对象并启动Servlet容器;
嵌入式的Servlet容器启动后,再将ioc容器中剩下没有创建出的对象获取出来(Controller,Service等);
使用外置的Servlet容器
将项目的打包方式改为war
编写一个类继承
SpringBootServletInitializer
,并重写configure方法,调用参数的sources方法springboot启动类传过去然后返回1
2
3
4
5
6public class ServletInitializer extends SpringBootServletInitializer {
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(HelloSpringBootWebApplication.class);
}
}Copy to clipboardErrorCopied然后把tomcat的依赖范围改为provided
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.2.1.RELEASE</version>
<scope>provided</scope>
</dependency>
......
</dependencies>Copy to clipboardErrorCopied最后就可以把项目打包成war放到tomcat中了
在IDEA中可以这样配置
在创建项目时使用Spring Initializr创建选择打包方式为war,1,2,3步骤会自动配置
如果启动tomcat,报了一大堆错误,不妨把Tomcat改为更高的版本试试,如果你项目中的Filter是继承了HttpFilter,请使用tomcat9版本,9以下好像没有HttpFilter
原理
TODO 2019-11-20
Servlet3.0标准ServletContainerInitializer扫描所有jar包中METAINF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载
还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
在spring-web-xxx.jar包中的METAINF/services下有javax.servlet.ServletContainerInitializer这个文件
文件中的类是:
1
org.springframework.web.SpringServletContainerInitializerCopy to clipboardErrorCopied
对应的类:
1
2
3
4
5
6
7
8.class}) ({WebApplicationInitializer
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
......Copy to clipboardErrorCopiedSpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的
Set>
;为这些WebApplicationInitializer类型的类创建实例;每一个WebApplicationInitializer都调用自己的onStartup方法;
WebApplicationInitializer的实现类
相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, null);
builder.environment(environment);
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
builder = configure(builder);
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(!application.getSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return run(application);
}Copy to clipboardErrorCopiedSpring的应用就启动并且创建IOC容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新IOC容器
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
Docker基本使用
Docker是一个开源的应用容器引擎;是一个轻量级容器技术;
Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;
运行中的这个镜像称为容器,容器启动是非常快速的。
核心概念
docker主机(Host)
:安装了Docker程序的机器(Docker直接安装在操作系统之上);
docker客户端(Client)
:连接docker主机进行操作;
docker仓库(Registry)
:用来保存各种打包好的软件镜像;
docker镜像(Images)
:软件打包好的镜像;放在docker仓库中;
docker容器(Container)
:镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用
使用Docker的步骤:
确认要安装docker的系统的linux内核高于
3.10
,低于3.10使用yum update
更新1
uname -rCopy to clipboardErrorCopied
安装docker
1
yum install dockerCopy to clipboardErrorCopied
查看docker版本
1
docker -vCopy to clipboardErrorCopied
查看docker状态
1
service docker statusCopy to clipboardErrorCopied
启动docker
1
service docker startCopy to clipboardErrorCopied
停止docker
1
service docker stopCopy to clipboardErrorCopied
设置docker开机自启
1
systemctl enable dockerCopy to clipboardErrorCopied
docker常用命令
镜像操作
操作 | 命令 | 说明 |
---|---|---|
检索 | docker search 关键字 eg:docker search redis | 我们经常去docker hub上检索镜像的详细信息,如镜像的TAG。 |
拉取 | docker pull 镜像名:tag | :tag是可选的,tag表示标签,多为软件的版本,默认是latest |
列表 | docker images | 查看所有本地镜像 |
删除 | docker rmi image-id | 删除指定的本地镜像 |
修改镜像源
修改 /etc/docker/daemon.json ,写入如下内容(如果文件不存在请新建该文件)
1 | vim /etc/docker/daemon.json |
国内镜像源 | 地址 |
---|---|
Docker 官方中国区 | https://registry.docker-cn.com |
网易 | http://hub-mirror.c.163.com |
中国科技大学 | https://docker.mirrors.ustc.edu.cn |
阿里云 | https://pee6w651.mirror.aliyuncs.com |
容器操作
以tomcat为例:
下载tomcat镜像
1
docker pull tomcatCopy to clipboardErrorCopied
如需选择具体版本,可以在https://hub.docker.com/搜索tomcat
1
docker pull tomcat:7.0.96-jdk8-adoptopenjdk-hotspotCopy to clipboardErrorCopied
根据镜像启动容器,不加TAG默认latest,如果没有下载latest会先去下载再启动
1
docker run --name mytomcat -d tomcat:latestCopy to clipboardErrorCopied
--name
:给容器起个名字-d
:后台启动,不加就是前端启动,然后你就只能开一个新的窗口连接,不然就望着黑乎乎的窗口,啥也干不了,Ctrl+C
即可退出,当然,容器也会关闭查看运行中的容器
1
docker psCopy to clipboardErrorCopied
停止运行中的容器
1
2
3
4
5docker stop 容器的id
或者
docker stop 容器的名称,就是--name给起的哪个名字Copy to clipboardErrorCopied查看所有的容器
1
docker ps -aCopy to clipboardErrorCopied
启动容器
1
docker start 容器id/名字Copy to clipboardErrorCopied
删除一个容器
1
docker rm 容器id/名字Copy to clipboardErrorCopied
启动一个做了端口映射的tomcat
1
docker run -d -p 8888:8080 tomcatCopy to clipboardErrorCopied
-d
:后台运行-p
: 将主机的端口映射到容器的一个端口主机端口(8888)
:容器内部的端口(8080)
外界通过主机的8888端口就可以访问到tomcat,前提是8888端口开放
关闭防火墙
1
2
3
4
5查看防火墙状态
service firewalld status
关闭防火墙
service firewalld stopCopy to clipboardErrorCopied查看容器的日志
1
docker logs 容器id/名字Copy to clipboardErrorCopied
以mysql为例:
1 | 拉取镜像 |
--name mysql
:容器的名字是mysql;
MYSQL_ROOT_PASSWORD=root
:root用户的密码是root (必须指定)
连接容器内mysql
在使用 -d 参数时,容器启动后会进入后台。此时想要进入容器,可以通过以下指令进入:
- docker attach
- docker exec:推荐使用 docker exec 命令,因为此退出容器终端,不会导致容器的停止。
1 | docker exec -it mysql bashCopy to clipboardErrorCopied |
-i
: 交互式操作。
-t
: 终端。
mysql
: 名为mysql的 镜像。
bash
:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash,也可以用/bin/bash
。
连接上以后就可以正常使用mysql命令操作了
1 | mysql -uroot -prootCopy to clipboardErrorCopied |
直接使用端口映射更加方便
1 | docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.28 |
SpringBoot与数据库连接
依赖
1 | <dependencies> |
配置数据库连接信息
1 | spring: |
测试能否连接上数据库
1 | @SpringBootTest |
springboot默认是使用com.zaxxer.hikari.HikariDataSource
作为数据源,2.0以下是用org.apache.tomcat.jdbc.pool.DataSource
作为数据源;
数据源的相关配置都在DataSourceProperties里面;
自动配置原理
TODO
jdbc的相关配置都在org.springframework.boot.autoconfigure.jdbc
包下
参考DataSourceConfiguration
,根据配置创建数据源,默认使用Hikari连接池;可以使用spring.datasource.type指定自定义的数据源类型;
springboot默认支持的连池:
- org.apache.commons.dbcp2.BasicDataSource
- com.zaxxer.hikari.HikariDataSource
- org.apache.tomcat.jdbc.pool.DataSource
自定义数据源类型:
1 | ( |
启动应用执行sql
SpringBoot在创建连接池后还会运行预定义的SQL脚本文件,具体参考org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration
配置类,
在该类中注册了dataSourceInitializerPostProcessor
下面是获取schema脚本文件的方法
1 | List<Resource> scripts = this.getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");Copy to clipboardErrorCopied |
可以看出,如果我们没有在配置文件中配置脚本的具体位置,就会在classpath下找schema-all.sql
和schema.sql
platform获取的是all,platform可以在配置文件中修改
具体查看createSchema()方法
和initSchema()方法
initSchema()方法获取的是data-all.sql
,data.sql
我们也可以在配置文件中配置sql文件的位置
1 | spring: |
测试:
在类路径下创建schema.sql
,运行程序查看数据库是否存在该表
1 | DROP TABLE IF EXISTS `department`; |
程序启动后发现表并没有被创建,DEBUG查看以下,发现在运行之前会有一个判断
上面方法也不知道在干什么,反正就是只要是NEVER
和EMBEDDED
就为true,而DataSourceInitializationMode枚举类中除了这两个就剩下ALWAYS
了,可以在配置文件中配置为ALWAYS
1 | spring: |
schema.sql
:建表语句
data.sql
:插入数据
当然混合使用也可以,愿意咋来咋来
注意:项目每次启动都会执行一次sql
整合Druid数据源
选择哪个数据库连接池
- DBCP2 是 Appache 基金会下的项目,是最早出现的数据库连接池 DBCP 的第二个版本。
- C3P0 最早出现时是作为 Hibernate 框架的默认数据库连接池而进入市场。
- Druid 是阿里巴巴公司开源的一款数据库连接池,其特点在于有丰富的附加功能。
- HikariCP 相较而言比较新,它最近两年才出现,据称是速度最快的数据库连接池。最近更是被 Spring 设置为默认数据库连接池。
不选择 C3P0 的原因:
- C3P0 的 Connection 是异步释放。这个特性会导致释放的在某些情况下 Connection 实际上 still in use ,并未真正释放掉,从而导致连接池中的 Connection 耗完,等待状况。
- Hibernate 现在对所有数据库连接池一视同仁,官方不再指定『默认』数据库连接池。因此 C3P0 就失去了『官方』光环。
不选择 DBCP2 的原因:
- 相较于 Druid 和 HikariCP,DBCP2 没有什么特色功能/卖点。基本上属于
能用,没毛病
的情况,地位显得略有尴尬。
在 Spring Boot 项目中加入
druid-spring-boot-starter
依赖 (点击查询最新版本)1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>Copy to clipboardErrorCopied在配置文件中指定数据源类型
1
2
3
4
5
6
7
8spring:
datasource:
username: root
password: root
url: jdbc:mysql://172.16.145.137:3306/springboot
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
type: com.alibaba.druid.pool.DruidDataSourceCopy to clipboardErrorCopied测试类查看使用的数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
class SpringbootJdbcApplicationTests {
private DataSource dataSource;
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
System.out.println(dataSource.getConnection());
}
}Copy to clipboardErrorCopied
配置参数
1 | spring: |
后台页面,访问http://localhost:8080/druid/login.html
SpringBoot整合Mybatis
引入依赖
1 | <dependencies> |
依赖关系
项目构建
在resources下创建
department.sql
和employee.sql
,项目启动时创建表1
2
3
4
5
6DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`departmentName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;Copy to clipboardErrorCopied1
2
3
4
5
6
7
8
9
10DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lastName` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`gender` int(2) DEFAULT NULL,
`d_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Copy to clipboardErrorCopied实体类
Department
Employee
1 | spring: |
Mybatis增删改查
创建mapper接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface DepartmentMapper {
"select * from department") (
public List<Department> selectAll();
"select * from department where id=#{id}") (
public Department selectById(Integer id);
true, keyProperty = "id") (useGeneratedKeys =
"insert into department(departmentName) values(#{departmentName})") (
public int save(Department department);
"update department set departmentName=#{departmentName}") (
public int update(Department department);
"delete from department where id =#{id}") (
public int delete(Integer id);
}Copy to clipboardErrorCopied创建Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DepartmentController {
private DepartmentMapper departmentMapper;
"/dep/{id}") (
public List<Department> getDepById(@PathVariable Integer id) {
return departmentMapper.selectAll();
}
"/dep") (
public Department getDepById(Department department) {
departmentMapper.save(department);
return department;
}
}Copy to clipboardErrorCopied
Mybatis配置
开启驼峰命名法
我们的实体类和表中的列名一致,一点问题也没有
我们把department表的departmentName列名改为department_name看看会发生什么
访问:http://localhost:8080/dep/1获取数据
1 | [{"id":1,"departmentName":null}]Copy to clipboardErrorCopied |
由于列表和属性名不一致,所以就没有封装进去,我们表中的列名和实体类属性名都是遵循驼峰命名规则的,可以开启mybatis的开启驼峰命名配置
1 | mybatis: |
然后重启项目,重新插入数据,再查询就发现可以封装进去了
也可以通过向spring容器中注入org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer
的方法设置mybatis参数
1 |
|
Mapper扫描
使用@mapper注解
的类可以被扫描到容器中,但是每个Mapper都要加上这个注解就是一个繁琐的工作,能不能直接扫描某个包下的所有Mapper接口呢,当然可以,在springboot启动类上加上@MapperScan
1 | "cn.clboy.springbootmybatis.mapper") ( |
使用xml配置文件
创建mybatis全局配置文件
1
2
3
4
5
6
7
<configuration>
<typeAliases>
<package name="cn.clboy.springbootmybatis.model"/>
</typeAliases>
</configuration>Copy to clipboardErrorCopied创建EmployeeMapper接口
1
2
3
4
5
6public interface EmployeeMapper {
List<Employee> selectAll();
int save(Employee employee);
}Copy to clipboardErrorCopied创建EmployeeMapper.xml映射文件
1
2
3
4
5
6
7
8
9
10
<mapper namespace="cn.clboy.springbootmybatis.mapper.EmployeeMapper">
<select id="selectAll" resultType="employee">
SELECT * FROM employee
</select>
<insert id="save" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
INSERT INTO employee(lastName,email,gender,d_id) VALUES (#{lastName},#{email},#{gender},#{d_id})
</insert>
</mapper>Copy to clipboardErrorCopied配置文件(application.yaml)中指定配置文件和映射文件的位置
1
2
3mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xmlCopy to clipboardErrorCopied给表中插入两个数据,用于测试
1
2INSERT INTO employee(lastName,email,gender,d_id) VALUES ('张三','123456@qq.com',1,1);
INSERT INTO employee(lastName,email,gender,d_id) VALUES ('lisi','245612@qq.com',1,1);Copy to clipboardErrorCopied创建EmployeeController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class EmployeeController {
private EmployeeMapper employeeMapper;
"/emp/list") (
public List<Employee> getALl() {
return employeeMapper.selectAll();
}
"/emp/{id}") (
public Employee save(Employee employee) {
employeeMapper.save(employee);
return employee;
}
}Copy to clipboardErrorCopied
SpringBoot整合JPA
SpringData:
依赖
1 | <dependencies> |
实体类
1 | package cn.clboy.springbootjpa.entity; |
DAO
1 | package cn.clboy.springbootjpa.repository; |
配置文件
1 | spring: |
Controller
1 | Copy to clipboardErrorCopied |
添加User,访问:
http://localhost:8080/user?lastName=zhangsan&email=123456@qq.com
http://localhost:8080/user?lastName=lisi&email=78215646@qq.com
查询用户访问:
http://localhost:8080/user/1,然后你就会发现抛出500错误,原因是getOne方法使用的懒加载,获取到的只是代理对象,转换为json时会报错
解决方法有两种:
关闭懒加载,在实体类上加
@Proxy(lazy = false)
注解1
2
3
4
"tbl_user") (name =
false) (lazy =
public class UserCopy to clipboardErrorCopied转json的时候忽略
hibernateLazyInitializer
和handler
属性1
2
3
4
"tbl_user") (name =
"hibernateLazyInitializer", "handler"}) (value = {
public class User
SpringBoot启动流程
启动原理
1 | public static void main(String[] args) { |
从run方法开始,创建SpringApplication,然后再调用run方法
1
2
3
4
5
6
7
8
9
10
11/**
* ConfigurableApplicationContext(可配置的应用程序上下文)
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
//调用下面的run方法
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}Copy to clipboardErrorCopied创建SpringApplication
1
2//primarySources:主配置类
new SpringApplication(primarySources)Copy to clipboardErrorCopied1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public SpringApplication(Class<?>... primarySources) {
//调用下面构造方法
this((ResourceLoader) null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//保存主配置类
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//获取当前应用的类型,是不是web应用,见2.1
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来,见2.2
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从类路径下找到META‐INF/spring.ApplicationListener;然后保存起来,原理同上
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类,见下图(在调run方法的时候是可以传递多个配置类的)
this.mainApplicationClass = this.deduceMainApplicationClass();
//执行完毕,SpringApplication对象就创建出来了,返回到1处,调用SpringApplication对象的run方法,到3
}Copy to clipboardErrorCopied2.1 判断是不是web 应用
2.2 getSpringFactoriesInstances(ApplicationContextInitializer.class)
1 | public ConfigurableApplicationContext run(String... args) { |
//容器创建完成,返回步骤1处,最后返回到启动类
3.1
3.2
3.3
3.4
3.5
配置在META-INF/spring.factories
- ApplicationContextInitializer
- SpringApplicationRunListener
只需要放在ioc容器中
- ApplicationRunner
- CommandLineRunner
测试
创建
ApplicationContextInitializer
和SpringApplicationRunListener
的实现类,并在META-INF/spring.factories文件中配置1
2
3
4
5
6
7public class TestApplicationContextInitializer implements ApplicationContextInitializer {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("TestApplicationContextInitializer.initialize");
}
}Copy to clipboardErrorCopied1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public class TestSpringApplicationRunListener implements SpringApplicationRunListener {
public void starting() {
System.out.println("TestSpringApplicationRunListener.starting");
}
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("TestSpringApplicationRunListener.environmentPrepared");
}
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("TestSpringApplicationRunListener.contextPrepared");
}
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("TestSpringApplicationRunListener.contextLoaded");
}
public void started(ConfigurableApplicationContext context) {
System.out.println("TestSpringApplicationRunListener.started");
}
public void running(ConfigurableApplicationContext context) {
System.out.println("TestSpringApplicationRunListener.running");
}
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("TestSpringApplicationRunListener.failed");
}
}Copy to clipboardErrorCopied1
2
3
4
5\ =
cn.clboy.springbootprocess.init.TestApplicationContextInitializer
\ =
to clipboardErrorCopied启动报错:说是没有找到带org.springframework.boot.SpringApplication和String数组类型参数的构造器,给TestSpringApplicationRunListener添加这样的构造器
1
2public TestSpringApplicationRunListener(SpringApplication application,String[] args) {
}Copy to clipboardErrorCopied创建
ApplicationRunner
实现类和CommandLineRunner
实现类,注入到容器中1
2
3
4
5
6
7
8
public class TestApplicationRunner implements ApplicationRunner {
public void run(ApplicationArguments args) throws Exception {
System.out.println("TestApplicationRunner.run\t--->"+args);
}
}Copy to clipboardErrorCopied1
2
3
4
5
6
7
8
public class TestCommandLineRunn implements CommandLineRunner {
public void run(String... args) throws Exception {
System.out.println("TestCommandLineRunn.runt\t--->"+ Arrays.toString(args));
}
}Copy to clipboardErrorCopied
修改Banner
默认是找类路径下的banner.txt
,可以在配置文件中修改
1 | xxx.txtCopy to clipboardErrorCopied = |
生成banner的网站:http://patorjk.com/software/taag
也可以使用图片(将其像素解析转换成assii编码之后打印),默认是在类路径下找名为banner
后缀为"gif", "jpg", "png"
的图片
1 | static final String[] IMAGE_EXTENSION = new String[]{"gif", "jpg", "png"};Copy to clipboardErrorCopied |
也可以在配置文件中指定
1 | classpath:abc.png = |
自定义Starter
启动器只用来做依赖导入
专门来写一个自动配置模块;
启动器依赖自动配置模块,项目中引入相应的starter就会引入启动器的所有传递依赖
启动器
启动器模块是一个空 JAR 文件,仅提供辅助性依赖管理,这些依赖可能用于自动 装配或者其他类库
命名规约
官方命名
spring-boot-starter-模块名
eg:
spring-boot-starter-web
、spring-boot-starter-jdbc
、spring-boot-starter-thymeleaf
自定义命名
模块名-spring-boot-starter
eg:
mybatis-spring-boot-start
如何编写自动配置
1 | //指定这个类是一个配置类 |
自动配置类要能加载,需要将启动就加载的自动配置类配置在META-INF/spring.factories
中
eg:
1 | # Auto Configure |
案例
创建一个自动配置模块,和创建普通springboot项目一样,不需要引入其他starter
删除掉多余的文件和依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<groupId>cn.clboy.spring.boot</groupId>
<artifactId>clboy-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>clboy-spring-boot-autoconfigure</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--引入spring‐boot‐starter;所有starter的基本配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--可以生成配置类提示文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>Copy to clipboardErrorCopied创建配置类和自动配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package cn.clboy.spring.boot.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
"clboy") (prefix =
public class ClboyProperties {
private String prefix;
private String suffix;
public ClboyProperties() {
this.prefix = "";
this.suffix = "";
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}Copy to clipboardErrorCopied1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package cn.clboy.spring.boot.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author cloudlandboy
* @Date 2019/11/24 上午10:40
* @Since 1.0.0
*/
//web应用才生效
.class) //让配置类生效,(注入到容器中) (ClboyProperties
public class ClboyAutoConfiguration {
private final ClboyProperties clboyProperties;
/**
* 构造器注入clboyProperties
*
* @param clboyProperties
*/
public ClboyAutoConfiguration(ClboyProperties clboyProperties) {
this.clboyProperties = clboyProperties;
}
public HelloService helloService() {
return new HelloService();
}
public class HelloService {
public String sayHello(String name) {
return clboyProperties.getPrefix() + name + clboyProperties.getSuffix();
}
}
}Copy to clipboardErrorCopied在resources文件夹下创建META-INF/spring.factories
1
2\ =
to clipboardErrorCopied安装到本地仓库
创建starter,选择maven工程即可,只是用于管理依赖,添加对AutoConfiguration模块的依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.clboy.spring.boot</groupId>
<artifactId>clboy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>cn.clboy.spring.boot</groupId>
<artifactId>clboy-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>Copy to clipboardErrorCopied安装到本地仓库
创建项目测试,选择添加web场景,因为设置是web场景才生效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.clboy</groupId>
<artifactId>starter-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>starter-test</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.clboy.spring.boot</groupId>
<artifactId>clboy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>Copy to clipboardErrorCopied创建Controller
1
2
3
4
5
6
7
8
9
10
11
12
public class HelloController {
private HelloService helloService;
"/hello") (
public String sayHello() {
String hello = helloService.sayHello("Peppa Pig");
return hello;
}
}Copy to clipboardErrorCopied在配置文件中配置
1
2hello! =
,你好啊...Copy to clipboardErrorCopied =启动项目访问:http://localhost:8080/hello
注意查看文件夹的命名是否正确,最好是从别的包中复制过去,正确的情况下spring.factories是有小绿叶图标的
Cache
搭建基本环境
导入数据库文件 创建出department和employee表
创建javaBean封装数据
整合MyBatis操作数据库
- 配置数据源信息
1
2
3
4jdbc:mysql://localhost:3306/spring_cache?serverTimezone=UTC =
root =
root =
true =
使用注解版的MyBatis;
1)@MapperScan指定需要扫描的mapper接口所在的包
1
2
3
4
5
6
7"com.zzl.mapper") (
public class Springboot01CahceApplication {
public static void main(String[] args) { SpringApplication.run(Springboot01CahceApplication.class, args);
}
}
快速体验缓存
步骤:
开启基于注解的缓存 @EnableCaching(如上图)
标注缓存注解即可
@Cacheable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**Cacheable中的参数:
* cacheNames/value:缓存组件的名字
* key:缓存数据用的key,默认是参数的值
* keyGenerator:key的生成器; key和keyGenerator 二选一
* cacheManager:缓存管理器,或者cacheResolver指定获取解析器
* condition:符合条件的情况才缓存
* #a0>1第一个参数大于1
* unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存
* sync:是否使用异步
* */
()
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
@CacheEvict
1 | /** |
@CachePut
1 | /** |
自定义keyGenerator
1 |
|
使用:
1 | "myKeyGenerator") (keyGenerator = |
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object>中
整合Redis作为缓存
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
- 安装Redis:使用docker
1 | docker pull redis |
- 引入redis的starter
1 | <dependency> |
- 配置redis
1 | spring.redis.host=111.229.141.185 |
1 | //序列化为Json,Emp类要实现序列化接口,默认序列化为jdk序列化 |
消息
RabbitMQ安装使用
采用docker安装
1 | docker pull rabbitmq:3-management |
创建exchange:direct、fanout、topic
创建队列:atguigu , atguigu.news , atguigu.emps , gulixueyuan.news
绑定exchange和队列
设置Routing key:direct、fanout与队列名相同,topic为尾缀
发布消息,即可在队列中收到消息
使用SpringBoot发送接受消息
- 在initializer中选择rabbitMQ,会引入相应的包
- 设置配置
1 | spring.rabbitmq.host=111.229.141.185 |
1 |
|
设置序列化转换器
1 |
|
基于注解的RabbitMQ
- 开启基于注解的RabbitMQ
1 | //开启基于注解的RabbitMQ |
- 使用RabbitListenner监听队列消息
1 |
|
AmqpAdmin管理组件的使用(利用java程序创建exchange,队列,绑定规则)
1 |
|
检索ElasticSearch
ElasticSearch安装和基本使用
使用docker安装
1 | docker pull elasticsearch:6.8.6 |
注意:elasticsearch版本应与springboot内版本向对应 springboot2.2.6 对应 elasticsearch6.8.6
在浏览器中访问 ip:9200可查看相关信息
在elasticsearch / 面向文档中 可快速入门elasticsearch
在Springboot中使用ElasticSearch
在Spring Initializer中选择Nosql中的ElasticSearch,导入相应的pom包
Jest(已过时)
- 配置jest
1 | spring.elasticsearch.jest.uris=http://111.229.141.185:9200/ |
- 创建Article实体类
1 | public class Article { |
1 |
|
在浏览器中访问ip:9200/atguigu/news/{id}
SpringData
- 配置文件
1 | #相关的信息在ip:9200中查看 |
两种用法:https://github.com/spring-projects/spring-data-elasticsearch
1)、编写一个接口继承ElasticSearchReoisitory
1 | public interface BookRepository extends ElasticsearchRepository<Book,Integer> { |
其中的Book实体类
1 | "atguigu",type = "book") (indexName = |
测试
1 |
|
任务
异步任务
1 开启异步注解
1 |
|
2 Service类
1 |
|
3 Controller类
1 |
|
定时任务
开启Schedule注解
1 |
|
Service类
1 | /** |
邮件任务
1 引入邮件相关的starter
1 | <dependency> |
2 配置相关配置
1 | 1422181938@qq.com = |
3 测试
1 |
|
安全
SpringSecurity
1 创建工程时选择Security
2 编写SpringSecurity的配置类
1 |
|
3 页面配置
1 | <html xmlns:th="http://www.thymeleaf.org" |
4 自定义登陆页面
1 | <html xmlns:th="http://www.thymeleaf.org"> |
开发热部署
热部署实现:
1 引入开发者工具
1 | <dependency> |
2 按Ctrl+F9(build)
监控管理
实现监控
1 创建项目时选择Ops中的Actuator
2 SpringBoot2.x 配置
1 | * = |
在Springboot2.x中,大多数监控已不再有用。可用的如图。