本篇文章的是看狂胜的springboot视频总结的笔记
javase:OOP
mysql:持久化
html+css+js+jquery+框架:视图,框架不熟练,css不好
javaWeb:独立开发MVC三层架构的网站了,原始
ssm:框架,简化了我们开发的流程,配置也开始较为复杂;
war:tomcat运行
spring再简化:springBoot jar:内嵌tomcat;微服务架构!
服务越来越多:springcloud;
约定大于配置:maven,spring,springMvc,springBoot
Springboot是构建在spring基础之上的一种优化解决方案。
微服务(Microservice Architecture) 是近几年流行的一种架构思想,关于它的概念很难一言以蔽之
究竟什么是微服务呢?我们在此引用ThoughtWorks 公司的首席科学家 Martin Fowler 于2014年提出的一段话
原文
汉化
再来从技术维度角度理解下:
后面再聊SpringCloud的时候会具体的将什么叫微服务和微服务架构。
官方:提供了一个快速生成的网站,IDEA集成了这个网站
目录结构
//自动装配
@RestController
public class HelloController {
//接口:http://localhost:8080/hello
@RequestMapping("/hello")
public String hello(){
return "hello,World";
}
}
通过idea创建(是我们经常用的一种方式)
彩蛋
如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;
只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。
图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!
自动装配:
pom.xml
启动器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
启动器:说白了就是SpringBoot的启动场景;
比如说spring-boot-starter-web,他就会帮我们导入web环境所有的依赖
SpringBoot会将所有的功能场景,都编程一个个启动器
我们要使用什么功能,就只需要找到对应的启动器就行了 starter
主程序
package com.zhao.springboot01helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication:标注这个类是一个SpringBoot的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
//将SpringBoot应用启动
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
注解
@SpringBootConfiguration //springboot的配置
@Configuration:spring配置类
@Component:说明这也是一个spring组件
@EnableAutoConfiguration:自动配置
@AutoConfigurationPackage:自动配置包
@Import(AutoConfigurationPackages.Registrar.class):自动配置‘包注册’
@Import({AutoConfigurationImportSelector.class}):自动配置导入选择
//获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories:自动配置的核心文件
这个核心配置文件内的东西我们是可以直接通过maven加入用的。
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有资源加载到配置类中!
结论:SpringBoot所有自动装配都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定会生效,要判断条件是否成立,只要导入了对应的start,就有了对应的启动器,有了启动器,我们自动装配就会生效,然后配置成功
SpringApplication
我最初以为就是运行了一个main方法,没想到却开启了一个服务;
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
SpringApplication.run分析
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
查看构造器:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
// ......
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances();
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
同时我们也可以通过这个方法查看容器中所有的组件。
代码如下
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
//2、查看容器里面的组件
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
application.properties
application.yml
配置文件的作用:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
server.port=8081
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
传统xml配置:
8081
yaml配置:
server: prot: 8080
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
注意:
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
对象、Map(键值对)
#对象、Map格式k: v1: v2:
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: qinjiang age: 3
行内写法
student: {name: qinjiang,age: 3}
数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets: - cat - dog - pig
行内写法
pets: [cat,dog,pig]
总结
# k=v
name: zhao
# 对象
student:
name: zhao
age: 20
# 行内写法
student1: {name: zhao,age: 20}
# 数组
pets:
- dog
- cat
- pig
pets1: [dag,cat,pig]
修改SpringBoot的默认端口号
配置文件中添加,端口号的参数,就可以切换端口;
server: port: 8082
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
1、在springboot项目中的resources目录下新建一个文件 application.yml
2、编写一个实体类 Dog;
package com.zhao.pojo;
//可以让包被扫描
@Component
public class Dog {
@Value("小雨")
private String name;
@Value("1")
private Integer age;
public Dog() {
}
public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:
@Component
public class Dog {
@Value("小雨")
private String name;
@Value("1")
private Integer age;
}
4、在SpringBoot的测试类下注入狗狗输出一下;
package com.zhao;
import com.zhao.pojo.Dog;
import com.zhao.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot02ConfigApplicationTests {
//@Autowired:自动装配,可以自动装配使用@Component注解的类
@Autowired
private Dog dog;
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(dog);
System.out.println(person);
}
}
结果成功输出,@Value注入成功,这是我们原来的办法对吧。
5、我们在编写一个复杂一点的实体类:Person 类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;
/**
* @ProjectName: springboot-02-config
* @ClassName: Person
* @Description: 请描述该类的功能
* @Author: 赵先生
* @Date: 2021/8/26 8:32
* @version v1.0
* Copyright (c) All Rights Reserved,山西优逸客科技有限公司,.
*/
/*
* @ConfigurationProperties(prefix = "xxx")作用:
* 将配置文件中的配置的每一个属性的值,映射到这个组件中:
* 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
* 参数prefix = "person":将配置文件中的person先的所有属性——一一对应
*
* 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties(prefix = "person")功能
* */
@Component//注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getHappy() {
return happy;
}
public void setHappy(Boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", dog=" + dog +
'}';
}
}
6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!
person:
name: 小雨
age: 20
happu: ture
birth: 2001/11/11
maps: {k1: v1,k2: v2}
lists: [code,music,boy]
dog:
name: 小明
age: 3
7、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;
/*
* @ConfigurationProperties(prefix = "xxx")作用:
* 将配置文件中的配置的每一个属性的值,映射到这个组件中:
* 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
* 参数prefix = "person":将配置文件中的person先的所有属性——一一对应
*
* 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties(prefix = "person")功能
* */
@Component//注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getHappy() {
return happy;
}
public void setHappy(Boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", dog=" + dog +
'}';
}
}
8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
9、确认以上配置都OK之后,我们去测试类中测试一下:
package com.zhao;
import com.zhao.pojo.Dog;
import com.zhao.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot02ConfigApplicationTests {
//@Autowired:自动装配,可以自动装配使用@Component注解的类
@Autowired
private Dog dog;
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(dog);
System.out.println(person);
}
}
结果:所有值全部注入成功!
yaml配置注入到实体类完全OK!
课堂测试:
1、将配置文件的key 值 和 属性的值设置为不一样,则结果输出为null,注入失败
2、在配置一个person2,然后将 @ConfigurationProperties(prefix = “person2”) 指向我们的person2;
@PropertySource: 加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值;
1、我们去在resources目录下新建一个person.properties文件
name=ming
2、然后在我们的代码中指定加载person.properties文件
@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
@Value("${name}")
private String name;
......
}
配置文件还可以编写占位符生成随机数
person:
name: qinjiang${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财
age: 1
我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties , 我们没有讲,我们来唠唠!
【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;
settings–>FileEncodings 中配置;
测试步骤:
1、新建一个实体类User
@Component //注册bean
public class User {
private String name;
private int age;
private String sex;
}
2、编辑配置文件 user.properties
user1.name=ming
user1.age=18
user1.sex=男
3、我们在User类上使用@Value来进行注入!
@Component //注册bean
//classpath指定在resource目录下,而后再指定user这个上
@PropertySource(value = "classpath:user.properties")
public class User {
//直接使用@value
@Value("${user.name}") //从配置文件中取值
private String name;
@Value("#{9*2}") // #{SPEL} Spring表达式
private int age;
@Value("男") // 字面量
private String sex;
}
4、Springboot测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
User user;
@Test
public void contextLoads() {
System.out.println(user);
}
}
@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
4、复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
新版SpringBoot不自动支持jsr,所以需要导包
org.springframework.boot
spring-boot-starter-validation
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
运行结果 :default message [不是一个合法的电子邮件地址];
使用数据校验,可以保证数据的正确性;
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod
# 是通过三个-去实现的文件分离
---
server:
port: 8083
spring:
profiles: dev #配置环境的名称
---
server:
port: 8084
spring:
profiles: prod #配置环境的名称
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
官方外部配置文件说明参考文档
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
项目路径的优先级大于资源路径,config文件夹的优先级大于直接放
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;
#配置项目的访问路径
server.servlet.context-path=/kuang
指定位置加载配置文件
我们还可以通过spring.config.location
来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
自动配置原理
配置文件到底能写什么?怎么写?
SpringBoot官方文档中有大量的配置,我们无法全部记住
我们以HttpEncodingAutoConfiguration(Http编码自动配置为例解释自动配置原理;
package com.example.demo.源码解释;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
* in web applications.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
* Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)
*/
//proxyBeanMethods = false 就代表这是一个配置类文件
@Configuration(proxyBeanMethods = false)
/**
* 启动指定类的ConfigurationProperties(配置属性)
* 进去ServerProperties查看,将配置文件中的内容和ServerProperties中的内容绑定
* 并把ServerProperties加入到了ioc让其中
*/
@EnableConfigurationProperties(ServerProperties.class)
/**
* Spring底层@Conditional注解
* 根据不同的条件判断,如果满足条件,整个配置类里面的配置就会生效
* 这个意思就是判断当前应用是否是web应用,如果是,当前配置类生效
*/
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有CharacterEncodingFilter;再SpringMvc中进行乱码解决的过滤器
@ConditionalOnClass(CharacterEncodingFilter.class)
/**
* 判断配置文件中是否存在某个配置:server.servlet.encoding;
* 如果不存在,判断也是成立的
* 即使我们配置文件中不配置pring.server.servlet.encoding=true,也是默认生效的;
*/
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class Http编码自动配置 {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
// .....
}
我们去配置文件里面试试前缀,看提示!
这就是自动装配的原理!
精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解:@Conditional
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
【演示:查看输出的日志】
其实SpringBoot的东西用起来非常简单,因为SpringBoot最大的特点就是自动装配。
使用SpringBoot的步骤:
1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好
2、手动在配置文件中配置部分配置项目就可以运行起来了
3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。
要熟悉掌握开发,之前学习的自动配置的原理一定要搞明白!
比如SpringBoot到底帮我们配置了什么?我们能不能修改?我们能修改哪些配置?我们能不能扩展?
没事就找找类,看看自动装配原理!
我们之后来进行一个单体项目的小项目测试,让大家能够快速上手开发!
首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!
写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?
如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!
我们先来聊聊这个静态资源映射规则:
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;
我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
有一个方法:addResourceHandlers 添加资源处理
@Override
//Registry 登记
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
读一下源代码:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;
Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。
使用SpringBoot需要使用Webjars,我们可以去搜索一下:
网站:https://www.webjars.org
要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!
org.webjars
jquery
3.6.0
导入完毕,查看webjars目录结构,并访问Jquery.js文件!
访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.6.0/jquery.js
那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;
我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:
// 进入方法
public String[] getStaticLocations() {
return this.staticLocations;
}
// 找到对应的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// 找到路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
};
ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。
所以得出结论,以下四个目录存放的静态资源可以被我们识别:
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;
比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件;
我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;
spring.resources.static-locations=classpath:/coding/,classpath:/kuang/
一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!
总结:
静态资源文件夹说完后,我们继续向下看源码!可以看到一个欢迎页的映射,就是我们的首页!
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService(格式转换服务),ResourceUrlProvider mvcResourceUrlProvider//
资源URL提供程序) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
//模板可用性提供程序
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 获得欢迎页
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
return welcomePageHandlerMapping;
}
点进去继续看
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
// ::是java8 中新引入的运算符
// Class::function的时候function是属于Class的,应该是静态方法。
// this::function的funtion是属于这个对象的。
// 简而言之,就是一种语法糖而已,是一种简写
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。
比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html
新建一个 index.html ,在我们上面的3个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果!
关于网站图标说明:
与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。
1、关闭SpringBoot默认图标(新版本可以不用这一步)
#关闭默认图标spring.mvc.favicon.enabled=false
2、自己放一个图标在静态资源目录下,我放在 public 目录下
前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。
jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。
那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?
SpringBoot推荐你可以来使用模板引擎:
模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。
我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。
怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
Spring官方文档:找到我们对应的版本
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
找到对应的pom依赖:可以适当点进源码看下本来的包!
org.thymeleaf
thymeleaf-spring5
org.thymeleaf.extras
thymeleaf-extras-java8time
Maven会自动下载jar包,我们可以去看下下载的东西;
前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?
我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。
我们去找一下Thymeleaf的自动配置类:ThymeleafProperties
@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";
private String mode = "HTML";
private Charset encoding;
}
我们可以在其中看到默认的前缀和后缀!
我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。
使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!
他做了我们spring中的视图解析器
测试
1、编写一个TestController
@Controller
public class TestController {
@RequestMapping("/t1")
public String test1(){
//classpath:/templates/test.html
return "test";
}
}
2、编写一个测试页面 test.html 放在 templates 目录下
Title
测试页面
要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;
Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!
我们做个最简单的练习 :我们需要查出一些数据,在页面中展示
1、修改测试请求,增加数据传输;
@RequestMapping("/t1")
public String test1(Model model){
//存入数据
model.addAttribute("msg","Hello,Thymeleaf");
//classpath:/templates/test.html
return "test";
}
2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。
我们可以去官方文档的#3中看一下命名空间拿来过来:
xmlns:th="http://www.thymeleaf.org"
3、我们去编写下前端页面
狂神说
测试页面
4、启动测试!
OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!
1、我们可以使用任意的 th:attr 来替换Html中原生属性的值!
2、我们能写哪些表达式呢?
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:#18
#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.
#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 : methods for formatting numeric objects.
#strings : methods 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.
==================================================================================
Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL;
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:(文本操作)
String 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
Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( 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: _
练习测试:
1、 我们编写一个Controller,放一些数据
@RequestMapping("/t2")
public String test2(Map<String,Object> map){
//存入数据
map.put("msg","Hello
");
map.put("users", Arrays.asList("qinjiang","kuangshen"));
//classpath:/templates/test.html
return "test";
}
2、测试页面取出数据
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>狂神说title>
head>
<body>
<h1>测试页面h1>
<div th:text="${msg}">div>
<div th:utext="${msg}">div>
<h4 th:each="user :${users}" th:text="${user}">h4>
<h4>
<span th:each="user:${users}">[[${user}]]span>
h4>
body>
html>
3、启动项目测试!
我们看完语法,很多样式,我们即使现在学习了,也会忘记,所以我们在学习过程中,需要使用什么,根据官方文档来查询,才是最重要的,要熟练使用官方文档!
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!
地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration
Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters:http的信息转换器
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration
(interceptors, formatters, view controllers, and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法!
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
我们可以点进这类看看!找到对应的解析视图的代码;
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List requestedMediaTypes =this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
// .....
}
我们继续点进去看,他是怎么获得候选的视图的呢?
getCandidateViews中看到他是把所有的视图解析器拿来,进行while循环,挨个解析!
Iterator var5 = this.viewResolvers.iterator();
所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!
protected void initServletContext(ServletContext servletContext) {
// 这里它是从beanFactory工具中获取容器中的所有视图解析器
// ViewRescolver.class 把所有的视图解析器来组合的
Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
ViewResolver viewResolver;
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
}
// ...............
}
既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?
我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;我们去实现一下
1、我们在我们的主程序中去写一个视图解析器来试试;
//如果你想diy一些定制化的功能,只需要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配
//全面扩展springmvc dispatchServlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//viewResolver 实现了视图解析器接口类,我们就可以把它看做视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
2,怎么看我们写的视图解析器是否起作用了呢?
我们给DispatcherServlet中的doDispatch方法标记一个断点,因为所有的请求都会走 到这个地方。
3,启动项目,随机开启一个页面,查看Debug的信息
所以说明,我们如果想要使用自己定义的东西,只需要给容器添加这个组件就可以了,剩下的事情全是springboot的。
找到格式化转换器:
@Bean
@Override
public FormattingConversionService mvcConversionService() {
// 拿到配置文件中的格式化规则
WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
点击去:
public String getDateFormat() {
return this.dateFormat;
}
/**
* Date format to use. For instance, `dd/MM/yyyy`. 默认的
*/
private String dateFormat;
可以看到在我们的Properties文件中,我们可以进行自动配置它!
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
其余的就不一一举例了,大家可以下去多研究探讨即可!
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
扩展使用SpringMVC 官方文档如下:
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;
//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送/test , 就会跳转到test页面;
registry.addViewController("/ming").setViewName("test");
}
}
我们去浏览器访问一下:
确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
我们可以去分析一下原理:
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter(adapter适配器)
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration(Delegating委派代表)
这个父类中有这样一段代码:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 从容器中获取所有的webmvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
4、我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
5、我们点进去看一下
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
// 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
官方文档:
If you want to take complete control of Spring MVCyou can add your own @Configuration annotated with @EnableWebMvc.
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;
不加注解之前,访问首页:
给配置类加上注解:@EnableWebMvc
我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;
当然,我们开发中,不推荐使用全面接管SpringMVC
思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:
1、这里发现它是导入了一个类,我们可以继续进去看
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
2、它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { // ......}
3、我们来回顾一下Webmvc自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~
Department.java
//部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
Employee.java
//员工表
@Data
@NoArgsConstructor
public class Employee {
private Integer id; //string类型的api相对较多
private String lastName;
private String email;
private Integer gender;//0:女 1:男人
private Department department;
private Date birth;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
this.birth = new Date();
}
}
EmployeeDao
import com.zhao.pojo.Department;
import com.zhao.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
//员工表
public class EmployeeDao {
//模拟数据库中的数据
private static Map<Integer, Employee> employees = null;
//员工有所属的部门
@Autowired
private DepartmentDao departmentDao;
static {
employees = new HashMap<Integer, Employee>();//创建一个部门表
employees.put(1001,new Employee(1001,"aa","[email protected]",1,new Department(101,"教学部")));
employees.put(1002,new Employee(1002,"bb","[email protected]",0,new Department(102,"市场部")));
employees.put(1003,new Employee(1003,"cc","[email protected]",1,new Department(103,"教研部")));
employees.put(1004,new Employee(1004,"dd","[email protected]",0,new Department(104,"运营部")));
employees.put(1005,new Employee(1005,"ee","[email protected]",1,new Department(105,"后勤部")));
}
//逐渐自增
private static Integer initId = 1006;
//增加一个员工
public void save(Employee employee){
if (employee.getId()==null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询全部员工信息
public Collection<Employee> getAll(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
//删除员工
public void delete(Integer id){
employees.remove(id);
}
}
DepartmentDao
//部门dao
@Repository//将类托管给spring
public class DepartmentDao {
//模拟数据库中的数据
private static Map<Integer, Department> departments = null;
static {
departments = new HashMap<Integer, Department>();//创建一个部门表
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"教研部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
//或得所有功能信息
public Collection<Department> getDepartments(){
return departments.values();
}
//通过id获得部门
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
MyMvcConfig
通过自定义一个视图控制器来进行跳转,当我的请求为"/“或者”/index.html"时跳转到index
//如果你想diy一些定制化的功能,只需要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配
//全面扩展springmvc dispatchServlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
这个时候测试时发现css,js样式无法加载,所以导入Thymeleaf模板引擎
org.thymeleaf
thymeleaf-spring5
org.thymeleaf.extras
thymeleaf-extras-java8time
再到html页面上加入依赖
<html lang="en" xmlns:th="http://www.thymeleaf.org">
同时将本地的css样式用Thymeleaf导入
${...}
*{...}
#{...}
@{...}
~{...}
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/css/signin.css}" rel="stylesheet">
首页配置:所有页面的静态资源都需要使用thymeleaf接管,所以相关连接和消息需要都需要使用thymeleaf格式
在资源包下创建一个i18n文件
点击Resource Bundle
进入并填写数据
在首页中找到应该替换的数据,进行修改格式。#{}
如果说我们需要再项目中进行按钮自动切换,我们就需要自定义一个组件LocaleResolver
这个是SpringBoot中原本的类
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
我们可以根据这个照猫画虎编辑自己的类
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("l");
Locale locale = Locale.getDefault();//如果没有就使用默认的
//如果请求的连接携带了国际化的参数
if (!StringUtils.isEmpty(language)){
//zh_CN
//split表示使用——分割
String[] split = language.split("_");
//国家,地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
然后再将我们自己写的组件配置到spring的容器中
//如果你想diy一些定制化的功能,只需要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配
//全面扩展springmvc dispatchServlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
//自定义的国际化组件
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
修改index代码
去控制层创建登录的代码实现
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model){
//具体业务
if (!StringUtils.isEmpty(username) && password.equals("123456")){
return "redirect:/main.html";
}else {
//告诉用户失败了
model.addAttribute("msg","用户名密码错误!");
return "index";
}
}
}
去编写一个相应的映射
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
因为登录失败以后必须要有提示信息,所以在index上增加提示信息
在config文件夹下写一个LoginHandlerInterceptor类
//拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功之后有用户的session
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 {
}
}
在MyMvcConfig中重写拦截器方法
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/css/**","/js/xx","/img/**");
}
提取公共页面
通过中的
th:fragment="sidebar"
标签标明要插入的片段并为其取名字在需要的地方或者
通过这个标签插入如果需要传递参数,可以直接使用()传参即可
列表循环
<thead>
<tr>
<th>idth>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>departmentth>
<th>birthth>
<th>操作th>
tr>
thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}">td>
<td th:text="${emp.getLastName()}">td>
<td th:text="${emp.getEmail()}">td>
<td th:text="${emp.getGender()==0?'女':'男'}">td>
<td th:text="${emp.department.getDepartmentName()}">td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}">td>
<td>
<button class="btn btn-sm btn-primary">编辑button>
<button class="btn btn-sm btn-danger">删除button>
td>
tr>
tbody>
通过th:each标签实现循环
添加按钮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fff6XCBU-1651396215800)(springBoot.assets/image-20210907092949957.png)]
在控制层去写一个对应的toAdd方法
@RequestMapping("/toAdd")
public String toAddPage(Model model){
//查出所有部门的信息
Collection<Department> departments = departmentDao.getDepartments();
//然后便利到add中
model.addAttribute("departments",departments);
return "add";
}
编写add页面
DOCTYPE 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 th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/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:insert="~{commons/commons::topbar}">div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}">div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/add}" method="post">
<div class=" form- group">
<label>LastNamelabel>
<input type= "text" name="lastName" class="form- control" placeholder=" kuangshen">
div>
<div class="form-group">
<label>Emaillabel>
<input type=" email" name="email" class="form-control" placeholder="[email protected]" >
div>
<form class="form-group">
<label>Genderlabel><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type= "radio" name="gender" value="1">
<label class=" form-check- label">男label>
div>
<div class="form-check form-check-inline">
<input class="form-check-input" type= "radio" name="gender" value="0">
<label class=" form-check- label">女label>
div>
<div class=" form-group">
<label>departmentlabel>
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">option>
select>
div>
<div class="form-group">
<label>Birthlabel>
<input type="text" class="form-control" placeholder="kuangstudy" name="birth">
div>
<button type="submit" class="btn btn-primary">添加button>
form>
form>
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>
点击提交表单的时候会执行add动作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-crMARqdX-1651396215800)(springBoot.assets/image-20210907093441167.png)]
@RequestMapping("/add")
public String add(Employee employee){
System.out.println("save=>"+employee);
employeeDao.save(employee);//调用底层业务方法保存
return "redirect:/emps";
}
在编辑界面中间添加动作,并获得当前id
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wJJDZ2LR-1651396215800)(springBoot.assets/image-20210907102608413.png)]
、
编写toupdate
@GetMapping("/toUpdate/{id}")
//去到员工的修改页面
public String toAdd(@PathVariable("id") Integer id,Model model){
//查出原来的数据
Employee employeeById = employeeDao.getEmployeeById(id);
model.addAttribute("updates",employeeById);
//查出所有部门的信息
Collection departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "update";
}
编写update页面,取值用th:value,单选框用checks
Title
Dashboard Template for Bootstrap
再去编写update方法
@RequestMapping("/update")
public String update(Employee employee){
employeeDao.save(employee);
return "redirect:/emps";
}
}
删除
RequestMapping("/out")
public String logout(HttpSession session){
session.invalidate();
return "redirect:/index.html";
}
导入错误页面
只需要在资源目录下创建一个error的文件夹把所有的错误页面放进去即可
1,有一套自己熟悉的后台模板:工作必要!x-admin
2,前端页面:至少自己能够通过前端框架,组合出来一个网站页面
-index
-about
-blog
-post
-user
3,让这个网站能够独立运行!
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
Sping Data 官网:https://spring.io/projects/spring-data
数据库相关的启动器 :可以参考官方文档:
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
创建测试项目和测试数据
创建一个测试项目:spring-data-jdbc;引入相应的模块!基础模块
创建好项目后,会发现比之前多了如下的启动器
编写application.yaml
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8
url可以自动选择最佳的,可以忽略
配置完这些东西以后,就可以直接使用,因为springboot已经帮我们自动配置好了;去测试类进行测试
package com.zhao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class Springboot04DataApplicationTests {
//获取数据源,DI注入
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源:com.zaxxer.hikari.HikariDataSource
System.out.println(dataSource.getClass());
//获取数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//xxx Template : SpringBoot已经配置好模板,拿来即用 CRUD
//关闭
connection.close();
}
}
结束:我们可以看到他给我们默认配置的数据源为class com.zaxxer.hikari.HikariDataSource,我们并没有手动配置
全局搜索一下,找到数据源的所有自动配置都在:DataSourceAutoConfiguration文件:
@Import(
{Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
protected PooledDataSourceConfiguration() {
}
}
这里导入的类都在DateSourceConfiguration配置类下,可以看出Spring Boot 2.2.5默认使用HikarDataSource数据源
HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;
可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。
关于数据源我们并不做介绍,有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate
JDBCTemplate
JdbcTemplate主要提供以下几类方法:
测试
编写一个Controller,注入jdbcTemplate,编写测试方法进行测试
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
//,没有实体类,数据库中的东西,怎么获取? Map
@RequestMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@RequestMapping("/addUser")
public String addUser(){
String sql = "insert into mybatis.user(id,name,pwd) values (7,'小明','123456')";
jdbcTemplate.update(sql);
return "add";
}
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id){
String sql = "update mybatis.user set name=?,pwd=? where id="+id;
//封装
Object[] objects = new Object[2];
objects[0] = "小明2";
objects[1] = "123456";
jdbcTemplate.update(sql,objects);
return "update";
}
@RequestMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id") int id){
String sql = "delete from mybatis.user where id=?";
jdbcTemplate.update(sql,id);
return "delete";
}
}
Durid简介
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
Github地址:https://github.com/alibaba/druid/
com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:
添加上Durid数据源依赖。
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.6version>
dependency>
切换数据源;可以通过spring.datasource.type指定数据源。
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8
type: com.alibaba.druid.pool.DruidDataSource
切换数据源以后,可以在测试类中注入DataSource,然后获取到他,输出一看便知道是否切换成功
切换成功以后我们就可以设置数据源连接初始化大小,最大连接数,等待时间,最小连接数等设置项
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
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,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
导入log4j的依赖
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
现在需要程序员手动为自己 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;
package com.kuang.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DruidConfig {
/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
}
在测试类中测试一下,看是否成功!
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());
//关闭连接
connection.close();
}
}
输出结果,可见配置参数已经生效
Durid数据源具有监控的功能,并提供了一个web界面便于用户查看,类似安装路由器时,人家也提供一个默认的web页面
所以第一步需要设置Durid的后台管理页面,比如密码,账号等;配置后台管理
//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
设置完毕后可以登录http://localhost:8080/druid/login.html
进入之后
配置 Druid web 监控 filter 过滤器
//配置 Druid 监控 之 web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
导入mybatis依赖(因为是用的druid的数据源所以还导入了相关包)
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.6version>
dependency>
配置数据库连接
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
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,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
测试数据库是否连接成功
创建实体类pojo,并使用lombok插件
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
去编写一个userMappper类
//这个注解表示了这是一个mybatis的mapper类
@Mapper
@Repository //要是没有这个会报错但是实际上并没有影响
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
对应mapper的映射文件
位置是在资源目录下编写的
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhao.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select * from mybatis.user
select>
<select id="queryUserById" resultType="User">
select * from mybatis.user where id = #{id}
select>
<insert id="addUser" parameterType="User">
insert into mybatis.user (id, name, pwd) value (#{id},#{name},#{pwd})
insert>
<update id="updateUser" parameterType="User">
update mybatis.user set name=#{name},pwd=#{pwd} where id = #{id};
update>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
delete>
mapper>
编写控制层
package com.zhao.controller;/**
* ************************************************************************
* 项目名称: springboot-05-mybatis
* 文件名称:
* 文件描述: 这里添加您的类文件描述,说明当前文件要包含的功能。
* 文件创建:赵先生
* 创建时间: 2021/9/8
* 山西优逸客科技有限公司 Copyright (c) All Rights Reserved.
*
* @version v1.0
* @update [序号][日期YYYY-MM-DD][更改人姓名][变更描述]
* ************************************************************************
*/
import com.zhao.mapper.UserMapper;
import com.zhao.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @ProjectName: springboot-05-mybatis
* @ClassName: UserController
* @Description: 请描述该类的功能
* @Author: 赵先生
* @Date: 2021/9/8 9:52
* @version v1.0
* Copyright (c) All Rights Reserved,山西优逸客科技有限公司,.
*/
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/queryUserList")
public List<User> queryUserList(){
List<User> userList = userMapper.queryUserList();
for (User user : userList) {
System.out.println(user);
}
return userList;
}
@GetMapping("/addUser")
public String addUser(){
userMapper.addUser(new User(5,"小雨","123456"));
return "ok";
}
@GetMapping("/updateUser")
public String updateUser(){
userMapper.updateUser(new User(5,"小雨","111111"));
return "ok";
}
@GetMapping("deleteUser")
public String deleteUser(){
userMapper.deleteUser(5);
return "ok";
}
}
简单描述:
在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。
市面上存在比较有名的:Shiro,Spring Security !
这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?
首先我们看下它的官网介绍:Spring Security官网地址
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。
怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
在web页面使用的时候需要导入语法依赖:xmlns:sec=“http://www.thymeleaf.org/thymeleaf-extras-springsecurity5”
目前可以访问所有的页面,我们可以通过SpringSecurity来设置认证和授权的功能。
添加maven依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
编写 Spring Security 配置类
参考官网:https://spring.io/projects/spring-security
查看我们自己项目中的版本,找到对应的帮助文档:
https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4
编写基础配置类,去定制一个请求和认证的授权规则,并打开登录页面,同时要去编写密码的加密格式不然会报错(There is no PasswordEncoder mapped for the id “null”)
package com.zhao.config;/**
* ************************************************************************
* 项目名称: springboot-06-security
* 文件名称:
* 文件描述: 这里添加您的类文件描述,说明当前文件要包含的功能。
* 文件创建:赵先生
* 创建时间: 2021/9/8
* 山西优逸客科技有限公司 Copyright (c) All Rights Reserved.
*
* @version v1.0
* @update [序号][日期YYYY-MM-DD][更改人姓名][变更描述]
* ************************************************************************
*/
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @ProjectName: springboot-06-security
* @ClassName: SecurityConfig
* @Description: 请描述该类的功能
* @Author: 赵先生
* @Date: 2021/9/8 21:44
* @version v1.0
* Copyright (c) All Rights Reserved,山西优逸客科技有限公司,.
*/
//AOP: 拦截器
@EnableWebSecurity// 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//链式编程
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人都可以访问,功能页只有对应有权限的人才访问
//authorizeRequests批准请求,antMatchers 匹配对应的动作,hasRole有作用对象
//请求授权的规则
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level2/**").hasRole("vip3");
//没有权限默认会到登录页面,需要开启登录页面
http.formLogin();
}
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//这些数据正常应该在数据库中访问,现在是在内存中访问,BCryptPasswordEncoder是一个加密方式
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zhao").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3","vip1")
.and()
.withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定
记住我功能
方法是在配置类中加一个
//开启记住我功能 cookie,保存两周
http.rememberMe();
原理:
定制首页
当我们调用了http.formLogin()开启了用户登录状态就请求资源将被重定向到登录页面的功能,但是跳转到的登录页面是security自带的,我们想用自己的登录页面,这时就可以查看源码
* Specifies to support form based authentication. If
* {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page
* will be generated.
* 支持用户指定基于表单的身份验证,就是支持我们自己指定一个表单页面来进行身份认证
* 如果用户没有指定,将使用spring security自带的身份认证表单页面
* The configuration below demonstrates customizing the defaults.
* 下面的配置演示如何自定义默认值
*
*
* @Configuration
* @EnableWebSecurity
* public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
*
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
* .usernameParameter("username") // default is username
* .passwordParameter("password") // default is password
* //get方式请求的默认login页面,我们就可以通过这个方法指定我们自己的登录页为身份认证页面
* //注意:loginPage()中指定的是这个页面的请求url,不是资源真实地址
* .loginPage("/authentication/login") // default is /login with an HTTP get
* .failureUrl("/authentication/login?failed") // default is /login?error
* .loginProcessingUrl("/authentication/login/process"); // default is /login
* // with an HTTP
* // post
* }
指定我们自己的登录页面为
//没有权限默认会到登录页面,需要开启登录页面
http.formLogin().loginPage("/toLogin");
那么页面的数据应该去提交到哪里才能进行身份验证呢?
它的源码如下, 但是源码中只有一个new FormLoginConfigurer<>()
所以点开new FormLoginConfigurer<>()看具体的运行结果
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>>...{
//构造
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
/**
*
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when
* this attribute is not specified.
*
*
*
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified
* URL to generate a login page. In general, the login page should create a form that
* submits a request with the following requirements to work with
* {@link UsernamePasswordAuthenticationFilter}:
*
*
*
* - It must be an HTTP POST
* - It must be submitted to {@link #loginProcessingUrl(String)}
* - It should include the username as an HTTP parameter by the name of
* {@link #usernameParameter(String)}
* - It should include the password as an HTTP parameter by the name of
* {@link #passwordParameter(String)}
*
*
* Example login.jsp
*
* Login pages can be rendered with any technology you choose so long as the rules
* above are followed. Below is an example login.jsp that can be used as a quick start
* when using JSP's or as a baseline to translate into another view technology.
*
*
*
* <c:url value="/login" var="loginProcessingUrl"/>
* <form action="${loginProcessingUrl}" method="post">
* <fieldset>
* <legend>Please Login</legend>
* <!-- use param.error assuming FormLoginConfigurer#failureUrl contains the query parameter error -->
* <c:if test="${param.error != null}">
* <div>
* Failed to login.
* <c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}">
* Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
* </c:if>
* </div>
* </c:if>
* <!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout -->
* <c:if test="${param.logout != null}">
* <div>
* You have been logged out.
* </div>
* </c:if>
* <p>
* <label for="username">Username</label>
* <input type="text" id="username" name="username"/>
* </p>
* <p>
* <label for="password">Password</label>
* <input type="password" id="password" name="password"/>
* </p>
* <!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter -->
* <p>
* <label for="remember-me">Remember Me?</label>
* <input type="checkbox" id="remember-me" name="remember-me"/>
* </p>
* <div>
* <button type="submit" class="btn">Log in</button>
* </div>
* </fieldset>
* </form>
*
*
* Impact on other defaults
*
* Updating this value, also impacts a number of other default values. For example,
* the following are the default values when only formLogin() was specified.
*
*
* - /login GET - the login form
* - /login POST - process the credentials and if valid authenticate the user
* - /login?error GET - redirect here for failed authentication attempts
* - /login?logout GET - redirect here after successfully logging out
*
*
* If "/authenticate" was passed to this method it update the defaults as shown below:
*
*
* - /authenticate GET - the login form
* - /authenticate POST - process the credentials and if valid authenticate the user
*
* - /authenticate?error GET - redirect here for failed authentication attempts
* - /authenticate?logout GET - redirect here after successfully logging out
*
*
*
* @param loginPage the login page to redirect to if authentication is required (i.e.
* "/login")
* @return the {@link FormLoginConfigurer} for additional customization
*/
@Override
public FormLoginConfigurer<H> loginPage(String loginPage) {
return super.loginPage(loginPage);
}
}
上面的源码中我只选出了FormLoginConfigurer的构造和一个成员方法loginPage(),loginPage()显然就是配置spring security的login页面的,他上面的注解的意思是当我们没有配置登录页面时就会自动为我们生成一个登录表单,而且它的表单代码也在其中,由于编码问题,html页面的表单的< >两个尖括号变成了& lt 。
注解中表单的定义为