1.DispatcherServlet初始化时机
- DispatcherServlet是由spring创建的,初始化是由Tomcat完成的,通过setLoadOnStartup来决定是否为tomcat启动时初始化
@Configuration
@ComponentScan // 没有设置扫描包的话默认扫描当前配置的包及其子包
@PropertySource("classpath:application.properties") // 指定资源文件读取的位置
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class}) //使 @ConfigurationProperties 注解生效,并且将组件加入 IOC 容器中
public class WebConfig {
// 内嵌web容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
serverFactory.setPort(serverProperties.getPort());
return serverFactory;
}
// 创建DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册DispatcherServlet到tomcat
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
// 1=tomcat启动时DispatcherServlet初始化
// -1=第一次请求到时候初始化
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registrationBean;
}
}
public class A10Application {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
context.registerBean(WebConfig.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh();
}
}
server.port=8080
spring.mvc.servlet.load-on-startup=1
2.DispatcherServlet初始化
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context); // 初始化文件上传解析器
this.initLocaleResolver(context); // 初始化本地语言解析器 (中文、英文...)
this.initThemeResolver(context);
this.initHandlerMappings(context); // 初始化映射处理器
this.initHandlerAdapters(context); // 初始化适配处理器
this.initHandlerExceptionResolvers(context); // 初始化异常解析处理器
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
-
RequestMappingHandlerMapping用途
/* 如果使用DispatcherServlet的默认RequestMappingHandlerMapping,从容器中是获取不到这个bean的,因为DispatcherServlet将它放入到了自已的成员变量中 */ @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { return new RequestMappingHandlerMapping(); } public class A10Application { public static void main(String[] args) throws Exception { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); // 解析RequestMapping以及派生注解,生成路径与控制器方法的映射关系,在初始化时完成 RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 获取映射结果 handlerMapping.getHandlerMethods().forEach((k, v) -> { System.out.println(k + "=" + v); }); // 请求来了,获取控制器方法,返回控制链结果 HandlerExecutionChain chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/test1")); System.out.println(chain); } }
-
RequestMappingHandlerAdapter用途
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { return new RequestMappingHandlerAdapter(); } public class A10Application { public static void main(String[] args) throws Exception { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); // 解析RequestMapping以及派生注解,生成路径与控制器方法的映射关系,在初始化时完成 RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 获取映射结果 handlerMapping.getHandlerMethods().forEach((k, v) -> { System.out.println(k + "=" + v); }); // RequestMappingHandlerMapping作用:请求来了,获取控制器方法,返回控制链结果 MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2"); request.setParameter("name", "张三"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = handlerMapping.getHandler(request); System.out.println(chain); // RequestMappingHandlerAdapter作用:调用控制器 RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); adapter.handle(request, response, chain.getHandler()); System.out.println(">>>>>>>>>>>>>>>>> 参数解析器"); for (HandlerMethodArgumentResolver resolver : adapter.getArgumentResolvers()) { System.out.println(resolver); } System.out.println(">>>>>>>>>>>>>>>>> 返回值解析器"); for (HandlerMethodReturnValueHandler returnValueHandler : adapter.getReturnValueHandlers()) { System.out.println(returnValueHandler); } } }
-
自定义参数解析器
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); // 自定义参数解析器 TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); adapter.setArgumentResolvers(Arrays.asList(tokenArgumentResolver)); return adapter; } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Token { } public class TokenArgumentResolver implements HandlerMethodArgumentResolver { // 是否支持某个参数 @Override public boolean supportsParameter(MethodParameter parameter) { Token annotation = parameter.getParameterAnnotation(Token.class); return annotation != null; } // 解析参数 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return webRequest.getHeader("token"); } } public class A10Application { public static void main(String[] args) throws Exception { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); // 解析RequestMapping以及派生注解,生成路径与控制器方法的映射关系,在初始化时完成 RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 获取映射结果 handlerMapping.getHandlerMethods().forEach((k, v) -> { System.out.println(k + "=" + v); }); // RequestMappingHandlerMapping作用:请求来了,获取控制器方法,返回控制链结果 MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3"); request.addHeader("token","令牌"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = handlerMapping.getHandler(request); System.out.println(chain); // RequestMappingHandlerAdapter作用:调用控制器 RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); adapter.handle(request, response, chain.getHandler()); } } @PutMapping("/test3") public ModelAndView test3(@Token String token){ System.out.println(token); return null; }
-
自定义结果处理器
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Yml { } 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 { // 转化结果为yaml字符串 String value = new Yaml().dump(returnValue); // 将yaml结果写入响应 HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); response.setContentType("text/plain;charset=utf-8"); response.getWriter().println(value); // 设置请求已经处理完毕 mavContainer.setRequestHandled(true); } } @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); // 自定义结果处理器 YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler(); adapter.setReturnValueHandlers(Arrays.asList(ymlReturnValueHandler)); // 自定义参数解析器 TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); adapter.setArgumentResolvers(Arrays.asList(tokenArgumentResolver)); return adapter; } public class A10Application { public static void main(String[] args) throws Exception { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); // 解析RequestMapping以及派生注解,生成路径与控制器方法的映射关系,在初始化时完成 RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 获取映射结果 handlerMapping.getHandlerMethods().forEach((k, v) -> { System.out.println(k + "=" + v); }); // RequestMappingHandlerMapping作用:请求来了,获取控制器方法,返回控制链结果 MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test4"); request.addHeader("token","令牌"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = handlerMapping.getHandler(request); System.out.println(chain); // RequestMappingHandlerAdapter作用:调用控制器 RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); adapter.handle(request, response, chain.getHandler()); // 查看响应 String result = response.getContentAsString(StandardCharsets.UTF_8); System.out.println(result); } }
3.参数解析器
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
) {
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class User {
private String name;
private int age;
}
}
4.对象绑定与类型转换
1.转换接口与实现
-
第一套底层转换接口与实现
- printer把其它类型转换为string
- parser把string转换为其它类型
- formatter综合printer与parser功能
- convert把类型S转为T
- printer、parser、convert适配转成GenericConverter放入converters集合
- formattingConversionService利用它们实现转换
-
第二套底层转换接口与实现
- jdk自带的
- propertyEditor把string与其它类型相互转换
- propertyEditorRegistry可以注册多个propertyEditor对象
- 与第一套接口直接可以通过formattingPropertyEditorAdapter来进行适配
-
高级转换接口与实现
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
- 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
- 再看有没有 ConversionService 转换
- 再利用默认的 PropertyEditor 转换
- 最后有一些特殊处理
- SimpleTypeConverter 仅做类型转换
- BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property
- DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field
- ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
public class TestDataBinder {
public static void main(String[] args) {
MyBean myBean = new MyBean();
/* BeanWrapperImpl
BeanWrapperImpl wrapper = new BeanWrapperImpl(myBean);
wrapper.setPropertyValue("a",10);
wrapper.setPropertyValue("b","b");
wrapper.setPropertyValue("c","2023/01/01");
System.out.println(myBean);
*/
/* DirectFieldAccessor
DirectFieldAccessor accessor = new DirectFieldAccessor(myBean);
accessor.setPropertyValue("a",10);
accessor.setPropertyValue("b","b");
accessor.setPropertyValue("c","2023/01/01");
System.out.println(myBean);
*/
/* ServletRequestDataBinder
ServletRequestDataBinder binder = new ServletRequestDataBinder(myBean);
MutablePropertyValues values = new MutablePropertyValues();
values.add("a",10);
values.add("b","b");
values.add("c","2023/01/01");
binder.bind(values);
System.out.println(myBean);*/
}
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 "User{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
public class TestServletDataBinder {
public static void main(String[] args) {
MyBean myBean = new MyBean();
ServletRequestDataBinder binder = new ServletRequestDataBinder(myBean);
// initDirectFieldAccess直接与Filed进行属性绑定 不需要set方法
binder.initDirectFieldAccess();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("a","19");
request.setParameter("b","10");
request.setParameter("c","2023/01/01");
binder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(myBean);
}
static class MyBean {
private int a;
private String b;
private Date c;
@Override
public String toString() {
return "User{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
5.绑定工厂
-
Date类型格式为yyyy|MM|dd 如果使用以下方式与对象绑定,birthday=null,因为默认的转换器无法识别此格式的类型,所以需要添加自定义转换器来绑定
-
ServletRequestDataBinder binder = new ServletRequestDataBinder(user); binder.bind(new ServletRequestParameterPropertyValues(request));
public class TestDataBinderFactory {
public static void main(String[] args) throws Exception {
User user = new User();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday","2023|01|01");
request.setParameter("address.name","湘潭");
/*
1.用工厂,无转换功能
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,null);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
*/
/*
2.用InitBinder转换 底层走是的jdk的PropertyEditorRegistry
InvocableHandlerMethod invocableHandlerMethod = new InvocableHandlerMethod(new MyController(),MyController.class.getMethod("aaa", WebDataBinder.class));
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Arrays.asList(invocableHandlerMethod),null);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
*/
/*
3.用ConversionService转换
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
FormattingConversionService conversionService = new FormattingConversionService();
conversionService.addFormatter(new MyDateFormatter("用ConversionService方式扩展的"));
initializer.setConversionService(conversionService);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,initializer);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
*/
/*
4.使用默认用ConversionService转换 配合@DateTimeFormat使用
*/
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
ApplicationConversionService conversionService = new ApplicationConversionService();
initializer.setConversionService(conversionService);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,initializer);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
binder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
static class MyController{
@InitBinder
public void aaa (WebDataBinder binder){
binder.addCustomFormatter(new MyDateFormatter("用InitBinder方式扩展的"));
}
}
static class User{
@DateTimeFormat(pattern = "yyyy|MM|dd")
private Date birthday;
private Address address;
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"birthday=" + birthday +
", address=" + address +
'}';
}
}
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 + '\'' +
'}';
}
}
}
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);
}
}
6.获取泛型参数
7.控制器方法执行流程
@Configuration
public class WebConfig {
@Controller
static class Controller1 {
@ResponseStatus(HttpStatus.OK) // 先不使用ReturnValueHandlerComposite 方便测试
public ModelAndView foo(User user) { // user加入到ModelAndViewContainer中 结果= user:User{name='张三'}
System.out.println("user"+":"+user);
return null;
}
}
static class User{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
public class A11Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name","张三");
/*
通过ServletInvocableHandlerMethod把这些整合在一起,并完成控制器方法的调用
*/
ServletInvocableHandlerMethod method = new ServletInvocableHandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo", WebConfig.User.class));
method.setDataBinderFactory(new ServletRequestDataBinderFactory(null,null));
method.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
method.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
ModelAndViewContainer container = new ModelAndViewContainer();
method.invokeAndHandle(new ServletWebRequest(request),container);
context.close();
}
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(Arrays.asList(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true),
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
);
return composite;
}
}
8.返回值处理器
@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;
}
}
/*
目标: 解析控制器方法的返回值
常见的返回值处理器
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 +
'}';
}
}
}
9.MessageConverter
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 +
'}';
}
}
}
10.ControllerAdvice
1.配合@InitBinder
- 作用:所有控制器需要自定义类型转换器时,可以用@InitBinder补充自定义的转换器
2.配合@ModelAttribute
- 作用:方法的返回值会作为模型数据补充到控制器的执行过程中
3.Response/RequestBodyAdvice
- 作用:对请求体或者响应体增强或者扩展,本质上对消息的一个扩展(读消息写消息时做扩展)
@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;
}
}
}
11.异常处理
public class ExceptionHandlerDemo {
public static void main(String[] args) throws NoSuchMethodException, UnsupportedEncodingException {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(ListUtil.of(new MappingJackson2HttpMessageConverter()));
// 默认参数解析器、结果解析器
resolver.afterPropertiesSet();
/*
测试json
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerMethod handler = new HandlerMethod(new Controller1(),Controller1.class.getMethod("foo"));
resolver.resolveException(request,response,handler,new ArithmeticException("被零整除了"));
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
*/
/*
测试mav
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerMethod handler = new HandlerMethod(new Controller2(),Controller2.class.getMethod("foo"));
ModelAndView modelAndView = resolver.resolveException(request, response, handler, new ArithmeticException("被零整除"));
System.out.println(modelAndView.getViewName());
System.out.println(modelAndView.getModelMap());
*/
/*
嵌套异常
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerMethod handler = new HandlerMethod(new Controller3(),Controller3.class.getMethod("foo"));
RuntimeException e = new RuntimeException("e2", new IOException("e3"));
resolver.resolveException(request,response,handler,e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
*/
// 异常处理方法参数解析
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerMethod handler = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
Exception e = new Exception("a");
resolver.resolveException(request, response, handler, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
static class Controller1 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handler(ArithmeticException e) {
return MapUtil.of("error", e.getMessage());
}
}
static class Controller2 {
public void foo() {
}
@ExceptionHandler
public ModelAndView handler(ArithmeticException e) {
return new ModelAndView("test2", MapUtil.of("error", e.getMessage()));
}
}
static class Controller3 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handler(IOException e3) {
return MapUtil.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 MapUtil.of("error", e.getMessage());
}
}
}
// 全局通用异常
@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;
}
}
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() {
}
}
}
12.HandlerMapping与HandlerAdapter
- SimpleControllerHandlerAdapter与BeanNameUrlHandlerMapping
- 通过beanName(一定要带/)找到控制器,然后SimpleControllerHandlerAdapter调用控制器执行
@Configuration
public class WebConfig {
// 内嵌web容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
// 创建DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册DispatcherServlet到tomcat
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
return registrationBean;
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter(){
return new SimpleControllerHandlerAdapter();
}
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping(){
return new BeanNameUrlHandlerMapping();
}
@Component("/test1")
public static class Controller1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().println("this is Controller1");
return null;
}
}
@Component("/test2")
public static class Controller2 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().println("this is Controller2");
return null;
}
}
@Bean("/test3")
public Controller Controller3() {
return (request, response) -> {
response.getWriter().println("this is Controller3");
return null;
};
}
}
public class A4Application {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig1.class);
}
}
-
自定义HandlerMapping与HandlerAdapter
package com.liubo.springmvc.a4; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.servlet.*; import org.springframework.web.servlet.mvc.Controller; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; import java.util.stream.Collectors; /** * @author lb * @date 2023/1/2 1:36 下午 */ @Configuration public class WebConfig { // 内嵌web容器工厂 @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory() { return new TomcatServletWebServerFactory(); } // 创建DispatcherServlet @Bean public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } // 注册DispatcherServlet到tomcat @Bean public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) { DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/"); return registrationBean; } // SimpleUrlHandlerMapping @Component static class MyHandlerMapping implements HandlerMapping { @Override public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { String uri = request.getRequestURI(); Controller controller = controllerMap.get(uri); if (controller == null){ return null; } return new HandlerExecutionChain(controller); } @Autowired private ApplicationContext applicationContext; private Map<String, Controller> controllerMap; @PostConstruct public void init(){ controllerMap = applicationContext.getBeansOfType(Controller.class) .entrySet().stream().filter(e -> e.getKey().startsWith("/")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } } // SimpleControllerHandlerAdapter @Component static class MyHandlerAdapter implements HandlerAdapter{ @Override public boolean supports(Object handler) { return handler instanceof Controller; } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof Controller){ ((Controller) handler).handleRequest(request, response); } return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { return 0; } } @Component("/test1") public static class Controller1 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().println("this is Controller1"); return null; } } @Component("/test2") public static class Controller2 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().println("this is Controller2"); return null; } } @Bean("/test3") public Controller Controller3() { return (request, response) -> { response.getWriter().println("this is Controller3"); return null; }; } } public class A4Application { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig1.class); } }
-
RouterFunctionMapping与HandlerFunctionAdapter
- RouterFunctionMapping,收集所有RouterFunction,它包括两部分:Requestpredicate设置映射条件,HandlerFunction包含处理逻辑,请求到达根据映射条件找到HandlerFunction,即handler,HandlerFunctionAdapter调用handler
@Configuration public class WebConfig1 { @Bean // ⬅️内嵌 web 容器工厂 public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(8080); } @Bean // ⬅️创建 DispatcherServlet public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口 public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); } @Bean public RouterFunctionMapping routerFunctionMapping() { return new RouterFunctionMapping(); } @Bean public HandlerFunctionAdapter handlerFunctionAdapter() { return new HandlerFunctionAdapter(); } @Bean public RouterFunction<ServerResponse> r1() { return route(GET("/r1"), request -> ok().body("this is r1")); } @Bean public RouterFunction<ServerResponse> r2() { return route(GET("/r2"), request -> ok().body("this is r2")); } } public class A4Application { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig1.class); } }
13.mvc执行流程
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
-
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
- 路径:默认映射路径为
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器- jsp 不会匹配到 DispatcherServlet
- 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
- 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
- HandlerMapping,初始化时记录映射关系
- HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
- HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
- ViewResolver
- 路径:默认映射路径为
-
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
-
例如根据 /hello 路径找到 @RequestMapping(“/hello”) 对应的控制器方法
-
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
-
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
-
-
DispatcherServlet 接下来会:
- 调用拦截器的 preHandle 方法
- RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
- @ControllerAdvice 全局增强点1️⃣:补充模型数据
- @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器
- 使用 HandlerMethodArgumentResolver 准备参数
- @ControllerAdvice 全局增强点3️⃣:RequestBody 增强
- 调用 ServletInvocableHandlerMethod
- 使用 HandlerMethodReturnValueHandler 处理返回值
- @ControllerAdvice 全局增强点4️⃣:ResponseBody 增强
- 根据 ModelAndViewContainer 获取 ModelAndView
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
- 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 调用拦截器的 postHandle 方法
- 处理异常或视图渲染
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
- 正常,走视图解析及渲染流程
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- 调用拦截器的 afterCompletion 方法