概念性的东西见: https://www.yuque.com/atguigu/springboot/rmxq85
需要环境: jdk8 & 兼容java14, maven 3.3+, idea 219.1.2
maven设置:
去到 maven 的配置文件中修改添加:
nexus-aliyun central Nexus aliyun http://maven.aliyun.com/nexus/content/groups/public jdk-1.8 true 1.8 1.8 1.8 1.8
Step 1: 创建 maven 工程
Step 2: 在pom文件引入依赖:
org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE org.springframework.boot spring-boot-starter-web
step 3: 在 src/main/java 下创建包和主类(也叫主程序应用)
// 告诉 springboot 这是一个springboot 应用
// 主程序应用
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
step 4: 在另一个包下创建 Controller 并编写业务代码
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01() {
return "hello SpringBoot";
}
}
step 5: 直接运行主应用的 main 方法
step 6: 在 resource 下创建 application.properties 文件, 该文件可以在全局进行配置, 详细见官方文档
step 7: 简化部署
通过加入插件便可以生成jar包, 直接在目标服务器执行即可
(命令行输入: 如 java -jar springboot01-1.0-SNAPSHOT.jar) 注意取消cmd的快速编辑模式
org.springframework.boot spring-boot-maven-plugin
在 target/下 SpringBoot 帮我们打包成了 jar 包, 里面有我们运行时所需的第三方 jar 包
SpringBoot自动配置原理 1.0 SpringBoot 基本特点 1.1 依赖管理依赖管理它的父项目 org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE 几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制, 所以我们无需声明版本号 org.springframework.boot spring-boot-dependencies 2.3.4.RELEASE
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的 jar,要写版本号。
自定义修改版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 2、在当前项目里面重写配置5.1.43
Staters
一组依赖的集合描述, 是 maven 的依赖特性
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、所有场景启动器最底层的依赖:1.2 自动配置org.springframework.boot spring-boot-starter 2.3.4.RELEASE compile
-
自动配置好 Tomcat
- 引入依赖
- 配置 Tomcat
-
自动配置好 SpringMVC
-
引入了 SpringMVC 的全套组件
如何查看 IOC 容器有那些组件:主程序应用下的主方法: public static void main(String[] args) { // 返回 IOC 容器 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 查看容器组件名 String[] names = run.getBeanDefinitionNames(); for(String s : names) { System.out.println(s); } run.getBean("yang", User.class); // 容器中只有一个组件 单实例 } -
自动配置好了 SpringMVC 常用组件 (功能)
-
-
自动配置好 Web 常见功能, 如: 字符编码问题
-
默认的包结构
- 主程序所在包及其下面的子包所有组件都会被默认扫描进来
- 无需以前的包扫描配置
- 想要改变扫描路径,@SpringBootApplication(scanbasePackages=“com.yangyu”)
或者 @ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot") // 可以在这里指定扫描路径
- 各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:MultipartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
- 按需加载所有自动配置项
- 非常多的 starter
- 只要引入了这个场景, 这个场景的自动配置就会开启
- SpringBoot 所有的自动配置功能都在 spring-boot-autoconfigure 包里面
1. @Configuration
Spring 添加组件的方法: 创建类, 创建 spring 的配置文件, 在 xml 文件中编写
SpringBoot 添加组件的方法:
-
配置类组件之间无依赖关系用 Lite 模式加速容器启动过程,减少判断
-
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用 Full 模式
@Configuration(proxyBeanMethods = true) // 告诉 SpringBoot 这是一个配置类 默认单实例
public class MyConfig {
// Full: 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
@Bean // 给容器添加组件, 以方法名作为组件的 id, 返回类型就是组件类型, 返回的值就是组件在容器中的实例
public User user01() {
return new User("01", 20);
}
@Bean("yang")
public User yangyu() {
return new User("yangyu", 20);
}
}
2. @Component @Controller @Service @Repository (适用于标注自己写的类) @Bean (适用于标注第三方包里面的组件)
3. @ComponentScan @import (快速给容器导入组件)
@import({User.class, DBHelper.class})
给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
@import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}
importSelector: 是一个接口, 需要自定义类进行实现, 需要重写的方法可以返回需要导入的组件的全类名数组, 需要返回哪些类的全类名就通过自己的逻辑去得到全类名然后返回一个全类名字符串数组 注意: 在config类中需要 @import 你实现的这个接口的类, 这样就会自动到容器中生成对应的实例, 你实现的这个接口的类不会在容器中生成实例
importBeanDefinitionRegistrar: 也是一个接口, 需要实现. 把所有需要添加到容器的 bean 调用BeanDefinitionRegistry.registerBeanDefinition 手工注册
**FactorBean (Spring 提供): **也是一个接口, 需要实现.
4. @Conditional
条件装配: 满足指定条件则进行组件注入
ConditionOnBean: 当容器中有这个组件时才…
@ConditionalOnBean(name = {"user01", "myConfig"})
2.2 原生配置文件引入
1. @importResource
原生xml: resouces/beans.xml
@importResource("classpath:beans.xml")
public class MyConfig {}
2.3 配置绑定
读取 properties 文件中的内容,并且把它封装到JavaBean中,以供随时使用;
原生java做法:
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。
}
}
}
1. @ConfigurationProperties
xxx.properties:
mycar.aaa=3 mycar.bbb=好
Car类:
@Component // 只有容器中的组件才有SpringBoot提供的强大功能
@ConfigurationProperties(prefix = "mycar")
public class Car {
private Integer aaa;
private String bbb;
}
2. @EnableConfigurationProperties + @ConfigurationProperties
如果是第三方类, 就在配置类上@EnableConfigurationProperties(Car.class)
- 开启 Car 配置绑定
- 把 Car 这个组件自动注册到容器里
和上一种区别: 要么用 @Component 加入到容器中, 要么用@EnableConfigurationProperties
3.0 自动配置原理入门 3.1 引导加载自动配置类@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}
1. @SpringBootConfiguration
其中的 @Configuration 代表当前类是一个配置类
2. @Component
指定要扫描哪些包, 参照 Spring 注解
3. EnableAutoConfiguration
@AutoConfigurationPackage
@import({AutoConfigurationimportSelector.class})
public @interface EnableAutoConfiguration {}
- @AutoConfigurationPackage
@import(AutoConfigurationPackages.Registrar.class) // 给容器中导入一个组件
public @interface AutoConfigurationPackage {}
// 利用Registrar给容器中导入一系列组件
// 将指定的一个包下的所有组件导入进来: MainApplication 所在包下
- @import({AutoConfigurationimportSelector.class})
1、利用 getAutoConfigurationEntry(annotationmetadata);给容器中批量导入一些组件 2、调用 List3.2 按需开启自动配置项configurations = getCandidateConfigurations(annotationmetadata, attributes)获取到所有需要导入到容器中的配置类 3、利用工厂加载 Map > 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 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。
@Bean
@ConditionalOnBean(MultipartResolver.class) // 容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) // 容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 给 @Bean 标注的方法传入了对象参数,这个参数的值就会从容器中找。
// SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
给容器中加入了文件上传解析器;
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {}
如果想自己配:
public CharacterEncodingFilter filter(){
return null;
}
总结:
-
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
-
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿. xxxProperties和配置文件进行了绑定
-
生效的配置类就会给容器中装配很多组件
-
只要容器中有这些组件,相当于这些功能就有了
-
定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
3.4 最佳实践- 引入场景依赖
https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters - 查看自己配置了哪些 (选做)
- 自己分析源码, 引入场景的自动配置一般都生效了
- application.properties中输入 debug=true 开启自动配置报告 Negative matches (不生效) Positive matches (生效)
- 是否需要修改
- 参照文档修改配置项
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
自己分析: xxxxProperties绑定了配置文件的哪些 - 自定义加入或替代组件
@Bean @Component - 自定义器 XXXCustomizer
- 参照文档修改配置项
简化 JavaBean 开发
并在 idea 搜索安装 lombok 插件 org.projectlombok lombok
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
============================================
// 简化日志开发
@Slf4j
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
log.info("请求进来了....");
return "123"
}
}
dev-tools
热更新 (实则是自动重启)
org.springframework.boot spring-boot-devtools true
项目或者页面修改以后:Ctrl+F9
Spring InitailizrIDEA 快速创建 SpringBoot 应用, 可以勾选你需要的场景, 并自动生成 pom 文件
4.0 配置文件 4.1 properties 4.2 yaml仍然是一种标记语言
非常适合用来做以数据为中心的配置文件
基本语法:
-
key: value;k v之间有空格
-
大小写敏感
-
使用缩进表示层级关系
-
缩进不允许使用tab,只允许空格
-
缩进的空格数不重要,只要相同层级的元素左对齐即可
-
'#'表示注释
-
字符串无需加引号,如果要加,’'与""表示字符串内容 会被 转义/不转义
数据类型:
-
字面量:单个的、不可再分的值。date、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
示例:
# yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33 # private Date birth;
age: 18
pet: # private Pet pet
name: tomcat
weight: 23.4
interests: [篮球,游泳] # private String[] interests;
animal: # private List animal;
- jerry
- mario
score: # private Map score;
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: # private Set salarys;
- 9999.98
- 9999.99
allPets: # private Map> allPets;
sick:
- {name: tom}
- {name: jerry,weight: 47}
- name: 阿虫
weight: 77.77
health: [{name: mario,weight: 47}]
4.3 配置提示
自定义的类和配置文件绑定一般没有提示
Web开发 1.0 SpringMVC自动配置概览org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-maven-plugin org.springframework.boot spring-boot-configuration-processor
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 and BeanNameViewResolver beans.
- 内容协商视图解析器和BeanName视图解析器
- Support for serving static resources, including support for WebJars (covered later in this document)).
- 静态资源 (包括webjars)
- Automatic registration of Converter, GenericConverter, and Formatter 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 改变默认底层组件
2.0 简单功能分析 2.1 静态资源访问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
只要静态资源放在类路径下: /static or /public or /resources or /meta-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射 private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; }
3. 欢迎页处理规则
EnableWebMvcConfiguration 类下
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) {
//要用欢迎页功能,必须是 {
//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);
}
}
}
2. Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 可以解析以上的部分参数
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
3. 复杂参数
Map、**Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、**Errors/BindingResult、RedirectAttributes (重定向携带数据)、ServletResponse (response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Mapmap, Model model, HttpServletRequest request 都是可以给request域中放数据, request.getAttribute();
Map, Model 类型的参数, 会返回 mavContainer.getModel(); (BindingAwareModelMap 类型, 是Model, 也是Map)
Model 存的是数据, View 存的是视图
4. 自定义对象参数
可以自动类型转换与格式化,可以级联封装
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private String age;
}
5. POJO 封装过程
ServletModelAttributeMethodProcessor 参数处理见下文
3.4 参数处理原理-
HandlerMapping 中找到能处理请求的 Handler(Controller.method())
-
为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
-
适配器执行目标方法并确定方法参数的每一个值
1. HandlerAdapter
0 - 支持方法上标注 @RequestMapping
1 - 支持函数式编程的
2. 执行目标方法
// Actually invoke the handler. // DispatcherServlet 类中的 doDispatch 方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); // 执行目标方法 // ServletInvocableHandlerMethod 真正执行目标方法 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 获取方法的参数值 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3. 参数解析器 - HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么
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;
}
6. 遍历找到哪个参数解析器能支持解析这个参数
@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;
}
7. 解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
8. 自定义类型参数 封装 POJO
ServletModelAttributeMethodProcessor 这个参数解析器支持
是否为简单类型:
public static boolean isSimplevalueType(Class> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
如: 文件上传: byte – > file
@FunctionalInterface public interface Converter
未来我们可以给 WebDataBinder 里面放自己的 Converter;
private static final class StringToNumber
自定义 Converter:
//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);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter() {
@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;
}
});
}
};
}
3.5 目标方法执行完成
将所有的数据都放在 ModelAndViewContainer; 包含要去的页面地址View. 还包含Model数据
3.5.1 处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView: @Override protected void renderMergedOutputModel( Mapmodel, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } rd.forward(request, response); } }
暴露模型作为请求域属性 // Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map4.0 数据响应与内容协商model, HttpServletRequest request) throws Exception { // model中的所有数据遍历挨个放在请求域中 model.forEach((name, value) -> { if (value != null) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); }
数据响应
- 响应页面
- 响应数据
- JSON
- XML
- xls
- 图片, 音视频
- 自定义协议数据
1. jackson.jar + @ResponseBody
org.springframework.boot spring-boot-starter-web
web 场景自动引入了 json 场景
返回值解析器
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 通过这个方法来决定
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
================= RequestResponseBodyMethodProcessor 类==============
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 使用消息转换器进行写出操作
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
返回值解析器原理
- 返回值处理器判断是否支持这种类型返回值 supportsReturnType
- 返回值处理器调用 handleReturnValue 进行处理
- RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的
- 利用 MessageConverters 进行处理 将数据写为 json
- 内容协商 (浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
- 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
- SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理
- 得到 MappingJackson2HttpMessageConverter 可以将对象写为json
- 利用 MappingJackson2HttpMessageConverter 将对象转为json再写出去
- 利用 MessageConverters 进行处理 将数据写为 json
SpringMVC 支持返回哪些返回值 由 returnValueHandlers 决定
ModelAndView Model View ResponseEntity ResponseBodyEmitter StreamingResponseBody HttpEntity HttpHeaders Callable DeferredResult ListenableFuture CompletionStage WebAsyncTask 有 @ModelAttribute 且为对象类型的 @ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
HTTPMessageConverter 原理
-
MessageConverter 规范
HttpMessageConverter: 看是否支持将此 Class 类型的对象, 转为MediaType类型的数据例如:Person 对象转为 JSON 或者 JSON 转为 Person
-
默认的 MessageConverter
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class SAXSource.class) StAXSource.class StreamSource.class Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的
最终 MappingJackson2HttpMessageConverter 把对象转为 JSON (利用底层的 jackson 的 objectMapper 转换的)
4.2 内容协商根据客户端接收能力不同,返回不同媒体类型的数据
1. 引入 xml 依赖
com.fasterxml.jackson.dataformat jackson-dataformat-xml
2. postman 测试返回 json 和 xml
只需要改变请求头中Accept字段. Http协议中规定的,告诉服务器本客户端可以接收的数据类型: application/xml
3. 开启浏览器参数方式协商功能
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml
确定客户端接收什么样的内容类型;
- Parameter策略优先确定是要返回 json 数据 (获取请求头中的format的值)
- 最终进行内容协商返回给客户端 json 即可。
内容协商原理
- 1、判断当前响应头中是否已经有确定的媒体类型。MediaType
- 2、获取客户端(PostMan、浏览器)支持接收的内容类型 (获取客户端Accept请求头字段)【application/xml】
- contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
- HeaderContentNegotiationStrategy() 确定客户端可以接收的内容类型
- 3、遍历循环所有当前系统的 MessageConverter, 看谁支持操作这个对象(Person)
- 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来
- 5、客户端需要【application/xml】. 服务端能力【10种、json、xml】
- 6、进行内容协商的最佳匹配媒体类型
- 7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化
实现多协议数据兼容。json、xml、x-guigu
-
@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
-
Processor 处理方法返回值。通过 MessageConverter 处理
-
所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
-
内容协商找到最终的 messageConverter;
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List> converters) {
converters.add(new GuiguMessageConverter());
}
}
}
============ GuiguMessageConverter ============
public class GuiguMessageConverter implements HttpMessageConverter {
@Override
public boolean canRead(Class> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class);
}
@Override
public List getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-guigu");
}
@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());
}
}
================== WebConfig =================
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//Map mediaTypes
Map mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
// parameterStrategy.setParameterName("gg");
HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
}
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?(提示: 参照SpringBoot官方文档web开发内容协商章节)
5.0 视图解析与模板引擎视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
5.1 视图解析1. 视图解析
- 转发
- 重定向
- 自定义视图
1. 视图解析原理流程
1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
2、任何目标方法执行完成以后都会返回 ModelAndView (数据和视图地址)
3、processDispatchResult() 处理派发结果 (页面该如何响应)
-
1、render(mv, request, response); 进行页面渲染逻辑
-
1、根据一些值(ViewName, )得到 View 对象【定义了页面的渲染逻辑】
-
1、所有的视图解析器尝试是否能根据当前返回值得到 View 对象
-
2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
-
3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象
-
4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
-
RedirectView 如何渲染【重定向到一个页面】
-
1、获取目标url地址
-
2、response.sendRedirect(encodedURL);
-
-
ThymeleafViewResolver:
-
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() --> render就是重定向
-
- 返回值是普通字符串: new ThymeleafView()–> render
自定义视图解析器+自定义视图: 大厂学院
5.2 模板引擎 - Thymeleaf现代化、服务端Java模板引擎
1. 基本语法
| 表达式 | 语法 | 用途 |
|---|---|---|
| 变量取值 | ${…} | 获取请求域, session 域, 对象等值 |
| 选择变量 | *{…} | 获取上下文对象值 |
| 消息 | #{…} | 获取国际化等值 |
| 链接 | @{…} | 生成链接 |
| 片段表达式 | ~{…} | jsp:include 作用, 引入公共页面片段 |
字面量
文本值: ‘one text’ , 'Another one!'
数字: 0 , 34 , 3.0 , 12.3
布尔值: true , false
空值: null
变量: one, two 变量不能有空格
文本操作
字符串拼接: +
变量替换: The name is ${name}
数学运算
+ , - , * , / , %
布尔运算
and, or, !, not
比较运算
比较: >, <, >=, <= (gt, lt, ge, le) 等式: ==, != (eq, ne)
条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
2. 设置属性值 - th:attr
代替写法




