RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对,分别用来
package com.itheima.a20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class A20 {
private static final Logger log = LoggerFactory.getLogger(A20.class);
public static void main(String[] args) throws Exception {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取映射结果
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k, v) -> {
System.out.println(k + "=" + v);
});
// 请求来了,获取控制器方法 返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test4");
request.setParameter("name", "张三");
request.addHeader("token", "某个令牌");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
// 检查响应
byte[] content = response.getContentAsByteArray();
System.out.println(new String(content, StandardCharsets.UTF_8));
/*System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有参数解析器");
for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) {
System.out.println(resolver);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有返回值解析器");
for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) {
System.out.println(handler);
}*/
/*
学到了什么
a. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化
b. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等
本章介绍两个最为重要的组件
a. RequestMappingHandlerAdapter, 以 @RequestMapping 作为映射路径
b. RequestMappingHandlerAdapter, 调用 handler
c. 控制器的具体方法会被当作 handler
- handler 的参数和返回值多种多样
- 需要解析方法参数, 由 HandlerMethodArgumentResolver 来做
- 需要处理方法返回值, 由 HandlerMethodReturnValueHandler 来做
*/
}
}
package com.itheima.a20;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {
// ⬅️内嵌 web 容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
return new TomcatServletWebServerFactory(serverProperties.getPort());
}
// ⬅️创建 DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// ⬅️注册 DispatcherServlet, Spring MVC 的入口
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registrationBean;
}
// 如果用 DispatcherServlet 初始化时默认添加的组件, 并不会作为 bean, 给测试带来困扰
// ⬅️1. 加入RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
// ⬅️2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
handlerAdapter.setCustomArgumentResolvers(List.of(tokenArgumentResolver));
handlerAdapter.setCustomReturnValueHandlers(List.of(ymlReturnValueHandler));
return handlerAdapter;
}
public HttpMessageConverters httpMessageConverters() {
return new HttpMessageConverters();
}
// ⬅️3. 演示 RequestMappingHandlerAdapter 初始化后, 有哪些参数、返回值处理器
// ⬅️3.1 创建自定义参数处理器
// ⬅️3.2 创建自定义返回值处理器
}
package com.itheima.a20;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
@Override
// 是否支持某个参数
public boolean supportsParameter(MethodParameter parameter) {
Token token = parameter.getParameterAnnotation(Token.class);
return token != null;
}
@Override
// 解析参数
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getHeader("token");
}
}
package com.itheima.a20;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.yaml.snakeyaml.Yaml;
import javax.servlet.http.HttpServletResponse;
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Yml yml = returnType.getMethodAnnotation(Yml.class);
return yml != null;
}
@Override // 返回值
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 1. 转换返回结果为 yaml 字符串
String str = new Yaml().dump(returnValue);
// 2. 将 yaml 字符串写入响应
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType("text/plain;charset=utf-8");
response.getWriter().print(str);
// 3. 设置请求已经处理完毕
mavContainer.setRequestHandled(true);
}
}
package com.itheima.a21;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/*
目标: 解析控制器方法的参数值
常见的参数处理器如下:
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216
org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b
org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3
*/
public class A21 {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();
// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
// 要点2. 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
// 多个解析器组合
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// false 表示必须有 @RequestParam
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
);
String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
if (composite.supportsParameter(parameter)) {
// 支持此参数
Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
// System.out.println(v.getClass());
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
System.out.println("模型数据为:" + container.getModel());
} else {
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
}
}
/*
学到了什么
a. 每个参数处理器能干啥
1) 看是否支持某种参数
2) 获取参数的值
b. 组合模式在 Spring 中的体现
c. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取
*/
}
private static HttpServletRequest mockRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name1", "zhangsan");
request.setParameter("name2", "lisi");
request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
System.out.println(map);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
request.setContentType("application/json");
request.setCookies(new Cookie("token", "123456"));
request.setParameter("name", "张三");
request.setParameter("age", "18");
request.setContent("""
{
"name":"李四",
"age":20
}
""".getBytes(StandardCharsets.UTF_8));
return new StandardServletMultipartResolver().resolveMultipart(request);
}
static class Controller {
public void test(
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") MultipartFile file, // 上传文件
@PathVariable("id") int id, // /test/124 /test/{id}
@RequestHeader("Content-Type") String header,
@CookieValue("token") String token,
@Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}
HttpServletRequest request, // request, response, session ...
@ModelAttribute("abc") User user1, // name=zhang&age=18
User user2, // name=zhang&age=18
@RequestBody User user3 // json
) {
}
}
static class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
package com.itheima.a22;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
/*
目标: 如何获取方法参数名, 注意把 a22 目录添加至模块的类路径
1. a22 不在 src 是避免 idea 自动编译它下面的类
2. spring boot 在编译时会加 -parameters
3. 大部分 IDE 编译时都会加 -g
*/
public class A22 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
// 1. 反射获取参数名
Method foo = Bean2.class.getMethod("foo", String.class, int.class);
/*for (Parameter parameter : foo.getParameters()) {
System.out.println(parameter.getName());
}*/
// 2. 基于 LocalVariableTable 本地变量表
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));
/*
学到了什么
a. 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
b. 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
1. 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
2. 接口, 不会包含局部变量表, 无法获得参数名 (这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名)
*/
}
}
package com.itheima.a23;
import org.springframework.beans.SimpleTypeConverter;
import java.util.Date;
public class TestSimpleConverter {
public static void main(String[] args) {
// 仅有类型转换的功能
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
Integer number = typeConverter.convertIfNecessary("13", int.class);
Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
System.out.println(number);
System.out.println(date);
}
}
package com.itheima.a23;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.GenericTypeResolver;
import org.springframework.format.Formatter;
import org.springframework.format.support.FormatterPropertyEditorAdapter;
import org.springframework.format.support.FormattingConversionService;
import java.util.Date;
public class TestBeanWrapper {
public static void main(String[] args) {
// 利用反射原理, 为 bean 的属性赋值
MyBean target = new MyBean();
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
wrapper.setPropertyValue("a", "10");
wrapper.setPropertyValue("b", "hello");
wrapper.setPropertyValue("c", "1999/03/04");
System.out.println(target);
}
static class MyBean {
private int a;
private String b;
private Date c;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public Date getC() {
return c;
}
public void setC(Date c) {
this.c = c;
}
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
package com.itheima.a23;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.validation.DataBinder;
import java.util.Date;
public class TestDataBinder {
public static void main(String[] args) {
// 执行数据绑定
MyBean target = new MyBean();
DataBinder dataBinder = new DataBinder(target);
dataBinder.initDirectFieldAccess();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("a", "10");
pvs.add("b", "hello");
pvs.add("c", "1999/03/04");
dataBinder.bind(pvs);
System.out.println(target);
}
static class MyBean {
private int a;
private String b;
private Date c;
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
package com.itheima.a23;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import java.util.Date;
public class TestServletDataBinder {
public static void main(String[] args) {
// web 环境下数据绑定
MyBean target = new MyBean();
ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("a", "10");
request.setParameter("b", "hello");
request.setParameter("c", "1999/03/04");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(target);
}
static class MyBean {
private int a;
private String b;
private Date c;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public Date getC() {
return c;
}
public void setC(Date c) {
this.c = c;
}
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
package com.itheima.a23;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.GenericTypeResolver;
import org.springframework.format.Formatter;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class MyDateFormatter implements Formatter<Date> {
private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class);
private final String desc;
public MyDateFormatter(String desc) {
this.desc = desc;
}
@Override
public String print(Date date, Locale locale) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.format(date);
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
log.debug(">>>>>> 进入了: {}", desc);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(text);
}
}
基本的类型转换与数据绑定用法
package com.itheima.a23;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import java.util.Date;
public class TestServletDataBinderFactory {
public static void main(String[] args) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday", "1999|01|02");
request.setParameter("address.name", "西安");
User target = new User();
// "1. 用工厂, 无转换功能"
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
// "2. 用 @InitBinder 转换" PropertyEditorRegistry PropertyEditor
// InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);
// "3. 用 ConversionService 转换" ConversionService Formatter
// FormattingConversionService service = new FormattingConversionService();
// service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
// ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
// initializer.setConversionService(service);
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
// "4. 同时加了 @InitBinder 和 ConversionService"
// InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
//
// FormattingConversionService service = new FormattingConversionService();
// service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
// ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
// initializer.setConversionService(service);
//
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), initializer);
// "5. 使用默认 ConversionService 转换"
ApplicationConversionService service = new ApplicationConversionService();
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(target);
}
static class MyController {
@InitBinder
public void aaa(WebDataBinder dataBinder) {
// 扩展 dataBinder 的转换器
dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
}
}
public static class User {
@DateTimeFormat(pattern = "yyyy|MM|dd")
private Date birthday;
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"birthday=" + birthday +
", address=" + address +
'}';
}
}
public static class Address {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Address{" +
"name='" + name + '\'' +
'}';
}
}
}
ServletRequestDataBinderFactory 的用法和扩展点
package com.itheima.a23.sub;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.ResolvableType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class TestGenericType {
public static void main(String[] args) {
// 小技巧
// 1. java api
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
Type type = TeacherDao.class.getGenericSuperclass();
System.out.println(type);
if (type instanceof ParameterizedType parameterizedType) {
System.out.println(parameterizedType.getActualTypeArguments()[0]);
}
// 2. spring api 1
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
Class<?> t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);
System.out.println(t);
// 3. spring api 2
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(ResolvableType.forClass(TeacherDao.class).getSuperType().getGeneric().resolve());
}
}
准备 @InitBinder 在整个 HandlerAdapter 调用过程中所处的位置
package com.itheima.a24;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/*
@InitBinder 的来源
*/
public class A24 {
private static final Logger log = LoggerFactory.getLogger(A24.class);
public static void main(String[] args) throws Exception {
/*
@InitBinder 的来源有两个
1. @ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并记录
2. @Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并记录
*/
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setApplicationContext(context);
handlerAdapter.afterPropertiesSet();
log.debug("1. 刚开始...");
showBindMethods(handlerAdapter);
Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);
getDataBinderFactory.setAccessible(true);
log.debug("2. 模拟调用 Controller1 的 foo 方法时 ...");
getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));
showBindMethods(handlerAdapter);
log.debug("3. 模拟调用 Controller2 的 bar 方法时 ...");
getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar")));
showBindMethods(handlerAdapter);
context.close();
/*
学到了什么
a. Method 对象的获取利用了缓存来进行加速
b. 绑定器工厂的扩展点(advice 之一), 通过 @InitBinder 扩展类型转换器
*/
}
@SuppressWarnings("all")
private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException {
Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
initBinderAdviceCache.setAccessible(true);
Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(handlerAdapter);
log.debug("全局的 @InitBinder 方法 {}",
globalMap.values().stream()
.flatMap(ms -> ms.stream().map(m -> m.getName()))
.collect(Collectors.toList())
);
Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
initBinderCache.setAccessible(true);
Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCache.get(handlerAdapter);
log.debug("控制器的 @InitBinder 方法 {}",
controllerMap.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> e.getKey().getSimpleName() + "." + v.getName()))
.collect(Collectors.toList())
);
}
}
HandlerMethod 需要
ServletInvocableHandlerMethod 需要
package com.itheima.a26;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.*;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import static com.itheima.a26.WebConfig.*;
public class A26 {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setApplicationContext(context);
adapter.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name", "张三");
/*
现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起, 并完成控制器方法的调用, 如下
*/
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
new Controller1(), Controller1.class.getMethod("foo", User.class));
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
handlerMethod.setDataBinderFactory(factory);
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取模型工厂方法
Method getModelFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getModelFactory", HandlerMethod.class, WebDataBinderFactory.class);
getModelFactory.setAccessible(true);
ModelFactory modelFactory = (ModelFactory) getModelFactory.invoke(adapter, handlerMethod, factory);
// 初始化模型数据
modelFactory.initModel(new ServletWebRequest(request), container, handlerMethod);
handlerMethod.invokeAndHandle(new ServletWebRequest(request), container);
System.out.println(container.getModel());
context.close();
/*
学到了什么
a. 控制器方法是如何调用的
b. 模型数据如何产生
c. advice 之二, @ModelAttribute 补充模型数据
*/
}
public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false),
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true),
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
);
return composite;
}
}
package com.itheima.a26;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@Configuration
public class WebConfig {
@ControllerAdvice
static class MyControllerAdvice {
@ModelAttribute("a")
public String aa() {
return "aa";
}
}
@Controller
static class Controller1 {
@ModelAttribute("b")
public String aa() {
return "bb";
}
@ResponseStatus(HttpStatus.OK)
public ModelAndView foo(@ModelAttribute("u") User user) {
System.out.println("foo");
return null;
}
}
static class User {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
准备 @ModelAttribute 在整个 HandlerAdapter 调用过程中所处的位置
package com.itheima.a27;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.*;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.util.UrlPathHelper;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
/*
目标: 解析控制器方法的返回值
常见的返回值处理器
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@4c9e38
org.springframework.web.method.annotation.ModelMethodProcessor@5d1e09bc
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@4bdc8b5d
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@3bcd426c
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@5f14a673
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@726a17c4
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@5dc3fcb7
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@c4c0b41
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@76911385
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@5467eea4
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@160396db
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@7a799159
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@40ab8a8
org.springframework.web.method.annotation.MapMethodProcessor@6ff37443
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@65cc8228
*/
public class A27 {
private static final Logger log = LoggerFactory.getLogger(A27.class);
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
// 1. 测试返回值类型为 ModelAndView
// 2. 测试返回值类型为 String 时, 把它当做视图名
// 3. 测试返回值添加了 @ModelAttribute 注解时, 此时需找到默认视图名
// 4. 测试返回值不加 @ModelAttribute 注解且返回非简单类型时, 此时需找到默认视图名
// 5. 测试返回值类型为 ResponseEntity 时, 此时不走视图流程
// 6. 测试返回值类型为 HttpHeaders 时, 此时不走视图流程
// 7. 测试返回值添加了 @ResponseBody 注解时, 此时不走视图流程
test7(context);
/*
学到了什么
a. 每个返回值处理器能干啥
1) 看是否支持某种返回值
2) 返回值或作为模型、或作为视图名、或作为响应体 ...
b. 组合模式在 Spring 中的体现 + 1
*/
}
private static void test7(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test7");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod methodHandle = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(request, response);
if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
if (!container.isRequestHandled()) {
renderView(context, container, webRequest); // 渲染视图
} else {
for (String name : response.getHeaderNames()) {
System.out.println(name + "=" + response.getHeader(name));
}
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
}
}
private static void test6(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test6");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod methodHandle = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(request, response);
if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
if (!container.isRequestHandled()) {
renderView(context, container, webRequest); // 渲染视图
} else {
for (String name : response.getHeaderNames()) {
System.out.println(name + "=" + response.getHeader(name));
}
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
}
}
private static void test5(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test5");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod methodHandle = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(request, response);
if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
if (!container.isRequestHandled()) {
renderView(context, container, webRequest); // 渲染视图
} else {
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
}
}
private static void test4(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test4");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod methodHandle = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test4");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
renderView(context, container, webRequest); // 渲染视图
}
}
private static void test3(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test3");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod methodHandle = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test3");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
renderView(context, container, webRequest); // 渲染视图
}
}
private static void test2(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test2");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod methodHandle = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
renderView(context, container, webRequest); // 渲染视图
}
}
private static void test1(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test1");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod methodHandle = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
renderView(context, container, webRequest); // 渲染视图
}
}
public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler() {
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
composite.addHandler(new ModelAndViewMethodReturnValueHandler());
composite.addHandler(new ViewNameMethodReturnValueHandler());
composite.addHandler(new ServletModelAttributeMethodProcessor(false));
composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new HttpHeadersReturnValueHandler());
composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
@SuppressWarnings("all")
private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
ServletWebRequest webRequest) throws Exception {
log.debug(">>>>>> 渲染视图");
FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
log.debug("没有获取到视图名, 采用默认视图名: {}", viewName);
// 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化
View view = resolver.resolveViewName(viewName, Locale.getDefault());
view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));
}
static class Controller {
private static final Logger log = LoggerFactory.getLogger(Controller.class);
public ModelAndView test1() {
log.debug("test1()");
ModelAndView mav = new ModelAndView("view1");
mav.addObject("name", "张三");
return mav;
}
public String test2() {
log.debug("test2()");
return "view2";
}
@ModelAttribute
// @RequestMapping("/test3")
public User test3() {
log.debug("test3()");
return new User("李四", 20);
}
public User test4() {
log.debug("test4()");
return new User("王五", 30);
}
public HttpEntity<User> test5() {
log.debug("test5()");
return new HttpEntity<>(new User("赵六", 40));
}
public HttpHeaders test6() {
log.debug("test6()");
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/html");
return headers;
}
@ResponseBody
public User test7() {
log.debug("test7()");
return new User("钱七", 50);
}
}
// 必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败
public static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
package com.itheima.a27;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
@Configuration
public class WebConfig {
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setDefaultEncoding("utf-8");
configurer.setTemplateLoaderPath("classpath:templates");
return configurer;
}
@Bean // FreeMarkerView 在借助 Spring 初始化时,会要求 web 环境才会走 setConfiguration, 这里想办法去掉了 web 环境的约束
public FreeMarkerViewResolver viewResolver(FreeMarkerConfigurer configurer) {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() {
@Override
protected AbstractUrlBasedView instantiateView() {
FreeMarkerView view = new FreeMarkerView() {
@Override
protected boolean isContextRequired() {
return false;
}
};
view.setConfiguration(configurer.getConfiguration());
return view;
}
};
resolver.setContentType("text/html;charset=utf-8");
resolver.setPrefix("/");
resolver.setSuffix(".ftl");
resolver.setExposeSpringMacroHelpers(false);
return resolver;
}
}
package com.itheima.a28;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class A28 {
public static void main(String[] args) throws IOException, NoSuchMethodException, HttpMediaTypeNotAcceptableException {
// test1();
// test2();
// test3();
test4();
/*
学到了什么
a. MessageConverter 的作用, @ResponseBody 是返回值处理器解析的, 但具体转换工作是 MessageConverter 做的
b. 如何选择 MediaType
- 首先看 @RequestMapping 上有没有指定
- 其次看 request 的 Accept 头有没有指定
- 最后按 MessageConverter 的顺序, 谁能谁先转换
*/
}
private static void test4() throws IOException, HttpMediaTypeNotAcceptableException, NoSuchMethodException {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(request, response);
request.addHeader("Accept", "application/xml");
response.setContentType("application/json");
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
List.of(
new MappingJackson2HttpMessageConverter(), new MappingJackson2XmlHttpMessageConverter()
));
processor.handleReturnValue(
new User("张三", 18),
new MethodParameter(A28.class.getMethod("user"), -1),
new ModelAndViewContainer(),
webRequest
);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
@ResponseBody
@RequestMapping(produces = "application/json")
public User user() {
return null;
}
private static void test3() throws IOException {
MockHttpInputMessage message = new MockHttpInputMessage("""
{
"name":"李四",
"age":20
}
""".getBytes(StandardCharsets.UTF_8));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
if (converter.canRead(User.class, MediaType.APPLICATION_JSON)) {
Object read = converter.read(User.class, message);
System.out.println(read);
}
}
private static void test2() throws IOException {
MockHttpOutputMessage message = new MockHttpOutputMessage();
MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {
converter.write(new User("李四", 20), MediaType.APPLICATION_XML, message);
System.out.println(message.getBodyAsString());
}
}
public static void test1() throws IOException {
MockHttpOutputMessage message = new MockHttpOutputMessage();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {
converter.write(new User("张三", 18), MediaType.APPLICATION_JSON, message);
System.out.println(message.getBodyAsString());
}
}
public static class User {
private String name;
private int age;
@JsonCreator
public User(@JsonProperty("name") String name, @JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
package com.itheima.a29;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.*;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class A29 {
// {"name":"王五","age":18}
// {"code":xx, "msg":xx, data: {"name":"王五","age":18} }
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
context.getBean(WebConfig.MyController.class),
WebConfig.MyController.class.getMethod("user")
);
handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(), null));
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers(context));
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndViewContainer container = new ModelAndViewContainer();
handlerMethod.invokeAndHandle(new ServletWebRequest(request, response), container);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
/*
学到了什么
a. advice 之三, ResponseBodyAdvice 返回响应体前包装
*/
}
public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false),
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true),
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
);
return composite;
}
public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers(AnnotationConfigApplicationContext context) {
// 添加 advice
List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);
List<Object> collect = annotatedBeans.stream().filter(b -> ResponseBodyAdvice.class.isAssignableFrom(b.getBeanType()))
.collect(Collectors.toList());
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
composite.addHandler(new ModelAndViewMethodReturnValueHandler());
composite.addHandler(new ViewNameMethodReturnValueHandler());
composite.addHandler(new ServletModelAttributeMethodProcessor(false));
composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new HttpHeadersReturnValueHandler());
composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()), collect));
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
}
package com.itheima.a29;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@Configuration
public class WebConfig {
@ControllerAdvice
static class MyControllerAdvice implements ResponseBodyAdvice<Object> {
// 满足条件才转换
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (returnType.getMethodAnnotation(ResponseBody.class) != null ||
AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) {
// returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)) {
return true;
}
return false;
}
// 将 User 或其它类型统一为 Result 类型
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
}
return Result.ok(body);
}
}
// @Controller
// @ResponseBody
@RestController
public static class MyController {
public User user() {
return new User("王五", 18);
}
}
public static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
ResponseBodyAdvice 增强 在整个 HandlerAdapter 调用过程中所处的位置
package com.itheima.a30;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
public class A30 {
public static void main(String[] args) throws NoSuchMethodException {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
resolver.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
// 1.测试 json
// HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));
// Exception e = new ArithmeticException("被零除");
// resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
// 2.测试 mav
// HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));
// Exception e = new ArithmeticException("被零除");
// ModelAndView mav = resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(mav.getModel());
// System.out.println(mav.getViewName());
// 3.测试嵌套异常
// HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
// Exception e = new Exception("e1", new RuntimeException("e2", new IOException("e3")));
// resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
// 4.测试异常处理方法参数解析
HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
Exception e = new Exception("e1");
resolver.resolveException(request, response, handlerMethod, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
/*
学到了什么
a. ExceptionHandlerExceptionResolver 能够重用参数解析器、返回值处理器,实现组件重用
b. 能够支持嵌套异常
*/
}
static class Controller1 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(ArithmeticException e) {
return Map.of("error", e.getMessage());
}
}
static class Controller2 {
public void foo() {
}
@ExceptionHandler
public ModelAndView handle(ArithmeticException e) {
return new ModelAndView("test2", Map.of("error", e.getMessage()));
}
}
static class Controller3 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(IOException e3) {
return Map.of("error", e3.getMessage());
}
}
static class Controller4 {
public void foo() {}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handler(Exception e, HttpServletRequest request) {
System.out.println(request);
return Map.of("error", e.getMessage());
}
}
}
package com.itheima.a31;
import com.itheima.a30.A30;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
public class A31 {
public static void main(String[] args) throws NoSuchMethodException {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
// ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
// resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
// resolver.afterPropertiesSet();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);
HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo"));
Exception e = new Exception("e1");
resolver.resolveException(request, response, handlerMethod, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
static class Controller5 {
public void foo() {
}
}
}
package com.itheima.a31;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import java.util.List;
import java.util.Map;
@Configuration
public class WebConfig {
@ControllerAdvice
static class MyControllerAdvice {
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(Exception e) {
return Map.of("error", e.getMessage());
}
}
@Bean
public ExceptionHandlerExceptionResolver resolver() {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
return resolver;
}
}
我们知道 @ExceptionHandler 只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内,那么如果是 Filter 出现了异常,如何进行处理呢?
在 Spring Boot 中,是这么实现的:
/error
也可以通过 ${server.error.path}
进行配置/error
这个地址
/error
,所以处理异常的职责就又回到了 Spring评价
- 一个错误处理搞得这么复杂,就问恶心不?
@Bean // ⬅️修改了 Tomcat 服务器默认错误地址, 出错时使用请求转发方式跳转
public ErrorPageRegistrar errorPageRegistrar() {
return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
}
@Bean // ⬅️TomcatServletWebServerFactory 初始化前用它增强, 注册所有 ErrorPageRegistrar
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
return new ErrorPageRegistrarBeanPostProcessor();
}
@Bean // ⬅️ErrorProperties 封装环境键值, ErrorAttributes 控制有哪些错误信息
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
errorProperties.setIncludeException(true);
return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
@Bean // ⬅️名称为 error 的视图, 作为 BasicErrorController 的 text/html 响应结果
public View error() {
return new View() {
@Override
public void render(
Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response
) throws Exception {
System.out.println(model);
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("""
服务器内部错误
""");
}
};
}
@Bean // ⬅️收集容器中所有 View 对象, bean 的名字作为视图名
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
@Bean("/c3")
public Controller controller3() {
return (request, response) -> {
response.getWriter().print("this is c3");
return null;
};
}
@Bean
public RouterFunctionMapping routerFunctionMapping() {
return new RouterFunctionMapping();
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
@Bean
public RouterFunction<ServerResponse> r1() {
// ⬇️映射条件 ⬇️handler
return route(GET("/r1"), request -> ok().body("this is r1"));
}
org.springframework.boot.autoconfigure.web.servlet.A35
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
Map<String, ResourceHttpRequestHandler> map
= context.getBeansOfType(ResourceHttpRequestHandler.class);
handlerMapping.setUrlMap(map);
return handlerMapping;
}
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
@Bean("/**")
public ResourceHttpRequestHandler handler1() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("static/")));
return handler;
}
@Bean("/img/**")
public ResourceHttpRequestHandler handler2() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("images/")));
return handler;
}
@Bean("/**")
public ResourceHttpRequestHandler handler1() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("static/")));
handler.setResourceResolvers(List.of(
// ⬇️缓存优化
new CachingResourceResolver(new ConcurrentMapCache("cache1")),
// ⬇️压缩优化
new EncodedResourceResolver(),
// ⬇️原始资源解析
new PathResourceResolver()
));
return handler;
}
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {
Resource resource = context.getResource("classpath:static/index.html");
return new WelcomePageHandlerMapping(null, context, resource, "/**");
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
例如根据 /hello 路径找到 @RequestMapping(“/hello”) 对应的控制器方法
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
DispatcherServlet 接下来会: