- SpringBoot
- 查看SpringBoot容器中所有组件
- 2,容器功能
- 3,自动配置原理入门
- 3.2,按需开启自动配置项
- 3.3,修改默认配置
- 3.4,最佳实践
- 4,开发小技巧
- 04,配置文件
- 05,Web开发
- 1,普通参数与基本注解
- 自定义Converter原理
- 5,视图解析器与模板引擎
- 1,视图解析
- 2,模板引擎-Thymeleaf
- 3,thymeleaf使用
- 4,构建后台管理系统
- 6,拦截器
- 7,文件上传
- 8,异常处理
- 9,原生组件注入
- 10,嵌入式web容器
- 11,定制化原理
- 6,数据访问
- 8,指标监控
- 2,Actuator Endpoint
- 4,管理Endpoints
- 3,定制 Endpoint
- 09,原理解析
- 2,外部化配置
- 3,自定义Starter
- 4,SpringBoot原理
SpringBoot
SpringBoot1.0.1
导入依赖
<!-- 引入了父Maven项目,继承父Maven项目所有的配置信息
spring-boot-starter-parent 有引入了一个父Maven项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
spring-boot-starter-parent 帮我们管理了SpringBoot应用中所有依赖的版本,
以后我们导入已有依赖就不需要写版本号了,它帮我们解决了第三方库之间的版本冲突问题
SpringBoot的版本仲裁中心
-->
<!--集成springboot的项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<!--
starter 场景启动器 : 不同的场景启动器维护了所有对应的所有依赖,从而简化maven文件书写
spring-boot-starter-web : 使用Spring MVC构建Web(包括RESTFUL)应用程序,使用TomCat作为默认的嵌入式容器
-->
<dependencies>
<!--引入springboot的web支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
@Configuration springBeans.xml : 也是配置类 声明Beans.xml 声明容器;
/**
* @SpringBootApplication : SpringBoot的启动类(入口)
*
* @Configuration spring.xml : 也是配置类
*
* @ComponentScan = <context:component-scan basePackages="com.xao."></context:component-scan>
*
* Spring底层在解析配置类, 会去解析@ComponentScan,读取basePackages,
* 如果没有读取到,会将当前配置类所在的包当做扫描包 com/xiao/Application ......
*
* 位置: 最后放在需要扫描包的根目录.或者说放在所有包的顶层目录中
*/
标记SpringBoot启动类 SpringBoot内置TomCat
@SpringBootApplication //标记成SpringBoot的启动类
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
打成jar包 cmd 命令直接启动 无需配置
<!-- 部署springboot的插件, 只有加了这个插件 当我们运行 java -jar xxx.jar才能正常启动 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
SpringBoot1.0.2
springboot配置文件 约定大于配置
resources —> application.properties
设置端口号
server.port=8088
设置路径前缀 localhost:8088/xiao/hello/world
server.servlet.context-path=/xiao
1.使用Spring Initializer快速创建SpringBoot项目
1.IDEA:使用Spring Initializer快速创建项目
继承关系-springboot的maven项目
1.使用spring initializer新建一个父maven,type选择POM
2.使用spring initizlizer新建一个子maven,type选择project
修改 子项目中的继承方式:
之前:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
之后:
<parent>
<groupId>com.xaio.springboot</groupId>
<artifactId>springboot_parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
spring_initializer —-> springboot_parent —->spring-boot-starter-parent
IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目:
选择我们需要的模块; 向导会联网创建Spring Boot项目;
默认生成的Spring Boot项目;
主程序已经生成好了,我们只需要我们自己的逻辑
resources文件夹中目录结构
static:保存所有的静态资源 js css images
templates: 保存所有的模板页面; (Spring Boot默认jar包使用嵌入式的TomCat,默认不支持JSP页面);
可以使用模板引擎(freemarker,thymeleaf)
application.properties:Spring Boot应用的配置文件; 可以修改一些默认设置;
SpringBoot1.0.3自定义SpringApplication
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SpringInitializrApplication.class);
app.setBannerMode(Banner.Mode.OFF); //可以关闭springboot启动横幅
app.run(args);
}
@RestController = @ResponseBody and @Controller 合体
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
return "Hello Spring Boot 2!";
}
public HelloController(){
System.out.println("HelloController被执行");
}
}
1.1、依赖管理
- 父项目做依赖管理
依赖管理
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
他的父项目
org.springframework.boot
spring-boot-dependencies
2.3.4.RELEASE
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
- 开发导入starter场景启动器
1、见到很多 spring-boot-starter- : 就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
org.springframework.boot
spring-boot-starter
2.3.4.RELEASE
compile
- 无需关注版本号,自动版本仲裁
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号 - 可以修改默认版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
1.2、自动配置
- 自动配好Tomcat
引入Tomcat依赖
配置TomCat org.springframework.boot spring-boot-starter-tomcat 2.3.4.RELEASE compile
自动配置好springMVC
引入springMVC全套组件
自动引入好SpringMVC常用组件(功能)
自动配好Web常见功能,如:字符编码问题
SpringBoot帮我们的配置好了所有web开发的常见场景
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
想要改变扫描器路径
无需以前的包扫描配置@SpringBootApplication(“com.xiao”) or @ComponentScan(“com.xiao”)
各种配置拥有默认值
默认配置都是映射到MultipartProperties
配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
非常多的starter
引入了哪些场景这个场景的自动配置才会开启
SpringBoot的所有自动配置都在spring-boot-autoconfigure包里:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
……
查看SpringBoot容器中所有组件
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
2,容器功能
2.1,组件添加
1,@Configuration
基本使用
//告诉springboot 这是一个配置类 == 配置文件
Full模式与Lite模式
示例
最佳实战
配置 类组件之间无依赖关系用List模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
2,@Bean,@Component,@Controller,@Service,@Repository
/**
* 1,配置类里面可以使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2,配置类本身也是组件
* proxyBeanMethods: 代理bean的方法
* Full(proxyBeanMethods = true),
* Lite(proxyBeanMethods = false)
* 组件依赖
*/
@Configuration(proxyBeanMethods = true) //告诉springboot 这是一个配置类 == 配置文件
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件.以方法名作为组件的id,返回类型就是组件类型,返回的值,就是组件在容器中的实例
public User user(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
/**
* 主程序类
* @SpringBootApplication : 这是一个SpringBoot应用
*
* @SpringBootApplication("com.xiao") 想要改变包扫描器的扫描路径
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
//从容器中获取组件
Pet tomcatPet = run.getBean("tom", Pet.class);
Pet tomcatPet1 = run.getBean("tom", Pet.class);
System.out.println("组件:"+(tomcatPet == tomcatPet1));
MyConfig bean = run.getBean(MyConfig.class);
//如果@Configuration(proxyBeanMethods = true)代理对象调用方法.springboot总会检查这个组件是否在容器中有
//保持组件单实例
User user = bean.user();
User user1 = bean.user();
System.out.println(user == user1);
User user2 = run.getBean("user", User.class);
Pet tom = run.getBean("tom", Pet.class);
System.out.println(user2.getPet() == tom);
}
}
3,@ComponentScan,@Import
4,@Import({User.class, DBHelper.class})
给容器中自动创建出这连个类型的组件,默认组件的名字就是全类名
user自己设置的
//@SpringBootApplication
String[] beanNamesForType = run.getBeanNamesForType(User.class);
System.out.println("=================");
for (String s : beanNamesForType) {
System.out.println(s);
}
String[] beanNamesForType1 = run.getBeanNamesForType(DBHelper.class);
System.out.println("=================");
for (String s : beanNamesForType1) {
System.out.println(s);
}
4,@Conditional 条件
在Config中使用 容器创建时
条件装配:满足Conditional指定的条件,则进行组件注册
2.2,原生配置文件引入
1,@ImportResource
//@ImportResource("classpath:beans.xml")导入Spring的配置文件
@ImportResource("classpath:beans.xml") 引入外部容器xml
public class MyConfig {}
2.3,属性绑定
1,@ConfigurationProperties
如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;
public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream(“a.properties”));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + “=” + strValue);
//封装到JavaBean。
}
}
}
2,@EnableConfigurationProperties + @ConfigurationProperties
@EnableConfigurationProperties(Car.class)
//1,开启属性配置绑定功能
//2,把这个Car这个组件自动注册到容器中
public class MyConfig {}
/**
* 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
*/
//@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {}
//比如导入依赖包 包内没有@Component 就可以使用这个
3,@Component + @ConfigurationProperties
/**
* 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
*/
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {}
3,自动配置原理入门
3.1,引导加载自动配置类
@EnableAutoConfiguration //启用自动配置
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
1,@SpringBootConfiguration
@Configuration //代表当前是一个配置类
2,@ComponentScan
指定扫描哪些,Spring注解;
3,@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
1,@AutoConfigurationPackage
@Import({Registrar.class}) //给容器导入一个组件
public @interface AutoConfigurationPackage {}
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication所在包下
2,@Import({AutoConfigurationImportSelector.class})
1,利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2,调用List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes)获取到所有需要导入到容器中的配置类
3,利用工厂加载Map<String,List<String>loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4,从META-INF/spring.factories位置来加载一个文件.默认扫描我们系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
3.2,按需开启自动配置项
1,虽然我们127个场景的所有自动配置启动的时候默认全部加载
按照条件装配规则(@Conditional),最终会按需配置
3.3,修改默认配置
@Bean
@ConditionalOnBean({MultipartResolver.class}) //容器中有这个类型组件
@ConditionalOnMissingBean(
name = {"multipartResolver"}
) //容器中没有这个名字multipartResolver的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找.
//springmvc multipartResolver.防止有些用户配置的文件上传解析器不符合规范 文件上传解析器
return resolver;
}
给容器加入了文件上传解析器
SpringBoot默认会在底层配置好所有的组件,但是如果用户配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {}
总结:
SpringBoot先加载所有的自动配置类 xxxxAutoConfiguration
每个指定配置类按照条件进行生效,默认配置文件指定的值. XXXXProperties里面拿.xxxPerperties和配置文件进行了绑定
生效的配置类就会给容器中装配很多的组件
只要容器中有这些组件,相当于这些功能就有了
只要用户有自己配置的,就以用户优先
定制化配置
用户直接自己@Bean替换底层组件
用户去看这个组件获取的配置文件什么值
xxxAutoConfiguration —>组件 —>xxxProperties里面拿值 —-> application.properties
3.4,最佳实践
引入场景依赖
[https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters)
查看自动配置了哪些(选做)
自己分析,引入场景对应的自动配置一般都生效了
配置文件中debug=true 开启自动配置报告. Negative(不生效) \ Positive(生效)
是否需要定制化
参考文档修改配置项https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.json
自己分析.xxxProperties绑定了配置文件的哪些
自定义加入或者替换组件
[@Bean ](/Bean ) , [@Component.... ](/Component.... )
自定义器 XXXCustomizer;
4,开发小技巧
4.1,Lombok
简化javaBean开发
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
并搜索安装lombok
@Data //get set
@ToString //toString
@NoArgsConstructor //无参构造
@AllArgsConstructor //有参构造
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
}
//写入日志
@Slf4j
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(String name) {
log.info("进入:handle01方法!"); //*********************
return "Hello Spring Boot 2! 喜傲!"+name;
}
}
4.2,dev-tools
<!-- springboot 自动快捷键重启-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
Ctel + F9;
4.3,Spring Initailizer
idea 快速创建 SpringBoot项目
04,配置文件
1,文件类型
1.1,properties
同以前的properties用法
1.2,yaml
1.2.1,简介
YAML是”YAML Ain’t Markup Language” (YAML 不是一种标记语言) 的递归缩写 .在开发的这种语言时,YAML的意思其实是:”Yet Another Markup Language” (扔是一种标记语言).
非常适合用来做以数据为中心的配置文件
1.2.2,基本语法
key:value; kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab , 只允许使用空格
缩进的空格数不重要 , 只要相同层级的元素左对齐即可
'#'表示注释
"与"表示字符串类容会被 转义/不转义
1.2.3,数据类型
字面量: 单个的,不可再分的值. data,boolean,string,number,null
k: v
对象: 键值对的集合.map,hash,set,object
行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3
数组: 一组按次序排列的值. array, list , queue
行内写法: k:[v1,v2,v3]
#或者
k:
- v1
- v2
- v3
Component 在yaml 配置时自动提示 不打包自动提示
<!-- bean类在yaml自动提示插件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
在springboot打包配置中 加入 可以在打包的时候 不打包processor自动提示包
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration- ` processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
05,Web开发
1,SpringMVC自动配置
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
- Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans. - 内容协商视图解析器和BeanName视图解析器
- Support for serving static resources, including support for WebJars (covered later in this document)).
- 静态资源(包括webjars)
- Automatic registration of
Converter
,GenericConverter
, andFormatter
beans. - 自动注册
Converter,GenericConverter,Formatter
- 自动注册
- Support for
HttpMessageConverters
(covered later in this document). - 支持
HttpMessageConverters
(后来我们配合内容协商理解原理)
- 支持
- Automatic registration of
MessageCodesResolver
(covered later in this document). - 自动注册
MessageCodesResolver
(国际化用)
- 自动注册
- Static
index.html
support. - 静态index.html 页支持
- Custom
Favicon
support (covered later in this document). - 自定义
Favicon
- 自定义
- Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document). - 自动使用
ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上)
- 自动使用
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration
class of type WebMvcConfigurer
but without @EnableWebMvc
.
不用@EnableWebMvc注解。使用 **@Configuration**
+ **WebMvcConfigurer**
自定义规则
If you want to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
, or ExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations
and use it to provide custom instances of those components.
声明 **WebMvcRegistrations**
改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
, or alternatively add your own @Configuration
-annotated DelegatingWebMvcConfiguration
as described in the Javadoc of @EnableWebMvc
.
使用 **@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC**
2,简单功能分析
2.1,静态资源访问
1,静态资源目录
类路径下: called/static(or/public or /resources or /META-INF/resources
访问 : 当前项目根路径 / + 静态资源名
静态资源 和ming请求同名
原理: 静态资源映射/**.
请求进来先去找controller看能不能处理,不能处理所有请求又都交给静态资源处理器.静态资源能不能找到:
静态资源能找到就返回静态资源,静态资源找不到就返回404
改变默认的静态资源路径
spring:
mvc:
static-path-pattern: /static/**
web:
resources:
# static-locations: [clsspath:/static,clsspath:/.......]
static-locations: clsspath:/static
2,静态资源访问前缀
springboot 默认 是/** 是没有前缀的
但是可以在yaml中: 增加前缀
spring:
mvc:
#默认是: /** 扫描根目录下所有资源
static-path-pattern: /static/**
当前项目 + static-path-pattem+静态资源名 = 静态资源文件下找
3,webjar
自动映射
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
访问地址: http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖地址的包路径
2.2,欢迎页支持
静态资源路径下 index.html
可以配置静态资源路径
但是不可以配置静态资源的访问前缀.否则导致index.html不能被默认访问
spring:
# mvc:
# static-path-pattern: /stat/** //这个会导致welcome page功能失效
controller能处理/index
2.3,自定义 Favicon
favicon.ico 放在静态资源目录下即可.
spring:
## mvc:
## static-path-pattern: /stat/** 这个会导致Favicon.ico 功能失效
2.4静态资源配置原理
SpringBoot启动默认加载 XXXAutoConfiguration类(自动配置类)
SpringMVC功能的自动配置类WebMvcAutoConfiguration,生效
@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 {}
给容器中配置了什么
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
配置文件的相关属性和xxx进行了绑定,WebMvcPropertiesspring.mvc,WebPropertiesspring.web
1,配置了只有一个有参构造器
//有参构器所有参数的值都会从容器中确定
//WebProperties webProperties: 获取和spring.web绑定的所有值的对象
//WebMvcProperties mvcProperties: 获取和spring.mvc绑定的所有值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器. ==========
//DispatcherServletPath
//ServletRegistrationBean //给应用注册Servlet,Filter...
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//webjars的规则
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);
}
});
}
spring:
web:
resources:
add-mappings: false #引用所有的静态资源
cache:
period: 120 #设置静态缓存时间
HandlerMapping: 处理器映射.保存了每一个handler能处理哪些请求
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
//要用欢迎页功能必须是/**
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
//掉用Controller /index
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
4,favicon
浏览器会发送/favicon.ico请求获取到图标,整个session期间不能在获取
3,请求参数处理
[@xxxMapping ](/xxxMapping ) GET POST
Rest风格支持(谁用Http请求方式动词来表示对资源的操作)
以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser保存用户
现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
核心Filter; HiddenHttpMethodFilter
用法: 表单method=post , 隐藏域_method=put
SpringBoot中手动开启
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
Rest原理(表单提交使用REST的时候)
表单上提交会带上_method=PUT
请求过来被HiddenHttpMethodFilter拦截
请求是否正常,并且是POST
获取到_method的值
兼容以下请求:
HttpMethod.PUT.name(),HttpMethod.DELETE.name(), HttpMethod.PATCH.name()
原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值
过滤器链放行的时候用wrapper.以后的方法调用getMethod是调用requestWrapper的
Rest使用客户端工具,
如PostMan直接发送Put,delete等方式请求,无需Filter
Spring
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
自定义修改_method
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
2,请求映射原理
SpringMVC功能分析都从DispatcherServlet —> doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//找到当前请求使用那个handler(Controller)处理
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
//HandlerMapping : 处理器映射. /xxx->>xxxxx
RequestMappingHandlerMapping : 保存了所有@RequestMapping和handler的映射规则
所有的请求映射都在HandlerMapping中.
-SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping HandlerMapping.访问/能访问到index.html;
-SpringBoot配置了默认的RequestMappingHandlerAdapter
-请求进来,挨个尝试所有的HandlerMapping看是否有请求信息.
如果有就找到请求对应的Handler
如果没有就是下一个HandlerMapping
我们需要一些自定义的映射处理,我们也可以给容器中放HandlerMapping . 自定义HandlerMapping
1,普通参数与基本注解
**注解**
@PathVariable,@RequestHeader,@ModelAttribute,@RequesParam,@MatrixVariable,@CookieValue,
@PathVariable 获取路径信息
<a href="/car/3/owner/lisi">/car/{id}/owner/{username}</a>
<li>@PathVariable(路径变量)</li>
// car/2/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable() Map<String,String> pv){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pv",pv);
return map;
}
@RequestHeader 获取请求头数据
// car/2/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable() Map<String,String> pv,
@RequestHeader("Host") String userHost,
@RequestHeader Map<String,String> header){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pv",pv);
map.put("userHost",userHost); //获取请求用户的Host
map.put("header",header); //获取请求用户的所有请求头信息
return map;
}
@RequesParam(“指定name”) 获取表单指定属性
@CookicValue 获取Cookic
//@CookieValue("_ga") String _ga, //获取指定Cookie
//@CookieValue Cookie cookie, //获取全部Cookie
@RequestBody post提交 获取表单全部数据
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String ,String> map = new HashMap<>();
map.put("content",content);
return map;
}
@RequestAttribute() //获取转发来的request域中的数据 记住 forward: 请求转发
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功了");
request.setAttribute("code",200);
return "forward:/success"; //转发到 /success请求
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String,Object> map = new HashMap<>();
map.put("reqMethod_msg",msg1);
map.put("annotation_msg",msg);
map.put("annotation_code",code);
return map;
}
@MatrixVariable
// /cars/sell;low=34;brand=byd,audi,yd
//SpringBoot默认禁用了矩阵变量的功能
// 手动开启 : 原理. 对于路径的处理.UrlPathHelper进行解析.
// removeSemicolonContent(移除分号内容)支持矩阵变量
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low
,@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String, Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
需要配置开启矩阵变量功能 因为默认是禁用矩阵变量 的 true 改成 set false
//1,WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//不移除:后面的内容. 矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
@MatrixVariable("low") //不同名
@MatrixVariable(value = "age" ,pathVar = "bossId") //同名
//如果两个var 是同名
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age" ,pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age" ,pathVar = "empId") Integer empAge){
Map<String, Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
<li>@PathVariable(路径变量)</li>
<li>@RequestHeader(获取请求头)</li>
<li>@RequestParam(获取请求参数)</li>
<li>@CookieValue(获取Cookie值)</li>
<li>@RequestAttribute(获取request域属性)</li>
<li>@RequestBody(获取请求体)</li>
<li>@MatrixVariable(矩阵变量)</li>
Servlet API
WebRequest, ServletRequest,MultipartRequest,HttpSession,javax.servlet.http.PushBuilder,Principal,
InputStream,Reader,HttpMethod,Locale,TimeZone,Zoneld
复杂参数
Map,Errors/BindingResult,Model,RedirectAttributes,ServletResponse,SessionStatus,UriComponentsBuider,ServletUriComponentsBuider
自定义对象参数
可以自动类型格式与格式化,可以级联封装.
2,POJO封装过程
3,参数处理原理
HandlerMappering中找到能处理请求的Handler(Controller.method())
为当前Handler找一个适配器HandlerAdapter; RequestMappingHandlerAdapter
1,HandlerAdapter
0-支持方法上标注@RequestMapping
1-支持函数式编程的
…
2,执行目标方法
// Actually invoke the handler.
//DispatcherServlet --- doDispach
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, request,handlerMethod); //执行目标方法
//ServietInvocableHandlerMethod
Object returnValue = invokForRequest(webRequest,mavContainer,providedArgs);
//获取方法参数值
Object[] args = getMethodArgumentValues(request,mavContainer,providedArgs)
3,参数解析器
确定将要执行的目标方法的每一个值是什么
SpringMVC目标方法能写多少种参数类型,取决于参数解析器.
当前解析器是否支持解析这种参数
支持就调用 resolveArgument
4,返回值处理器
5,如何确定目标方法每一个参数的值
============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
5.1挨个判断所有参数解析器那个支持解析这个参数
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
自定义Converter原理
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br>
年龄: <input name="age" value="18"/> <br>
生日: <input name="birth" value="2019/12/10"><br>
<!-- 宠物姓名: <input name="pet.name" value="阿猫"><br>-->
<!-- 宠物年龄; <input name="pet.age" value="5"><br>-->
宠物: <input name="pet" value="阿猫,3"/>
<input type="submit" value="提交">
</form>
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private Integer age;
}
/**
* 数据绑定: 页面提交的请求数据(GET,POST)都可以和对象属性进行绑定
* @param person
* @return
*/
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
自定义Converter
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String , Pet>() {
@Override
public Pet convert(String source) {
//阿猫. 3
if (!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
浏览器会先传送一个name=pet的,值=”阿猫,3“的数据给服务器,
底层在处理到自定义对象参数(此时没有发生判断,在服务器层面这个参数此时和上面服务器传来的数据无关)的时候会先找到这个自定义对象参数类型,然后调用参数解析器,当调用自定义参数解析器
处理这个参数的时候,会先找所有的Converter进行类型转换,此时自定义的Converter是String->Pet,此时浏览器传送过来的是String类型的数据,服务器找到的接收数据的对象参数是Person,而Person中有Pet,所以在绑定Person中的Pet属性时,就会调用到String->Pet这个自定义的Converter进行属性数据绑定,而Person中的其他属性比如Integer类型的age和Date类型的birth就会调用其他的Converter比如String->Integer,String->Date来进行属性数据绑定
————————————————
版权声明:本文为CSDN博主「陪安琪度过一生」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_56965737/article/details/118656010
4,响应数据与内容协商
1,响应JSON
1.1,jackson.jar+ResponseBody
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
web场景自动引入了json依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.6.4</version>
<scope>compile</scope>
</dependency>
给前段自动返回json数据;
2,返回值解析器原理
1,返回值处理判是否支持这种类型返回值 supportsReturnType
2,返回值处理器调用HandleReturnValue进行处理
3,RequestResponseBodyMethodProcessor可以处理返回值标注了@ResponseBody注解的.
1,利用MessageConverters进行处理 将数据写为json
1,内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
2,服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
3,SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理
1,
1.2,SpringMVC到底支持哪些返回值
ModelAndView
Mode
View
ResponseEntity
ResponseBodyEmitter
StreamingResonseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTack
有 @ModelAttribute
@ResponseBady 注解 -----> ResponseBodyMethodProcessor;
1.2,HTTPMessageConverter原理
1,MessageConverter规范
HttpMessageConverter: 看是否支持将此Class类型的对象,转为MediaType类型的数据.
例子:Person对象转为JSON
2,默认的MessageConverter
3,开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能.
#开启参数方式的内容协商 地址后面携带 ?format=你要 指定的获取的请求类型类型
spring:
mvc:
contentnegotiation:
favor-parameter: true
#http://localhost:8080/test/person?format=json
#http://localhost:8080/test/person?format=xml
确定客户端接受什么样的内容类型;
1,Parameter策略优先确定是要返回json数据
return request.getParameter(getParameterName());
4,内容协商
WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
导入了jackson处理xml的包,xml的converter就会自动进来
5,自定义MessageConverter
实现多协议数据兼容.json,xml,x-guigu
0,@ResponseBody,响应数据出去 调用RequestResponseBodyMethodProcessor处理
1,Processor处理方法返回值,通过MessageConverter处理
2,所有MessageConverter合起来可以支持各种媒体类型数据的操作(读,写)
3,内容协商找到最终的messageConverter;
SpringMVC的什么功能.一个入口 给容器中添加一个: WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer(){
//矩阵变量生效
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
}
/**
* 自定义的Converter
*/
public class XiaoMessageConverter implements HttpMessageConverter<Person> {
//能不能读 Mappring(String name)
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
//能不能写
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class);
}
/**
* 服务器要统计所有MessageConverter都能写出哪些内容类型
*
* application/x-xiao
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-xiao");
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
//写出去
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
/**
* 自定义内容协商策略
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//Map<String, MediaType> mediaTypes
HashMap<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_ATOM_XML);
mediaTypes.put("xiao",MediaType.parseMediaType("application/x-xiao"));
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
//软件 基于头的不可以是有加入
HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(strategy));
}
@Override
//新增MessageConverters
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//新增MessageConverters
converters.add(new XiaoMessageConverter());
}
有可能我们添加的自定义的功能会覆盖默认很多功能,导致默认功能失效.
5,视图解析器与模板引擎
视图解析:SpringBoot默认不支持JSP 需要引入第三方模板引擎技术实现页面渲染.
1,视图解析
1,视图解析原理
1,目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面.包括数据和视图地址
2,方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
3,任何目标方法执行完成以后都会返回 ModelAndView(视图和视图地址).
4,processDispatchResult 处理派发结果.(页面改如何响应)
1,render(mv,request,response);进行界面渲染
2,模板引擎-Thymeleaf
1,thymeleaf简介
现代化,服务端Java模板引擎
2,基本语法
1,表达式
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域,session域,对象等值 |
选择变量 | *{…} | 获取上下文对象值 |
消息 | #{…} | 获取国际化等值 |
链接 | @{…} | 生成链接 |
片段表达式 | ~{…} | jsp:include 作用,引入公共页面 |
2,字面量
文本值:’one text’,’Another one!’,…
数字:0 , 34 , 3.0 , 12.3 ,…
布尔值:true , false
空值:null
变量: one,two,…变量不能有空格
3,文本操作
字符串拼接:+
变量替换:|The name is ${name}|
4,数学运算
运算符:+,-,*,/,%
5,布尔运算
运算符:and , or
一元运算:! , not
6,比较运算
比较:>,<,>=,<=(gt,lt,ge,le)
等式:=,=,!=(eq,ne)
7,条件运算
If-then:(if)?(then)
If-then-else:(if)?(then):(else)
Default: (value)?:(defaultvalue)
8,特殊操作
无操作: _
遍历操作
<tr class="gradeX" th:each="user:${users}">
<td>Trident</td>
<td th:text="${user.userName}"></td>
<td th:text="${user.password}"></td>
</tr>
stats 当前状态
<tr class="gradeX" th:each="user,stats:${users}">
<td th:text="${stats}">Trident</td>
<td th:text="${user.userName}"></td>
<td th:text="${user.password}"></td>
</tr>
当前状态图片解释
引入页面
要引入的页面 common
<div th:fragment="copy">
...
</div>
被引入的页面
<div th:insert="~{common :: copy}">
</div>
被引入的页面 要引入的文件名字 和 引进设置的fragment value
<div th:insert="common :: copy">
</div>
也可以根据声明的id name class 进行 不用自己再次声明
<div id="common">
</div>
被引入的页面
<div th:insert="~{commom :: #common}">
</div>
有三种方法引入
<div th:inscrt="footer :: copy">
</div>
<div th:replacr="footer :: copy">
相当于div 默认没有了
只有div内的内容
</div>
<div th:include="footer :: copy">
直接把内容放进来
</div>
th:insert :保留自己的主标签,保留th:fragment的主标签。
th:replace :不要自己的主标签,保留th:fragment的主标签。
th:include :保留自己的主标签,不要th:fragment的主标签。(官方3.0后不推荐)
3,设置属性值-th:attr
设置多个值
<from action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}" />
</fieldset>
</from>
设置多个值
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglog.png},title=#{logo}"/>
以上两个的替代写法th:xxx
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}" />
<input action="subscribe.html" th:action="@{/subscribe}" />
所有h5兼容的标签写法
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
4,迭代
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}" >Onions</td>
<td th:text="${prod.price}" >2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}" >yes</td>
</tr>
<tr class="gradeX odd" th:each="user:${users}">
<tr class="gradeX odd" th:each="user,stat:${users}">
<td th:text="${stat.count}">Trident</td>
<td th:text="${user.getName()}">Internet
Explorer 4.0</td>
<td th:text="${user.getAge()}">Win 95+</td>
<td class="center hidden-phone" th:text="${user.getEmail()}">4</td>
<td class="center hidden-phone">X</td>
</tr>
5,条件运算
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">
View
</a>
<div th:switch="${user.role}" >
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is som other thing</p>
</div>
3,thymeleaf使用
1,引入Starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2,自动配置好了thymeleaf
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
自动配置好的策略
1,所有thymeleaf的配置都在 **ThymeleafProperties**
2,配置好了 **SpringTemplateEngine**
3,配置好了 **ThymeleafViewResolver**
4,我们只需要直接开发好页面
public static final String DEFAULT_PREFIX = "classpath:/templates/"; //前缀
public static final String DEFAULT_SUFFIX = ".html"; //后缀
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!-- 加入获取代码提示 -->
3,页面开发
<!DOCTYPE html> //引入代码提示
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!-- *** -->
<head>
<meta charset="UTF-8">
<title>Xiao</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
<a href="www.baidu.com" th:href="${link}">去百度1</a>
<a href="www.baidu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>
4,构建后台管理系统
1,项目创建
2,静态资源处理
3,路径构建
4,模板抽取
5,页面跳转
6,数据渲染
6,拦截器
1,HandlerInterceptor接口
实现拦截器接口
/**
* 登录检查
* 1,配置好拦截器要拦截哪些请求
* 2,把这些配置放在容器中
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser !=null){
//放行
return true;
}
//拦截,未登录
request.setAttribute("msg","请先登录");
response.sendRedirect("/");
return false;
}
/**
* 目标方法执行完成以后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 页面渲染以后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置到容器中
/**
* 1,编写一个拦截器实现 HandlerInterceptor 接口
* 2,拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3,指定拦截规则[如果是拦截所有,静态拦截也会被拦截]
*/
@Configuration
public class AdMainWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
//拦截哪些
.addPathPatterns("/**") //所有请求都会被拦截 包括 静态资源
//放行哪些
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求 放行静态资源
}
}
或者在yaml配置静态资源的路径id 但是所有静态资源请求都需要加上你设置的id /static/img…
spring:
mvc:
static-path-pattern: /static/**
2,配置拦截器
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
3,验证拦截器
7,文件上传
1,页面表单
<form method="post" action="/upload" enctype="multipart/from-data">
<input type="file" name="file" />
<input type="submit" value="提交"
</form>
2,文件上传代码
前段代码
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮件</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照片</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
后端代码
/**
* MultipartFile 自动封装上传过来的文件
* @param email
* @param username
* @param headerImg
* @param photos
* @return
*/
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg, //MultipartFile 获取上传文件的对象
//RequestPart获取上传来的文件
//MultipartFile[] 获取多个
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},username={},headerImg={},photos={}",
email,username,headerImg.getSize(),photos.length);
if(!headerImg
.isEmpty()){
//保存到文件服务器.OSS服务器
String originalFilename = headerImg.getOriginalFilename(); //拿取图片上传来了原生名
//spring写自带的io
headerImg.transferTo(new File("D:\\cache\\"+originalFilename));
}
if (photos.length > 0){
for (MultipartFile photo : photos) {
if (!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("D:\\cache\\"+originalFilename));
}
}
}
return "main";
}
3,自动配置原理
#spring自己设置了文件上传的最大值是1mb 所有可以自己修改
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
@EnableConfigurationProperties(MultipartProperties.class)
MultipartAutoConfiguration
文件上传自动配置类-MultipartAutoConfiguration-MultiartProperties
**自动配置好了 StandardServletMultipartResolver [文件上传]**
**原理步骤**
**1,请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求**
**2,参数解析器来解析请求中的文件内容封装成multipartFile**
3,将request中文件信息封装为一个Map; MultiValueMap<String,MultipartFile>
FileCopyUtils.实现文件流的拷贝
8,异常处理
1,错误处理
1,默认规则
默认情况下,Spring Boot提供/error处理所有错误的映射
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息.对于浏览器客户端,
响应一个"whitelabel"错误视图,以HTML格式呈现相同的数据
要对其进行自定义,添加View解析为error
要完全替换默认行为,可以实现ErrorController并注册改类型的Bean定义,或添加ErrorAttributes类型的组件 以使用现有机制但替换其内容.
2,定义错误处理逻辑
自定义错误页
error/404.html error/5xx.html
[@ControllerAdvice ](/ControllerAdvice ) + @ExceptionHandler异常处理
实现HandlerExceptionResolver处理异常
3,异常处理原理
**ErrorMvcAutoConfiguration 自动配置异常处理规则**
**容器中的组件: DefaultErrorAttributes->id:errorAttributes**
**public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {}**
**容器中的组件: BasicErrorController->id:basicErrorController**
**处理默认/error路径的请求; 页面响应 new ModelAndView("error",model)**
**容器中有组件View->id:error;(响应默认错误页)**
容器中放组件 **BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象.**
#server:
# error:
# path: /error 修改路径
9,原生组件注入
1,使用Servlet API
@Web…原生组件名称 //好记忆
@WebServlet //原生Servlet
@WebFilter //原生过滤器
@WebListener //原生监听器
@ServletComponentScan(basePackages = "com.xiao.servlet") //指定扫描Servlet原生组件
@SpringBootApplication
public class Boot05WebAdminApplication { }
@WebServlet(urlPatterns = "/my") //声明这是java HttpServlet 原生组件
public class MyServlet extends HttpServlet {} //效果:直接响应,没有Spring拦截器
@WebFilter(urlPatterns = {"/css/*","/images"}) //java 原生 过滤器声明
public class MyFilter implements Filter {}
@WebListener //Java原生 声明监听器
public class MyServletContextListener implements ServletContextListener {}
推荐可以使用这种方式;
扩展:DispachServlet如何注册进来
容器中自动配置了 DispatchServlet 属性绑定到 WebMvcproperties;对应的配置文件配置项是 spring.mvc
**通过** ServletRegistrationBean 把DispatchServlet 配置进来
默认映射的是"/"路径.
Tomcat-Servlet;
多个Servlet都能处理到同一层,精确优先原则
A:/my/
B:/my/1
2,使用RegistrationBean
ServletRegistrationBean,FilterRegistrationBean,and ServletlistenerRegistrationBean
//(proxyBeanMethods = false):保证依赖的组件始终是单实例的
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/my");
}
@Bean
public FilterRegistrationBean myFilter01(){
MyFilter myFilter = new MyFilter();
return new FilterRegistrationBean(myFilter,myServlet()); //第一种Filter设置
}
// @Bean
// public FilterRegistrationBean myFilter02(){
// MyFilter myFilter = new MyFilter();
// FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
// filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css")); //第二种filter自定义设置路径
// return filterRegistrationBean;
// }
@Bean
public ServletListenerRegistrationBean myListener(){
MyServletContextListener myServletContextListener = new MyServletContextListener();
return new ServletListenerRegistrationBean(myServletContextListener);
}
}
10,嵌入式web容器
1,切换嵌入式Servlet容器
默认支持的webServlet
Tomcat,Jetty,orUndertow
ServletWebServerApplicationContext 容器启动寻找ServletWerbServerFactory 并引导创建服务器
切换服务器
原理;
SpringBoot应用发现是Web应用.web场景包-导入tomcat
web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
ServletWebServerApplicationContext 启动的时候寻找 **ServeltWebServerFactory**(Servlet 的web服务器工厂--->Servlet 的web服务器)
SpringBoot底层默认有很多WebServer工厂
TomcatServletWebServerFactory,JettyServletWebServerFactory,or UndertowServletWebServerFactory
底层直接会有一个自动配置类. ServletWebServerFactoryAutoConfiguration
ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryAutoConfiguration(配置类)
ServletWebServerFactoryAutoConfiguration 配置类 根据动态判断系统中到底导入了那个web服务器的包.(默认是导入Web-start Tomcat包),容器中就有 TomcatServletWebServerFactory
TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer的构造器拥有初始化 方法
内嵌服务器,就是把手动启动服务器的代码调用(Tomcat核心jar包存在)s
2,定制Servlet容器
实现WebServerFactoryCustomizer
把配置文件的值和 **ServletWebServerFactory** 进行绑定绑定
修改配置文件server.xxx
**直接自定义 ConfigurableServletWebServerFactory**
xxxxxCustomizer: 定制化器,可以改变xxx的默认规则
11,定制化原理
1,定制化的常见方式
@Bean替换,增加容器中默认组件;视图解析器
修改配置文件;
xxxxCustomizer;
编写自定义的配置类 xxxConfiguration;
web应用 实现 WebMvcConfigurer即可定制化web功能;
[@EnableWebMvc ](/EnableWebMvc ) + WebMvcConfigurer ---- [@Bean ](/Bean ) 可以全面接管 SpringMVC, 所有规则全部自己重新配置; 实现定制和扩展功能
........
2,原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties — 绑定配置文件项
6,数据访问
1,SQL
1,数据源的自动配置
1,导入JDBC场景
导入了数据源,jdbc,事务
1,自动配置的类
DataSourceAutoConfiguration : 数据源的自动配置
修改数据源相关的配置: **spring.datasource**
数据库连接池的配置, 是自己容器中没有DataSource才自动配置的
底层默认配置好的连接池是: HikariDataSource
数据源配置跟 spring.datasource 绑定的
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
jdbcTemplateAutoConfiguration: **JdbcTemplate的自动配置, 可以来对数据库进行crud**
**可以修改这个配置项**[**@Configurationproperties(prefix **](/Configurationproperties(prefix )** = "spring.jdbc")来修改 jdbcTemplate **
@Bean[@Primary ](/Primary ) JdbcTemplate; 容器中有这个组件
JndiDataSourceAutoConfiguration: jndi的自动配置
XADataSourceAutoConfiguration: 分布式事务相关
修改配置项
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springdb
username: root
password: zhang..0902
#type: 数据源
2,使用Druid数据源
1,druid官方github地址
https://github.com/alibaba/druid
整合第三方技术的两种方式
自定义
找starter
2,自定义方式
1,创建数据源
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
</bean>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springdb
username: root
password: zhang..0902
druid:
#2.连接池配置
#初始化连接池的连接数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连接等待超时的时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache 官方建议MySQL下建议关闭 个人建议如果想用SQL防火墙 建议打开
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filter:
stat:
merge-sql: true
slow-sql-millis: 5000
wall:
enabled: true
#3.基础监控配置
web-stat-filter:
enabled: true
url-pattern: /*
#设置不统计哪些URL
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
session-stat-enable: true
session-stat-max-count: 100
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: true
#设置监控页面的登录名和密码
login-username: admin
login-password: admin
allow: 127.0.0.1
#deny: 192.168.1.100
2,分析自动配置
扩展配置项 spring.datasource.druid
@Import({DruidSpringAopConfiguration.class, //监控springBean的;配置spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration.class,//监控页的配置:spring.datasource.druid.stat-view-servlet.enabled 默认开启
DruidWebStatFilterConfiguration.class, //web监控配置spring.datasource.druid.web-stat-filter.enabled 默认开启
DruidFilterConfiguration.class}) //所有Druid自己filter的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
private static final String FILTER_WALL_CONFIG_PREFIX = "spring.datasource.druid.filter.wall.config";
3,整合MyBatis
starter
SpringBoot官方的Starter: spring-boot-starter-*
第三方: *-spring-boot-starter
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
1,配置模式
全局配置文件
SqlSessionFactory : 自动配置好了
SqlSession 自动配置了**SqlSessionTemplate**组合了**SqlSession**
@Import(**AutoConfiguredMapperScannerRegistrar.class**)
Mapper : 只要我们写的操作MyBatis的接口标准了[**@Mapper **](/Mapper )** ** 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) //MyBatis配置项绑定类
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {}
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
}
可以修改配置文件中mybatis 开始的所有;
使用非注解
#配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #配置mybatis主配置文件
mapper-locations: classpath:mybatis/mapper/*.xml #配置mybatis mappere文件
configuration: #指定mybatis全局配置文件中的相关配置 #使用yaml配置文件 就不可以 指定主配置文件
map-underscore-to-camel-case: true
所有mybatis yaml配置 都在 MybatisProperties 中
导入mybatis官方starter
编写mapp接口 . 标注@Mapper注解
编写sql映射文件并绑定mapper接口
在applicattion.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议,配置在mybatis.configuration)
2,注解模式
@Mapper
public interface AccountDao {
@Select("select * from account_tbl where id = #{id}")
Account selectAcctById(int id);
}
3,混合模式
在配置中加入mapper就行了
最佳实战:
引入mybatis-starter
配置application.yaml中,指定mapper-location位置即可
编写Mapper接口并标注@Mapper注解
简单方法直接注解方式
复杂方法编写Mapper.xml进行映射绑定映射
//@MapperScan("com.xiao.dao,mapper")可以使用简化开发 在@SpringBootApplication类 可以不用每个 dao都写上[@Mapper ](/Mapper )
4,整合MyBatis-Plus完成CRUD
1,什么是MyBatis-Plus
MyBatis-Plus(简称 MP) 是一个MyBtis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发,提高效率而生.
mybatis plus 官网
不(我写的不 哈哈哈哈) 建议安装MyBatisX插件
2,整合MyBatis-Plus
自动配置
**MybatisPlusAutoConfiguration**配置类,**MybatisPlusProperties**配置项绑定.**mybatis-plus: xxx就是对 mybatis-plus的定制**
**SqlSessionFactory 自动配置好了** **底层是容器默认的数据源**
**mapperLocations 自动配置好了的.有默认值. classpath_:/mapper/_/**.xml;**任意包的类路径下的所有 mapper文件夹下任意路径下的所有xml都是映射sql映射文件,建议以后sql映射文件,放在mapper下****
**容器中也自动配置好了 SqlSessionTemplate**
@**Mapper 标注的接口也会被自动扫描;** 建议直接MapperScan("com.xiao.dao.mapper")进行批量扫描
继承BaseMapper 优点:只需要继承BaseMapper就可以拥有crud能力
3,CRUD功能
service <查询哪些数据类型>
public interface User1Service extends IService<User1> {
}
serviceimpl
@Service
<当前service要用那个Dao,返回数据的类型>
public class User1ServiceImpl extends ServiceImpl<User1Dao, User1> implements User1Service {
}
分页插件
@GetMapping("/dynamic_table")
public String dynamic_table(Model model,@RequestParam(value = "page",defaultValue = "1")Integer page){
//从数据库中查出user表中的用户进行展示
// List<User1> list = user1Service.list();
//
//分页查询数据
Page<User1> userPage = new Page<>(page, 2);
//分页查询结果
Page<User1> page1 = user1Service.page(userPage, null);
//当前页
long current = page1.getCurrent();
//总页数
long pages = page1.getPages();
//总条记录
long total = page1.getTotal();
//查出数据库的数据
List<User1> records = page1.getRecords();
model.addAttribute("page",page1);
return "table/dynamic_table";
}
分页配置文件
@Configuration
public class MyBatisConfig {
/**
* MybatisPlusInterceptor
* @return
*/
@Bean
public MybatisPlusInterceptor paginationInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//这是分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
//设置请求的页面大于最大页后操作, true 调回到首页, false 继续请求 默认false
paginationInnerInterceptor.setOverflow(true);
//设置最大单页限制数量, 默认500条, -1 不受限制
paginationInnerInterceptor.setMaxLimit(500L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
HTML Thymeleaf使用
<tr class="gradeX" th:each="user,start:${page.records}">
<td th:text="${start.count}">Trident</td>
<td th:text="${user.name}">Internet
Explorer 4.0
</td>
<td th:text="${user.age}">Win 95+</td>
<td class="center hidden-phone" th:text="${user.email}">4</td>
<td class="center hidden-phone">
<a class="btn btn-danger" type="button" th:href="@{/user/delete/{id}(id=${user.id},pn=${page.current})}">删除</a>
</td>
数据
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 [[${page.current}]] 页,总计 [[${page.pages}]] 页,共 [[${page.total}]] 条记录
</div>
</div>
翻页
<li class="prev disabled"><a href="#">← Previous</a></li>
<li th:class="${num == page.current?'active':''}" th:each="num:${#numbers.sequence(1,page.pages)}"> //#numbers.sequence(遍历数字几到,几)
<a th:href="@{/dynamic_table(page=${num})}">[[${num}]]</a>//**********
</li>
<li class="next disabled"><a href="#">Next → </a></li>
删除
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
@RequestParam(value = "pn",defaultValue = "1")Integer pn,
RedirectAttributes ra){
user1Service.removeById(id);
//重定向后会携带 page 这个值 也就是页数
ra.addAttribute("pn", pn);
return "redirect:/dynamic_table";
}
2,NoSQL
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
1,Redis自动配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
自动配置:
RedisAutoConfiguration 自动配置类. RedisProperties 属性类 --> spring.redis.xxx 是对redis的配置
连接工厂是准备好的.LettuceConnectionConfiguration,JedisConnectionConfiguration
自动注入了**RedisTemplate**<**Object**, **Object**> : xxxTemplate;
自动注入了StringRedisTemplate;k:v都是String
**key: value**
**底层只要我们使用RedisTemplate,StringRedisTemplate就可以操作redis**
2,RedisTemplate与Lettuce
@Test
void testRedis(){
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
String hello = operations.get("hello");
System.out.println(hello);
}
3,切换至jedis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
配置redis and 声明 jedis
spring:
redis:
url: redis://localhost:6379 #本地redis连接
client-type: jedis #声明jedis
设置监听器 所有路径访问 默认 key为访问路径 每访问一次 就 value+1
@Autowired
StringRedisTemplate redisTemplate;
String uri = request.getRequestURI();
//默认每次访问当前uri就会计数+1
redisTemplate.opsForValue().increment(uri);
获取redis 数据库中的数据 放入 域中
ValueOperations<String, String> operations = redisTemplate.opsForValue();
String mains = operations.get("/main.html");
model.addAttribute("mains",mains);
7,单元测试
1,JUnit5的变化
SpringBoot2.2.0版本开始引入JUnit5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同.由三个不同子项目的几个不同模块组成.
JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: JUnit Platform是在JVM上启动测试框架的基础,不仅支持JUnit自制的测试引擎,其他测试引擎也都可以接入.
Junit Jupiter : Junit Jupiter提供了JUnit5的新的编程模型,是Junit5新特性核心.包含了一个测试引擎,用于在Junit Platform上运行.
JUnit Vintage : 由于JUnit已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容Junit4.x,Junit3.x的测试引擎.
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖.如果需要兼容junit4需要自行引入
<!-- 单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
现在版本:
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Test
void testRedis(){
}
}
以前:
@SpringBootTest + @RunWith(SpringTest.class)
SpringBoot真个Junit以后
编写测试方法 : @Test标注 (注意需要使用junit5版本的注解)
Junit类具有Spring的功能,@Autowired,比如 [@Transactional ](/Transactional ) 标注方法 测试完成后 会自动回滚
2,JUnit5常用注解
JUnit5的注解与JUnit4的注解有所变化
@Test: 表示方法是测试方法,但是与JUnit4的@Test不同,他的责任非常单一不能声明任何属性, 拓展的测 试将会由Jupiter提供额外测试
@**ParameterizedTest**:表示方法是参数化测试,下面会有详细介绍
@**RepeatedTest**:表示方法可以重复执行,下方会有详细介绍
@**DisplayName**:为测试类或者测试方法设置展示名称
@**BeforeEach**:表示在每个单元测试执行之前执行
@**AfterEach**:表示在每个单元测试执行之后执行
@**BeforeAll**:表示在所有单元测试执行之前执行
@**AfterAll**:表示在所有单元测试之后执行
@**Tag**:表示单元测试类别,类似与JUnit4中的[@lgnore ](/lgnore )
@**Disabled** : 禁用当前测试方法
@**Timeout**:表示测试方法运行如果超过了指定时间将会返回错误
@**ExtendWith**:为测试类或者测试方法提供扩展类引用
3,断言(assertions)
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证.这些断言方法都是org.juniter.api.Assertions的静态方法. JUnit 5 内置的断言可以分成如下几个类型:
检查业务逻辑返回的数据是否合理.
所有的测试运行结束以后,会有一个详细的测试报告;
1,简单断言
用来对单个值进行简单的验证.如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或者两个原始类型是否相等 |
assertNotEquals | 判断两个对象或者两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为true |
assertFalse | 判断给定的布尔值是否为false |
assertNull | 判断给定的对象引用是否为null |
assertNotNull | 判断给定的对象引用是否不为null |
2,数组断言
通过assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
@Test
@DisplayName("array assertion")
void TestArrayAssertArrayEquals(){
Assertions.assertArrayEquals(new int[]{1,2},new int[]{1,2},"数组内容不相等");
}
3,组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过lambda表达式很容易的提供这些断言
@Test
@DisplayName("array all 组合断言")
void TestAll(){
/**
* 所有断言全部需要成功
* 可以使用函数写法
*/
Assertions.assertAll("test",
()-> Assertions.assertTrue(true && true,"结果不为true"),
()->Assertions.assertEquals(1,1,"结果不是预期"));
}
4,异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的.而JUnit5提供了一种新的断言方式Assertions.assertThrows(),配合函数式编程就可以进行使用.
@Test
@DisplayName("异常断言")
void testExceptionAssert(){
//断定业务逻辑一定会出现异常
Assertions.assertThrows(ArithmeticException.class,
()->{int i=10/0;},
"业务逻辑数据运算居然正常运行");
}
5,超时断言
Junit5还提供了Assertions.asserTimeout()为测试方法设置了超时时间
@Test
@DisplayName("超时断言")
void timeoutTest(){
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000),
() -> Thread.sleep(500),"业务方法超时");
}
6,快速失败
通过 fail 方法直接使得测试失败
@Test
@DisplayName("快速失败断言")
void shouldFail(){
//判断业务逻辑一定出现异常
if (2 == 2){
Assertions.fail("This should fail");
}
}
4,前置条件(assumptions)
JUnit5的前置条件 (assumptions [假设]) 类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止.前置条件可以看成是测试方法执行的前提,当该前提不满足是,就没有据需执行的必要.
/**
* 测试前置条件
*/
@Test
@DisplayName("测试前置条件")
void testAssumptions(){
Assertions.assertTrue(false,"结果不是true");
System.out.println(111);
}
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false , 不满足条件会使得测试执行终止. assumingThat的参数是表示条件的布尔值和对应的Executable接口的实现对象.只有条件满足时,Executable对象才会被执行;当条件不满足时,测试执行并不会终止.
5,嵌套测试
JUnit5可以通过java中的内部类和@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起.在内部类中可以使用@BeforeEach和@AfterEach注解,而且嵌套的层次没有限制.
@DisplayName("嵌套测试")
public class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
//在嵌套测试情况下,外层的Test不能驱动内层的Before(After)Each/All之类的方法提前/之后运行
// Assertions.assertNotNull(stack);
Assertions.assertNull(stack);
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
//判断元素是空的
Assertions.assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
Assertions.assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
Assertions.assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
/**
* 内层的Test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行
*/
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
Assertions.assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
Assertions.assertEquals(anElement, stack.pop());
Assertions.assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
Assertions.assertEquals(anElement, stack.peek());
Assertions.assertFalse(stack.isEmpty());
}
}
}
}
6,参数化测试
参数化测试是JUnit5很重要的一个新特性,他使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利.
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码.
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型.Class类型
@NullSource: 表示参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource: 表示读取指定CSV文件内容作为参数化测试入参
@MethodSource: 表示读取指定方法的返回值作为参数化测试入参(注意方法返回值需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步. 让我真正感到他的强大之处的地方在于他可以支持外部的各类入参. 如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参.只需要去实现 ArgumentsProvuder接口, 任何外部文件都可以作为它的入参.
@ValueSource(ints = {1,2,3,4,5})
@DisplayName("参数化测试")
//声明这不是一个普通的测试 而是一个参数化测试
@org.junit.jupiter.params.ParameterizedTest
//这是个什么类型 valueSource 普通类型都有
@ValueSource(ints = {1,2,3,4,5})
void ParameterizedTest1(int i){
System.out.println(i);
}
@MethodSource(“要获取的方法名”)
@DisplayName("参数化测试")
//声明这不是一个普通的测试 而是一个参数化测试
@org.junit.jupiter.params.ParameterizedTest
//获取方法流中的 字符串 参数
@MethodSource("stringProvider")
void ParameterizedTest2(String i){
System.out.println(i);
}
static Stream<String> stringProvider(){
return Stream.of("apple","banana","atguigu");
}
8,指标监控
1,SpringBoot Actuator
1,简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控,追踪,审计,控制等. SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获取生产级别的应用监控,审计功能.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2,1.x与2.x的不同
3,如何使用
引入场景
访问http://localhost:8080/actuator/**
暴露所有监控信息为HTTP
management:
endpoints:
enabled-by-default: true #默认开始所有监控端点 暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露所有端点
测试
[http://localhost:20010/actuator/beans](http://localhost:20010/actuator/beans)
[http://localhost:20010/actuator/configprops](http://localhost:20010/actuator/configprops)
[http://localhost:20010/actuator/metrics](http://localhost:20010/actuator/metrics)
[http://localhost:20010/actuator/mertrics/jvm.gc.pause](http://localhost:20010/actuator/mertrics/jvm.gc.pause)
.......
4,可视化
[http://github.com/codecentric/spring-boot-admin](http://github.com/codecentric/spring-boot-admin)
2,Actuator Endpoint
1,最常用的端点
ID | 描述 |
---|---|
auditevents |
公开当前应用程序的审计事件信息。需要一个AuditEventRepository 豆子。 |
beans |
显示应用程序中所有 Spring bean 的完整列表。 |
caches |
公开可用的缓存。 |
conditions |
显示在配置和自动配置类上评估的条件以及它们匹配或不匹配的原因。 |
configprops |
显示所有@ConfigurationProperties . |
env |
公开 Spring 的ConfigurableEnvironment . |
flyway |
显示已应用的任何 Flyway 数据库迁移。需要一个或多个Flyway 豆子。 |
health |
显示应用程序运行状况信息。 |
httptrace |
显示 HTTP 跟踪信息(默认情况下,最近 100 个 HTTP 请求-响应交换)。需要一个HttpTraceRepository 豆子。 |
info |
显示任意应用程序信息。 |
integrationgraph |
显示 Spring 集成图。需要依赖spring-integration-core . |
loggers |
显示和修改应用程序中记录器的配置。 |
liquibase |
显示已应用的任何 Liquibase 数据库迁移。需要一个或多个Liquibase 豆子。 |
metrics |
显示当前应用程序的“指标”信息。 |
mappings |
显示所有@RequestMapping 路径的整理列表。 |
quartz |
显示有关 Quartz 调度程序作业的信息。 |
scheduledtasks |
显示应用程序中的计划任务。 |
sessions |
允许从 Spring Session 支持的会话存储中检索和删除用户会话。需要使用 Spring Session 的基于 servlet 的 Web 应用程序。 |
shutdown |
让应用程序正常关闭。默认禁用。 |
startup |
显示由. _ ApplicationStartup 需要 SpringApplication 配置 BufferingApplicationStartup . |
threaddump |
执行线程转储。 |
如果您的应用程序是 Web 应用程序(Spring MVC、Spring WebFlux 或 Jersey),您可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump |
返回一个堆转储文件。在 HotSpot JVM 上,HPROF 返回一个 -format 文件。在 OpenJ9 JVM 上, PHD 返回一个 -format 文件。 |
jolokia |
当 Jolokia 在类路径上时,通过 HTTP 公开 JMX bean(不适用于 WebFlux)。需要依赖jolokia-core . |
logfile |
返回日志文件的内容(如果已设置logging.file.name 或属性)。 logging.file.path 支持使用 HTTP Range 标头检索部分日志文件内容。 |
prometheus |
以 Prometheus 服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus . |
最常用的Endpoint
**Health: 监控状况**
**Metrics: 运行时指标**
**Loggers: 日志记录**
2,Health Endpoint
健康检查点,我们一般用于在云平台,平台会定时的检查应用健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合.
重要的几点:
health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
很多的健康检查默认已经自动配置好了,比如:数据库,redis等
可以很容易的添加自定义的健康检查机制
对某个端点的具体配置
#management 是所有actuator的配置
#management.endpoint.端点名.xxx: 对某个端点的具体配置
management:
#配置所有端点的默认行为
endpoints:
enabled-by-default: true #默认开始所有监控端点 暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露所有端点
#配置单个的端点的默认行为
endpoint:
health:
show-details: always #health的详细信息 always 显示health的详细信息
3,Metrics Endpoint
提供详细的,层级的,空间指标信息,这些信息可以被pull(主动推送) 或者push (被动获取) 方式得到;
通过Metrics对接多种监控系统
简化核心Metrics开发
添加自定义Metrics或者拓展已有Metrics
4,管理Endpoints
1,开始与禁用Endpoints
默认所有的Endpoint除过shutdown都是开启的.
需要开启或者禁用摸个Endpoint.配置模式为 **management.endpoint..enabled = true**
management:
endpoints:
beans:
enabled: true
或者禁用所有的Endpoint然后手动开启指定的Endpoint
management:
endpoints:
enabled-by-default: false #暴露所有端点信息
endpoints:
beans:
enabled: true
health:
enabled: true
2,暴露Endpoints
支持的暴露方式
HTTP: 默认只暴露**health**和**info** Endpoint
JMX: 默认暴露所有Endpoint
除过health和info,剩下的Endpoint都应该进行保护访问.如果引入SpringSecurity,则会默认配置安全访问规则
ID | JMX | Web |
---|---|---|
auditevents |
是的 | 不 |
beans |
是的 | 不 |
caches |
是的 | 不 |
conditions |
是的 | 不 |
configprops |
是的 | 不 |
env |
是的 | 不 |
flyway |
是的 | 不 |
health |
是的 | 是的 |
heapdump |
不适用 | 不 |
httptrace |
是的 | 不 |
info |
是的 | 不 |
integrationgraph |
是的 | 不 |
jolokia |
不适用 | 不 |
logfile |
不适用 | 不 |
loggers |
是的 | 不 |
liquibase |
是的 | 不 |
metrics |
是的 | 不 |
mappings |
是的 | 不 |
prometheus |
不适用 | 不 |
quartz |
是的 | 不 |
scheduledtasks |
是的 | 不 |
sessions |
是的 | 不 |
shutdown |
是的 | 不 |
startup |
是的 | 不 |
threaddump |
是的 | 不 |
management:
endpoints:
enabled-by-default: true #默认开始所有监控端点 暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露所有端点
#management 是所有actuator的配置
#management.endpoint.端点名.xxx: 对某个端点的具体配置
management:
#配置所有端点的默认行为
endpoints:
enabled-by-default: false #默认开始所有监控端点 暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露所有端点
#配置单个的端点的默认行为
endpoint:
health:
show-details: always #health的详细信息 always 显示health的详细信息
enabled: true
info:
enabled: true
beans:
enabled: true
metrics:
enabled: true
3,定制 Endpoint
1,定制Health信息
实现 implements(继承) HealthIndicator 或者 继承抽象类 AbstractHealthIndicator
类名必须是 XXXHealthIndicator
@Component //XXXHealthIndicator
public class MyComHealthIndicator extends AbstractHealthIndicator {
/**
* 真实的检查方法
* @param builder
* @throws Exception
*/
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//mongodb 获取连接进行测试
Map<String,Object> map = new HashMap<>();
if (1 == 1){
// builder.up(); //健康
builder.status(Status.UP);
map.put("count",1);
map.put("ms",100);
}else {
// builder.down(); //不健康
builder.status(Status.OUT_OF_SERVICE);
map.put("err","连接超时");
map.put("ms",3000);
}
builder.withDetail("code",100)
.withDetails(map);
}
}
2,定制info信息
常用两种方式
1,编写配置文件
#设置当前info信息 但是好像使用不了了 可能是版本更新了
#info:
# appName: boot-admin
# appVersion: 1.0.0
# mavenProjectName: @project.artifactId@
# mavenProjectVersion: @project.version@
2,编写InfoContributor info 实现 InfoContributor 接口
@Component
public class AppInfoInfoContributor implements InfoContributor{
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("function","web")
.withDetail("edition","1.0.0");
}
}
http://localhost:8080/actuator/info 会输出以上方式返回所有info信息
3,定制Metrics信息
1,SpringBoot支持自动适配的Metrics
JVM metrics,report utilization of:
Various memory and buffer pools
Statisics related to garbage collection
Threads utilization
Number of classes loaded/unloaded
CPU metrics
File descriptor metrics
Kafka consumer and producer metrics
@Service
public class CityServicImpl implements CityServic {
@Autowired
private CityDao cityDao;
Counter counter;
//写在构造方法里 ****
public CityServicImpl(MeterRegistry meterRegistry){
counter = meterRegistry.counter("cityService.saveCity.count");
}
@Override
public City SelectCityById(long id) {
//每执行一次CityServicImpl counter ++
counter.increment();
City byId = cityDao.getById(id);
return byId;
}
}
//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue){
return (registry) -> Gauge.builder("queueSize",queue::size).register(registry)
}
4,定制Endpoint
放入容器中
@Endpoint(id = “myservice”) 设置这是一个Endpoint 命名为 myservice
@ReadOperation 声明是一个端点的读操作
@WriteOperation 声明是一个端点的写操作
http://localhost:20010/actuator/myservice
@Component
@Endpoint(id = "myservice") //设置这是一个Endpoint
public class MyServiceEndPoint {
//@ReadOperation 声明是一个端点的读操作
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("dockerInfo","docker started.....");
}
//@WriteOperation 声明是一个端点的写操作
@WriteOperation
public void stopDocker(){
System.out.println("docker stopped....");
}
}
09,原理解析
1,Profile功能
为了方便多环境适配,SpringBoot简化了Profile功能
1,application-profile功能
默认指定配置文件 application.yaml; 任何时候都会加载
指定环境配置文件 application-{env}.yaml
激活指定环境
配置文件激活
命令行激活: java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
可以修改配置文件的任意值,命令行优先
@RestController
public class HelloController {
//如果拿不到person.name 那就返回默认值 : 后是默认值
@Value("${person.name:李四}")
private String name;
@GetMapping("/")
public String hello(){
System.out.println(name);
return "Hello" + name;
}
}
//使用那个配置文件 application.yaml 默认配置文件 默认加载
//如果出现同名配置 会以指定激活的配置文件优先
spring.profiles.active=prod //指定激活环境那个配置文件
//prod 环境 application-prod.yaml
person:
name: prod-张三
//test 环境 application-test.yaml
person:
name: test-张三
打成jar包 开启时 可以指定使用那个环境
java -jar boot-09-features-profile-0.0.1-SNAPSHOT.jar —spring.profiles.active=指定环境
2,@Profile条件装配功能
@Profile("test") //放在类上或者方法上 当前环境激活是 指定环境 才会开启使用
@Configuration //也一样里面
3,profile分组
#设置使用那个组
spring.profiles.active=myprod
#定义分组
spring.profiles.group.myprod[0]=ppd
spring.profiles.group.myprod[1]=prod
spring.profiles.group.mytest[0]=test
#同个组里的属性会合在一起
2,外部化配置
1,外部配置源
常用: java属性文件, YAML文件,环境变量,命令行参数;
//从环境变量里取值
@Value("${JAVA_HOME}")
private String msg;
//获取操作系统
@Value("${os.name}")
private String osName;
2,配置文件查找位置
(1)classpath 根路径
(2)classpath 根路径下config目录
(3)jar包当前目录
(4)jar包当前目录的config目录
(5)/config 子目录的直接子目录
3,配置文件加载顺序:
1,当前jar包内部的application.properties和application.yaml
2,当前jar包内部的application-{profile}.properties 和 application-{profile}.yaml
3,引用的外部jar包的application.properties和application.yaml
4,引用的外部jar包的applicat-{profile}.properties 和 application-{profile}.yaml
4,指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
3,自定义Starter
1,starter启动原理
autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
编写自动配置类 xxxAutoConfiguration -> xxxxProperties
[**@Configuration **](/Configuration )** **
[**@Conditional **](/Conditional )** **
**@EnableConfigurationProperties**
[**@Bean **](/Bean )** **
**....**
引入starter —- xxxAutoConfiguration —-容器中放入组件 —- 绑定xxxProperties —- 配置项
2,自定义starter
HelloProperties
@ConfigurationProperties("xiao.hello")
public class HelloProperties {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
——————————>
HelloServiceAutoConfiguration
@Configuration
@EnableConfigurationProperties(HelloProperties.class) //默认HelloProperties放在容器中
public class HelloServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService(){
HelloService helloService = new HelloService();
return helloService;
}
}
——————————>
HelloServce
/**
* 默认不要放在容器中
*/
public class HelloService {
@Autowired
HelloProperties helloProperties;
public String sayHello(String userName){
return helloProperties.getPrefix() + ":" + userName + ">" + helloProperties.getSuffix();
}
}
4,SpringBoot原理
Spring原理[Spring注解],SpringMVC原理,自动配置原理,SpringBoot原理
1,SpringBoot启动过程
- 创建 SpringApplication
- 保存一些信息。
- 判定当前应用的类型。ClassUtils。Servlet
- bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper
- 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer
- List
> initializers
- List
- 找 ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener
- List
> listeners
- List
- 运行 SpringApplication
- StopWatch
- 记录应用的启动时间
- 创建引导上下文(Context环境)createBootstrapContext()
- 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
- 让当前应用进入headless模式。java.awt.headless
- 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
- getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener.
- 遍历 SpringApplicationRunListener 调用 starting 方法;
- 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
- 保存命令行参数;ApplicationArguments
- 准备环境 prepareEnvironment();
- 返回或者创建基础环境信息对象。StandardServletEnvironment
- 配置环境信息对象。
- 读取所有的配置源的配置属性值。
- 绑定环境信息
- 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
- 创建IOC容器(createApplicationContext())
- 根据项目类型(Servlet)创建容器,
- 当前会创建 AnnotationConfigServletWebServerApplicationContext
- 准备ApplicationContext IOC容器的基本信息 prepareContext()
- 保存环境信息
- IOC容器的后置处理流程。
- 应用初始化器;applyInitializers;
- 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
- 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
- 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
- 刷新IOC容器。refreshContext
- 创建容器中的所有组件(Spring注解)
- 容器刷新完成后工作?afterRefresh
- 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
- 调用所有runners;callRunners()
- 获取容器中的 ApplicationRunner
- 获取容器中的 CommandLineRunner
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner。调用 run 方法
- 如果以上有异常,
- 调用Listener 的 failed
- 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
- running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed
2,Application Events and Listeners
ApplicationContextInitializer
ApplicationListener
SpringApplicationRunListener