SpringBoot 是 Spring 家族中的一个全新的框架,它用来简化 Spring 应用程序的创建和开发过程,也可以说是 Spring Boot 能简化我们之前采用的 SpringMVC+Spring+Mybatis 框架进行开发的过程。
在以往我们采用 SpringMVC+Spring+Mybatis 框架进行开发的时候,搭建和整合三大框架,我们需要做很多工作,比如配置 web.xml,配置 Spring, 配置 Mybatis,并将它们整合在一起等,而 SpringBoot 框架对此开发过程进行了革命性的颠覆,抛弃了繁琐的 xml 配置过程,采用大量的默认配置简化我们的开发过程。
采用 Spring Boot 可以非常容易和快速的创建基于 Spring 框架的应用程序,它让代码变得简单了,配置变简单了,部署变简单了,监控变简单了
让开发变得极其简单和快速。
能够快速创建基于 Spring 的应用程序
能够直接使用 java main 方法启动内嵌的 tomcat,jetty 服务器运行 Spring boot 程序。
根据约定的 start pom 来简化 maven 的配置,让 maven 的配置变得简单。
根据项目的 maven 依赖配置, Spring boot 自动配置 Spring, Springmvc 等
提供了程序的健康检查等功能
基本可以完全不使用 xml 配置文件,采用注解配置
自动配置:针对很多 Spring 应用程序和常见的应用功能,Spring Boot 能自动提供相关配置
起步依赖:告诉 SpringBoot 需要什么功能,他就能引入需要的依赖库
Actuator:让你能够深入运行中的 SpringBoot 应用程序,一探SpringBoot 程序的内部信息
命令行界面:这是 SpringBoot 的可选特性,主要针对 Groovy 语言使用。
快速创建独立运行的 Spring 项目以及与主流框架集成
使用嵌入式的 Servlet 容器,应用无需打成 war 包
Starters 自动 依赖与版本控制
大量的自动配置,简化开发,也可修改默认值
无需配置 xml ,无代码生成,开箱即用
准生产环境的运行时应用监控
与云计算的天然集成
架构服务
一个应用应该是一个小型服务;可以通过 HTTP 的方式进行互通。
每一个功能元素最终都是一个可独立替换,可独立升级的软件单元。
环境约束
Jdk1.8 : SpringBoot 1.7及以上
Maven3.x : maven 3.3
IntelligIDEA2017
SpringBoot 1.5.9.RELEASE
给 maven 的 settings.xml 配置文件的 profiles 标签内添加代码如下
在 settings.xml 182行添加以下代码:
jdk-1.8
true
1.8
1.8
1.8
1.8
![enter description here](./images/idea_配置.png)
[外链图片转存失败(img-tyGedOr9-1566610817792)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564815501985.png)]
一个功能:
浏览器发送 hello 请求,服务器接受请求并处理,响应 hello world 字符串。
步骤:
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
org.springframework.boot
spring-boot-starter-web
import org.springframework.boot.SpringApplication;
/**
* @SpringBootApplication 来标注一个主程序类,说明这是一个 SpringBoot 应用
*/
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
//Spring 应用启动起来
SpringApplication.run(HelloWorldMainApplication.class,args);
}
}
package lan.springboot;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String hello() {
return "hello world,欢迎来到 springboot 的世界";
}
}
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
[外链图片转存失败(img-vF3b2S57-1566610817793)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757598191.png)]
[外链图片转存失败(img-maQR7L9b-1566610817794)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757669992.png)]
Lifecycle ——> package
[外链图片转存失败(img-zKiwEA43-1566610817794)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757769324.png)]
[外链图片转存失败(img-WgqmRqZW-1566610817795)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757802145.png)]
[外链图片转存失败(img-1lArrfns-1566610817795)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757841968.png)]
启动项目 java -jar jar包名
[外链图片转存失败(img-k6dzaARe-1566610817796)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564758111941.png)]
将这个应用打成 jar 包,直接使用 java -jar 的命令执行。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.5.9.RELEASEversion>
parent>
它的父项目是:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>1.5.9.RELEASEversion>
<relativePath>../../spring-boot-dependenciesrelativePath>
parent>
它来真正管理 SpringBoot 应用里面的所有依赖版本;
SpringBoot 的版本仲裁中心:
以后我们导入依赖默认是不需要写版本;(没有在 dependencies 里面管理的依赖自然需要声明版本号)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
spring-boot-starter-web:
spring-boot-starter : spring-boot场景启动器(starter):帮我们导入了 web 模块正常运行所依赖的组件。
Spring Boot 将所有的功能场景都抽取出来,做成一个个的 starters(启动器),只需要在项目里面引入这些 starter 相关场景的所有依赖都会导入进来。要什么功能就导入什么启动器即可。
package lan.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @SpringBootApplication 来标注一个主程序类,说明这是一个 SpringBoot 应用
*/
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
//Spring 应用启动起来
SpringApplication.run(HelloWorldMainApplication.class,args);
}
}
@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的配置类,SpringBoot 就应该运行这个类的 main 方法来启动 SpringBoot 应用;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@SpringBootConfiguration : SpringBoot 的配置类:
标注在某个类上,表示这是一个 SpringBoot 的配置类。
@Configuration : 配置类上来标注这个注解。
配置类 ----- 配置文件;配置类也是容器中的一个组件:@Component
@EnableAutoConfiguration : 开启自动配置功能
SpringBoot 开启自动配置功能。
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage : 自动配置包
@Import({Registrar.class}):
Spring 的底层注解 @import ,给容器中导入一个组件;导入的组件由 Registrar.class
将主配置类(@SpringBootApplication 标注的类)的所在包及下面所有子包里面的所有组件扫描到的Spring 容器。
@Import({EnableAutoConfigurationImportSelector.class});
EnableAutoConfigurationImportSelector : 导入哪些组件的选择器。
将所有需要导入的组件以全类名的方式返回,然后这些组件 就会被添加到容器中。
会给容器中导入非常多的自动配置类(xxxAutoConfiguration),给容器中导入这个场景需要的所有组件,并配置好这些组件。
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
SpringBoot 在启动的时候从类路径下的 META-INF/spring.factories 中获取 EnableAutoConfiguration 指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置。
J2EE 的整体整合解决方案和自动配置都在 spring-boot-autoconfigure-1.x.x.RELEASE.jar 中。
IDE 都支持使用 Spring 的项目创建向导快速创建一个 SpringBoot 项目
[外链图片转存失败(img-wYgfxkfB-1566610817797)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564762566356.png)]
[外链图片转存失败(img-KTwHFYBS-1566610817797)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564762683870.png)]
[外链图片转存失败(img-TF70pmxG-1566610817798)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564762731415.png)]
默认生成的 Spring Boot 项目:
SpringBoot 使用一个全局的配置文件,配置文件名是固定的;
application.properties
application.yml
配置文件的作用:修改 SpringBoot 自动配置的默认值;SpringBoot 在底层都给我们自动配置好。
配置文件放在 src/main/resources 目录或者 类路径/config 下。
YAML(YAML Ain’t Markup Lanauage)
YAML A Markup Language : 是一个标记语言
YAML isn’t Markup Language :
标记语言:
-.yml 是 YAML 语言的文件,以数据为中心,比 Json 、xml 等更适合做配置文件。
YAML : 配置例子
server:
port : 8081
k : v : 表示一对键值对**(空格必须有)**;
以空格的缩进来控制层级关系;只要左对齐的一列数据都是同一个层级的
server:
port : 8081
path : /hello
属性和值都是大小写敏感。
k: v : 字面直接来写
字符串默认不用加上单引号或双引号;
“” : 双引号 ; 不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思
‘’ : 单引号; 回转仪特殊字符,特殊字符最终只是一个普通的字符串数据
k: V : 在下一行来写对象的属性和值的关系;注意缩进
对象还是 k: v 的方式
friends :
lastName : zhangsan
age : 20
行内写法:
friends : {lastName : zhangsan,age : 18}
数组(List、Set)
用 - 值表示数组中的一个元素
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
配置文件application.yml
server:
port: 8081
person:
lastName: zhangsan
age: 18
boss: false
birth: 2019/08/03
maps: {k1: v1,k2: 12}
lists:
- lisi
- zhaoliu
dog:
name: 小狗
age: 2
配置文件 application.properties
#server.port = 8081
#idea,properties 配置文件 utf-8
person.last-name=张三
person.age=18
person.birth=1993/04/28
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=15
会出现中文乱码,需要进行设置。
[外链图片转存失败(img-Rb78q1Y9-1566610817799)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564795235990.png)]
javabean
/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties : 告诉 SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "person" : 配置文件中哪个下面的所有随性进行一一映射
*
* 只有这个组件是容器中的组件,才能使用容器提供的 @ConfigurationProperties 功能;
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
可以导入配置文件处理器,以后编写配置就会存在提示。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
[外链图片转存失败(img-6bikEJJd-1566610817799)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564795477292.png)]
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装(Map等) | 支持 | 不支持 |
配置文件 yml 还是 properties 他们都能获取到值。
如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用 @Value();
如果说,我们专门编写了一个 Javabean 来和配置文件进行映射,我们直接使用 @ConfigurationProperties ;
松散绑定
[外链图片转存失败(img-oe8XNw94-1566610817800)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564795813972.png)]
/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties : 告诉 SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "person" : 配置文件中哪个下面的所有随性进行一一映射 : 默认从全局配置文件中获取值;
*
* 只有这个组件是容器中的组件,才能使用容器提供的 @ConfigurationProperties 功能;
*/
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
/**
*
*
*
*/
// @Value("${person.last-name}")
private String lastName;
// @Value("#{11*2}")
private Integer age;
// @Value("true")
private Boolean boss;
private Date birth;
person.properties
#server.port = 8081
#idea,properties 配置文件 utf-8
person.last-name=李四
person.age=18
person.birth=1993/04/28
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=15
SpringBoot 里面没有 Spring 的配置文件,我们自己编写的配置文件,也不能自动识别;
想要 Spring 的配置文件生效,加载进来;@ImportSource 标注在类上。
@ImportResource(locations = {"classpath:beans.xml"})
SpringBoot 推荐给容器中添加组件的方式;推荐使用全注解的方式
1.配置类 === Spring 配置文件
2.@Configuration :指明当前类是一个配置类,就是来替代之前的 Spring 配置文件。
3.@Bean 将方法的返回值添加到容器中
/**
* @Configuration : 指明当前类是一个配置类,就是来替代之前的 Spring 配置文件
*/
@Configuration
public class MyAppConfig {
/**
* @Bean 将方法的返回值添加到容器中;容器中这个组件默认的 Id 就是方法名
* @return
*/
@Bean
public HelloService helloService() {
System.out.println("配置类@Bean给容器中的组件添加了....");
return new HelloService();
}
}
${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}
app.name=MyApp
app.description=${app.name} is a Spring Boot application
#server.port = 8081
#idea,properties 配置文件 utf-8
person.last-name=张三${random.uuid}
person.age=${random.int}
person.birth=1993/04/28
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.last-name}_dog
person.dog.age=15
Profile 是 Spring 对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速切换环境。
1.多 profile 文件形式
-格式 : applicatioin-{profile}.properties
application-dev.properties、application-prod.properties
2.yml 多 profile 文档块模式
3.激活方式:
- 命令行 --spring.profiles.active = dev
- 配置文件 spring.profiles.active = dev
- jvm 参数 -Dspring.profiles.active = dev
-格式 : applicatioin-{profile}.properties
application-dev.properties、application-prod.properties
server:
port: 8081
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev
---
server:
port: 8084
spring:
profiles: prod
- 命令行 --spring.profiles.active = dev
- 配置文件 spring.profiles.active = dev
- jvm 参数 -Dspring.profiles.active = dev
命令行:
java -jar spring-boot-xxx.jar --spring-profiles.active=dev;
[外链图片转存失败(img-Aguy9yNd-1566610817801)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564803561771.png)]
虚拟机参数:
-Dspring.profiles.active = dev
SpringBoot 启动会扫描以下位置的 application.properties 或者 application.yml 文件作为 spring boot 的默认配置文件
—file: ./config/
—file: ./
—classpath: /config/
—classpath:/
—以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级内容会覆盖低优先级配置内容。
SpringBoot 会从这四个位置全部加载主配置文件:互补配置。
也可以通过配置 spring.config.location 来改变默认配置。
[外链图片转存失败(img-BXQsUOlz-1566610817801)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564804292884.png)]
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候里指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;
SpringBoot支持多种外部配置方式;以下按照优先级从高到低排列;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置。
命令行参数
java -jar spring-boot.xxx.jar --server.port=8087 --server.context = /abc
多个配置用空格分开; --配置项=值
来自 java:comp/env 的 JNDI 属性
java 系统属性(System.getProperties)
操作系统环境变量
RandomValuePropertySource 配置的 random.* 属性值
优先加载带 profile
jar 包外部的 application-{profile}.properties 或 application.yml(带 spring.profiles) 配置文件
jar 包内部的 application-{profile}.properties 或 application.yml(带 spring.profile) 配置文件
jar 包外部的 application-properties 或 application.yml(不带 spring.profile) 配置文件
jar 包内部的 application-properties 或 application.yml(不带 spring.profile) 配置文件
[外链图片转存失败(img-oD5ephT6-1566610817802)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564805888679.png)]
@Configuration 注解类上的 @PropertySource
通过 SpringApplication.setDefaultProperties 指定的默认属性。
1.SpringBoot 启动的时候加载朱配置类,开启了自动配置功能@EnableAutoConfiguration;
2.@EnableAutoConfiguration作用:
利用 EnableAutoConfigurationImportSelector 给容器中导入一些组件?
可以插入 selectImports() 方法的内容;
List<String> configurations = getCandidateConnfigurations(annotationMetadata, attributes); //获取候选配置
SpringFactoriesLoader.loadFacotoryNames()
//扫描所有 jar 包类路径下 META-INF/spring.factories
把扫描到的这些文件的内容包装成 properties 对象
从 properties 中获取到 EnableAutoConfiguration.class 类(类名)对应的值,然后把他们添加到容器中
将类路径下 META-INF/spring.factories 里面配置的所有 EnableAutoConfiguration 的值添加到了容器中。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
每一个这样的 xxxAutoConfiguration 类都是容器中的一个组件,都加入到容器中;用他们来做自动配置。
每一个自动配置类进行自动配置功能
以 HttpEncodingAutoConfiguration 为例解释自动配置原理;
@Configuration //表示这是一个配置类,
@EnableConfigurationProperties({HttpProperties.class}) //启用指定类的ConfigurationProperties功能;将配置文件中对应的值和 HttpProperties 绑定起来;
@ConditionalOnWebApplication( //Spring底层 @Conditional 注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;判断当前应用是否是 web 应用,如果是,当前配置类生效
type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class}) //判断当前项目有没有这个类
@ConditionalOnProperty( //判断配置文件中是否存在某个配置;
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//已经和 SpringBoot 的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean //给容器中添加一个组件, 这些组件的某些值需要从 properties 中获取
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
根据当前不同条件进行判断,决定这个配置类是否生效?
一旦这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的 properties 类中获取的,这个类里面的每一个属性又是和配置文件绑定的。
所有在配置文件中能配置的属性都是在 xxxProperties 类中封装;配置文件能配置什么就可以参照某个功能对应的这个属性类。
@ConfigurationProperties( // 从配置文件中获取指定的值和 bean 属性进行绑定
prefix = "spring.http"
)
public class HttpProperties {
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存在指定项 |
自动配置类在一定条件下才能生效。
可以通过启用 debug=true 属性;来让控制台打印自动配置报告,这样就可以很方便的知道哪些自动配置生效。
# 开启 SpringBoot 的 debug
debug=true
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:(自动配置类启用的)
-----------------
CodecsAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)
CodecsAutoConfiguration.JacksonCodecConfiguration matched:
- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
CodecsAutoConfiguration.JacksonCodecConfiguration#jacksonCodecCustomizer matched:
- @ConditionalOnBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found bean 'jacksonObjectMapper' (OnBeanCondition)
DispatcherServletAutoConfiguration matched:
Negative matches:(没有启动,没有匹配成功的自动配置类)
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)
ArtemisAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
BatchAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.springframework.batch.core.launch.JobLauncher' (OnClassCondition)
1. **==SpringBoot 启动会加载大量的自动配置类==**
2. **==看我们需要的功能有没有 SpringBoot 默认写好的自动配置类;==**
3. **==再来看这个自动配置类到底配置了哪些组件;(如果存在我们要用到的组件,就不需要再来配置了)==**
4. **==给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性。我们就可以在配置文件中指定这些属性的值;==**
日志门面(日志的抽象层) | 日志实现 |
---|---|
Log4j JUL(java.util.logging) Log4j2 Logback |
左边选一个门面(抽象层)、右边选择一个实现。
日志门面:SLF4j;
日志实现: Logback;
SpringBoot : 底层是 Spring 框架, Spring 框架默认是用 JCL;
SpringBoot 选用 SLF4j 和 logback;
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统中导入 slf4j 的 jar 和 logback 的实现 jar.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
图示:
[外链图片转存失败(img-pbEweDef-1566610817802)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564839139682.png)]
每一个日志的实现框架都有自己的配置文件。使用 slf4j 以后,配置文件还是做成日志实现框架自己本身的配置文件;
slf4j + logback : Spring(commons-logging)、 Hibernate(jboss-logging)、MyBatis、xxx
统一日志记录,即使是别的框架和我一起统一使用 sff4j 进行输出?
[外链图片转存失败(img-p6IBhkaE-1566610817803)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564839593148.png)]
如何让系统中所有的日志都统一到 slf4j;
1.将系统中其他日志框架先排除出去;
2.用中间包来替换原有的日志框架;
3.我们导入 slf4j 其他的实现;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
SpringBoot 使用它来做日志功能
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>
总结:
1.SpringBoot 底层也是使用 slf4j + logback 的方式进行日志记录。
2.SpringBoot 也把其他的日志都替换成了 slf4j;
@SuppressWarnings("rawtypes")
public abstract class LogFactory {
static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";
static LogFactory logFactory = new SLF4JLogFactory();
}
[外链图片转存失败(img-ZmSFOj3F-1566610817803)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564841250015.png)]
如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉?
Spring 框架用的是 commons-loggings;
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
SpringBoot 能自动适配所有的日志,而且底层使用 slf4j+logback 的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉;
SpringBoot 默认帮我们配置好了日志;
//记录器
Logger logger = LoggerFactory.getLogger(getClass());
@Test
public void contextLoads() {
//日志的级别;
//由低到高 trace < debug < info < warn < error.
//可以调整需要输出的级别;日志就会在这个级别以后的高级别生效。
logger.trace("这是 trace 日志...");
logger.debug("这是 debug 日志...");
//SpringBoot 默认给我们使用的是 info 日志级别,没有指定级别的就用 SpringBoot 默认规定的级别;root 级别
logger.info("这是 info 日志...");
logger.warn("这是 warn 日志");
logger.error("这是 error 日志...");
}
日志输出格式:
%d 表示日期时间
%thread 表示线程名
%-5level : 级别从左现实 5 个字符宽度
%logger{50} 表示 logger 名字最长 50 个字符,否则按照句点分割。
%msg : 日志消息
%n 是换行符
SpringBoot 修改日志的默认配置 : 配置文件中配置日志信息:
logging.level.lan=trace
#logging.path=
#当前项目下生成 springboot.log 日志,也可以指定完整的路径
#logging.file=springboot.log
#在当前磁盘的根路径下创建 spring 文件夹和里面的 log 文件夹;使用 spring.log 作为默认文件
logging.path=/spring/log
#在控制台输出日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
#指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} === %msg%n
给类路径下放上每个日志框架自己的配置文件即可;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 功能
<springProfile name="staging">
可以指定某段配置只在某个环境下生效
springProfile>
[外链图片转存失败(img-B9NN4s38-1566610817804)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564844661919.png)]
可以按照 slf4j 的日志适配图,进行相关的切换;
slf4j + log4j 的方式;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>logback-classicartifactId>
<groupId>ch.qos.logbackgroupId>
exclusion>
<exclusion>
<artifactId>log4j-over-slf4jartifactId>
<groupId>org.slf4jgroupId>
exclusion>
exclusions>
dependency>
切换为 log4j2
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-loggingartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
使用 SpringBoot ;
1. **创建 SpringBoot 应用,选中我们需要的模块;**
2. **SpringBoot 已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来;**
3. **自己编写业务代码;**
自动配置原理?
这个场景 SpringBoot 帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguratiion : 帮我们给容器中自动配置组件
xxxxProperties : 配置类来封装配置文件的内容;
@ConfigurationProperties(
prefix = "spring.resources",
ignoreUnknownFields = false
)
public class ResourceProperties{
//可以设置和静态资源有关的参数
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
}
//配置欢迎页
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
}
//配置喜欢的图标
@Configuration
@ConditionalOnProperty(
value = {"spring.mvc.favicon.enabled"},
matchIfMissing = true
)
public static class FaviconConfiguration implements ResourceLoaderAware {
private final ResourceProperties resourceProperties;
private ResourceLoader resourceLoader;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(-2147483647);
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(this.resolveFaviconLocations());
return requestHandler;
}
private List<Resource> resolveFaviconLocations() {
String[] staticLocations = WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter.getResourceLocations(this.resourceProperties.getStaticLocations());
List<Resource> locations = new ArrayList(staticLocations.length + 1);
Stream var10000 = Arrays.stream(staticLocations);
ResourceLoader var10001 = this.resourceLoader;
this.resourceLoader.getClass();
var10000.map(var10001::getResource).forEach(locations::add);
locations.add(new ClassPathResource("/"));
return Collections.unmodifiableList(locations);
}
[外链图片转存失败(img-ZqfvZ2WU-1566610817805)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564851462582.png)]
1.所有 /webjars/**,都去 classpath:/META-INF/resources/webjars/找资源;
webjars : 以 jar 包的方式引入 静态资源。 https://www.webjars.org/
在访问的时候只需要写 webjars 下面资源的名称即可
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>jqueryartifactId>
<version>3.4.1version>
dependency>
http://localhost:8080/webjars/jquery/3.4.1
2.“/**"访问当前项目的任何资源,(静态资源文件夹)
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/" : 当前项目的根目录
3.欢迎页 ;静态资源文件夹下的所有 index.html 页面,被 "/**"映射;
localhost:8080/ 找 index 页面
4.所有的 **/favicon.ico 都是在静态资源文件下找;
jsp、Velocity、FreeMarker、 Thymeleaf
[外链图片转存失败(img-L7trbe40-1566610817806)(C:\Users\laughig\Desktop\template-engine.png)]
SpringBoot 推荐的 Thymeleaf;
语法简单,功能强大;
1.引入 Thymeleaf;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
2.1.6
dependency>
切换 thymeleaf 版本
<thymeleaf.verison>3.0.11.RELEASEthymeleaf.verison>
>
<thymeleaf-layout-dialect.version>2.2.2thymeleaf-layout-dialect.version>
2.Thymeleaf 使用&语法
@ConfigurationProperties(
prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
//只要我们把 HTML 页面放在 classpath:/templates/, thymeleaf就能自动渲染
private String mode = "HTML";
private Charset encoding;
private boolean cache;
private Integer templateResolverOrder;
private String[] viewNames;
private String[] excludedViewNames;
private boolean enableSpringElCompiler;
private boolean renderHiddenMarkersBeforeCheckboxes;
private boolean enabled;
private final ThymeleafProperties.Servlet servlet;
private final ThymeleafProperties.Reactive reactive;
只要我们把 HTML 页面放在 classpath:/templates/, thymeleaf就能自动渲染
使用:
2.1.导入 thymeleaf 的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2.2.使用 thymeleaf 语法;
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>success页面title>
head>
<body>
<h1>success.html————> 成功!h1>
<div th:text="${hello}">div>
body>
html>
2.3.thymeleaf 语法规则
th:text ;改变当前元素里面的文本内容。
th : 任意 html 属性;来替换原生属性的值。
[外链图片转存失败(img-CgPYT14E-1566610817807)(C:\Users\laughig\Desktop\2018-02-04_123955.png)]
表达式?
Simple expressions(表达式语法):
Variable Expressions : ${...} : 获取变量值 ; ORGL
1.获取对象的属性、调用方法
2.使用内置的基本对象
#ctx : the context object.
#vars : the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
3.
Selection Variable Expressions : *{...} :
Message Expressions : #{...}
Link URL Expressions : @{...}
Fragment Expressions : ~{...}
Literals
Text literals : 'one text', 'Another one!', ...
Number literals : 0,34,3.0,12.3,...
Boolean literals : true,,false
Null literal : null
Literal tokens : one, sometext, main,...
Text operations :
Strinng concatenation : +
Literal substitutions : |The name is ${name}|
Arithmetic operations :
Binary operators : +,-,*,/,%
Minus sign(unary operator): -
Boolean operations :
Simple expressions(表达式语法):
Variable Expressions : ${...} : 获取变量值 ; ORGL
1.获取对象的属性、调用方法
2.使用内置的基本对象
#ctx : the context object.
#vars : the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
3.内置的一些工具对象:
#execInfo : information about the template being processed.
#messages : methods for obtaining externalized messages inside variables expressions,in the same way as they would be obtained using
#{...} syntax.
#uris : methods for escaping parts of URLs/URIs.
#conversions : methods for executing the configured conversion service(if any)
#dates : methods for java.util.Date objects : formatting, component extraction, etc.
#calendars : analogous to #dates, but for java.util.Calendar objects.
#numbers : method for formatting numberic objects.
#strings : method for String objects : contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an interation).
Selection Variable Expressions : *{...} : 选择表达式 : 和 ${} 表达式功能一样
补充:配合 th:object = "${session.user}":
Name : Sebastian.
Surname : Papper.
Nationality : Saturn.
Message Expressions : #{...} 获取国际化内容
Link URL Expressions : @{...} 定义 URL;
@{/order/process(execId=${execId},execType='FAST')}
Fragment Expressions : ~{...} : 片段引用表达式
...
Literals(字面量)
Text literals : 'one text', 'Another one!', ...
Number literals : 0,34,3.0,12.3,...
Boolean literals : true,,false
Null literal : null
Literal tokens : one, sometext, main,...
Text operations (文本操作):
Strinng concatenation : +
Literal substitutions : |The name is ${name}|
Arithmetic operations(数学运算) :
Binary operators : +,-,*,/,%
Minus sign(unary operator): -
Boolean operations(布尔运算) :
Binary operators : and, or
Boolean negation (unary operator) : ! , not
Commparsons and equality (比较运算):
Comparators : > , < m >=, <= (gt, lt, ge, le)
Equality operators : ==, != (eq, ne)
Conditional operators条件运算(三元运算符) :
If-then : (if) ? (then)
If-then-else : (if) ? (then) : (else)
Default : (value) ?: (defaultvalue)
Special tokens :
No-Operation : _3.SpringMVC 自动配置
https://docs.spring.io/spring-boot/docs/1.5.21.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc
Spring Boot 自动配置好了 SpringMVC
以下是 SpringBoot 对 SpringMVC 的默认配置:
Inclusion of ContentNegotiatingViewResolver
and BeanNameViewResolver
beans.
Support for serving static resources, including support for WebJars (see below). 静态资源文件夹路径,webjars
自动注册了 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()); //日期格式化组件
}
自己添加的格式化器转换器,我们只需要放在容器中即可。
Support for HttpMessageConverters
(see below).
HttpMessageConverters : SpringMVC 用来转换 Http 请求和响应的;User-Json;
HttpMessageConverters : 是从容器中确定;获取所有的 HttpMessageConverter;
自己给容器中添加 HttpMessageConverter ,只需要将自己的组件注册容器中(@Bean)
Automatic registration of MessageCodesResolver
(see below). 定义错误代码生成规则。
Static index.html
support. 静态首页访问
Custom Favicon
support (see below). favicon.ico
Automatic use of a ConfigurableWebBindingInitializer
bean (see below).
我们可以配置一个 ConfigurableWebBindingInitializer 来替换默认的;
初始化 WebDataBinder
请求数据 ==== JavaBean.
org.springframework.boot.autoconfigure.web : web 的所有自动场景。
If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration
class of type WebMvcConfigurerAdapter
, but without @EnableWebMvc
. If you wish to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
or ExceptionHandlerExceptionResolver
you can declare a WebMvcRegistrationsAdapter
instance providing such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
.
<mvc:view-controller path="/hello" view-name="success"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean>bean>
mvc:interceptor>
mvc:interceptors>
编写一个配置类(@Configuration), 是 WebMvcConfigurerAdapter 类型;不能标注 @EnableWebMvc
既保留了所有的自动配置,也能用我们扩展的配置;
//使用 WebMvcConfigurerAdapter 可以来扩展 SpringMVC 功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers
//浏览器发送 /lan 请求来到 success
registry.addViewController("/lan").setViewName("success");
}
}
原理:
1.WebMvcAutoConfiguration 是 SpringMVC 的自动配置类
在做其他自动配置时会导入; @Import(EnableWebMvcConfiguration.class)
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//从容器中获取所有的 WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurers> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现:将所有的 WebMvcConfigurer 相关配置都来一起调用
@Override
public void addViewController(ViewControllerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
}
}
容器中所有的 WebMvcConfigurer 都会一起使用;
我们的配置类也会被调用;
效果: SpringMVC 的自动配置和我们的扩展配置都会起作用。
SpringBoot 对 SpringMVC 的自动配置不需要了,所有的配置都是我们自己配;所有的 SpringMVC 的自动配置都失效了;
我们需要在配置类中添加 @EnableWebMvc即可.
//使用 WebMvcConfigurerAdapter 可以来扩展 SpringMVC 功能
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers
//浏览器发送 /lan 请求来到 success
registry.addViewController("/lan").setViewName("success");
}
}
原理:
为什么 @EnableWebMvc 自动配置就失效了?
1.@EnableWebMvc 的核心
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc{}
@Configuration
public class DelegatingWebMvcConfigutation extends WebMvcConfigurationSupport{}
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
//容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration{}
4.@EnableWebMvc 将 WebMvcConfigurationSupport 组件导入进来;
5.导入的 WebMvcConfigurationSupport 只是 SpringMVC 最基本的功能;
模式:
1.SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver) 将用户配置的和自己默认的组合起来;
2. 在 SpringBoot 中会有非常多的 xxxConfigurer 帮我们进行扩展配置,
直接启动项目即可或者在配置类中编写如下代码:
//所有的 WebMvcConfigurerAdapter 组件都会一起起作用
@Bean //将组件注册在容器中
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
};
return adapter;
}
[外链图片转存失败(img-UgOoprcW-1566610817808)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564917277761.png)]
[外链图片转存失败(img-Kv4pfMRb-1566610817808)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564917345102.png)]
修改引入前端样式地址。
也可以修改配置文件修改访问项目地址。(http://localhost:8080 改为 http://localhost:8080/crud)
[外链图片转存失败(img-JXdBo6h4-1566610817809)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564917408578.png)]
1.编写国际化配置文件;(SpringMVC)
2.使用 ResourceBundleMessageSource 管理国际化资源文件;(SpringMVC)
3.在页面使用 fmt:message 取出国际化内容(SpringMVC)
SpringBoot
步骤:
1.编写国际化配置文件,抽取页面需要显示的国际化消息
[外链图片转存失败(img-g9714mtw-1566610817809)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564918060832.png)]
[外链图片转存失败(img-ZiSO1dve-1566610817810)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564918281324.png)]
2.SpringBoot 自动配置好了管理国际化资源文件的组件;
[外链图片转存失败(img-MDRhlpEj-1566610817810)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564918801693.png)]
3.去页面获取国际化的值;
设置全局配置;properties 文件:修改为 utf-8,并且转换为 ascii.
[外链图片转存失败(img-1HxIFhuY-1566610817810)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564919413988.png)]
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstraptitle>
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link href="asserts/css/signin.css" th:href="@{asserts/css/signin.css}" rel="stylesheet">
head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:href="@{asserts/img/bootstrap-solid.svg}" 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 inh1>
<label class="sr-only" th:text="#{login.username}">Usernamelabel>
<input type="text" class="form-control" placeholder="Username" required="" autofocus="" th:placeholder="#{login.username}">
<label class="sr-only" th:text="#{login.password}">Passwordlabel>
<input type="password" class="form-control" placeholder="Password" required="" th:placeholder="#{login.password}">
<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 inbutton>
<p class="mt-5 mb-3 text-muted">© 2017-2018p>
<a class="btn btn-sm">中文a>
<a class="btn btn-sm">Englisha>
form>
body>
html>
效果:根据浏览器语言设置的信息切换了国际化
原理:
国际化 Locale(区域信息对象);LocaleResolver(获取区域信息对象);
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;
}
}
默认的就是根据请求头带来的区域信息获取Localer进行国际化。
4.点击链接(中文/English) 切换国际化
编写解析器:编写组件。
package lan.springboot.crud.springbootwebrestfulcrud.component;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* 可以在连接上携带区域信息
*
*/
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("l");
Locale locale = Locale.getDefault(); //系统默认的
if (!StringUtils.isEmpty(l)) {
String[] split = l.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
将组件添加到容器中。
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
[外链图片转存失败(img-j7tuHbEJ-1566610817811)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564921476875.png)]
模板引擎页面修改以后,要实时生效
1.禁用模板引擎的缓存
#禁用缓存
spring.thymeleaf.cache=false
2.页面修改完成以后 ctrl+f9 : 重新编译。
登录错误消息的提示
<p style="color:red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
防止重复提交;
[外链图片转存失败(img-cl3tBTTC-1566610817812)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564924386154.png)]
registry.addViewController("main.html").setViewName("dashboard");
登录:
@PostMapping("/user/login")
// @RequestMapping(value = "/user/login", method = RequestMethod.POST)
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map map) {
if(!StringUtils.isEmpty(username) && "123456".equals(password)) {
//登录成功,防止表单重复提交,可以重定向到主页
return "redirect:/main.html";
} else{
//登录失败
map.put("msg","用户名密码错误");
return "index";
}
}
session.setAttribute("loginUser",username);
[外链图片转存失败(img-7xSPYG8P-1566610817812)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564928361706.png)]
package lan.springboot.crud.springbootwebrestfulcrud.component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 登录检查
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
//目标方法执行之前
@Override
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.html").forward(request,response);
return false;
} else {
//已登录,执行请求
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// super.addInterceptors(registry);
//SpringBoot 已经做好了静态资源映射
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login");
}
};
[外链图片转存失败(img-eEFZmRrS-1566610817813)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564928325262.png)]
实验要求:
RestfuCRUD: CRUD 满足 Rest 风格;
URI : /资源名称/资源标识 HTTP 请求方式区分对资源 CRUD 操作
普通 CRUD(uri 来区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmp | emp—GET |
添加 | addEmp?xxx | emp—POST |
修改 | updateEmp?id=xxx&xxx=xx | emp/{id}—PUT |
删除 | deleteEmp?id=1 | emp/{id}—DELETE |
实验的请求架构:
请求 URI | 请求方式 | |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工 | emp/{id} | GET |
添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查询员工进行信息回显) | emp/{id} | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
员工列表:
thymeleaf 公共页面元素抽取:
1.抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
div>
2.引入公共片段
<div th:insert="~{footer :: copy}">div>
~{templatename :: selector} : 模板名 :: 选择器
~{templatename :: fragmentname} : 模板名 :: 片段名
3.默认效果:
insert 的功能片段在 div 标签中
如果使用 th:insert 等属性进行引入,可以不用写 ~{}
三种引入功能片段的 th 属性:
th:insert : 将公共片段整个插入到声明引入的元素中
th:replace : 将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
footer>
引入方式
<div th:insert="footer :: copy">div>
<div th:replace="footer :: copy">div>
<div th:include="footer :: copy">div>
效果
链接高亮
引入片段的时候传入参数:
[外链图片转存失败(img-999ojfps-1566610817813)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565015845897.png)]
首页
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstraptitle>
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:replace="commons/bar::topbar">div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebar(activeUri='main.html')">div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0">div>
div>
<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:200%;height:200%;left:0; top:0">div>
div>
div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboardh1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button class="btn btn-sm btn-outline-secondary">Sharebutton>
<button class="btn btn-sm btn-outline-secondary">Exportbutton>
div>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2">rect><line x1="16" y1="2" x2="16" y2="6">line><line x1="8" y1="2" x2="8" y2="6">line><line x1="3" y1="10" x2="21" y2="10">line>svg>
This week
button>
div>
div>
<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;">canvas>
main>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" >script>
<script type="text/javascript" src="asserts/js/popper.min.js" >script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" >script>
<script type="text/javascript" src="asserts/js/feather.min.js" >script>
<script>
feather.replace()
script>
<script type="text/javascript" src="asserts/js/Chart.min.js" >script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
script>
body>
html>
员工管理页面
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstraptitle>
<link href="asserts/css/bootstrap.min.css" rel="stylesheet">
<link href="asserts/css/dashboard.css" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:replace="commons/bar :: topbar">div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebar(activeUri='emps')">div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">员工添加a> h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>#th>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>departmentth>
<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} == 0 ? '男' : '女'">td>
<td th:text="${emp.department.departmentName}">td>
<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}">td>
<td>
<button class="btn btn-sm btn-primary">编辑button>
<button class="btn btn-sm btn-danger">删除button>
td>
tr>
tbody>
table>
div>
main>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js">script>
<script type="text/javascript" src="asserts/js/popper.min.js">script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js">script>
<script type="text/javascript" src="asserts/js/feather.min.js">script>
<script>
feather.replace()
script>
<script type="text/javascript" src="asserts/js/Chart.min.js">script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
script>
body>
html>
公共页面
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>公共页面title>
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]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 outa>
li>
ul>
nav>
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active"
th:class="${activeUri == 'main.html'?'nav-link active':'nav-link'}"
href="#" th:href="@{main.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
<polyline points="9 22 9 12 15 12 15 22">polyline>
svg>
Dashboard <span class="sr-only">(current)span>
a>
li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
<polyline points="13 2 13 9 20 9">polyline>
svg>
Orders
a>
li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart">
<circle cx="9" cy="21" r="1">circle>
<circle cx="20" cy="21" r="1">circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
svg>
Products
a>
li>
<li class="nav-item">
<a class="nav-link" href="#"
th:class="${activeUri == 'emps'?'nav-link active':'nav-link'}"
th:href="@{/emps}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
<circle cx="9" cy="7" r="4">circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
svg>
员工管理
a>
li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2">
<line x1="18" y1="20" x2="18" y2="10">line>
<line x1="12" y1="20" x2="12" y2="4">line>
<line x1="6" y1="20" x2="6" y2="14">line>
svg>
Reports
a>
li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers">
<polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
<polyline points="2 17 12 22 22 17">polyline>
<polyline points="2 12 12 17 22 12">polyline>
svg>
Integrations
a>
li>
ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Saved reportsspan>
<a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
a>
h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
<polyline points="14 2 14 8 20 8">polyline>
<line x1="16" y1="13" x2="8" y2="13">line>
<line x1="16" y1="17" x2="8" y2="17">line>
<polyline points="10 9 9 9 8 9">polyline>
svg>
Current month
a>
li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
<polyline points="14 2 14 8 20 8">polyline>
<line x1="16" y1="13" x2="8" y2="13">line>
<line x1="16" y1="17" x2="8" y2="17">line>
<polyline points="10 9 9 9 8 9">polyline>
svg>
Last quarter
a>
li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
<polyline points="14 2 14 8 20 8">polyline>
<line x1="16" y1="13" x2="8" y2="13">line>
<line x1="16" y1="17" x2="8" y2="17">line>
<polyline points="10 9 9 9 8 9">polyline>
svg>
Social engagement
a>
li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
<polyline points="14 2 14 8 20 8">polyline>
<line x1="16" y1="13" x2="8" y2="13">line>
<line x1="16" y1="17" x2="8" y2="17">line>
<polyline points="10 9 9 9 8 9">polyline>
svg>
Year-end sale
a>
li>
ul>
div>
nav>
body>
html>
CRUD—新增
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstraptitle>
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:replace="commons/bar::topbar">div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebar(activeUri='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">
<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
<div class="form-group">
<label>LastNamelabel>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
div>
<div class="form-group">
<label>Emaillabel>
<input name="email" type="email" class="form-control" placeholder="[email protected]" th:value="${emp!=null}?${emp.email}">
div>
<div class="form-group">
<label>Genderlabel><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${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!=null}?${emp.gender==0}">
<label class="form-check-label">女label>
div>
div>
<div class="form-group">
<label>departmentlabel>
<select class="form-control" name="department.id">
<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1option>
select>
div>
<div class="form-group">
<label>Birthlabel>
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加button>
form>
main>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}">script>
<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}">script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}">script>
<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}">script>
<script>
feather.replace()
script>
body>
html>
Controller
//来到员工添加页面
@GetMapping("/emp")
public String toAddPage(Model model) {
//来到添加页面,查询所有的部门,在页面显示
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts",departments);
return "emp/add";
}
//员工添加
//SpringMVC 自动将请求参数和入参对象的属性进行一一绑定;要求了请求参数的名字和 javaBean 入参的属性名是一致的。
@PostMapping("/emp")
public String addEmp(Employee employee) {
//来到员工列表页面
System.out.println("保存的员工信息:"+employee);
//保存员工
employeeDao.save(employee);
//redirect : 表示重定向到一个地址 /代表当前项目路径
//forward : 表示转发到一个地址
return "redirect:/emps";
}
提交的数据格式不对:生日:日期;
2017-12-12;2017/12/12;2019.08.05
日期格式化:SpringMVC 将页面提交的值需要转换为指定的类型;
2017-12-12 — Date; 类型转换,格式化。
修改页面
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstraptitle>
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:replace="commons/bar::topbar">div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebar(activeUri='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">
<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
<div class="form-group">
<label>LastNamelabel>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
div>
<div class="form-group">
<label>Emaillabel>
<input name="email" type="email" class="form-control" placeholder="[email protected]" th:value="${emp!=null}?${emp.email}">
div>
<div class="form-group">
<label>Genderlabel><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${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!=null}?${emp.gender==0}">
<label class="form-check-label">女label>
div>
div>
<div class="form-group">
<label>departmentlabel>
<select class="form-control" name="department.id">
<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1option>
select>
div>
<div class="form-group">
<label>Birthlabel>
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加button>
form>
main>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}">script>
<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}">script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}">script>
<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}">script>
<script>
feather.replace()
script>
body>
html>
Controller
//来到修改页面,查出当前员工,在页面回显
@GetMapping("/emp/{id}")
public String toEditPage(@PathVariable("id") Integer id, Model model) {
Employee employee = employeeDao.get(id);
model.addAttribute("emp",employee);
//页面显示所有的部门列表
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts",departments);
//回到修改页面(add 是一个修改添加二合一页面);
return "emp/add";
}
//员工修改
@PutMapping("/emp")
public String updateEmployee(Employee employee) {
System.out.println("修改的员工数据:"+employee);
employeeDao.save(employee);
return "redirect:/emps";
}
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstraptitle>
<link href="asserts/css/bootstrap.min.css" rel="stylesheet">
<link href="asserts/css/dashboard.css" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:replace="commons/bar :: topbar">div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebar(activeUri='emps')">div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">员工添加a> h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>#th>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>departmentth>
<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} == 0 ? '男' : '女'">td>
<td th:text="${emp.department.departmentName}">td>
<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}">td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑a>
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除button>
td>
tr>
tbody>
table>
div>
main>
<form id="deleteForm" method="post">
<input type="hidden" name="_method" value="delete"/>
form>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js">script>
<script type="text/javascript" src="asserts/js/popper.min.js">script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js">script>
<script type="text/javascript" src="asserts/js/feather.min.js">script>
<script>
feather.replace()
script>
<script type="text/javascript" src="asserts/js/Chart.min.js">script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
script>
<script type="text/javascript">
$(".deleteBtn").click(function () {
//删除当前员工的
$("#deleteForm").attr("action", $(this).attr("del_uri")).submit();
return false;
});
script>
body>
html>
Controller
//员工删除
@DeleteMapping("/emp/{id}")
public String deleteEmployee(@PathVariable("id") Integer id) {
employeeDao.delete(id);
return "redirect:/emps";
}
默认效果:
返回一个默认的错误页面
[外链图片转存失败(img-7pbNe5XZ-1566610817814)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565022942409.png)]
浏览器请求头:
[外链图片转存失败(img-KBkVim5F-1566610817815)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565024106122.png)]
其他客户端,默认响应一个 json 数据
[外链图片转存失败(img-Fxjx3ZIq-1566610817815)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565023108493.png)]
原理:
可以参照 ErrorMvcAutoConfiguration 错误处理的自动配置;
给容器中添加了以下组件:
1.DefaultErrorAttributes:
帮我们在页面共享信息
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
Throwable error = this.getError(request);
HttpStatus errorStatus = this.determineHttpStatus(error);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", this.determineMessage(error));
this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
return errorAttributes;
}
2.BasicErrorController: 处理默认 /error 请求
@Controller
@RequestMapping("$(server.error.path:${error.path/error})")
public class BasicErrorController extends AbstractErrorController{
@RequestMapping(
produces = {"text/html"} //产生 html 类型的数据
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping //产生 json 数据
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
}
3.ErrorPageCustomizer:
@Value("${error.path:/error}")
private String path = "/error"; 系统出现错误以后来到 error 请求进行处理;( web.xml 注册的错误页面规则 )
4.DefaultErrorViewResolver:
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认 SpringBoot 可以去找到一个页面? error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析这个页面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
步骤:
一旦系统出现 4xx 或者 5xx之类的错误 : ErrorPageCustomizer 就会生效(定制错误的响应规则);就会来到 /error 请求;就会被 BasicErrorController 处理;
1.响应页面;去哪个页面是由 DefaultErrorViewResolver
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
Iterator var5 = this.errorViewResolvers.iterator();
ModelAndView modelAndView;
do {
if (!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null);
return modelAndView;
}
2.
如何定制错误响应:
1.如何定制错误的页面;
1.1.有模板引擎的情况下: error/状态码;【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error 的文件夹下】,发生此状态码的错误就会来到对应的页面。
我们可以使用 4xx 和 5xx 作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)
页面能够获取的信息:
timestamp : 时间戳
status : 状态码
error : 错误提示
exception : 异常对象
message : 异常消息
errors : JSR303数据校验的错误
1.2.没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
1.3.以上都没有错误页面,就是默认来到 SpringBoot 的默认的错误提示页面。
2.如何定制错误的 Json 数据;
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public String handleException(HttpServletRequest request, Exception e) {
Map<String, Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx
/**
* Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",400);
map.put("code","user.notexist");
map.put("message",e.getMessage());
return "forward:/error";
}
}
携带定制数据:
出现错误以后,回来到 /errror 请求,会被 BasicErrorController 处理,响应出去可以获取的数据是由 getErrorAttributes 得到的(是 AbstractErrorController(ErrorController)规定的方法);
1.完全编写一个 ErrorController 的实现类【或者是编写 AbstractErrorController 的子类】,放在容器中。
2.页面上能用的数据,或者是 json 返回能用的数据都是通过 errorAttributes.getErrorAttributes 得到的。
容器中 DefaultErrorAttributes.getErrorAttributes():默认进行数据处理的。i
自定义:
//给容器中加入自定义的 ErrorAtttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("lover","lanbao");
return map;
}
}
最终的效果:响应是自适应的,可以通过定制 ErrorAttributes 改变需要返回的内容。
//给容器中加入自定义的 ErrorAtttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
//返回值的 map 就是页面和 json 能够获取的 所有字段
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("lover","lanbao");
//异常处理器携带的数据
Map<String, Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
map.put("ext",ext);
return map;
}
}
SpringBoot 默认使用Tomcat作为嵌入式的 Servlet 容器;
问题:
7.1.1. 修改和 Server 有关的配置(ServerProperties.java)
server.port=8081
server.context-path=/crud
server.tomcat.uri-encoding=UTF-8
//通用的 servlet 容器设置
server.xxx
//Tomcat 的设置
server.tomcat.xxx
7.1.2. 编写一个 EmbeddedServletContainerCustomizer: 嵌入式的 Servlet 容器的定制器;来修改 Servlet 的一些配置。
@Bean //一定要将这个定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);
}
};
}
由于 SpringBoot 默认是以jar 包的方式启动嵌入式的 Servlet 容器来启动 SpringBoot 的 web 应用,没有 web.xml 文件。
所以需要注册三大组件:
servlet 类:
package lan.springboot.crud.springbootwebrestfulcrud.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyServlet extends HttpServlet {
//处理 get 请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello MyServlet");
}
}
注册 ServletRegistrationBean:
@Configuration
public class MyServerConfig {
//注册三大组件
@Bean
public ServletRegistrationBean myServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return servletRegistrationBean;
}
}
Filter 类:
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter process ....");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
注册FilterRegistrationBean:
/**
* 注册 filter
*/
@Bean
public FilterRegistrationBean myFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","myServlet"));
return filterRegistrationBean;
}
Listener 类:
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized...web应用启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed...当前 web 项目销毁");
}
}
注册:
/**
* listener
*/
@Bean
public ServletListenerRegistrationBean myListener() {
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(new MyListener());
return servletListenerRegistrationBean;
}
SpringBoot 帮我们自动配置 SpringMVC 的时候,自动的注册了 SpringMVC 的前端 DispatcherServlet.
2.SpringBoot 能不能支持其他的 servlet 容器。
默认支持:
Tomcat(默认使用)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
引入 web 模块默认就是使用嵌入式的 tomcat
dependency>
Jetty
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcatartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<artifactId>spring-boot-starter-jettyartifactId>
<groupId>org.springframework.bootgroupId>
dependency>
Undertow
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcatartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<artifactId>spring-boot-starter-undertowartifactId>
<groupId>org.springframework.bootgroupId>
dependency>
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置?
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件
//导入了EmbeddedServletContainerCustomizerBeanPostProcessor:
//后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖;
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)
public interface EmbeddedServletContainerFactory {
//获取嵌入式的Servlet容器
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}
[外链图片转存失败(img-86ohftcf-1566610817816)(E:/%E8%B5%84%E6%96%99/%E8%A7%86%E9%A2%91%E8%B5%84%E6%96%99/Java/%E5%B0%9A%E7%A1%85%E8%B0%B7%20SpringBoot/%E6%BA%90%E7%A0%81%E3%80%81%E8%B5%84%E6%96%99%E3%80%81%E8%AF%BE%E4%BB%B6/%E6%96%87%E6%A1%A3/Spring%20Boot%20%E7%AC%94%E8%AE%B0/images/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20180302144835.png)]
2)、EmbeddedServletContainer:(嵌入式的Servlet容器)
[外链图片转存失败(img-YEYCk7AU-1566610817816)(E:/%E8%B5%84%E6%96%99/%E8%A7%86%E9%A2%91%E8%B5%84%E6%96%99/Java/%E5%B0%9A%E7%A1%85%E8%B0%B7%20SpringBoot/%E6%BA%90%E7%A0%81%E3%80%81%E8%B5%84%E6%96%99%E3%80%81%E8%AF%BE%E4%BB%B6/%E6%96%87%E6%A1%A3/Spring%20Boot%20%E7%AC%94%E8%AE%B0/images/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20180302144910.png)]
3)、以TomcatEmbeddedServletContainerFactory为例
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
//创建一个Tomcat
Tomcat tomcat = new Tomcat();
//配置Tomcat的基本环节
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器
return getTomcatEmbeddedServletContainer(tomcat);
}
4)、我们对嵌入式容器的配置修改是怎么生效?
ServerProperties、EmbeddedServletContainerCustomizer
EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置?
怎么修改的原理?
5)、容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor
//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
if (bean instanceof ConfigurableEmbeddedServletContainer) {
//
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
//从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer
//定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
ServerProperties也是定制器
步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法
###5)、嵌入式Servlet容器启动原理;
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;
获取嵌入式的Servlet容器工厂:
1)、SpringBoot应用启动运行run方法
2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
3)、refresh(context);刷新刚才创建好的ioc容器;
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
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容器
什么时候创建嵌入式的 Servlet 容器工厂?什么时候获取嵌入式的 Servlet 容器并启动 Tomcat;
获取嵌入式的 Servlet 容器工厂:
1.SpringBoot 应用启动运行 run 方法;
2.refreshContext(context);SpringBoot 刷新 IOC 容器【创建 IOC 容器对象,并初始化容器,创建容器中的们每一个组件】;如果是 web 应用创建
AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext;
3.refresh(context);刷新刚才创建好的 IOC 容器;
onRefresh(); web的 ioc 容器重写了 onRefresh 方法。
webioc 容器会创建嵌入式的 Servlet:createEmbeddedServletContainer();
获取嵌入式的 Servlet 容器工厂:
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从 Ioc 容器中获取 EmbeddedServletContainerFactory 组件;
TomcatEmbeddedServletContainerFactory 创建对象,后置处理器一个是这个对象,就获取所有的定制器来先定制 Servlet 容器的相关配置;
使用容器工厂获取嵌入式的Servlet 容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
嵌入式的 Servlet 容器创建对象并启动 Servlet 容器;
先启动嵌入式的 Servlet 容器,再将 ioc 容器中剩余没有创建出的对象获取出来。
IOC 容器启动创建嵌入式的 Servlet 容器。
嵌入式 Servlet 容器:应用打成可执行的 jar.
优点:简单、便携;
缺点:默认不支持 JSP、优化定制比较复杂(使用定制器【Serverproperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式 Servlet 容器的创建工厂
【EmbeddedServletContainerFactory】);
外置的 Servlet 容器:外面安装 Tomcat — 应用 war 包的方式打包。
步骤:
1.必须创建一个 war 项目;
2.将嵌入式的 Tomcat 指定为 provided;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
dependency>
3.必须编写一个 SpringBootServletInitializer 的子类,并调用 configure 方法
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入 SpringBoot 应用的主程序
return application.sources(SpringBootWebJspApplication.class);
}
}
4.启动服务器就可以使用;
jar 包: 执行 SpringBoot 主类和 main 方法,启动 IOC 容器,创建嵌入式的 Servlet 容器;
war 包:
启动服务器,服务器启动 SpringBoot 应用【SpringBootServletInitializer】,启动 IOC 容器;
Servlet3.0(Spring 注解版):
规则:
1.服务器启动(web 应用启动)会创建当前 web 应用里面每一个 Jar 包里面 ServletContainerInitializer 的实例。
2.ServletContainerInitializer 的实现放在 jar 包的 META-INF/services 文件夹下,有一个名为 javax.servlet.ServletContainerInitializer 的文件,内容就是
ServletContainerInitializer 实现类的全类名。
3.还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
流程:
1.启动 tomcat
org\springframework\spring-web\4.xxx.RELEASE\spring-web-4.xx.RELEASE.jar\META-INF\services\javax.servlet.ServletContainerInitializer :
Spring 的 web 模块里面有这个文件: org.springframework.web.SpringServletContainerInitializer.
SpringServletContainerInitializer 将 @HandlesTypes(WebApplicationInitializer.class) 标注的所有这个类型的类都传入到 onStartup 方法的 Set
为这些 WebApplicationInitializer 类型的类创建实例。
每一个 WebAppicationInitializer 都调用自己的 onStartup 方法。
相当于我们的 SpringBootServletInitializer 的类会被创建对象,并执行 onStartup 方法。
SpringBootServletInitializer 实例执行 onStartup 方法会 createRootApplicationContext;创建容器;
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//创建 SpringApplicationBuilder
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//调用 configure 方法 ,子类重写了这个方法,将 SpringBoot 的主程序类传入了进来。
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
return this.run(application);
}
Spring 的应用启动并且创建 IOC 容器。
启动 Servlet 容器,再启动 SpringBoot 应用。
Docker 是一个开源的应用容器引擎。
Docker 支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像。运行中的这个镜像称为容器。
docker 镜像:Docker 镜像是用于创建 Docker 容器的模板。软件打包好的镜像;放在 docker 仓库中。
docker 容器:容器是独立运行的一个或一组应用。
docker 客户端:客户端通过命令行或者其他工具使用 Docker. 连接 Docker 主机进行操作的。
docker 主机:一个物理或者虚拟的及其用于执行 Docker 守护进程和容器。安装了 Docker 程序的机器(直接安装在操作系统之上的。)
docker 仓库:Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。 Docker Hub提供了庞大的镜像集合供使用。用来保存各种打包好的软件镜像。
使用 docker 的步骤:
1.安装 Docker
2.去 docker 仓库找到这个软件对应的镜像。
3.使用 docker 运行这个镜像,这个镜像就会生成一个 docker 容器。
4.对容器的启动停止就是对软件的启动停止。
可供选择的方式:
1.Vmware
2.VirtualBox(oracle 提供的虚拟机)
小巧,免费。
[外链图片转存失败(img-B8n7cALq-1566610817817)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565200208098.png)]
用户: root, 密码:123456
SmarTTy
[外链图片转存失败(img-AVVvpYuA-1566610817818)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565201301743.png)]
service network restart
ip addr
docker 要求 Centos系统的内核版本高于 3.10.
uname -r
yum update
yum install docker
systemctl start docker
systemctl enable docker
systemctl stop docker
操作 | 命令 | 说明 |
---|---|---|
检索 | docker search 关键字 eg:docker search redis | 经常去 docker hub 上检索镜像的详细信息,如镜像的 TAG. |
拉取 | docker pull 镜像名 : tag | :tag 是可选的,tag 表示标签,多为软件的版本,默认是 latest. |
列表 | docker images | 查看所有本地镜像 |
删除 | docker rmi image-id | 删除指定的本地镜像 |
https://hub.docker.com/
例:
检索:
docker search mysql
拉取(下载):
docker pull mysql
列表
docker images
删除
docker rmi image-id
软件镜像(QQ安装程序) —— 运行镜像 —— 产生一个容器(正在运行的软件,运行的QQQ)
操作 | 命令 | 说明 |
---|---|---|
运行 | docker run --name container-name -d image-name eg:docker run --name myredis --d redis | –name:自定义荣容器名 -d:后台运行 image-name :指定镜像模板 |
列表 | docker ps(查看运行中的容器); | 加上-a; 可以查看所有容器 |
停止 | docker stop container-name/container-id | 停止当前你运行的容器 |
启动 | docker stop container-name/container-id | 启动容器 |
删除 | docker rm container-id | 删除指定容器 |
端口映射 | -p 6379:6379 eg:docker run -d -p 6379:6379 --name myredis docker.io/redis | -p : 主机端口(映射到)容器内部的端口 |
容器日志 | docker logs container-name/coontainer-id | |
更多命令 | https://docs.docker.com/engine/reference/commandline/docker/ |
步骤:
步骤:
1.搜索镜像
docker search tomcat
2.拉取镜像
docker pull tomcat
3.根据镜像启动容器
docker run --name mytomcat -d tomcat(镜像名tag);
4.查看运行中的容器(tomcat)
docker ps
5.停止运行中的容器
docker stop 容器 id
6.查看所有容器
docker ps -a
7.启动容器
docker start 容器 id
8.删除一个容器(容器需要是停止状态)
docker rm 容器 id
9.启动一个做了端口映射的 tomcat
docker run -d -p 8888:8080 tomcat
-d : 代表后台运行
-p : 将主机端口映射到容器的一个端口 主机端口:容器内部的端口
10.
步骤:
1.搜索镜像
docker search mysql
2.拉取下载镜像
docker pull mysql
3.查看镜像
docker images
4.启动(端口映射未做)
docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
4.启动(端口做了映射)
docker run -p 3306:3306 --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d msyql
高级操作
1.将主机的 /my/custom 文件夹挂载到 mysqldocker 容器的 /etc/msyql/conf.d 文件上。
docker run --name msyql03 -v /my/custom:/etc/mysql/conf.d -e MSYQL_ROOT_PASSWORD=my-secret-pw-d msyql:tag
对于数据访问层,无论是 SQL 还是 NOSQL, SpringBoot 默认采用 Spring Data 的方式进行统一处理,添加大量自动配置,屏蔽了很多设置。引入各种 xxxTemplate,xxxRepository 来简化我们对数据访问层的操作。对我们而言,只需要进行简单的设置即可。
1.引入 starter
- spring-boot-starter-jdbc
2.配置 application.yml
3.测试
4.高级配置:使用 druid 数据源
- 引入 druid
- 配置属性
5.配置 druid 数据源监控
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
application.yml
配置数据源
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/jdbc
driver-class-name: com.mysql.jdbc.Driver
效果:
默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;
数据源的相关配置都在 DataSourceProperties 里面。
自动配置原理:
org.springframework.boot.autoconfigure.jdbc:
1.参考 DataSourceConfiguration ,根据配置创建数据源,默认使用 Tomcat 连接池;可以使用 spring.datasource.type 指定自定义的数据源类型;
2.SpringBoot 可以默认支持;
org.apache.tomcat.jdbc.pool.DataSource、HikarDataSource、BasicDataSource
3.自定义数据源类型
@Configuration
@ConditionalOnMissingBean({DataSource.class})
@ConditionalOnProperty(
name = {"spring.datasource.type"}
)
static class Generic {
Generic() {
}
@Bean
public DataSource dataSource(DataSourceProperties properties) {
//使用 DataSourceBuilder 创建数据源,利用反射创建响应 type 的数据源,并且绑定相关属性
return properties.initializeDataSourceBuilder().build();
}
}
4.DataSourceInitializer: ApplicationListener;
作用:
runSchemaScripts() : 运行建表语句;
runDataScripts() : 运行插入数据的 sql 语句;
默认只需要将文件命名为:
schema-*.sql、data-*.sql
默认规则: schema.sql, schema-all.sql
可以在 application.yml 中配置:
schema:
- classpath:department.sql
指定位置
**注意点:SpringBoot 生成创建表语句 ,需要在配置文件中配置 initialization-mode: always**,还需要修改数据驱动,url:
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
schema:
- classpath:department.sql
5.操作数据源:自动配置了 JdbcTemplate 来操作数据库。
案例:
配置文件:
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
schema:
- classpath:department.sql
sql : 建表语句
/*
Navicat MySQL Data Transfer
Source Server : 本地
Source Server Version : 50528
Source Host : 127.0.0.1:3306
Source Database : restful_crud
Target Server Type : MYSQL
Target Server Version : 50528
File Encoding : 65001
Date: 2018-03-05 10:41:40
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP 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;
controller 层:jdbctemplate操作
@Controller
public class HelloController {
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/query")
public Map<String,Object> map() {
List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from department");
return list.get(0);
}
}
6.整合 Druid 数据源(配置指定数据源)
6.1.导包
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.18version>
dependency>
6.2.在配置文件中配置
type: com.alibaba.druid.pool.DruidDataSource #指定数据源类型
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
完整配置文件
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #指定数据源类型
initialization-mode: always
# schema:
# - classpath:department.sql
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
6.3.编写配置数据源类
@Configuration
public class DruidConfig {
//配置数据源属性 : 从 application.yml 中获取
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid() {
return new DruidDataSource();
}
//配置 Druid 的监控
//1.配置一个管理后台的 Servlet
@Bean
public ServletRegistrationBean statViwServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<String, String>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","localhost");
initParams.put("deny","192.168.15.21");
servletRegistrationBean.setInitParameters(initParams);
return servletRegistrationBean;
}
//2.配置一个监控的 filter
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<String, String>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
1.导包
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.0version>
dependency>
2.配置 application.yml基本配置
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #指定数据源类型
initialization-mode: always
# schema:
# - classpath:department.sql
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
3.编写数据源配置类
@Configuration
public class DruidConfig {
//配置数据源属性 : 从 application.yml 中获取
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid() {
return new DruidDataSource();
}
//配置 Druid 的监控
//1.配置一个管理后台的 Servlet
@Bean
public ServletRegistrationBean statViwServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<String, String>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","localhost");
initParams.put("deny","192.168.15.21");
servletRegistrationBean.setInitParameters(initParams);
return servletRegistrationBean;
}
//2.配置一个监控的 filter
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<String, String>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
4.给数据库建表
schema:
- classpath:sql/department.sql
- classpath:sql/employee.sql
创建之后,注释掉 schema;防止重新启动之后重新建表,数据丢失。
5.创建 JavaBean
6.MyBatis注解版
/**
* 指定这是一个操作数据库的 mapper
*/
@Mapper
public interface DepartmentMapper {
@Select("select * from department where id=#{id}")
public Department getDeptById(Integer id);
@Delete("delete from department where id=#{id}")
public int delDeptById(Integer id);
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into department(departmentName) values(#{departmentName})")
public int insertDept(Department department);
@Update("update department set department={department} where id=#{id}")
public int updateDept(Department department);
}
可以采用自定义的mybatis 的配置规则:给容器添加一个 ConfigurationCustomizer ;
@Configuration
public class MyBatisConfig {
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.setMapUnderscoreToCamelCase(true);
}
};
}
}
批量扫描 Mapper接口
//使用 MapperScan 批量扫描所有的 Mapper 接口
@MapperScan(value="lan.spring.boot.mybatis.springbootmybatis.Mapper")
@SpringBootApplication
public class SpringBootMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMybatisApplication.class, args);
}
}
7.配置版 MyBatis
配置文件:
mybatis:
config-location : classpath:mybatis/mybatis-config.xml 指定全局配置文件的位置
mapper-locations: classpath:mybatis/mapper/*.xml 指定 sql 配置文件的位置
简介:
Spring Data 项目的目的是为了简化构建基于 Spring 框架引用的数据访问技术,包括非关系数据库、Map-Reduce 框架、云数据服务等等,另外也包含对关系数据库的访问支持。
特点:
Spring Data 为我们提供使用同一的 API 来对数据访问层进行操作;这主要是 Spring Data Commons 项目来实现的。 Spring Data Commons 让我们在使用关系型或者非关系型数据访问技术时都基于 Spring 提供的统一标准,标准包含了 CRUD(创建、获取、更新、删除)、查询、排序和分页的相关操作。
统一的 Repository 接口
Repository : 统一接口
RevisionRepository>:基于乐观锁机制。
CrudRepository : 基本 CRUD 操作。
PagingAndSortingRepository : 基本 CRUD 及分页。
提供数据访问模板类:xxxTemplate: 如 : MongoTemplate、RedisTemplate 等。
JPA 与 Spring Data:
1.JpaRepository 基本功能:
编写接口继承 JpaRepository 既有 crud 及分页等基本功能
2.定义符合规范的方法命名
在接口中只需要声明符合规范的方法,即拥有对应的功能
3.@Query 自定义查询,定制查询 SQL
4.Specifications 查询(Spring Data JPA 支持 JPA2.0 的 Criteria 查询)
[外链图片转存失败(img-N0RUOP4H-1566610817819)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565405527869.png)]
步骤:
1.引入 spring-boot-starter-data-jpa
2.配置文件打印 SQL 语句
3.创建 Entity 标注 JPA 注解
4.创建 Repository 接口继承 JpaRepostory
5.测试
JAP : ORM (Object Relational Mapping) :
1.编写一个实体类(bean) 和数据库进行映射,并且配置好映射关系;
//使用 JPA 注解配置映射关系
@Entity //告诉 JPA 这是一个实体类(和数据表映射的类)
@Table(name="tal_user") //@Table 来指定与那张数据表对应;如果省略默认表名就是 user
public class User {
@Id //这是一个主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //自增主键
private Integer id;
@Column(name = "last_name", length = 50) //这是和数据表对应的一个列
private String lastName;
@Column(name = "email", length = 20)
private String email;
2.编写 dao 接口来操作实体类对应的数据表(Repository)
//继承 JpaRepository 来完成对数据库的操作
public interface UserRepository extends JpaRepository<User, Serializable> {
}
3.基本配置 (JpaProperties)
application.yml
jpa:
hibernate:
# 更新或者创建数据表结构
ddl-auto: update
# 显示 sql
show-sql: true
完整 application.yml(注意 jpa 放置位置)
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/jpa?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
jpa:
hibernate:
# 更新或者创建数据表结构
ddl-auto: update
# 控制台显示SQL
show-sql: true
controller
@RestController
public class UserController {
@Autowired
UserRepository userRepository;
@GetMapping("/user")
public User insertUser(User user) {
User user1 = userRepository.save(user);
return user1;
}
}
启动原理、运行流程、自动配置原理
重要的事件回调机制:
配置在 META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener
只需要放在 IOC 容器中
ApplicationRunner
CommandLineRunner
步骤:
1创建 SpringApplication 对象:
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.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//保存主配置类
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//判断是否是 web 应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从类路径下找到 META-INF/spring.factories 配置的所有 ApplicationContextInitalizer;然后保存起来。
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从类路径下找到 META-INF/spring.factories 配置的所有 ApplicationListener
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有 main 方法的主配置类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
2.运行 run 方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
//获取SpringApplicationRunListeners;从类路径下 META-INF/spring.factories 中获取的。
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//回调所有的获取 SpringApplicationRunListener.starting() 方法。
listeners.starting();
Collection exceptionReporters;
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
//创建环境完成后,回调SpringApplicationRunListener.environmentPrepared() 方法;表示环境准备完成
//打印 banner
Banner printedBanner = this.printBanner(environment);
//创建 ApplicationContext ;决定创建 web 的 ioc 还是普通的 ioc
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//准备上下文环境;将 environment 保存到 ioc 中;而且 applyInitializers();
//applyInitializers() : 回调之前保存的所有的 ApplicationContextInitializer 的方法。
//回调所有的 SpringApplicationRunListener 的 contextPrepared() 方法。
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//prepareContext 运行完成以后回调所有的 SpringApplicationRunListener的contextLoaded() 方法。
//刷新容器:ioc 容器初始化;
//扫描,创建,加载所有组件的地方
this.refreshContext(context);
//从 ioc 容器中获取所有的 ApplicationRunner 和 CommanLineRunner 进行回调
//ApplicationRunner 先回调, CommandLineRunner 再回调。
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//所有的 SpringApplicationRunnerListener 回调 started() 方法。
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
//整个 SpringBoot 应用启动完成以后返回启动的 IOC 容器。
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
配置在 META-INF/spring.factories (需要配置)
ApplicationContextInitializer
SpringApplicationRunListener
只需要放在 IOC 容器中 (容器中的需要添加注解 @Component)
ApplicationRunner
CommandLineRunner
1.场景依赖
2.编写自动配置
@Configuration //指定这个类是一个配置类
@ConditionalOnXxx //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件
@ConfigurationProperties //结合相关的xxxProperties 类绑定相关的配置
@EnableConfigurationProperties //让 xxxProperties 生效并且加入到容器中
自动配置类要能加载
将标注 @Configuration 的自动配置类,放在 classpath 下 META-INF/spring.factories 中。
3.模式:
自动装配使用配置类(@Configuration) 结合 Spring4 提供的条件判断注解 @Conditional 及 Spring Boot 的派生注解如 @ConditionOnClass 完成;
将标注 @Configuration 的自动配置类,放在 classpath 下 META-INF/spring.factories 中。
启动器模块是一个空 jar 文件,仅提供辅助性依赖管理,这些依赖可能用户自动装配或者其他类库。
命名规范:
官方命名空间:
前缀: spring-boot-starter-
模式: spring-boot-starter-模块名
举例: spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc
自定义命名空间
后缀: -spring-boot-starter
模式: 模块-spring-boot-starter
举例: mybatis-spring-boot-starter
SpringBoot 高级
Java Caching 定义了5个核心接口,分别是 CachingProvider,CacheManager,Cache,Entry 和 Expiry.
CachingProvider 定义了创建、配置、获取、管理和控制多个 CacheManager.一个应用可以在运行期间访问多个 CachingProvider.
CacheManager 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache,这些 Cache 存在于 CacheManager 的上下文中。一个 CacheManager 仅被一个 CachingProvider 所拥有。
Cache 是一个类似 Map 的数据结构并临时存储以 key 为索引的值。一个 Cache 仅被一个 CacheManager 所拥有的。
Entry 是一个存储在 Cache 中的 key-value 对。
Expiry 每一个存储在 Cache 中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过 ExpiryPolicy 设置。
[外链图片转存失败(img-cej0HNRA-1566610817819)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565793079616.png)]
使用 JSR107 需要导入依赖:
<dependency>
<groupId>javax.cachegroupId>
<artifactId>cache-apiartifactId>
dependency>
Spring 从3.1 开始定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来同一不同的缓存技术;
并支持使用 JCache(JSR-107) 注解来简化我们开发;
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合。
Cache 接口下 Spring 提供了各种 xxxCache 的实现;如 RedisCache, EhCacheCache , ConcurrentMapCache 等
每次调用需要和缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。
下次调用直接从缓存中获取。
使用 Spring 缓存抽象时我们需要关注以下两点:
1.确定方法需要被缓存以及他们的缓存策略
2.从缓存中读取之前缓存存储的数据。
几个重要概念&缓存注解
Cache | 缓存接口,定义缓存操作。实现有:RedisCache,EhCacheCache,ConcurrentMapCache 等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时 key 生成策略 |
serialize | 缓存数据时 value 序列化策略 |
整合缓存:
步骤:
1.开启基于注解的缓存 @EnableCaching
@MapperScan("com.spring.boot.cache.mapper")
@SpringBootApplication
@EnableCaching //开启基于注解的缓存
public class SpringbootCacheApplication {
2.标注缓存注解即可。
CacheManager 管理多个 Cache 组件的,对缓存的真正 CRUD 操作在 Cache 组件中,每一个缓存组件有自己唯一一个名字;
@Cacheable :
属性:
cacheNames/value : 指定缓存的名字;将方法的犯规结果放在哪个缓存中,是数组的方式,可以指定多个缓存。
key: 缓存数据使用的 key.可以用它来指定。默认是使用方法的参数。 可以编写 SpEL. #id,参数 id 的值 #a0 #p0 #root.args[0]
keyGenerator : key 的生成器;可以自己指定 key 的生成器的组件 id
key/keyGenerator : 二选一使用、
cacheManager : 指定缓存管理器;或者指定 cacheResolver 缓存解析器。
condition : 指定符合条件的情况下才缓存;
unless: 否定缓存;当 unless 指定的条件为 true,方法的返回值就不会被缓存。可以获取到结果进行判断。 unless=" #result == null "
sync : 是否使用异步
@Cacheable(cacheNames = {"emp"})
public Employee getEmp(Integer id) {
@CachePut : 既调用方法,又更新缓存数据;修改了数据库的某个数据,同时更新缓存;
运行时机:
1.先调用目标方法
2.将目标方法的结果缓存起来
结果可用 result 取出。
@CacheEvict :缓存清除
key : 指定要清除的数据
allEntries = true : 表示清除缓存中的所有数据
beforeInvocation : 缓存的清除是否在方法之前执行(默认值为 false,表示在方法之后执行);如果出现异常缓存就不会清除
beforeInvocation = true : 代表清除缓存操作是在方法运行之前执,无论方法是否出现异常,缓存都会清除。
@Caching & @CacheConfig
@Caching 定义复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(value="emp",key="#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key= "#result.email")
}
)
@CacheConfig : 抽取缓存的公共配置
注解在类上。
[外链图片转存失败(img-3aZsJ4k6-1566610817820)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565796202918.png)]
#开启驼峰命名匹配规则
mybatis:
configuration:
map-underscore-to-camel-case: true
#打印日志
logging:
level:
com.spring.boot.cache.mapper: debug
缓存原理
缓存原理:
1.自动配置类: CacheAutoConfiguration
2.缓存的配置类:
org.springframework.boot.autoconfigure.cache.GenericCacheConfigutation
org.springframework.boot.autoconfigure.cache.JCacheCacheConfigutation
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfigutation
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfigutation
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfigutation
org.springframework.boot.autoconfigure.cache.RedisCacheConfigutation
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfigutation
org.springframework.boot.autoconfigure.cache.GuavaCacheConfigutation
org.springframework.boot.autoconfigure.cache.SimpleCacheConfigutation
org.springframework.boot.autoconfigure.cache.NoOpCacheConfigutation
3. 哪个配置类默认生效 : SimpleCacheConfigutation
4. SimpleCacheConfiguration 给容器中注册了一个 CacheManager:ConcurrentMapCacheManager
5. 可以获取和创建 ConcurrentMapCache 类型的缓存组件;它的作用是将数据保存在 ConcurrentMap 中;
运行流程:
@Cacheable :
1.方法运行之前,先去查询 Cache(查询组件),按照 cacheNames 指定的名字获取;(CacheManager 先获取相应的缓存),第一次获取缓存会自动创建,如果没有Cache 组件,会先自
动创建。
2.去 Cache 中查找缓存的内存,使用一个 key,默认就是方法的参数。
key 是按照某种策略生成的;默认是使用 keyGenerator 生成的,默认使用 SimpleKeyGenerator 生成 key。
SimpleKeyGenerator 生成 key 的默认策略:
如果没有参数:key = new SimpleKey();
如果有一个参数: key = 参数的值
如果有多个参数: key = new SimpleKey(params);
3.没有查找缓存,就调用目标方法;
4.将目标方法返回的结果,就放到缓存中。
@Cacheable 标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为 key 去查询缓存,如果没有就运行方法并将结果放入缓存;
以后再来调用就可以直接使用缓存中的数据;
核心:
1.使用 CacheManager【ConcurrentMapCacheManager】 按照名字得到 Cache 【ConcurrentMapCache】 组件
2.key 使用 keyGenerator 生成的,默认是 SimpleKeyGenerator
#项目启动时打开配置报告
debug:
true
默认使用的缓存是 ConcurrentMapCacheManager == ConcurrentMapCache 组件 : 将数据保存在 ConcurrentMap
开发中使用的缓存中间件:redis, memcached, ehcache.
1.安装 Redis.使用 docker. Linux 环境中安装 redis , docker pull registry.docker-cn.com/libray/redis
2.引入 redis 的 starter.
<denpendency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
denpendency>
3.配置redis
spring.redis.host=主机地址ip
Redis 常见的五大数据类型:
String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
SpringBoot RedisTemplate 操作 Redis:
stringRedisTemplate.opsForValue()[String(字符串)]/redisTemplate
stringRedisTemplate.opsForList()[List(列表)]/redisTemplate
stringRedisTemplate.opsForSet()[Set(集合)]/redisTemplate
stringRedisTemplate.opsForHash()[Set(散列)]/redisTemplate
stringRedisTemplate.opsForSet()[Set(有序集合)]/redisTemplate
4.测试缓存
缓存原理:
CacheManager === Cache 缓存组件来实际给缓存中存取数据
1.引入 redis 的 starter ,容器中保存的是 RedisCacheManager
2.RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件; RedisCache 通过操作 redis 缓存数据。
3.默认保存数据 k-v 都是 Object; 利用序列化保存;如何保存为 json.
1.引入了 redis 的 starter, cacheManager 变为 RedisCacheManager;
2.默认创建的 RedisCacheManager 操作 Redis 的时候,使用的 RedisTemplate
自定义 CacheManager:
[外链图片转存失败(img-IKhuxLjy-1566610817820)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565884008014.png)]
[外链图片转存失败(img-qsCeAIBZ-1566610817821)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565884734941.png)]
将某个缓存器作为默认的: @Primary
1.大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力
2.消息服务中两个重要概念:
消息代理(message broker) 和目的地(destination)
当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。
3.消息队列主要有两种形式的目的地
1.队列(queue) : 点对点消息通信(point-to-point)
2.主题(topic) : 发布(publish)/订阅(subsrcibe) 消息通信
4.点对点式:
1.消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列
2.消息只有唯一的发送者和接受者,但并不是说只能有一个接收者。
(唯一的接受者并不代表只有一个接收者,接收者可以是多个,但是只有一个接收者可以收到消息,一旦有人收到消息,消息就会销毁)
5.发布订阅式:
1.发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达的同时接收到信息。
6.JMS(Java Message Service) : JAVA 消息服务
基于 JVM 消息代理的规范。ActiveMQ、HornetMQ 是 JMS 实现。
7.AMQP (Advanced Message Queuing Protocol)
1.高级消息队列协议,也是一个消息代理的规范,兼容 JMS
2.RabbitMQ 是 AMQP 的实现
8.Spring 支持
1.spring-jms 提供了对 JMS 的支持
2.spring-rabbit 提供了对 AMQP 的支持
3.需要 ConnectionFactory 的实现来连接消息代理
4.提供 JmsTemplate、RabbitTemplate 来发送消息
5.@JmsListener(JMS)、@RabbitListener(AMQP) 注解在方法上监听消息代理发布的消息
6.@EnableJms、@EnableRabbit 开启支持
9.Spring boot 自动配置
1.JmsAutoConfiguration
2.RabbitAutoConfiguration
[外链图片转存失败(img-xIYf3XdU-1566610817821)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565885175101.png)]
[外链图片转存失败(img-AiS8YxtA-1566610817822)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565885253543.png)]
[外链图片转存失败(img-216tLM20-1566610817822)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565885267564.png)]
[外链图片转存失败(img-ujwL03CN-1566610817823)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565886210549.png)]
RabbitMQ 是一个由 erlang 开发的AMQP(Advanced Message Queue Protocol)的开源实现。
核心概念:
Message :
消息,消息是不具名的,它有消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先
权)、delivery-mode(指出该消息可能需要持久性存储)等。
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Exchange 有4种类型: direct(默认)【点对点】,fanout【消息订阅】, topic【消息订阅】, 和 headers【消息订阅】, 不同类型的 exchange 转发消息的策略有所不同。
Queue
消息队列,用于保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在 队列里面,等待消费者连接到这个队列将其取走。
Binding
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于理由键将交换器和消息队列连接起来的路有规则,所以可以将交换器理解成一个由绑定构成的路由表。
Exchange 和 Queue 的绑定可以是多对多的关系。
Connection
网络连接
Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的 TCP 连接内的虚拟连接, AMQP 命令都是通过信道发出去的,
不管是发布消息、订阅队列还是接受消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所
以引入了信道的概念,以复用一条 TCP 连接。
Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上是一个 mini 版的
RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /.
Broker
表示消息队列服务器实体。
[外链图片转存失败(img-R0hhobNK-1566610817823)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565887730719.png)]
AMQP 中的消息路由
AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差别,AMQP 中增加了 Exchange 和 Binding 的角色。生产者把消息发布到 Exchange 上,
消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到哪个队列。
[外链图片转存失败(img-ZYNoSTRx-1566610817824)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565888209787.png)]
Exchange 类型:
Exchange 分发消息时根据类型的不同分发策略有区别,目前共有四种类型:direct、fanout、topic、headers。headers 匹配 AMQP 消息的 header 而不是路由键,
headers 交换器和 direct 交换器完全一致,但性能差很多,几乎用不到了。
direct :
消息中的路由键如果和 Binding 中的 binding key 一致,交换器就将消息发送到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换器
要求路由键为 "dog",则只转发 routing key 标记为 "dog"的消息,不会转发"dog.puppy",也不会转发"dog.guard" 等等。它是完全匹配、单播的模式。
[外链图片转存失败(img-M1HCi0Q5-1566610817824)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565888673808.png)]
fanout:
每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到
与该交换器绑定的所有队列上。像子网广播,每台子网内的主机都获得了一份复制的消息。 fanout 类型转发消息时最快的。
[外链图片转存失败(img-NEynbAZu-1566610817825)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565889236504.png)]
Topic:
topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定建的字符串切分成单词,这些单词
之间用点隔开。它同样也会识别两个通配符:符号"#"和符号"*".#匹配 0 个或多个单词,* 匹配一个单词。
[外链图片转存失败(img-0R5obkwl-1566610817825)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565889373598.png)]
1.引入 spring-boot-starter-amqp
2.application.yml 配置
3.测试 RabbitMQ
1.AmqpAdmin : 管理组件
2.RabbitTemplate : 消息发送处理组件
利用 docker 安装 rabbitmq
: docker pull registy.docker-cn.com/library/rabbitmq:3-management
自动配置:
1.RabbitAutoConfigutation
2.有自动配置了配置工厂 ConnectionFactory ;
3.RabbitProperties 封装了 RabbitMQ 的配置
4.RabbitTemplate : 给 RabbitMQ 发送和接受消息
5.AdqpAdmin : RabbitMQ 系统管理功能组件
6.@EnableRabbit + @RabbitListener 监听消息队列的内容
application.properties
spring.rabbitmq.host=ip
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.port=5672
#spring.rabbitmq.virtual-host=
测试
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 1.单播:(点对点)
*/
@Test
public void contextLoads() {
//Mwssage 需要自己构造一个;定义消息体内容和消息头
//rabbitTemplate.send(exchange,routeKey,message);
//object 默认当成消息体,只需要传入要发送的对象,自动序列化发送给 rabbitmq
// rabbitTemplate.convertAndSend(exchange, routeKey,object);
Map<String, Object> map = new HashMap<String, Object>();
map.put("msg","这是第一个消息");
map.put("data", Arrays.asList("helloworld",123,true));
//对象被默认序列化后发送出去
rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",new Book("西游记","吴承恩"));
}
/**
* 接受数据,如何将数据自动的转为 json 发送出去
*/
@Test
public void receive() {
Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
System.out.println(o.getClass());
System.out.println(o);
}
/**
* 广播
*/
@Test
public void sendMsg() {
rabbitTemplate.convertAndSend("exchange.fanout", "", new Book("Java 编程","黎彭飞"));
}
自定义发送的消息格式(json)
@Configuration
public class MyAMQPConfig {
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
开启基于注解的 RabbitMQ
/**
* 自动配置:
* 1.RabbitAutoConfigutation
* 2.有自动配置了配置工厂 ConnectionFactory ;
* 3.RabbitProperties 封装了 RabbitMQ 的配置
* 4.RabbitTemplate : 给 RabbitMQ 发送和接受消息
* 5.AdqpAdmin : RabbitMQ 系统管理功能组件
* 6.@EnableRabbit + @RabbitListener 监听消息队列的内容
*/
@EnableRabbit //开启基于注解的 RabbitMQ
@SpringBootApplication
public class SpringBootRabbitmqApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRabbitmqApplication.class, args);
}
}
监听
@Service
public class BookService {
@RabbitListener(queues = "atguigu.news")
public void receive(Book book) {
System.out.println("收到消息:"+book);
}
@RabbitListener(queues = "atguigu")
public void receive02(Message message) {
System.out.println(message.getBody());
System.out.println(message.getMessageProperties());
}
}
@Autowired
AmqpAdmin amqpAdmin;
@Test
public void createExchange() {
//创建交换器
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
System.out.println("创建交换器完成");
//创建队列
amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));
//创建绑定
amqpAdmin.declareBinding(new Binding("amqpadmin.queue",
Binding.DestinationType.QUEUE,"amqpadmin.exchange","exchange.haha"
,null));
}
我们的应用经常需要添加检索功能,开源的 ElasticSearch 是目前全文搜索引擎的首选。他可以快速的存储、搜索和分析海量数据。
SpringBoot 通过整合 Spring Data ElasticSearch 为我们提供了非常便捷的检索功能支持;
Elasticsearch 是一个分布式搜索服务,提供 Restful API,底层基于 Lucene, 采用多 shard(分片) 的方式保证数据安全,
并且提供自动 resharding 的功能,github等大型的站点也是采用了 ElasticSearch 作为其搜索服务,
利用 docker 安装 ElasticSearch
docker putll registry.docker-cn.com/library/elasticsearch
运行 elasticsearch
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 - 9300:9300 --name ES01 镜像 id
中文学习文档: https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
1.以员工文档的形势存储为例:一个文档代表一个员工数据。存储数据到 ElasticSearch 的行为叫做索引,但在索引一个文档之前,需要确定将文档存储在哪里。
2.一个 ElasticSearch 集群可以包含多个索引,相应的每个索引可以包含多个类型。这些不同的类型存储着多个文档,每个文档又有多个属性。
3.类似关系:
索引—数据库
类型—表
文档—表中的记录
属性—列
[外链图片转存失败(img-hY2uu2d2-1566610817826)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566009928498.png)]
步骤
1.引入 spring-boot-starter-data-elasticsearch
2.安装 Spring Data 对应版本的 ElasticSearch
3.application.yml 配置
4.Spring Boot 自动配置的
ElasticsearchRepository、ElasticsearchTemplate、Client
5.测试 ElasticSearch
elasticsearch
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
Jest
<dependency>
<groupId>io.searchboxgroupId>
<artifactId>jestartifactId>
<version>5.3.3version>
dependency>
/**
* SpringBoot 默认支持两种技术和 ES 交互
* 1.Jest(默认不生效)
* 需要导入 jest 的工具包(io.searchbox.client.JestClient)
* 2.SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
如果版本不适配:
1.升级 SpringBoot 版本
2.安装对应版本的 ES
* 1.Client 节点信息 clusterNodes clusterName
* 2.ElasticsearchTemplate 操作 ES
* 3.编写一个 ElasticsearchRepository 的子接口来操作 ES。
*/
@SpringBootApplication
public class SpringBootElasticsearchApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootElasticsearchApplication.class, args);
}
}
测试 jest 方式:
spring.elasticsearch.jest.uris=http://127.0.0.1:9200
在 pom.xml 文件中加入依赖
<dependency>
<groupId>io.searchboxgroupId>
<artifactId>jestartifactId>
<version>5.3.3version>
dependency>
测试 Jest 方式:
spring.elasticsearch.jest.uris=http://127.0.0.1:9200
测试类:
@Autowired
JestClient jestClient;
@Test
public void contextLoads() {
//1.给 ES 中索引一个文档;
Article article = new Article();
article.setId(1);
article.setTitle("好消息");
article.setAuthor("张三");
article.setContent("hello world");
//构建一个索引功能
Index index = new Index.Builder(article).index("atguigu").type("news").build();
try {
jestClient.execute(index);
}catch (Exception e) {
}
}
/**
* 测试搜索
*/
@Test
public void search() {
//查询表达式
String json = "{\n" +
" \"query\" : {\n" +
" \"match\" : {\n" +
" \"content\" : \"hello\"\n" +
" }\n" +
" }\n" +
" }" ;
//构建搜索功能
Search search = new Search.Builder(json).addIndex("atguigu").addType("news").build();
//执行
try {
SearchResult result = jestClient.execute(search);
System.out.println(result.getJsonString());
} catch (Exception e) {
e.printStackTrace();
}
}
pom.xml
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
配置文件
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
如果版本不适配:
1.升级 SpringBoot 版本
2.安装对应版本的 ES
SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
如果版本不适配:
1.升级 SpringBoot 版本
2.安装对应版本的 ES
1.Client 节点信息 clusterNodes clusterName
2.ElasticsearchTemplate 操作 ES
3.编写一个 ElasticsearchRepository 的子接口来操作 ES。
两种方法:
1.编写一个 ElasticsearchRepository
编写 repository
public interface BookRepository extends ElasticsearchCrudRepository<Book, Integer> {
//参照 https://docs.spring.io/spring-data/elasticsearch/docs/3.0.6.RELEASE/reference/html/
public List<Book> findByBookNameLike(String bookName);
}
测试
@Autowired
BookRepository bookRepository;
@Test
public void test02() {
for (Book book : bookRepository.findByBookNameLike("游")) {
System.out.println(book);
}
;
}
/**
* SpringBoot 默认支持两种技术和 ES 交互
* 1.Jest(默认不生效)
* 需要导入 jest 的工具包(io.searchbox.client.JestClient)
* 2.SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
* 版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
* 如果版本不适配:
* 1.升级 SpringBoot 版本
* 2.安装对应版本的 ES
* 1.Client 节点信息 clusterNodes clusterName
* 2.ElasticsearchTemplate 操作 ES
* 3.编写一个 ElasticsearchRepository 的子接口来操作 ES。
* 两种方法:
* 1.编写一个 ElasticsearchRepository
*/
@EnableAsync :开启异步方法
@EnableAsync //开启异步注解
@SpringBootApplication
public class SpringBootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTaskApplication.class, args);
}
}
@Async : 标注异步方法
@Service
public class AsyncService {
//告诉 Spring 这是一个异步方法
@Async
public void hello() {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("处理数据中...");
}
}
controller 层简单调用
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/hello")
public String hello() {
asyncService.hello();
return "success";
}
}
项目开发中经常需要执行一些定时任务,比如需要每天凌晨时候,分析一次前一天的日志信息。 Spring 为我们提供了异步执行任务调度的方式,提供 TaskExecutor、TashScheduler 接口。
两个注解 : @EnableScheduling、@Scheduled
cron 表达式
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒 | 0-59 | ,- * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 1-12 | , - * / |
星期 | 0-7或SUN-SAT,0,7是SUN | , - * ? / L C # |
特殊字符 | 代表含义 |
---|---|
, | 枚举 |
- | 区间 |
* | 任意 |
/ | 步长 |
? | 日/星期冲突匹配 |
L | 最后 |
W | 工作日 |
C | 和 calendar 联系后计算过的值 |
# | 星期,4#2,第2个星期三 |
@EnableScheduling : 开启基于注解的定时任务
@EnableScheduling //开启基于注解的定时任务
@EnableAsync //开启异步注解
@SpringBootApplication
public class SpringBootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTaskApplication.class, args);
}
}
@Scheduled : 标注定时方法
@Service
public class ScheduledService {
//标注一个定时任务
/**
* second,minute,hour,day of month(日),month,day of week
* 0 * * * MON-FRI
*/
@Scheduled(cron = "0 * * * * MON-SAT")
public void hello() {
System.out.println("hello ...");
}
}
【0 0/5 14,18 * * ?】 每天14点整,和18点整,每隔5分钟执行一次
【0 15 10 ? * 1-6】 每个月的周一至周六 10:15分执行一次
【0 0 2 ? * 6L】 每个月的最后一个周六凌晨2点执行一次
【0 0 2 LW * ?】每个月的最后应工作日凌晨2点执行一次
【0 0 2-4 ? * 1#1】每个月的第一个周一凌晨2点到4点期间,每隔整点都执行一次
[外链图片转存失败(img-4fkc23xh-1566610817826)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566025160508.png)]
1.邮件发送需要引入 spring-boot-starter-mail
2.Spring Boot 自动配置 MailSenderAutoConfiguration
3.定义 MailProperties 内容,配置在 application.yml 中
4.自动装配 JavaMailSender
5.测试邮件发送
1.引入 spring-boot-starter-mail
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
[外链图片转存失败(img-Awu9Tf3H-1566610817827)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566025823995.png)]
[email protected]
spring.mail.password=授权码
spring.mail.host=smtp.qq.com
spring.mail.properties.mail.stmp.ssl.enable=true
测试
@Autowired
JavaMailSenderImpl javaMailSender;
@Test
public void contextLoads() {
SimpleMailMessage message = new SimpleMailMessage();
//邮件设置
message.setSubject("通知");
message.setText("测试内容");
message.setTo("[email protected]");
message.setFrom("[email protected]");
javaMailSender.send(message);
}
@Test
public void test02() throws Exception {
//1.创建一个复杂的消息邮件
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
//邮件设置
mimeMessageHelper.setSubject("通知");
mimeMessageHelper.setText("测试内容");
mimeMessageHelper.setTo("[email protected]");
mimeMessageHelper.setFrom("[email protected]");
//上传附件
mimeMessageHelper.addAttachment("1.jpg",new File("D:\\..."));
mimeMessageHelper.addAttachment("2.jpg",new File("D:\\..."));
javaMailSender.send(mimeMessage);
}
Spring Security 是针对 Spring 项目的安全框架,也是 Spring Boot 底层安全模块默认的技术选型。它可以实现强大的 web 安全控制。对于安全控制,
我们仅仅需要引入 Spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理。
几个类
WebSecurityConfigurerAdapter : 自定义 Security 策略
AuthenicationManagerBuilder : 自定义认证策略
@EnableWebSecurity : 开启 WebSecurity 模式
应用程序的两个主要区域是"认证"和"授权"(或者访问控制)。
这两个主要区域是 Spring Security 的两个目标。
"认证"(Authentication) : 是建立一个它声明的主体的过程(一个"主体"一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)
"授权"(Authorization) : 指明确一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的话店,主体的身份已经有认证过程建立。
这个概是通用的,而不是只在 Spring Security 中。
/**
* 1、引入SpringSecurity;
* 2、编写SpringSecurity的配置类;
* @EnableWebSecurity extends WebSecurityConfigurerAdapter
* 3、控制请求的访问权限:
* configure(HttpSecurity http) {
* http.authorizeRequests().antMatchers("/").permitAll()
* .antMatchers("/level1/**").hasRole("VIP1")
* }
* 4、定义认证规则:
* configure(AuthenticationManagerBuilder auth){
* auth.inMemoryAuthentication()
* .withUser("zhangsan").password("123456").roles("VIP1","VIP2")
* }
* 5、开启自动配置的登陆功能:
* configure(HttpSecurity http){
* http.formLogin();
* }
* 6、注销:http.logout();
* 7、记住我:Remeberme();
*/
@SpringBootApplication
public class Springboot05SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot05SecurityApplication.class, args);
}
}
@Controller
public class KungfuController {
private final String PREFIX = "pages/";
/**
* 欢迎页
* @return
*/
@GetMapping("/")
public String index() {
return "welcome";
}
/**
* 登陆页
* @return
*/
@GetMapping("/userlogin")
public String loginPage() {
return PREFIX+"login";
}
/**
* level1页面映射
* @param path
* @return
*/
@GetMapping("/level1/{path}")
public String level1(@PathVariable("path")String path) {
return PREFIX+"level1/"+path;
}
/**
* level2页面映射
* @param path
* @return
*/
@GetMapping("/level2/{path}")
public String level2(@PathVariable("path")String path) {
return PREFIX+"level2/"+path;
}
/**
* level3页面映射
* @param path
* @return
*/
@GetMapping("/level3/{path}")
public String level3(@PathVariable("path")String path) {
return PREFIX+"level3/"+path;
}
}
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
//定制请求的授权规则
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
//开启自动配置的登陆功能,效果,如果没有登陆,没有权限就会来到登陆页面
http.formLogin().usernameParameter("user").passwordParameter("pwd")
.loginPage("/userlogin");
//1、/login来到登陆页
//2、重定向到/login?error表示登陆失败
//3、更多详细规定
//4、默认post形式的 /login代表处理登陆
//5、一但定制loginPage;那么 loginPage的post请求就是登陆
//开启自动配置的注销功能。
http.logout().logoutSuccessUrl("/");//注销成功以后来到首页
//1、访问 /logout 表示用户注销,清空session
//2、注销成功会返回 /login?logout 页面;
//开启记住我功能
http.rememberMe().rememberMeParameter("remeber");
//登陆成功以后,将cookie发给浏览器保存,以后访问页面带上这个cookie,只要通过检查就可以免登录
//点击注销会删除cookie
}
//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("VIP1","VIP2")
.and()
.withUser("lisi").password("123456").roles("VIP2","VIP3")
.and()
.withUser("wangwu").password("123456").roles("VIP1","VIP3");
}
}
在分布式系统中,国内常用 zookeeper + dubbo 组合,而 Spring Boot 推荐使用全栈的 Spring , Spring Boot + Spring Cloud .
分布式系统;
[外链图片转存失败(img-TLFXqBSH-1566610817827)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566031783364.png)]
Zookeeper:
Zookeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式提供一致性服务的软件,提供的功能包括:
配置维护、域名服务、分布式同步、组服务等。
Dubbo:
Dubbo 是 Alibaba 开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者
最大限度的松耦合)。从服务模型的角度来看,Dubbo 采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,
所以基于这一点可以抽象出服务提供方和服务消费方两个角色。
[外链图片转存失败(img-O2g15SeD-1566610817828)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566032198406.png)]
Spring Cloud
Spring Cloud 是一个分布式的整体解决方案。Spring Cloud 为开发者提供了 在分布式系统(配置管理,服务发现,熔断,路由,
微代理,控制总线,一次性 token, 全局锁,leader 选举,分布式 session, 集群状态)中快速构建的工具,使用 Spring Cloud
的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。
Spring Cloud 分布式开发五大常用组件
服务实现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config
1.配置 Eureka 信息
2.@EnableEurekaServer : 启动注册中心
1.配置 Eureka 信息
server:
port: 8761
euraka:
instance:
hostname: eureka-server # eureka 实例的主机名
client:
register-with-eureka: false # 不将自己注册到 eureka 上
fetch-registry: false # 不从 eureka 上来获取服务的注册信息
service-url:
defaultZone: httpL//localhost:8761/eureka/ #注册中心地址
2.启动注册中心
/**
* 注册中心
* 1.配置 Eureka 信息
* 2. @EnableEurekaServer : 启动注册中心
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
1.编写服务以及访问
2.配置服务
1.编写服务
@Service
public class TicketService {
public String getTicket() {
System.out.println("8082...");
return "《厉害了,我的国》";
}
}
1.服务访问
@RestController
public class TicketController {
@Autowired
TicketService ticketService;
@GetMapping("/ticket")
public String getTicket() {
return ticketService.getTicket();
}
}
2.配置服务
server:
port: 8082
spring:
application:
name: provider-ticket
euraka:
instance:
prefer-ip-address: true # 注册服务的时候使用服务的 ip 地址
client:
service-url:
defaultZone: httpL//localhost:8761/eureka/ #注册中心地址
1.编写访问服务
2.配置服务
1.编写服务访问
@RestController
public class UserController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/buy")
public String buyTicket(String name) {
String s = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
return name+"购买了"+s;
}
}
@EnableDiscoveryClient :开启服务发现功能
@EnableDiscoveryClient //开启发现服务功能
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@LoadBalanced //使用负载均衡机制
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
配置服务
spring:
application:
name: consumer-user
server:
port: 8181
euraka:
instance:
prefer-ip-address: true # 注册服务的时候使用服务的 ip 地址
client:
service-url:
defaultZone: httpL//localhost:8761/eureka/ #注册中心地址
热部署
在开发中我们修改一个 Java 文件后想看到效果不得不重启应用,这导致大量时间花费,我们希望不重启应用的情况下,程序可以自动部署(热部署)。
有以下四种情况,如何实现热部署:
1.模板引擎
在 Spring Boot 中开发情况下禁用模板引擎的 cache
页面模板修改 ctrl + F9 可重新编译当前页面并且生效
2.Spring Loaded
Spring 官方提供的热部署程序,实现修改类文件的热部署
下载 Spring Loaded (项目地址: https://github.com/spring-projects/spring-loaded)
添加运行时参数:
-javagent:C:/springloaded-1.2.5.RELEASE.jar --noverify
3.JRebel
收费的一个热部署软件
安装插件使用即可
4.Spring Boot Devtools(推荐)
引入依赖
org.springframework.boot
spring-boot-devtools
通过引入 spring-boot-starter-actuator ,可以使用 Spring Boot 为我们提供的准生产环境下的应用监控和管理功能。
我们可以通过 HTTP、JMX、SSH 协议来进行操作,自动得到审计、健康及指标信息等。
步骤:
1.引入 spring-boot-starter-actuator
2.通过 http 方式访问监控端点
3.可进行 shutdown(POST 提交,此端点默认关闭)
监控和管理端点
端点名 | 描述 |
---|---|
autoconfig | 所有自动配置信息 |
auditevents | 审计事件 |
beans | 所有 Bean 的信息 |
configprops | 所有配置属性 |
dump | 线程状态信息 |
env | 当前环境信息 |
health | 应用健康状况 |
info | 当前应用信息 |
metrics | 应用的各项指标 |
mappings | 应用@RequesMapping 映射路径 |
shutdown | 关闭当前应用(默认关闭) |
trace | 追踪信息(最新的 http 请求) |
定制端点一般通过 endpoints + 端点名 + 属性名来设置。
修改端点 id(endpoints.beans.id=mybeans)
开启远程应用关闭功能(endpoints.shutdown.enabled=true)
关闭端点(endpoints.beans.enabled=false)
开启所需端点
endpoints.enabled=false
endpoints.beans.enabled=true
定制端点访问路径
management.context-path=/manage
关闭 http 端点
management.port=-1
自定义健康状态指示器
1.编写一个指示器(必须实现 HealthIndicator 接口)
2.指示器的名字为 xxxHealthIndicator
3.加入容器中
public class MyAppHealthIndicator implements HealthIndicator {
@Override
public Health health() {
//自定义的检查方法
//Health.up().build(); //代表健康
return Health.down().withDetail("msg","服务异常").build();
}
}