10、RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
代码演示:
/**
* 例如经常要用到请求头中的 token 信息,用下面的注解来标注由哪个参数来获取它
* token=令牌
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
}
@Slf4j
@Controller
public class Controller1 {
@GetMapping("/test1")
public ModelAndView test1() throws Exception {
log.info("test1()");
return null;
}
@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
log.info("test2({})", name);
return null;
}
@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
log.info("test3({})", token);
return null;
}
@RequestMapping("/test4.yml")
@Yml // 定义作用:将返回的 User 转换为 yml 格式
public User test4() {
log.info("test4()");
return new User("张三", 18);
}
public static void main(String[] args) {
String str = new Yaml().dump(new User("张三", 18));
System.out.println(str);
}
}
// 自定义请求映射处理器适配器(将protect方法重写为public方法方便调用)
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
@Override
public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
return super.invokeHandlerMethod(request, response, handlerMethod);
}
}
/**
* 方法参数解析器 - 解析 @Token
*/
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");
}
}
/**
* 返回值处理器 - 处理 @Yml
*/
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Yml yml = returnType.getMethodAnnotation(Yml.class);// 获取方法参数所在方法上的注解
return yml != null;
}
/**
*
* @param returnValue Controller 返回的对象
* @param returnType
* @param mavContainer
* @param webRequest
* @throws Exception
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 1、转换返回结果为 yml
String str = new Yaml().dump(returnValue);
// 2、将 yaml 字符串写入响应
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType("test/plain;charset=utf-8");
response.getWriter().print(str);
// 2、设置请求已经处理完毕
mavContainer.setRequestHandled(true);
}
}
@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
// WebMvcProperties :绑定配置文件中以 spring.webmvc 开头的所有属性
// ServerProperties :绑定配置文件中以 server 开头的所有属性
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class}) // 存入容器
public class WebConfig {
// 内嵌 Web 容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
serverFactory.setPort(serverProperties.getPort());
return serverFactory;
}
// 创建 DispatchServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册DispatchServlet,SpringMVC 的入口
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); // 值大于0,则在创建DispatchServlet之后初始化
return registrationBean;
}
// 1、如果用 DispatchServlet 初始化时默认添加的组件,并不会作为Bean,给测试带来困扰
// 因此我们手动往容器中加入 RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
// 2、继续加入 RequestMappingHandlerAdapter,会替换掉 DispatchServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
// 添加自定义参数解析器
TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); // @Token
handlerAdapter.setCustomArgumentResolvers(Arrays.asList(tokenArgumentResolver));
// 添加自定义返回值处理器
YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler(); // @Yml
handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(ymlReturnValueHandler));
return handlerAdapter;
}
}
// application.properties
server.port=81
spring.mvc.servlet.load-on-startup: 1
编写测试代码:
@Slf4j
public class Test16Application {
static MockHttpServletRequest test1Request() {
return new MockHttpServletRequest("GET", "/test1");
}
static MockHttpServletResponse test1Response() {
return new MockHttpServletResponse();
}
static MockHttpServletRequest test2Request() {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name", "张三");
return request;
}
static MockHttpServletResponse test2Response() {
return new MockHttpServletResponse();
}
static MockHttpServletRequest test3Request() {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
request.addHeader("token", "某个令牌");
return request;
}
static MockHttpServletResponse test3Response() {
return new MockHttpServletResponse();
}
static MockHttpServletRequest test4Request() {
return new MockHttpServletRequest("GET", "/test4.yml");
}
static MockHttpServletResponse test4Response() {
return new MockHttpServletResponse();
}
public static void main(String[] args) throws Exception {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 1、假设请求来了
MockHttpServletRequest request = test4Request(); // 模拟请求
MockHttpServletResponse response = test4Response(); // 模拟响应
// 2、获取控制器方法,返回处理器执行链对象
System.out.println("--------------");
// 作用:解析 @RequestMapping注解和派生注解,生成路径与控制器方法的映射关系,在初始化时就生成了
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 根据请求获取到对应的处理器执行链 - 会找到2.1获取到的对应处理器方法(Controller 的对应方法),并和拦截器一起封装成执行链
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
// 2.1、调试:handlerMapping.getHandler(request) 方法会获取请求的 HandlerMethod(代表控制器方法信息):
System.out.println("--------------");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k,v) -> {
System.out.println(k + "=" + v);
});
// 3、获取请求映射处理器适配器,作用是调用控制器方法,需要使用到上面获取到的处理器执行链。会使用我们添加的参数解析器、返回值处理器处理
System.out.println("--------------");
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, ((HandlerMethod) chain.getHandler()));
// 4、检查响应
System.out.println("--------------");
byte[] content = response.getContentAsByteArray();
System.out.println(new String(content, StandardCharsets.UTF_8));
// 查看适配器的所有参数解析器
System.out.println(">>>>>>>>>>>>>>>");
List<HandlerMethodArgumentResolver> argumentResolvers = handlerAdapter.getArgumentResolvers();
for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {
System.out.println(argumentResolver);
}
// 查看返回值解析器
System.out.println(">>>>>>>>>>>>>>>");
List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
System.out.println(returnValueHandler);
}
}
}
结果:
...
--------------
15:21:24.198 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.clp.test16.Controller1#test4()
HandlerExecutionChain with [com.clp.test16.Controller1#test4()] and 0 interceptors
--------------
{POST [/test2]}=com.clp.test16.Controller1#test2(String)
{PUT [/test3]}=com.clp.test16.Controller1#test3(String)
{GET [/test1]}=com.clp.test16.Controller1#test1()
{ [/test4.yml]}=com.clp.test16.Controller1#test4()
--------------
15:21:24.207 [main] INFO com.clp.test16.Controller1 - test4()
--------------
!!com.clp.test16.User {age: 18, name: 张三}
>>>>>>>>>>>>>>>
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@2de366bb
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@3f093abe
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@61a002b1
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@4eeea57d
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@780ec4a5
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@e24ddd0
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@6f70f32f
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@548e76f1
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@5aabbb29
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@72c927f1
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@1ac85b0c
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@3dd69f5a
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@3aa3193a
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@1ee4730
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@59a67c3a
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@5003041b
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@724bade8
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@16fb356
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@6bc248ed
org.springframework.web.method.annotation.ModelMethodProcessor@23a9ba52
org.springframework.web.method.annotation.MapMethodProcessor@ca27722
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@70ab80e3
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@9573b3b
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@67427b69
com.clp.test16.TokenArgumentResolver@78461bc4
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@544630b7
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@64f857e7
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1095f122
>>>>>>>>>>>>>>>
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@58c540cf
org.springframework.web.method.annotation.ModelMethodProcessor@3d6300e8
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@1b822fcc
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@24a1c17f
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@56102e1c
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@73511076
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@7927bd9f
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@532721fd
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@410954b
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@7fb9f71f
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@3b366632
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@51f49060
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@514eedd8
org.springframework.web.method.annotation.MapMethodProcessor@617fe9e1
com.clp.test16.YmlReturnValueHandler@6970140a
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1cf2fed4
11、参数解析器
11.1、参数解析器模拟实现
/**
* 目标:解析控制器方法的参数值
* 常见的参数处理器如下:
* org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@2de366bb
* org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@3f093abe
* org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@61a002b1
* org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@4eeea57d
* org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@780ec4a5
* org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@e24ddd0
* org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@6f70f32f
* org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@548e76f1
* org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@5aabbb29
* org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@72c927f1
* org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@1ac85b0c
* org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@3dd69f5a
* org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@3aa3193a
* org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@1ee4730
* org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@59a67c3a
* org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@5003041b
* org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@724bade8
* org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@16fb356
* org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@6bc248ed
* org.springframework.web.method.annotation.ModelMethodProcessor@23a9ba52
* org.springframework.web.method.annotation.MapMethodProcessor@ca27722
* org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@70ab80e3
* org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@9573b3b
* org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@67427b69
* org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@544630b7
* org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@64f857e7
* org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1095f122
*
* 每个参数处理器能干啥:
* 1、看是否支持某种参数;
* 2、获取参数的值
* HandlerMethodArgumentResolverComposite:组合模式在Spring中的体现
* @RequestParam、@CookieValue 等注解中的参数名、默认值,都可以写成活的,都从 ${} #{} 中获取
*/
public class Test17Application {
public static void main(String[] args) throws Exception {
// 解析配置类的容器(非Web环境)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 准备测试 Request
HttpServletRequest multiRequest = mockRequest();
// 要点1:控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(Controller.class, 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 binderFactory = new ServletRequestDataBinderFactory(null, null);
// 要点3:准备 ModelAndViewController 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 要点4:解析每个参数值
for (MethodParameter methodParameter : handlerMethod.getMethodParameters()) {
String annotations = Arrays.stream(methodParameter.getParameterAnnotations())
.map(anno -> anno.annotationType().getSimpleName())
.collect(Collectors.joining());
String str = annotations.length() > 0 ? "@" + annotations + " " : " ";
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
// 多个解析器的组合
HandlerMethodArgumentResolverComposite resolverComposite = new HandlerMethodArgumentResolverComposite();
resolverComposite.addResolvers(
// 解析 @PathVariable
new PathVariableMethodArgumentResolver(),
// 解析 @RequestHeader
new RequestHeaderMethodArgumentResolver(beanFactory),
// 解析 @CookieValue
new ServletCookieValueMethodArgumentResolver(beanFactory),
// 解析 @Value
new ExpressionValueMethodArgumentResolver(beanFactory),
// 获取 HttpServletRequest (该解析器不只解析 HttpServletRequest)
new ServletRequestMethodArgumentResolver(),
// 解析 @RequestBody
new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter())),
// 解析 @ModelAttribute
new ServletModelAttributeMethodProcessor(false), // true:可以省略 @ModelAttribute
// 解析 @RequestParam false 表示方法参数必须有 @RequestParam 才能解析
new RequestParamMethodArgumentResolver(beanFactory, false)
);
// 判断解析器是否支持此方法参数的解析
if (resolverComposite.supportsParameter(methodParameter)) {
Object obj = resolverComposite.resolveArgument(methodParameter, container, new ServletWebRequest(multiRequest), binderFactory);
// 打印调试信息
System.out.println("[" + methodParameter.getParameterIndex() + "] " + str
+ methodParameter.getParameterType().getSimpleName()
+ " " + methodParameter.getParameterName() + " -> " + obj + "(" + obj.getClass().getSimpleName() + ")"
);
System.out.println("模型数据为:" + container.getModel());
} else {
// 打印调试信息
System.out.println("[" + methodParameter.getParameterIndex() + "] " + str
+ methodParameter.getParameterType().getSimpleName()
+ " " + methodParameter.getParameterName()
);
}
System.out.println();
}
}
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)));
// 为 @PathVariable 解析器提供数据支持 // {id=123}
Map<String, String> uriTemplateVariables = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
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=值 的,涉及类型转换
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // 如果没有获取到值,则使用默认值(从环境变量、配置文件中获取)
@RequestParam("file") MultipartFile file, // 解析上传文件数据
@PathVariable("id") int id, // 解析路径参数,如: 映射路径为test/{id} -> 请求为test/123时id值为123
@RequestHeader("Content-Type") String header, // 获取请求头信息
@CookieValue("token") String token, // 获取 cookie 中数据
@Value("${JAVA_HOME}") String home2, // 从 spring 中获取数据
HttpServletRequest request, // 解析特殊类型的参数,如 request、response、session...
@ModelAttribute("user1") User user1, // name=zhangsan&age=18
User user2, // 省略 @ModelAttribute
@RequestBody User user3 // 从请求体中获取数据(默认请求体为json)
) {
}
}
static class Config {
}
@Data
static class User {
private String name;
private int age;
@Override
public String toString() {
return "User" + "@" + hashCode() + "{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
结果:
...
[0] @RequestParam String name1 -> zhangsan(String)
模型数据为:{}
[1] String name2
[2] @RequestParam int age -> 18(Integer)
模型数据为:{}
17:18:09.741 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'JAVA_HOME' in PropertySource 'systemEnvironment' with value of type String
[3] @RequestParam String home1 -> D:\jdks\jdk1.8(String)
模型数据为:{}
[4] @RequestParam MultipartFile file -> org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@4fb3ee4e(StandardMultipartFile)
模型数据为:{}
[5] @PathVariable int id -> 123(Integer)
模型数据为:{}
[6] @RequestHeader String header -> application/json(String)
模型数据为:{}
[7] @CookieValue String token -> 123456(String)
模型数据为:{}
17:18:09.775 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'JAVA_HOME' in PropertySource 'systemEnvironment' with value of type String
[8] @Value String home2 -> D:\jdks\jdk1.8(String)
模型数据为:{}
[9] HttpServletRequest request -> org.springframework.web.multipart.support.StandardMultipartHttpServletRequest@43dac38f(StandardMultipartHttpServletRequest)
模型数据为:{}
[10] @ModelAttribute User user1 -> User@779432{name='张三', age=18}(User)
模型数据为:{user1=User@779432{name='张三', age=18}, org.springframework.validation.BindingResult.user1=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[11] User user2
17:18:09.897 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Read "application/json" to [User@846722{name='李四', age=20}]
[12] @RequestBody User user3 -> User@846722{name='李四', age=20}(User)
模型数据为:{user1=User@779432{name='张三', age=18}, org.springframework.validation.BindingResult.user1=org.springframework.validation.BeanPropertyBindingResult: 0 errors, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
11.2、获取方法参数名
/**
* Java 编译器默认不存储方法参数名,可以在编译时添加参数: javac -parameters .\Bean2.java
*
* 目标:如何获取方法参数名
* springboot 在编译时会加 -parameters(接口和类都会有效);
* 大部分 IDE 编译时都会加 -g (对类有效,对接口无效)
*/
public class Test17Application2 {
static interface Bean1 {
public void foo(String name, int age);
}
static class Bean2 {
public void foo(String name, int age) {}
}
public static void main(String[] args) throws NoSuchMethodException {
System.out.println("接口:");
System.out.println("--------------------");
extracted(Bean1.class.getMethod("foo", String.class, int.class));
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println("类:");
System.out.println("--------------------");
extracted(Bean2.class.getMethod("foo", String.class, int.class));
}
private static void extracted(Method method) throws NoSuchMethodException {
// 1、反射获取参数名 - 默认编译后运行是获取不到的
List<String> list = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.toList());
System.out.println(list);
// 2、基于 Spring提供的工具, 本地变量表获取
System.out.println("--------------------");
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
System.out.println(Arrays.toString(parameterNames));
// 3、二者的结合实现
System.out.println("--------------------");
DefaultParameterNameDiscoverer defaultDiscoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames2 = defaultDiscoverer.getParameterNames(method);
System.out.println(Arrays.toString(parameterNames2));
}
}
结果:
接口:
--------------------
[arg0, arg1]
--------------------
null
--------------------
null
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
类:
--------------------
[arg0, arg1]
--------------------
[name, age]
--------------------
[name, age]
11.3、对象绑定与类型转换
两套底层转换接口,一套高层转换接口。
11.3.1、底层第1套接口与实现(Spring 提供)
- Printer:把其他类型转为 String;
- Parser:把 String 转为其他类型;
- Formatter:综合 Printer 与 Parser 功能;
- Converter:把类型 S 转为类型 T;
Printer、Parser、Converter 经过适配器转换成 GenericConverter 放入 Converters 集合。FormatteringConversionService 利用它们实现转换。
11.3.2、底层第2套转换接口(JDK 提供)
- PropertyEditor:把 String 与其他类型相互转换;
- PropertyEditorRegistry:可以注册多个 PropertyEditor 对象;
- 与第1套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配。
11.3.3、高层接口与实现
ConversionService 与 PropertyEditorRegistry 都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派 ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)。
- 首先看是否有自定义转换器,@InitBinder 添加的即属于这种(用了适配器模式把 Formatter 转为需要的 PropertyEditor);
- 在看看有没有 ConversionService 转换;
- 再利用默认的 PropertyEditor 转换;
- 最后有一些特殊处理。
SimpleTypeConverter:仅做类型转换;
BeanWrapperImpl:为 bean 的属性赋值,当需要时做类型转换,走 Property(Getter/Setter);
DirectFieldAccessor:为 bean 的属性赋值,当需要时做类型转换,走 Field(Field字段);
ServletRequestDataBinder:为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能。
11.3.4、类型转换与数据绑定示例
代码演示1:
public class Test18 {
@Getter
@Setter
@ToString
static class MyBean {
private int a;
private String b;
private Date c;
}
public static void main(String[] args) {
// 仅有类型转换的功能
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
Integer number = typeConverter.convertIfNecessary("13", int.class);
System.out.println(number);
Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
System.out.println(date);
System.out.println("----------------------------------------");
// 利用反射原理(Getter/Setter),为 bean 的属性赋值
MyBean myBean1 = new MyBean();
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(myBean1);
beanWrapper.setPropertyValue("a", "10");
beanWrapper.setPropertyValue("b", "hello");
beanWrapper.setPropertyValue("c", "1999/03/04");
System.out.println(myBean1);
System.out.println("----------------------------------------");
// 利用反射原理(Field字段),为 bean 的属性赋值
MyBean myBean2 = new MyBean();
DirectFieldAccessor accessor = new DirectFieldAccessor(myBean2);
accessor.setPropertyValue("a", "10");
accessor.setPropertyValue("b", "hello");
accessor.setPropertyValue("c", "1999/03/04");
System.out.println(myBean2);
System.out.println("----------------------------------------");
// 执行数据绑定(要绑定的数据封装在 MutablePropertyValues 中)
MyBean myBean3 = new MyBean();
// 准备原始数据
DataBinder dataBinder = new DataBinder(myBean3);
MutablePropertyValues pvs = new MutablePropertyValues();
dataBinder.initDirectFieldAccess(); // 如果 Bean 没有提供 Getter/Setter 方法,设置为直接字段访问即可
pvs.add("a", "10");
pvs.add("b", "hello");
pvs.add("c", "1999/03/04");
dataBinder.bind(pvs);
System.out.println(myBean3);
System.out.println("----------------------------------------");
// 执行数据绑定(要绑定的数据封装在 HttpServletRequest 中)
MyBean myBean4 = new MyBean();
// 准备原始数据
ServletRequestDataBinder servletRequestDataBinder = new ServletRequestDataBinder(myBean4);
servletRequestDataBinder.initDirectFieldAccess(); // 如果 Bean 没有提供 Getter/Setter 方法,设置为直接字段访问即可
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("a", "10");
request.setParameter("b", "hello");
request.setParameter("c", "1999/03/04");
// 执行数据绑定
servletRequestDataBinder.bind(request);
System.out.println(myBean4);
}
}
结果:
13
Thu Mar 04 00:00:00 CST 1999
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)
代码演示2:
public class Test18_2 {
@Getter
@Setter
@ToString
static class User {
@DateTimeFormat(pattern = "yyyy|MM|dd") // 使用方法5(默认的ConversionService)需要添加这个注解
private Date birthday;
private Address address;
}
@Getter
@Setter
@ToString
static class Address {
private String name;
}
public static void main(String[] args) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday", "1999|01|02");
request.setParameter("address.name", "西安");
// 1、用工厂,无转换功能
System.out.println("----------------------");
System.out.println(bindToUser1(request));
// 2、用 @InitBinder 转换 PropertyEditorRegistry PropertyEditor
System.out.println("----------------------");
System.out.println(bindToUser2(request));
// 3、用 ConversionService 转换 ConversionService Formatter
System.out.println("----------------------");
System.out.println(bindToUser3(request));
// 4、同时加了 @InitBinder 和 ConversionService,会优先使用 @InitBinder 的转换器
System.out.println("----------------------");
System.out.println(bindToUser4(request));
// 5、使用默认 ConversionService 转换
System.out.println("----------------------");
System.out.println(bindToUser5(request));
}
// 1、用工厂,无转换功能
public static User bindToUser1(HttpServletRequest request) throws Exception {
User targetBean = new User();
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
return targetBean;
}
// 2、用 @InitBinder 转换 PropertyEditorRegistry PropertyEditor
public static User bindToUser2(HttpServletRequest request) throws Exception {
User targetBean = new User();
// 获取 Controller 类上标注有 @InitBinder 的方法,并调用该方法对 Binder 进行自定义处理
InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
// 将该方法作为创建工厂的参数,之后调用 createBinder() 时会反射调用该方法
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Collections.singletonList(method), null);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
return targetBean;
}
static class MyController {
@InitBinder
public void aaa(WebDataBinder dataBinder) {
// 扩展 DataBinder 转换器功能(该方法内部使用适配器 FormatterPropertyEditorAdapter 适配成 PropertyEditor 来使用的)
dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
}
}
@Slf4j
static class MyDateFormatter implements Formatter<Date> {
private final String desc;
public MyDateFormatter(String desc) {
this.desc = desc;
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
log.info(">>>>>> 进入了:{}", desc);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(text);
}
@Override
public String print(Date date, Locale locale) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.format(date);
}
}
// 3、同 ConversionService 转换 ConversionService Formatter
public static User bindToUser3(MockHttpServletRequest request) throws Exception {
User targetBean = new User();
FormattingConversionService conversionService = new FormattingConversionService();
conversionService.addFormatter(new MyDateFormatter("用 ConversionService 扩展转换功能"));
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(conversionService);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
return targetBean;
}
// 4、同时加了 @InitBinder 和 ConversionService
public static User bindToUser4(MockHttpServletRequest request) throws Exception {
User targetBean = new User();
// 准备初始化器
FormattingConversionService conversionService = new FormattingConversionService();
conversionService.addFormatter(new MyDateFormatter("用 ConversionService 扩展转换功能"));
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(conversionService);
// 获取 Controller 类上标注有 @InitBinder 的方法,并调用该方法对 Binder 进行自定义处理
InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
// 优先使用 @InitBinder 的转换器
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Collections.singletonList(method), initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
return targetBean;
}
// 5、使用默认 ConversionService 转换(需要在User类的日期字段上添加@DateTimeFormat注解)
public static User bindToUser5(MockHttpServletRequest request) throws Exception {
User targetBean = new User();
// DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); // 非 SpringBoot 程序用这个
ApplicationConversionService conversionService = new ApplicationConversionService(); // SpringBoot 程序用这个
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(conversionService);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
return targetBean;
}
}
结果:
----------------------
16:53:43.650 [main] DEBUG org.springframework.beans.TypeConverterDelegate - Construction via String failed for type [java.util.Date]
org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.Date]: Constructor threw exception; nested exception is java.lang.IllegalArgumentException
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:224)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:198)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:590)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:609)
at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:458)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:266)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:104)
at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:889)
at org.springframework.validation.DataBinder.doBind(DataBinder.java:780)
at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:207)
at org.springframework.validation.DataBinder.bind(DataBinder.java:765)
at com.clp.test18.Test18_2.bindToUser1(Test18_2.java:75)
at com.clp.test18.Test18_2.main(Test18_2.java:54)
Caused by: java.lang.IllegalArgumentException: null
at java.util.Date.parse(Date.java:617)
at java.util.Date.<init>(Date.java:274)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:211)
... 13 common frames omitted
Test18_2.User(birthday=null, address=Test18_2.Address(name=西安))
----------------------
16:53:43.702 [main] INFO com.clp.test18.Test18_2$MyDateFormatter - >>>>>> 进入了:用 @InitBinder 方式扩展的
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))
----------------------
16:53:43.712 [main] INFO com.clp.test18.Test18_2$MyDateFormatter - >>>>>> 进入了:用 ConversionService 扩展转换功能
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))
----------------------
16:53:43.714 [main] INFO com.clp.test18.Test18_2$MyDateFormatter - >>>>>> 进入了:用 @InitBinder 方式扩展的
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))
----------------------
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))
11.4、Spring 提供的泛型操作技巧
代码演示:
public class Test18_3 {
static class Student {}
static class BaseDao<T> {}
static class StudentDao extends BaseDao<Student> {}
public static void main(String[] args) {
// 1、jdk api
Type type = StudentDao.class.getGenericSuperclass();// 获取带有泛型信息的父类
System.out.println("---------------------");
System.out.println(type);
// 因为继承的父类不一定有泛型信息(如 MyList extends ArrayList,但没有指定泛型参数),所以需要进行判断
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();// 获取真实的参数化类型
System.out.println("---------------------");
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
// 2、spring api
// 2.1、参数1:本类;参数2:泛型父类(默认只获取一个泛型参数类型)
Class<?> clz = GenericTypeResolver.resolveTypeArgument(StudentDao.class, BaseDao.class);
System.out.println("---------------------");
System.out.println(clz);
// 2.2、参数1:本类;参数2:泛型父类(获取所有的泛型参数类型)
Class<?>[] classes = GenericTypeResolver.resolveTypeArguments(StudentDao.class, BaseDao.class);
System.out.println("---------------------");
for (Class<?> aClass : classes) {
System.out.println(aClass);
}
}
}
结果:
---------------------
com.clp.test18.Test18_3$BaseDao<com.clp.test18.Test18_3$Student>
---------------------
class com.clp.test18.Test18_3$Student
---------------------
class com.clp.test18.Test18_3$Student
---------------------
class com.clp.test18.Test18_3$Student
11.5、@ControllerAdvice & @InitBinder
代码演示:
@Slf4j
public class MyDateFormatter implements Formatter<Date> {
private final String desc;
public MyDateFormatter(String desc) {
this.desc = desc;
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
log.info(">>>>>> 进入了:{}", desc);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(text);
}
@Override
public String print(Date date, Locale locale) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.format(date);
}
}
@Configuration
public class WebConfig {
@ControllerAdvice // 该注解表示对 Controller 进行增强(但是没有使用代理)
static class MyControllerAdvice {
@InitBinder // @InitBinder 注解添加方式1:在 @ControllerAdvice 里添加 ,用于补充自定的类型转换器,作用范围是全局
public void binder3(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器"));
}
// @ControllerAdvice 中可添加的其他注解:
// @ExceptionHandler // 异常处理器
// @ModelAttribute // 会将注解的模型数据补充到 controller 的执行过程中
}
@Controller
static class Controller1 {
@InitBinder // @InitBinder 注解添加方式2:直接在 @Controller 里添加 ,用于补充自定的类型转换器,作用范围是局部
public void binder1(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器"));
}
public void foo() {}
}
@Controller
static class Controller2 {
@InitBinder
public void binder21(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder21 转换器"));
}
@InitBinder
public void binder22(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder22 转换器"));
}
public void bar() {}
}
}
@Slf4j
public class Test19Application {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
/**
* @InitBinder 的来源有2个:
* 1、@ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并记录;
* 2、@Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并记录
*/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
// 不放入容器,而是手动调用 , handlerAdapter 会先解析 @InitBinder,再执行控制器方法
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setApplicationContext(context); // ApplicationContextAware 接口的方法,如果这个 handlerAdapter 放入容器中,会自动执行该方法
handlerAdapter.afterPropertiesSet(); // InitializingBean 接口的方法,如果这个 handlerAdapter 放入容器中,会自动执行该方法
log.info("------------------------------");
log.info("1、刚开始...");
showBindMethods(handlerAdapter);
Method getDataBinderFactoryMethod = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);
getDataBinderFactoryMethod.setAccessible(true);
log.info("------------------------------");
log.info("2、模拟调用 Controller1 的 foo() 方法时...");
getDataBinderFactoryMethod.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));
showBindMethods(handlerAdapter);
log.info("------------------------------");
log.info("3、模拟调用 Controller2 的 bar() 方法时...");
getDataBinderFactoryMethod.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar")));
showBindMethods(handlerAdapter);
context.close();
}
@SuppressWarnings("unchecked")
public static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException {
Field initBinderAdviceCacheField = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
initBinderAdviceCacheField.setAccessible(true);
Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCacheField.get(handlerAdapter);
log.info("全局的 @InitBinder 方法:{}", globalMap.values().stream().flatMap(methods -> methods.stream().map(method -> method.getName())).collect(Collectors.toList()));
Field initBinderCacheField = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
initBinderCacheField.setAccessible(true);
Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCacheField.get(handlerAdapter);
log.info("控制器的 @InitBinder 方法:{}", controllerMap.entrySet()
.stream().flatMap(classSetEntry -> classSetEntry.getValue().stream().map(v -> classSetEntry.getKey().getSimpleName() + "." + v.getName()))
.collect(Collectors.toList())
);
}
}
结果:
10:07:12.909 [main] INFO com.clp.test19.Test19Application - ------------------------------
10:07:12.909 [main] INFO com.clp.test19.Test19Application - 1、刚开始...
10:07:12.911 [main] INFO com.clp.test19.Test19Application - 全局的 @InitBinder 方法:[binder3]
10:07:12.913 [main] INFO com.clp.test19.Test19Application - 控制器的 @InitBinder 方法:[]
10:07:12.915 [main] INFO com.clp.test19.Test19Application - ------------------------------
10:07:12.915 [main] INFO com.clp.test19.Test19Application - 2、模拟调用 Controller1 的 foo() 方法时...
10:07:12.923 [main] INFO com.clp.test19.Test19Application - 全局的 @InitBinder 方法:[binder3]
10:07:12.924 [main] INFO com.clp.test19.Test19Application - 控制器的 @InitBinder 方法:[Controller1.binder1]
10:07:12.924 [main] INFO com.clp.test19.Test19Application - ------------------------------
10:07:12.924 [main] INFO com.clp.test19.Test19Application - 3、模拟调用 Controller2 的 bar() 方法时...
10:07:12.924 [main] INFO com.clp.test19.Test19Application - 全局的 @InitBinder 方法:[binder3]
10:07:12.925 [main] INFO com.clp.test19.Test19Application - 控制器的 @InitBinder 方法:[Controller2.binder22, Controller2.binder21, Controller1.binder1]
12、控制器方法执行流程
图示:
HandlerMethod 需要:
- bean:即是哪个 Controller;
- method:即是 Controller 中的哪个方法。
ServletInvocationHandlerMethod 需要:
- WebDataBinderFactory:负责对象绑定、类型转换;
- ParameterNameDiscover:负责参数名解析;
- HandlerMethodArgumentResolverComposite:负责解析参数;
- HandlerMethodReturnValueHandlerComposite:负责处理返回值。
代码演示:
@Configuration
public class WebConfig {
@Getter
@Setter
@ToString
static class User {
private String name;
}
@Controller
static class Controller1 {
// 局部的(作用于该控制器)
@ModelAttribute("a") // 模型名
public String aa() {
return "bb"; // 模型值
}
@ResponseStatus(HttpStatus.OK)
public ModelAndView foo(@ModelAttribute("user1") User user) {
System.out.println("foo");
return null;
}
}
@ControllerAdvice
static class MyControllerAdvice {
// 全局的
@ModelAttribute("a") // 模型名
public String aa() {
return "aa"; // 模型值
}
}
}
public class Test20Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
// 模拟请求
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name", "张三");
// 现在可以通过 ServletInvocationHandlerMethod 把这些整合在一起,并完成控制器方法的调用
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo", WebConfig.User.class)
);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
handlerMethod.setDataBinderFactory(factory);
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
ModelAndViewContainer container = new ModelAndViewContainer();
// 调用,加了@ModelAttribute注解的方法参数会放在 container 的 model 中
handlerMethod.invokeAndHandle(new ServletWebRequest(request), container, null);
System.out.println(container.getModel());
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;
}
}
结果:
...
foo
{user1=WebConfig.User(name=张三), org.springframework.validation.BindingResult.user1=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
...
13、返回值处理器
13.1、代码示例
代码演示:
@Configuration
public class WebConfig {
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setDefaultEncoding("utf-8");
configurer.setTemplateLoaderPath("classpath:template");
return configurer;
}
// FreeMarkerView 在借助 Spring 初始化时,会要求 web 环境才会走 setConfiguration(),这里想办法去掉了 web 环境的约束
@Bean
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;
}
}
public class Test21Application {
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
static class User {
private String name;
private int age;
}
@Slf4j
static class Controller {
// 返回值处理器:ModelAndViewMethodReturnValueHandler
public ModelAndView test1() {
log.info("test1()");
ModelAndView mav = new ModelAndView("view1");
mav.addObject("name", "张三");
return mav;
}
// 返回值处理器:ViewNameMethodReturnValueHandler
public String test2() {
log.info("test2()");
return "view2"; // 返回的是视图名称
}
// 返回值处理器:ServletModelAttributeMethodProcessor(annotationNotRequired=false)
@ModelAttribute
@RequestMapping("/test3") // 没有返回视图,则默认视图名称为请求路径名
public User test3() {
log.info("test3");
return new User("李四", 20); // 返回模型对象
}
// 返回值处理器:ServletModelAttributeMethodProcessor(annotationNotRequired=true)
public User test4() {
log.info("test4()");
return new User("王五", 30);
}
// 返回值处理器:HttpEntityMethodProcessor
public HttpEntity<User> test5() {
log.info("test5()");
return new HttpEntity<>(new User("赵六", 40)); // 会将User对象转为json格式写在响应体中
}
// 返回值处理器:HttpHeadersReturnValueHandler
public HttpHeaders test6() {
log.info("test6()");
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/html");
return headers; // 会将 header 作为响应头
}
// 返回值处理器:RequestResponseBodyMethodProcessor
@ResponseBody
public User test7() {
log.info("test7()");
return new User("钱七", 50); // 会将User对象转为json格式写在响应体中
}
}
// 提供视图
private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
ServletWebRequest webRequest) throws Exception {
}
// 提供返回值处理器
private static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
composite.addHandler(new ModelAndViewMethodReturnValueHandler());
composite.addHandler(new ViewNameMethodReturnValueHandler());
composite.addHandler(new ServletModelAttributeMethodProcessor(false));
composite.addHandler(new HttpEntityMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new HttpHeadersReturnValueHandler());
composite.addHandler(new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
test1(context);
context.close();
}
private static void test1(AnnotationConfigApplicationContext context) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
// 获取 Controller 方法调用的返回值、 HandlerMethod
Method method = Controller.class.getMethod("test1");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
// 1、测试返回值类型为 ModelAndView
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
// 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue, handlerMethod.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 {
ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
// 获取 Controller 方法调用的返回值、 HandlerMethod
Method method = Controller.class.getMethod("test2");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
// 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue, handlerMethod.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 {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test3");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
// 获取 Controller 方法调用的返回值、 HandlerMethod
Method method = Controller.class.getMethod("test3");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
// 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
renderView(context, container, webRequest); // 渲染视图
}
}
private static void test4(AnnotationConfigApplicationContext context) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test4");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
// 获取 Controller 方法调用的返回值、 HandlerMethod
Method method = Controller.class.getMethod("test4");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
// 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
renderView(context, container, webRequest); // 渲染视图
}
}
private static void test5(AnnotationConfigApplicationContext context) throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);
// 获取 Controller 方法调用的返回值、 HandlerMethod
Method method = Controller.class.getMethod("test5");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
// 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue, handlerMethod.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 test6(AnnotationConfigApplicationContext context) throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);
// 获取 Controller 方法调用的返回值、 HandlerMethod
Method method = Controller.class.getMethod("test6");
Controller controller = new Controller();
Object returnValue = method.invoke(controller); // 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
// 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
System.out.println(container.getModel());
System.out.println(container.getViewName());
if (!container.isRequestHandled()) {
renderView(context, container, webRequest); // 渲染视图
} else {
for (String headerName : response.getHeaderNames()) {
System.out.println(headerName + "=" + response.getHeader(headerName));
}
}
}
}
}
13.2、MessageConverter
代码演示:
/**
* MessageConverter 的作用:@ResponseBody 是 对应的返回值处理器 解析的,但具体转换工作是 MessageConverter 做的
* 如何选择 MediaType:
* - 首先看 @RequestMapping 上有没有指定(produces属性)
* - 其次看 request 的 Accept 上有没有指定
* - 最后按 MessageConverter 的顺序,谁能谁先转换
*/
public class Test22Application {
@Getter
@Setter
@ToString
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;
}
}
/**
* 将 java对象 转成 json 格式消息
* {"name":"张三", "age":18}
* @throws IOException
*/
public static void test1() throws IOException {
MockHttpOutputMessage message = new MockHttpOutputMessage();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {
// 把 java 对象转换成 json字符串 并写入到 message 中
converter.write(new User("张三", 18), MediaType.APPLICATION_JSON, message);
System.out.println(message.getBodyAsString());
}
}
/**
* 将 java对象 转成 xml 格式消息
* <User>
* <name>李四</name>
* <age>19</age>
* </User>
* @throws IOException
*/
public static void test2() throws IOException {
MockHttpOutputMessage message = new MockHttpOutputMessage();
MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {
// 把 java 对象转换成 json字符串 并写入到 message 中
converter.write(new User("李四", 19), MediaType.APPLICATION_XML, message);
System.out.println(message.getBodyAsString());
}
}
/**
* 将 消息 转成 Java对象
*/
public 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);
}
}
/**
* 将 java对象 转成
*/
public static void test4() {}
@ResponseBody
public User user() {
return null;
}
public static void main(String[] args) throws IOException {
System.out.println("---------------------");
test1();
// System.out.println("---------------------");
// test2();
System.out.println("---------------------");
test3();
}
}
结果:
---------------------
{"name":"张三","age":18}
---------------------
Test22Application.User(name=李四, age=20)
13.3、@ControllerAdvice 之 @ResponseBodyAdvice
代码演示:
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result<T> {
private int code;
private String msg;
private T data;
@JsonCreator
private Result(@JsonProperty("code") int code, @JsonProperty("data") T data) {
this.code = code;
this.data = data;
}
private Result(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static <T> Result<T> ok() {
return new Result<>(200, null);
}
public static <T> Result<T> ok(T data) {
return new Result<>(200, data);
}
public static <T> Result<T> error(String msg) {
return new Result<>(500, "服务器内部错误:" + msg);
}
}
@Configuration
public class WebConfig {
@Getter
@Setter
@ToString
public static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
// @Controller
// @ResponseBody // 位置1
@RestController // 上面两个注解的合并
public static class MyController {
// @ResponseBody // 位置2
public User user() {
return new User("王五", 18);
}
}
@ControllerAdvice
public static class MyControllerAdvice implements ResponseBodyAdvice<Object> {
// 满足该条件才转换
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 检查类上或方法上是否有 @ResponseBody 注解
if (returnType.getMethodAnnotation(ResponseBody.class) != null
// 从 类上的注解以及该注解上的注解找到 @ResponseBody 注解
|| AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) {
return true;
}
return false;
}
// 执行转换逻辑,将 User或其他类型 封装为 Result<>
@Override
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);
}
}
}
public class Test23Application {
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(new ArrayList<>(), null));
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers());
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
handlerMethod.invokeAndHandle(new ServletWebRequest(request, response), new ModelAndViewContainer());
System.out.println("----------------------");
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
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;
}
// 提供返回值处理器
private static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
composite.addHandler(new ModelAndViewMethodReturnValueHandler());
composite.addHandler(new ViewNameMethodReturnValueHandler());
composite.addHandler(new ServletModelAttributeMethodProcessor(false));
composite.addHandler(new HttpEntityMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new HttpHeadersReturnValueHandler());
composite.addHandler(new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
}
结果:
...
{"code":200,"data":{"name":"王五","age":18}}
...
14、异常处理
14.1、ExceptionHandlerExceptionResolver
代码演示:
/**
* 1、ExceptionHandlerExceptionResolver
* 能够重用参数解析器、返回值处理器,实现组件重用
* 能够支持嵌套异常
*/
public class Test24Application {
static class Controller1 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(ArithmeticException e) {
HashMap<String, Object> map = new HashMap<>();
map.put("error", e.getMessage());
return map;
}
}
static class Controller2 {
public void foo() {
}
@ExceptionHandler
public ModelAndView handle(ArithmeticException e) {
return new ModelAndView("test2", Collections.singletonMap("error", e.getMessage()));
}
}
static class Controller3 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(IOException e) {
return Collections.singletonMap("error", e.getMessage());
}
}
static class Controller4 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(Exception e, HttpServletRequest request) {
System.out.println(request);
return Collections.singletonMap("error", e.getMessage());
}
}
public static void main(String[] args) throws NoSuchMethodException {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter())); // 添加 json 消息转换器
resolver.afterPropertiesSet(); // 添加一些默认的解析器(跟handlerAdapter使用相同类型的解析器)
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ArithmeticException single_ex = new ArithmeticException("被0除 异常");
Exception nested_ex = new Exception("e1", new RuntimeException("e2", new IOException("e3")));
// 测试 json
System.out.println("---------------------------------------");
// testJson(resolver, request, response, single_ex);
// 测试 ModelAndView
System.out.println("---------------------------------------");
// testModelAndView(resolver, request, response, single_ex);
// 测试嵌套异常
System.out.println("---------------------------------------");
// testNestedException(resolver, request, response, nested_ex);
// 测试异常处理方法参数解析
System.out.println("---------------------------------------");
testExceptionHandlerMethodArguments(resolver, request, response, nested_ex);
}
private static void testJson(ExceptionHandlerExceptionResolver resolver, MockHttpServletRequest request,
MockHttpServletResponse response, ArithmeticException single_ex) throws NoSuchMethodException {
HandlerMethod handlerMethod1 = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));
resolver.resolveException(request, response, handlerMethod1, single_ex);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
private static void testModelAndView(ExceptionHandlerExceptionResolver resolver, MockHttpServletRequest request,
MockHttpServletResponse response, ArithmeticException single_ex) throws NoSuchMethodException {
HandlerMethod handlerMethod2 = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));
ModelAndView modelAndView = resolver.resolveException(request, response, handlerMethod2, single_ex);
System.out.println(modelAndView.getModel());
System.out.println(modelAndView.getViewName());
}
private static void testNestedException(ExceptionHandlerExceptionResolver resolver, MockHttpServletRequest request,
MockHttpServletResponse response, Exception nested_ex) throws NoSuchMethodException {
HandlerMethod handlerMethod3 = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
resolver.resolveException(request, response, handlerMethod3, nested_ex);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
private static void testExceptionHandlerMethodArguments(ExceptionHandlerExceptionResolver resolver,
MockHttpServletRequest request, MockHttpServletResponse response,
Exception nested_ex) throws NoSuchMethodException {
HandlerMethod handlerMethod4 = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
resolver.resolveException(request, response, handlerMethod4, nested_ex);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
}
结果:
---------------------------------------
---------------------------------------
---------------------------------------
---------------------------------------
...
{"error":"e1"}
14.2、ControllerAdvice 之 @ExceptionHandler
代码演示:
@Configuration
public class WebConfig {
@Bean
public ExceptionHandlerExceptionResolver resolver() {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
return resolver;
}
@ControllerAdvice // 可以提供全局的异常处理方法
static class MyControllerAdvice {
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(Exception e) {
return Collections.singletonMap("error", e.getMessage());
}
}
}
public class Test25Application {
static class Controller5 {
public void foo() {}
}
public static void main(String[] args) throws NoSuchMethodException {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
// ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
// resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
// resolver.afterPropertiesSet();
// 上面3行改写成下面,交给容器处理:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);
Exception e1 = new Exception("e1");
HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo"));
resolver.resolveException(request, response, handlerMethod, e1);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
}
结果:
...
{"error":"e1"}
14.3、Tomcat 异常处理
代码演示:
@Configuration
public class WebConfig {
// ------------------------ web 开发三件套--------------------------
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
return registrationBean;
}
// ----------------------------------------------------------
@Bean // 解析 @RequestMapping,进行路径映射
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean // 注意默认的 RequestMappingHandlerAdapter 不会带有 jackson 转换器,需要我们手动添加
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter())); // 手动添加
return adapter;
}
@Bean // 这个 bean 可以修改 服务器的默认错误地址,会使用请求转发 forward 跳转至 /error 请求
public ErrorPageRegistrar errorPageRegistrar() {
return new ErrorPageRegistrar() {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage("/error"));
}
};
}
@Bean
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
// 会在 TomcatServletWebServerFactory 创建后,初始化之前 执行这个bean,并 回调 ErrorPageRegistrar 的 registerErrorPages()方法
return new ErrorPageRegistrarBeanPostProcessor();
}
@Controller
public static class MyController {
@RequestMapping("test")
public ModelAndView test() {
int i = 1 / 0;
return null;
}
// 提供错误跳转请求处理,这里不使用自定义的,因为SpringBoot 给我们提供了现成的,叫 BasicErrorController
// @RequestMapping("error")
// @ResponseBody
// public Map<String, Object> error(HttpServletRequest request) {
// Throwable e = ((Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
// return Collections.singletonMap("error", e.getMessage());
// }
}
@Bean
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
errorProperties.setIncludeException(true); // 添加错误属性 - includeException
return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
// BasicErrorController 需要提供额外的2个对象: View 和 ViewResolver
@Bean
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(
"<h3>服务器内部错误</h3>"
);
}
};
}
@Bean // 提供基于 bean 名称的 view 解析器,会解析到上面的 view bean
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
}
public class Test26Application {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
handlerMapping.getHandlerMethods().forEach((RequestMappingInfo i, HandlerMethod m) -> {
System.out.println("映射路径:" + i + "\t方法信息:" + m);
});
}
}
结果(浏览器访问 localhost:8080/test):
服务器内部错误
15、HandlerMapping 与 HandlerAdapter
15.1、BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
代码演示:
@Configuration
public class WebConfig {
// ------------------------ web 开发三件套--------------------------
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
return registrationBean;
}
// ----------------------------------------------------------
// 比如请求url路径为:/c1,则会在容器中找一个名为 "/c1" 的 bean 来处理这个请求,要求这个bean必须以 / 开头,不然就是普通的bean
// 这里不使用这个,使用我们自定义的 MyHandlerMapping 实现相同功能
// @Bean
// public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
// return new BeanNameUrlHandlerMapping();
// }
// 自定义 HandlerMapping:收集所有以 / 开头的 bean
@Component
static class MyHandlerMapping implements HandlerMapping {
@Autowired // 1、注入容器对象
private ApplicationContext context;
private Map<String, Controller> controllerMap;
@PostConstruct // 2、初始化
public void init() {
// 查找所有实现 Controller 接口的类
controllerMap = context.getBeansOfType(Controller.class).entrySet().stream()
.filter(e -> e.getKey().startsWith("/"))
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
System.out.println(controllerMap);
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
String key = request.getRequestURI();
Controller controller = controllerMap.get(key);
if (controller == null) return null; // 返回null,会抛出404异常
return new HandlerExecutionChain(controller);
}
}
// 这里不使用这个 Adapter,而是使用我们自定义的 MyHandlerAdapter
// @Bean
// public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
// return new SimpleControllerHandlerAdapter();
// }
@Component
public static class MyHandlerAdapter implements HandlerAdapter {
// 该 handlerAdapter 是否支持这个 handler(这里需要支持 Controller)
@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 controller = (Controller) handler;
controller.handleRequest(request, response);
}
return null; // 表示不走视图渲染流程
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return 0;
}
}
// 作为控制器的类 还需要实现 Controller 接口
@Component("/c1")
public static class Controller1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().print("this is c1");
return null;
}
}
@Component("c2") // 这里 c2 没有加 / ,因此不会找到 Controller2
public static class Controller2 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().print("this is c2");
return null;
}
}
@Bean("/c3")
public Controller controller3() {
return new Controller() {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().print("this is c3");
return null;
}
};
}
}
/**
* BeanNameUrlHandlerMapping:以 / 开头的 bean 的名字会被当做映射路径
* 这些 bean 本身当做 handler,要求实现 Controller 接口
* SimpleControllerHandlerAdapter:调用 handler
*
* SimpleControllerHandlerAdapter 与 RequestMappingHandlerAdapter 对比:
* 1、RequestMappingHandlerAdapter:以 @RequestMapping 作为映射路径
* 2、控制器的具体方法会被当作 handler
* 3、RequestMappingHandlerAdapter:调用 handler
*/
public class Test27Application {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}
结果(浏览区访问 localhost:8080/c1):
this is c1
15.2、RouterFunctionMapping 与 HandlerFunctionAdapter
- RouterFunctionMapping:收集所有 RouterFunction,它包括两部分:① RequestPredicate 设置映射条件;② HandlerFunction 包含处理逻辑。
- 请求到达,根据映射条件找到 HandlerFunction,即 handler;
- HandlerFunctionAdapter:调用 handler。
代码演示:
@Configuration
public class WebConfig {
// ------------------------ web 开发三件套--------------------------
@Bean // 内嵌 web 容器工厂
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean // 创建 DispatcherServlet
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean // 注册 DispatcherServlet,SpringMVC 的入口
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
return registrationBean;
}
// ----------------------------------------------------------
// 会收集所有 RouterFunction<> 并记录下来。当请求来了之后,会根据请求路径找到匹配的 RouterFunction<>
// 找到之后,交给 HandlerFunctionAdapter 来调用处理器 HandlerFunction,执行完之后将相应返回给浏览器
@Bean
public RouterFunctionMapping routerFunctionMapping() {
return new RouterFunctionMapping();
}
// 用来调用处理器 HandlerFunction
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
@Bean
public RouterFunction<ServerResponse> r1() {
// 表示如果请求路径为 /r1 ,则由后面的 HandlerFunction<> 来处理请求
return RouterFunctions.route(RequestPredicates.GET("/r1"), new HandlerFunction<ServerResponse>() {
@Override
public ServerResponse handle(ServerRequest request) throws Exception {
return ServerResponse.ok().body("this is r1");
}
});
}
@Bean
public RouterFunction<ServerResponse> r2() {
// 表示如果请求路径为 /r2 ,则由后面的 HandlerFunction<> 来处理请求
return RouterFunctions.route(RequestPredicates.GET("/r2"), new HandlerFunction<ServerResponse>() {
@Override
public ServerResponse handle(ServerRequest request) throws Exception {
return ServerResponse.ok().body("this is r2");
}
});
}
}
/**
* 函数式控制器:
* 1、RouterFunctionMapping:通过 RequestPredicate 映射路径
* 2、handler 要实现 HandlerFunction 接口
* 3、HandlerFunctionAdapter:调用 handler
* RequestHandlerMapping 与 HandlerFunctionAdapter 对比:
* 1、RequestMappingHandlerMapping:以 @RequestMapping 作为映射路径
* 2、控制器的具体方法会被当做 handler
* 3、RequestMappingHandlerAdapter:调用 handler
*/
public class Test28Application {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}
结果(浏览器访问 localhost:8080/r1):
this is r1
15.3、SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter
静态资源处理:
- SimpleUrlHandlerMapping 做映射;
- ResourceHttpRequestHandler 作为处理器处理静态资源;
- HttpRequestHandlerAdapter 调用处理器。
代码演示:
@Configuration
public class WebConfig {
// ------------------------ web 开发三件套--------------------------
@Bean // 内嵌 web 容器工厂
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean // 创建 DispatcherServlet
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean // 注册 DispatcherServlet,SpringMVC 的入口
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
return registrationBean;
}
// ----------------------------------------------------------
@Bean // 这个类没有实现 InitializingBean 接口,没法初始化,需要我们手动添加 ResourceHttpRequestHandler
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("/**") // 访问 /index.html 或者 /some-page.html 都会匹配到这个处理器
public ResourceHttpRequestHandler handler1() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(Arrays.asList(new ClassPathResource("static/")));
// 添加资源处理器,所有解析器形成责任链(依次调用这些解析器)
handler.setResourceResolvers(Arrays.asList(
new CachingResourceResolver(new ConcurrentMapCache("cache1")), // 读取资源时可以加入缓存
new EncodedResourceResolver(), // 可以读压缩资源
new PathResourceResolver() // 类路径资源解析器
));
return handler;
}
@Bean("/img/**") // 访问 /img/1.jpg 、 /img/2.jpg、... 都会匹配到这个处理器
public ResourceHttpRequestHandler handler2() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(Arrays.asList(new ClassPathResource("images/")));
return handler;
}
// // 创建一个欢迎页映射器(SpringBoot提供)
// @Bean
// public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
// Resource resource = applicationContext.getResource("classpath:static/index.html");
// return new WelcomePageHandlerMapping(null, applicationContext, resource, "/**");
// // 会生成一个处理器,实现了 Controller 接口,由 SimpleControllerHandlerAdapter 来执行
// }
//
// @Bean
// public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
// return new SimpleControllerHandlerAdapter();
// }
// 需要我们自己提供压缩文件
@PostConstruct
@SuppressWarnings("all")
public void initGzip() throws IOException {
ClassPathResource resource = new ClassPathResource("static");
File dir = resource.getFile();
for (File file : dir.listFiles(pathname -> pathname.getName().endsWith(".html"))) {
System.out.println(file);
try (FileInputStream fis = new FileInputStream(file); GZIPOutputStream fos = new GZIPOutputStream(new FileOutputStream(file.getPath() + ".gz"))) {
byte[] bytes = new byte[8 * 1024];
int len;
while((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}
}
}
}
public class Test29Application {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}
15.4、总结
HandlerMapping 负责建立请求与控制器之间的关系(有顺序问题,顺序从上到下,上一个映射器无法处理则交给下一个):
- RequestMappingHandlerMapping:与 @RequestMapping 匹配;
- WelcomePageHandleMapping: / 匹配欢迎页;
- BeanNameHandlerMapping:与 Bean 的名字匹配,以 / 开头;
- RouterFunctionMapping:函数式 RequestPredicate, HandlerFunction;
- SimpleUrlHandlerMapping:静态资源,如通配符 /**、/img/** 。
HandlerAdapter 负责实现对各种各样的 handler 的适配调用(典型适配器模式体现):
- RequestMappingHandlerAdapter 处理:@RequestMapping 方法。参数解析器、返回值处理器体现了组合模式。
- SimpleControllerHandlerAdapter 处理:Controller 接口。
- HandlerFunctionAdapter 处理:HandlerFunction 函数式接口。
- HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口(静态资源处理)。
ResourceHttpRequestHandler.setResourceResolvers() 这是典型责任链模式体现。
标签:return,Spring,request,高级,public,method,new,部分,class From: https://www.cnblogs.com/aoe1231/p/17315826.html