Spring Boot
查看版本:java -version、mvn -v
找到mvn安装目录,配置如下依赖:
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
一、创建一个Maven项目
配置
在pom.xml中添加:
引入Spring Boot父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
新建一个主程序类
新建 com.hzc.boot包以及 MainApplication.java
添加@SpringBootApplication注解(告诉spring boot 这是一个 spring boot应用),以及运行主程序的run方法。
/**
* 主程序类
* 这是一个SpringBoot应用
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
写一个控制器
controller/HelloController
添加Controller注解:
@Controller
public class HelloController {
}
处理何种请求,@RequestMapping("/hello")
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String handle01() {
return "Hello, Spring Boot 2 !";
}
}
添加注解@ResponseBody表示在body中返回,,而我们想让所有请求的返回都在body中,则可以把@ResponseBody` 放到HelloController上。
在Spring Boot中 @RestController 可以代替 @ResponseBody和@Controller注解:
//@ResponseBody
//@Controller
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01() {
return "Hello, Spring Boot 2 !";
}
}
运行
直接运行 MainApplication,在浏览器打开localhost:8080/hello
抽取配置文件
在Spring Boot中,我们把所有的配置文件都抽取在一个配置文件resources/application.properties(以前在tomcat中配置等等)
server.port=8888
官网查看:docs.spring.io/spring-boot…
打包部署
引入插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
把项目打成jar包,直接在目标服务器执行即可
打Maven包
选择IDEA右侧 Maven -> 项目 -> Lifecycle -> 选中clean和package运行
在target中可以看到jar包,直接执行:java -jar com.hzc-1.0-SNAPSHOT.jar
去掉cmd的快速编辑模式
右键cmd,属性,取消 快速编辑模式
二、SpringBoot-依赖管理
父项目做依赖管理
依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
上面项目的父项目如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
它几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
开发导入starter场景启动器
- 见到很多
spring-boot-starter-*:*就是某种场景 - 只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
- 更多SpringBoot所有支持的场景
- 见到的
*-spring-boot-starter:第三方为我们提供的简化开发的场景启动器。
所有场景启动器最底层的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4RELEASE</version>
<scope>compile</scope>
</dependency>
无需关注版本号,自动版本仲裁
- 引入依赖默认都可以不写版本
- 引入非版本仲裁的jar,要写版本号
可以修改默认版本号
- 查看
spring-boot-dependencies里面规定当前依赖的版本用的 key。 - 在当前项目里面重写配置,如下面的代码
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
IDEA快捷键
ctrl + shift + alt + u:以图的方式显示项目中依赖之间的关系。alt + ins:创建新类、新包等。
自动配置特性
自动配好Tomcat
- 引入Tomcat依赖
- 配置Tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
自动配好SpringMVC
- 引入Spring MVC全套组件
- 自动配好Spring MVC常用组件(功能)
自动配好Web常见功能,如:字符编码问题
- SpringBoot帮我们配置好了所有web开发的常见场景(在容器中有就会生效)
public static void main(String[] args) {
// 1. 返回我们的IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
// 2. 查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for(String name : names) {
System.out.println(name);
}
}
默认的包结构
- 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
- 无需以前的包扫描配置
- 想要改变扫描路径
@SpringBootApplication(canBasePackages="com.lun")@ComponentScan指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.lun")
各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:
MultipartProperties - 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
- 非常多的starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在
spring-boot-autoconfigure包里面
三、底层注解
1. 底层注解 @Configuration 讲解
基本使用
- Full模式与Lite模式
- 示例
/**
* 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2. 配置类本身也是组件
* 3. proxyBeanMethods: 代理bean的方法
* Full(proxyBeanMethods = true)、(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
* Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)
* 组件依赖
*/
@Configuration(proxyBeanMethods = true) // 告诉SpringBoot这是一个配置类 ==配置文件
public class MyConfig {
@Bean // 给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回值,就是组件在容器中的实例
public User user01() {
User zhangsan = new User("zhangsan", 18);
// user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet() {
return new Pet("tomcat");
}
}
@Configuration测试代码:
/**
* 主程序类
* 这是一个SpringBoot应用
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
// 1. 返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
// 2. 查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for(String name : names) {
System.out.println(name);
}
// 3. 从容器中获取组件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("组件:" + (tom01 == tom02));
// 4. com.atguigu.boot.config.MyConfig...
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
// 如果@Configuration(proxyBeanMethods = true) 代理对象调用方法。SpringBoot总会检查这个组件是否在容器中
// 保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);
User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);
System.out.println("用户的宠物:" + (user01.getPet() == tom));
}
}
最佳实战
- 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
- 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)
IDEA快捷键:
Alt + Ins:生成getter、setter构造器等代码Ctrl + Alt + B:查看类的具体实现代码。
2. 底层注解-@Import导入组件
@Bean、@Component、@Controller、@Service、@Repository,它们是spring的基本标签,在Spring Boot中并未改变它们原来的功能。
@Import({User.class, DBHelper.class})给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) // 告诉SpringBoot这是一个配置累 == 配置文件
public class MyConfig {}
测试类:
// 1. 返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
// ...
// 5. 获取组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);
for(String s : beanNamesForType) {
System.out.println(s);
}
DBHelper bean1 = run.getBean(DBHelper.class);
System.out.println(bean1);
3. 【源码分析】 - 请求映射原理
图片
SpringMVC功能分析都从org.springframework.web.servlet.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的方法)处理
mappedHandler = getHandler(processedRequest);
// HandlerMapping: 处理器映射。/xxx->>xxxx
...
}
}
}
getHandler()方法下:
@Nullable
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;
}
this.handlerMappings在Debug模式下展现的内容:
图片
其中,保存了所有@RequestMapping和handler的映射规则。
图片
所有的请求映射都在HandlerMapping中:
- SpringBoot自动配置欢迎页的WelcomePageHandlerMapping。访问
/能访问到index.html; - SpringBoot自动配置了默认的RequestMappingHandlerMapping。
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义HandlerMapping。
IDEA快捷键:
- Ctrl + Alt + U:以UML的类图展现类有哪些继承类,派生类以及实现哪些接口。
- Ctrl + Alt + Shift + U:同上,区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现。
- Ctrl + H:以树形方式展现类层次结构图。
4. 常用参数注解使用
注解:
@PathVariable路径变量@RequestHeader获取请求头@RequestParam获取请求参数(指问号后的参数,url?a=1&b=2)@CookieValue获取Cookie值@RequestAttribute获取request域属性@RequestBody获取请求体[POST]@MatrixVariable矩阵变量@ModelAttribute
使用用例:
@RestController
public class ParameterTestController {
// 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("User-Agent") String userAgent,
@RequestHeader Map<String, String> params,
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie 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;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content) {
Map<String, Object> map = new HashMap<>();
map.put("content", content);
return map;
}
}
5. @RequestAttribute
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request) {
request.setAttribute("msg", "成功了...");
request.setAttribute("code", 200);
return "forward:/success"; // 转发到 /success请求
}
@GetMapping("/params")
public String testParam(Map<String, Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response) {
map.put("hello", "world666");
model.addAttribute("world", "hello666");
request.setAttribute("message", "HelloWorld");
Cookie cookie = new Cookie("c1", "v1");
response.addCookie(cookie);
return "forward:/success";
}
// <------------------主角@RequestAttribute在这个方法
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg", required = false) String msg,
@RequestAttribute(value = "code", required = false) Integer code,
HttpServletRequest request) {
Object msg1 = request.getAttribute("msg");
Map<String, Object> map = new HashMap<>();
Object hello = request.getAttribute("hello");
Object world = request.getAttribute("world");
Object message = request.getAttribute("message");
map.put("reqMethod_msg", msg1);
map.put("annotation_msg", msg);
map.put("hello", hello);
map.put("world", world);
map.put("message", message);
return map;
}
}
6. @MatrixVariable与UrlPathHelper
- 语法:请求路径:
/cars/sell;low=34;brand=byd,audi,yd - SpringBoot默认是禁用了矩阵变量的功能
- 手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。
- 举证变量必须有url路径变量才能被解析
手动开启矩阵变量
实现WebMvcConfigurer接口:
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
创建返回WebMvcConfigurerBean:
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
}
}
@MatrixVariable的用例
@RestController
public class ParameterTestController {
///cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map carsSell(@MetrixVariable("low") Integer low,
@MatrixVar("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;
}
// /boss/1;age=20/2;age=10
@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;
}
}
7. 【源码分析】- 各种类型参数解析原理
这要从DispatcherServlet开始说起:
public class DispatcherServlet extends FrameworkServlet {
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);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if(mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
}
}
}
}
HandlerMapping中找到能处理请求的Handler(Controller.method())。- 为当前Handler找一个适配器
HandlerAdapter,用的最多的是RequestMappingHandlerAdapter。 - 适配器执行目标方法并确定方法参数的每一个值。
HandlerAdapter
默认会加载所有HandlerAdapter
public class DispatcherServlet extends FrameworkServlet {
/** Detect all HandlerAdapters or just expect "handlerAdapter" bean?. */
private boolean detectAllHandlerAdapters = true;
// ...
private void initHandlerAdatapters(ApplicationContext context) {
this.handlerAdapters = null;
if(this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancesotr contexts.
Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if(!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
}
}
有这些HandlerAdapter:
图片
- 支持方法上标注
@RequestMapping - 支持函数式编程的
- ....
执行目标方法
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = null;
// ...
// Determin handler for the current request.
mappedHandler = getHandler(processedRequest);
if(mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determin handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// ...
// 本节重点
// Actually invoke the handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
}
HandlerAdapter接口实现类RequestMappingHandlerAdapter(主要用来处理@RequestMapping)
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
// ...
// AbstractHandlerMethodAdapter类的方法,RequestMappingHandlerAdapter继承AbstractHandlerMethodAdapter
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throw Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// handleInternal的核心
mav = invokeHandlerMethod(request, response, handlerMethod);
// ...
return mav;
}
}
参数解析器
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器argumentResolvers。
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception{
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if(this.argumentResolvers != null) { // <-----关注点
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// ...
}
}
this.argumentResolvers在afterPropertiesSet()方法内初始化
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
@Override
public void afterPropertiesSet() {
// ...
if(this.argumentResolvers == null) { // 初始化argumentResolvers
List<HandlerMethodArgumentResolver> resolvers = getDefaultArumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
//...
}
// 初始化了一堆的实现HandlerMethodArgumentResolver接口的
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argment resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if(KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// Custom arguments
if(getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
}
HandlerMethodArgumentResolverComposite类如下:(众多参数解析器argumentResolvers的包装类)。
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
// ...
public HandlerMethodArgumentResolverComposite addResolvers(
@Nullable HandlerMethodArgumentResolver... resolvers
) {
if(resolvers != null) {
Collections.addAll(this.argumentResolvers, resolvers);
}
return this;
}
// ...
}
我们看看HandlerMethodArgumentResolver的源码:
public interface HandlerMethodArgumentResolver {
// 当期那解析器是否支持解析这种参数
boolean supportsParameter(MethodParameter parameter);
@Nullable // 如果支持,就调用 resolveArgument
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
返回值处理器
ValueHandler
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if(this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if(this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// ...
}
}
this.returnValueHandlers在afterPropertiesSet()方法内初始化
public class RequestMappingHandlerAdapter extends AbstarctHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
@Override
public void afterPropertiesSet() {
// ...
if(this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
// 初始化了一堆的实现HandlerMethodReturnValueHandler接口的
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation-based return value types
handlers.add(new ServletModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if(getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch-all
if(!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
} else {
handlers.add(new ServletModelAttributeMethodProcessor(true));
}
return handlers;
}
}
HandlerMethodReturnValueHandlerComposite类如下:
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
// ...
public HandlerMethodReturnValueHandlerComposite addHandlers(
@Nullable List<? extends HandlerMethodReturnValueHandler> handlers) {
if(handlers != null) {
this.returnValueHandlers.addAll(handlers);
}
return this;
}
}
HandlerMethodReturnValueHandler接口:
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
回顾执行目标方法
public class DispatcherServlet extends FrameworkServlet {
// ...
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = null;
// ...
mv = ha.handle(processedRequest, response, mappedHandler.getHander());
}
}
RequestMappingHandlerAdapter的handle()方法:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializaingBean {
// ...
// AbstactHandlerMethodAdapter类的方法,RequestMappingHandlerAdapter继承AbstractHandlerMethodAdapter
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
@Overrid
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// handleInternal的核心
mav = invokeHandlerMethod(request, response, handlerMethod); // 解释看下节
// ...
return mav;
}
}
RequestMappingHandlerAdapter的invokeHandlerMethod()方法:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// ...
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if(this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArguemntResolvers(this.argumentResolvers);
}
if(this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// ...
// 关注点:执行目标方法
invocableMethod.onvokeAndHandle(webRequest, mavContainer);
if(asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
}
invokeAndHandle()方法如下:
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... provideArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// ...
try {
// returnValue存储起来
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch(Exception ex) {
// ...
}
}
@Nullable // InvocableHandlerMethod类的,ServletInvocableHandleMethod类继承InvocableHandlerMethod类
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... provideArgs) throws Exception {
// 获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// ...
return doInvoke(args);
}
@Nullable
protected Object doInvoke(Object... args) throws Exception {
Method method = getBridgedMethod(); // @RequestMapping的方法
ReflectionUtils.makeAccessible(method);
try {
if(KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
// 通过反射调用
return method.invoke(getBean(), args); // getBean() 指@RequestMapping的方法所在类的对象。
}
catch(IllegalArumentException ex) {
// ...
}
catch(InvocationTargetEception ex) {
// ...
}
}
}
如何确定目标方法每一个参数的值
重点分析ServletInvocableHandlerMethod的getMethodArgumentValues方法
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
// ...
@Nullable // InvocableHandlerMethod类的,ServletInvocableHandlerMethod类继承InvocableHandlerMethod类
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// ...
return doInvoke(args);
}
// 本节重点,获取方法的参数值
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;
}
// 查看resolvers是否有支持
if(!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 支持的话开始解析吧
arg[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch(Exception ex) {
// ...
}
}
return args;
}
}
this.resolvers的类型为HandlerMethodArgumentResolverComposite(在参数解析器章节提及)
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if(resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "].supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@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); // 找到了,resolver就缓存起来,方便稍后resolveArgument()方法使用
break;
}
}
}
return result;
}
}
小结
本节描述,一个请求发送到DispatcherServlet后的具体处理流程,也就是SpringMVC的主要原理。
本节内容较多且硬核,对日后编程很有帮助,需耐心对待。
可以运行一个示例,打断点,在Debug模式下,查看程序流程。
8. 【源码分析】- Servlet API参数解析原理
- WebRequest
- ServletRequest
- MultipartRequest
- HttpSession
- javax.servlet.http.PushBuilder
- Principal
- InputStream
- Reader
- HttpMethod
- Locale
- TimeZone
- ZoneId
ServletRequestMethodArgumentResolver用来处理以上的参数
public class ServletRequestMethodArgumentResolver implemnets HandlerMethodArgumentResolver {
@Nullable
private static Class<?> pushBuilder;
static {
try {
pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder", ServletRequestMethodArgumentResolver.class.getClassLoader());
} catch(ClassNotFoundException ex) {
// Servlet 4.0 PushBuilder not found - not supported for injection
pushBuilder = null;
}
}
@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) && !parameter.hasParameterAnnotations()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
// WebRequest / NativeWebRequest / ServletWebRequest
if(WebRequest.class.isAssignableFrom(paramType)) {
if(!paramType.isInstance(webRequest)) {
throw new IllegalStateException(
"Current request is not of type [" + paramType.getName() + "]: " + webRequest
);
}
return webRequest;
}
// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
if(ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
return resolveNativeRequest(webRequest, paramType);
}
// HttpServletRequest required for all further argument types
return resolveArugmnet(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}
private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
T nativeRequest = webRequest.getNativeRequest(requiredType);
if(nativeRequest == null) {
throw new IllegalStateException(
"Current request is not of type [" + requiredType.getName() + "]: " + webRequest
);
}
return nativeRequest;
}
@Nullable
private Object resolveAruemnt(Class<?> paramType, HttpServletRequest request) throws IOException {
if(HttpSession.class.isAssignableFrom(paramType)) {
HttpSession session = request.getSession();
if(session != null && !paramType.isInstance(session)) {
throw new IllegalStateException(
"Current session is not of type [" + paramType.getName() + "]: " + session);
}
return session;
} else if(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) {
return PushBuilderDelegate.resolvePushBuilder(request, paramType);
} else if(InputStream.class.isAssignableFrom(paramType)) {
InputStream inputStream = request.getInputStream();
if(inputStream != null && !paramType.isInstance(inputStream)) {
throw new IllegalStateException(
"Request input stream is not of type [" + paramType.getName() + "]: " + inputStream);
}
return inputStream;
} else if(Reader.class.isAssignableFrom(paramType)) {
Reader reader = request.getReader();
if(reader != null && !paramType.isInstance(reader)) {
throw new IllegalStateException(
"Request body reader is not of type [" + paramType.getName() + "]: " + reader);
}
return reader;
} else if(Principal.class.isAssignableFrom(paramType)) {
Principal userPrincipal = request.getUserPrincipal();
if(userPrincipal != null && !paramType.isInstance(userPrincipal)) {
throw new IllegalStateException(
"Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
}
return userPrincipal;
} else if(HttpMethod.class == paramType) {
return HttpMethod.resolve(request.getMethod());
} else if(Locale.class == paramType) {
return RequestContextUtils.getLocale(request);
} else if(TimeZone.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone : TimeZone.getDefault());
} else if(ZoneId.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault());
}
// Should never happen...
throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName());
}
/**
* Inner class to avoid a hard dependency on Servlet API 4.0 at runtime.
*/
private static class PushBuilderDelegate {
@Nullable
public static Object resolvePushBuilder(HttpServletRequest request, Class<?> paramType) {
PushBuilder pushBuilder = request.newPushBuilder();
if(pushBuilder != null && !paramType.isInstance(pushBuilder)) {
throw new IllegalStateException(
"Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder);
}
return pushBuilder;
}
}
}
用例:
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request) {
request.setAttribute("msg", "成功了....");
request.setAttribute("code", 200);
return "forward:/success"; // 转发到 /success请求
}
}
9. 【源码分析】- Model、Map原理
复杂参数:
- Map
- Model(map、model里面的数据会被放在request的请求域 request.setAttribute)
- Errors/BindingResult
- RedirectAttributes(重定向携带数据)
- ServletResponse(response)
- SessionStatus
- UriComponentsBuilder
- ServletUriComponentsBuilder
用例:
@GetMapping("/params")
public String testParam(Map<String, Object> map,
Model model,
HettpServletRequest request,
HttpServletResponse response) {
// 下面三位都是可以给request域中放数据
map.put("hello", "world666");
model.addAttribute("world", "hello666");
request.setAttribute("message", "HelloWorld");
Cookie cookie = new Cookie("c1", "v1");
response.addCookie(cookie);
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg", required = false) String msg,
@RequestAttribute(value="code", required = false) Integer code,
HttpServletRequest request) {
Object msg1 = request.getAttribute("msg");
Map<String, Object> map = new HashMap<>();
Object hello = request.getAttribute("hello"); // 得出testParam方法赋予的值 world666
Object world = request.getAttribute("world"); // 得出testParam方法赋予的值 hello666
Object message = request.getAttribute("message"); // 得出testParam方法赋予的值 HelloWorld
map.put("reqMethod_msg", msg1);
map.put("annotation_msg", msg);
map.put("hello", hello);
map.put("world", world);
map.put("message", message);
return map;
}
Map<String, Object> mapModel modelHttpServletRequest request
上面三位都是可以给request域中放数据,用request.getAttribute()获取
接下来我们看看,Map map与Model model用什么参数处理器。
Map<String, Object> map参数用MapMethodProcessor处理:
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length === 0);
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
// ....
}
mavContainer.getModel()如下:
public class ModelAndViewContainer {
// ...
private final ModelMap defaultModel = new BindingAwareModelMap();
@Nullable
private ModelMap redirectModel;
// ...
public ModelMap getModel() {
if(useDefaultModel()) {
return this.defaultModel;
} else {
if(this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
private boolean useDefaultModel() {
return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}
// ...
}
Model model用ModelMethodProcessor处理:
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderyFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
// ...
}
return mavContainer.getModel();这跟MapMethodProcessor的一致
图片
Model也是另一种意义的Map。
接下来看看Map<String, Object> map与Model model值是如何做到用request.getAttribute()获取的。
众所周知,所有的数据都放在ModelAndView包含要去的页面地址View,还包含Model数据。
先看ModelAndView接下来是如何处理的?
public class DispatcherServlet extends FrameworkServlet {
// ...
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
try {
ModelAndView mv = null;
// ...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// ...
}
catch(Exception ex) {
dispatchException = ex;
}
catch(Throwable err) {
// As of 4.3 we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理分发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// ...
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
// ...
// Did the handler return a view to render?
if(mv != null && !mv.wasCleared()) {
render(mv, request, response);
// ...
}
// ...
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
View view;
String viewName = mv.getViewName();
if(viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if(view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
}
} else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if(view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
view.render(mv.getModelInternal(), request, response);
// ...
}
在Debug模式下,view属为InternalResourceView类。
public class InternalResourceView extends AbstractUrlBasedView {
@Override // 该方法在AbstractView, AbstractUrlBasedView 继承了AbstractView
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
// 看下一个方法实现
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
@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);
// ...
}
// 该方法在AbstractView,AbstractUrlBasedView继承了AbstactView
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
model.forEach((name, value) -> {
if(value != null) {
request.setAttribute(name, value);
} else {
request.removeAttribute(name);
}
})
}
}
exposeModelAsRequestAttributes方法看出,Map<String, Object> map,Model model这两种类型数据可以给request域中放数据,用request.getAttribute()获取。
9. 【源码分析】- 自定义参数绑定原理
@RestController
public class ParameterTestController {
/**
* 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
* @param person
* @return
*/
@PostMapping("/saveuser")
public Person saveuser(Person person) {
return person;
}
}
/**
* 姓名: <input name="userName"/> <br/>
* 年龄: <input name="age"/> <br/>
* 生日: <input name="birth"/> <br/>
* 宠物姓名:<input name="pet.name"/><br/>
* 宠物年龄:<input name="pet.age"/>
*/
@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;
}
封装过程用到ServletModelAttributeMethodProcessor
public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
@Override // 本方法在ModelAttributeMethodProcessor类
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
@Override
@Nullable // 本方法在ModelAttributeMethodProcessor类
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// ...
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(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)) {
// web数据绑定器,将请求参数的值绑定到指定的JavaBean里面**
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if(binder.getBindResult().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利用它里面的Converters将请求数据转成指定的数据类型。再次封装到JavaBean中
在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter哪个可以将这个数据类型(request带来参数的字符串)转换到指定的类型
10. 【源码分析】- 自定义Converter原理
未来我们可以给WebDataBinder里面放自己的Converter;
下面演示将字符串"阿猫,3"转换成Pet对象。
// 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 conver(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;
}
})
}
}
}