1、什么是 yaml ?
YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。
适用于以数据为中心的配置文件
2、基本语法
key: value
, kv之间有一个空格3、针对每类数据类型的语法
(1)字面量:单个的、不可再分的值。 date、boolean、string、number、null
k: v
(2)对象:键值对的集合。map、hash、set、object
# 第一种写法
k: {k1:v1,k2:v2,k3:v3}
# 第二种写法
k:
k1: v1
k2: v2
k3: v3
(3)数组:一组按照次序排列的值。 array、list、queue
# 第一种写法
k: [v1,v2,v3]
# 第二中写法
k:
- v1
- v2
- v3
我们通过一个案例来演示,具体在yaml文件中如何对属性值进行配置
package com.atguigu.boot.bean;
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Bonbons
* @version 1.0
* 演示各种数据类型对应的yaml语法编写
*/
@Data
@Component
@ToString
@ConfigurationProperties(prefix = "person")
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String [] interest;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}
@Data
@Component
@ToString
class Pet{
private String name;
private Double weight;
}
person:
userName: "JiMei"
boss: true
birth: 2002/10/10
age: 21
interest: ['唱', '跳', 'Rap', '篮球']
pet:
name: "JiGe"
weight: 20
animal:
- '小猫'
- '小狗'
score: {english:99,history:82}
salarys:
- 12000.0
- 6000.0
allPets:
sick:
- {name:阿芙,weight:10}
- name: 赖皮蛇
weight: 88
health: [{name:诺手,weight:99}, {name:双面龟,weight:999}]
可以看到我在上面用到了 @ConfigurationProperties注解,因为也对 application.properties 进行了配置
server.port=8888
person.userName=Xxx
在附上我们的主程序
package com.atguigu.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Bonbons
* @version 1.0
*/
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
System.out.println("项目启动成功!");
}
}
运行结果如下
4、如何开启配置提示呢?
就是我们在编写 yml 文件的时候,会根据我们打出的部分提示可能输入的内容
我们可以导入一个依赖来完成这个功能
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
还有一个打包插件:
<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>
1、如何访问到静态资源?
resources 的六个子目录,出了vue是我自定义的静态资源文件夹,其他几个都是默认的
查找静态资源的优先级: 自定义 > /META-INF/resources/ > resources > static > public
spring:
resources:
static-locations: [classpath:/vue/]
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
使用了自定义的静态资源路径后,原来默认的静态资源路径仍会生效
下面为默认路径在源码中的相关体现:
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
2、如果一个项目中有很多静态资源,我们如何区分调用它们呢?
我们可以为静态资源设置访问前缀
spring:
mvc:
static-path-pattern: /res/**
之后我们想要在浏览器发送请求访问我们的静态资源,就要采取 当前项目 + static-path-pattern + 静态资源名 的方式
如果觉得加上前缀很麻烦,我们可以使用 webjar 来解决:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
/webjars/**
;/static-path-pattern属性值/webjars/**
3、SpringBoot 支持欢迎页特性
需要我们将欢迎页 index 放到静态资源路径下
可以配置额外的静态资源路径,但是不能设置访问前缀,否则我们只能通过 Controller 来处理 /index 请求 【底层源码是这样解释的】
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage.get());
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
我们可以采取下面的方案,来实现添加前缀也能找到Welcome页 【在配置文件中进行配置】
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, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
//要用欢迎页功能,必须是/**
logger.info("Adding welcome page: " + welcomePage.get());
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
// 调用Controller /index
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
4、我们还可以自定义网站图标
5、静态资源的配置原理
@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, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
配置文件的相关属性和xxx进行了绑定。WebMvcProperties==spring.mvc、ResourceProperties==spring.resources
在配置类中只有一个有参构造器
//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean 给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
1、rest(一种编程风格)的使用和原理
REST 风格支持 使用HTTP请求方式动词来表示对资源的操作
正常不支持直接将method设置为 put 和 delete 的表单请求,会默认改为get请求
hidden-method: filter: enabled: true
(1) 编写Mapping映射
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
(2) 在欢迎页里编写表单
<form action="/user" method="get">
<input value="REST-GET 提交" type="submit" />
form>
<form action="/user" method="post">
<input value="REST-POST 提交" type="submit" />
form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="put">
<input value="REST-PUT 提交" type="submit" />
form>
<form action="/user" method="post">
<input name="_zbc" type="hidden" value="delete">
<input name="_method" type="hidden" value="delete">
<input value="REST-DELETE 提交" type="submit" />
form>
(3)在 application.yml 中开启restful风格的设定
package com.atguigu.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
/**
* @author Bonbons
* @version 1.0
*/
@Configuration
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
//修改_method为_zbc
hiddenHttpMethodFilter.setMethodParam("_zbc");
return hiddenHttpMethodFilter;
}
}
2、在使用Rest完成表单提交的原理 【只用于表单提交】
需要注意:上面的这个应用原理是针对表单请求而言的,而且在SpringBoot中是通过配置选择开启的
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
3、我们需要了解请求映射的原理
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的方法)处理
mappedHandler = getHandler(processedRequest);
//HandlerMapping:处理器映射。/xxx->>xxxx
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则
所有的请求映射都在HandlerMapping中。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
1、先介绍@PathVariable、@RequestHeader、@RequestAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
@PathVariable("name") String name
,用{}代表路径接下来我们通过具体的代码进行演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试基本注解</title>
</head>
<body>
<!--用户发起GET请求-->
<a href="/car/3/owner/zhangsan?age=18&inters=basket&inters=game">点击发起测试</a>
<!--&age=18&inters=basket&inters=game-->
<!--获取请求的请求体,因为只有post请求才有请求体,所以我们设计一个表单-->
<form action="/save" method="post">
测试RequestBody获取数据 <br/>
用户名 <input name="username" /><br>
邮箱 <input name="email" />
<input type="submit" value="提交">
</form>
<!--测试矩阵变量-->
<a href="/cars/sell;low=32;brand=biyadi,benchi">一个属性对应多个值,采用逗号隔开</a> <br>
<a href="/cars/sell;low=21;brand=aodi;brand=laosilaisi">一个属性对应多个值,采用k=v分号隔开</a> <br>
<a href="/boss/1;age=38/2;age=22">绑定多个路径,存在相同属性</a>
</body>
</html>
package com.atguigu.boot.controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Bonbons
* @version 1.0
*
*/
@RestController
public class ParameterTestController {
//结合表单发起POST请求,测试@RequestBody获取请求体
@PostMapping("/save")
public Map postMethod(@RequestBody String context){
Map<String, Object> map = new HashMap<>();
map.put("context", context);
return map;
}
//测试了路径变量、请求体、请求参数、Cookie值
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie){
//这里直接运行会产生保存,原因:我没有 _ga 这个@Cookie
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pv",pv);
map.put("userAgent",userAgent);
map.put("headers",header);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
map.put("_ga",_ga);
System.out.println(cookie.getName()+"===>"+cookie.getValue());
return map;
}
//测试的是矩阵变量
// 测试/cars/sell;low=31;brand=biyadi,benchi
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<Object> 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;
}
//针对不同路径有相同属性名的演示
@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;
}
}
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//关闭自动移除分号后内容 -- 开启矩阵变量
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
2、补充几个知识点
@RestController注解声明控制器时,代表将控制器方法的返回值当做数据响应浏览器
当浏览器的Cookie被禁用,我们如何获取请求数据呢?
@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);
}
编写一个两个控制器方法进行测试:
//我们要测试Map、Model、HttpServletRequest、HttpServletResponse 作为参数的效果
@GetMapping("/params")
public String testParams(Map<String, Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response){
//对于Map、Model、HttpServletRequest 都可以向请求域中添加数据
map.put("hello", "world");
model.addAttribute("world", "hello");
request.setAttribute("message", "HelloWorld");
//创建一个Cookie
Cookie cookie = new Cookie("c1", "v1");
response.addCookie(cookie);
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(name = "msg", required = false) String msg,
@RequestAttribute(name = "code", required = false) Integer code,
HttpServletRequest request){
Map<String, Object> map = new HashMap<>();
//尝试获取我们添加到请求域中的数据
Object hello = request.getAttribute("hello");
Object world = request.getAttribute("world");
Object message = request.getAttribute("message");
map.put("hello", hello);
map.put("world", world);
map.put("message", message);
return map;
}
将Map、Model作为参数,相当于调用了RequestAttribute,将参数返回值添加到请求域中
请求域参数不是必须的 required = false;
为什么 Model 和 Map都是对同一个请求域进行操作呢?
(1) 我们设计的POJO类
package com.atguigu.boot.bean;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.Date;
import javax.xml.crypto.Data;
/**
* @author Bonbons
* @version 1.0
*/
@lombok.Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Date birth;
private Pet pet;
}
package com.atguigu.boot.bean;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @author Bonbons
* @version 1.0
*/
@lombok.Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
private String name;
private Integer age;
}
(2) 发起请求的前端表单
<!--自定义对象函数测试-->
<form action="/savePerson" method="post">
姓名:<input name="userName" value="zhangsan"> <br>
年龄:<input name="age" value="18" />
生日:<input name="birth" value= "2002-06-01" />
宠物姓名:<input name="pet.name" value="小花">
宠物年龄:<input name="pet.age" value="2">
<input type="submit" value="保存" />
</form>
(3) Controller 类
@PostMapping("/savePerson")
public Person savePerson(Person person){
return person;
}
SpringBoot 之所以可以自动获取表单值封装为指定类型对象,是因为SpringBoot 具有严密的参数解析机制, 但是若我们的输入值SpringBoot 不能解析时,那么应该如何处理呢?
<form action="/savePerson" method="post">
姓名:<input name="userName" value="zhangsan"> <br>
年龄:<input name="age" value="18" />
生日:<input name="birth" value= "2002-06-01" />
宠物:<input name="pet.name" value="小花,2">
<input type="submit" value="保存" />
</form>
我们可以使用自定义参数封装POJO
package com.example.demo2.config;
import com.example.demo2.bean.Pet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer{
//1、WebMvcConfigurer定制化SpringMVC的功能
@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) {
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(split[1]);
return pet;
}
return null;
}
});
}
};
}
}
1、HandlerAdapter
2、执行目标方法
在源码中执行目标方法的核心语句是下面这条:
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav 是ModelAndView的对象
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3、参数解析器 HandlerMethodArgumentResolver
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;
}
@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;
}
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));
}
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file
@FunctionalInterfacepublic interface 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<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;
}
});
}
};
}
6、在目标方法执行完成后
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据
7、最后派发处理结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, 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(Map<String, Object> model,
HttpServletRequest request) throws Exception {
//model中的所有数据遍历挨个放在请求域中
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
1、我们如何实现让浏览器响应JSON数据呢?
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
2、引出返回值解析器的概念:
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);
}
3、那么具体是怎么处理的呢?
(1)先获取返回值和返回值类型,找那个返回值处理器能处理,找到了(handler不为空)就用它处理返回值
@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);
}
4、SpringMVC到底支持哪些返回值
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
5、分析RequestResponseBodyMethodProcessor是如何处理的?
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
*/*
代表能接受所有请求 【postman用于模拟浏览器发送请求】
演示一下我们使用PostMan模拟浏览器发送请求
6、说一下内容协商的原理是什么?
拿到返回值先判断是不是资源类型,接下来是内容协商的核心环节【判断当前响应头中是否已经有确定的媒体类型。MediaType】
然后获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)
遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
找到支持操作Person的converter,把converter支持的媒体类型统计出来。【获得一个集合】
用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化【前俩支持Person -> JSON、后俩支持Person -> XML】
导入了jackson处理xml的包,xml的converter就会自动进来
WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
7、自定义 MessageConverter
方案一:设计三个方法,让这三种不同的请求的请求路径不一样
方案二:就要说到如何自定义converter了
我们要定义一个类实现 HttpMessageConverter 接口,在接口中指定规则
package com.atguigu.boot.converter;
import com.atguigu.boot.bean.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* @author Bonbons
* @version 1.0
*/
public class MyMessageConverter implements HttpMessageConverter<Person> {
@Override
public boolean canRead(Class<?> aClass, MediaType mediaType) {
//读我们指的是通过@RequestBody注解作为参数携带过来的数据,此处我们不关心读
return false;
}
@Override
public boolean canWrite(Class<?> aClass, MediaType mediaType) {
//返回值类型是Person类型就能写
return aClass.isAssignableFrom(Person.class);
}
/**
* 服务器要统计MessageConverter能写出哪些类型[兼容的媒体类型]
* 我们通过parseMediaTypes将我们自定义类型传递进去
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-zwh");
}
@Override
public Person read(Class<? extends Person> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
//将我们Person中的数据拼装成一个用分号分隔的字符串
String data = person.getName() + ";" + person.getAge() + ";" + person.getBirth();
//通过输出流将我们的数据写出去
OutputStream body = httpOutputMessage.getBody();
body.write(data.getBytes());
}
}
接下来要到我们的配置类中,重写 extendsMessageConverters 方法
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyMessageConverter());
}
此时已经可以处理我们自定义请求,此处我通过 PostMan 模拟 【测试成功】
我们需要注意一点:重写那个方法要写在配置类的 WebMvcConfigurer 中
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
}
}
8、如何以参数的方式进行内容协商
那么我们是否可以采用基于参数的形式制定自定义协商策略呢?
重写 configureContentNegotiation 方法配置协商功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
//向集合中添加策略
mediaTypes.put("xml", MediaType.APPLICATION_ATOM_XML);
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("gg", MediaType.parseMediaType("application/x-guigu"));
//指定支持哪些参数对应的媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
configurer.strategies(Arrays.asList(parameterStrategy));
}
};
如果我们现在不进行处理,那么基于请求头的协商策略就会失效(accept无论为什么都会以json的形式返回)
我们需要在自定义的协商策略中,添加基于请求头的协商策略
也就是将上面这段重写的方法替换为
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
//向集合中添加策略
mediaTypes.put("xml", MediaType.APPLICATION_ATOM_XML);
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("gg", MediaType.parseMediaType("application/x-guigu"));
//指定支持哪些参数对应的媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//添加基于请求头的协商策略
HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy, headerStrategy));
}
几个注意事项: