第二十讲-DispatcherServlet
第二十讲-DispatcherServlet
1. DispatcherServlet初始化时机
我们首先创建一个支持Tomcat WEB的Spring容器:
package com.cherry.a20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
public class A20 {
private static final Logger log = LoggerFactory.getLogger(A20.class);
public static void main(String[] args) {
// 创建一个支持内嵌Tomcat的容器
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}
由于AnnotationConfigServletWebServerApplicationContext
需要一个配置文件,接下来我们编写这个配置文件,在这个配置文件中,我们需要创建一个Tomcat容器和servlet对象,并将servlet对象注册进tomcat容器中,因为servlet对象依托于tomcat中才能运行:
package com.cherry.a20;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
@Configuration
@ComponentScan
public class WebConfig {
// 1. 内嵌 web 容器
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
// 2. 创建DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet();
}
// 3. 注册DispatcherServlet到Tomcat容器中,Spring MVC的入口
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
}
启动测试:
18:20:31.480 [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)
八月 06, 2024 6:20:31 下午 org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
八月 06, 2024 6:20:31 下午 org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
八月 06, 2024 6:20:31 下午 org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.53]
八月 06, 2024 6:20:31 下午 org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring embedded WebApplicationContext
18:20:31.597 [main] DEBUG org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext - Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
18:20:31.597 [main] INFO org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1226 ms
18:20:31.601 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dispatcherServlet'
18:20:31.630 [main] DEBUG org.springframework.boot.web.servlet.ServletContextInitializerBeans - Mapping filters:
18:20:31.630 [main] DEBUG org.springframework.boot.web.servlet.ServletContextInitializerBeans - Mapping servlets: dispatcherServlet urls=[/]
18:20:31.664 [main] DEBUG org.springframework.context.support.DefaultLifecycleProcessor - Starting beans in phase 2147483646
八月 06, 2024 6:20:31 下午 org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
18:20:31.694 [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''
我们从控制台看到,Tomcat服务器已经创建好了,且端口号为8080,Spring容器也是创建好了。但是此时的DispatcherServlet初始化好了吗?其实现在并没有(因为控制台上找不到DispatcherServlet初始化的任何信息)。
DispatcherServlet对象是由Spring容器创建和托管的,但是DispatcherServlet的初始化并不是Spring容器管理的,而是Tomcat服务器在首次使用到的时候由Tomcat完成初始化。DispatcherServlet的生命周期是交给Tomcat管理的!下面呢,我们来访问一下:http:://localhost:8080/
,我们看一下控制台的信息:
八月 06, 2024 6:31:54 下午 org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring DispatcherServlet 'dispatcherServlet'
18:31:54.099 [http-nio-8080-exec-3] INFO org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
18:31:54.125 [http-nio-8080-exec-3] DEBUG _org.springframework.web.servlet.HandlerMapping.Mappings - 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping' {}
18:31:54.713 [http-nio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - ControllerAdvice beans: none
18:31:54.765 [http-nio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - ControllerAdvice beans: none
18:31:54.784 [http-nio-8080-exec-3] DEBUG org.springframework.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
18:31:54.784 [http-nio-8080-exec-3] INFO org.springframework.web.servlet.DispatcherServlet - Completed initialization in 685 ms
18:31:54.796 [http-nio-8080-exec-3] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/", parameters={}
18:31:54.807 [http-nio-8080-exec-3] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /
18:31:54.809 [http-nio-8080-exec-3] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
18:31:55.693 [http-nio-8080-exec-5] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/favicon.ico", parameters={}
18:31:55.693 [http-nio-8080-exec-5] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /favicon.ico
18:31:55.693 [http-nio-8080-exec-5] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
我们发现,此时Servlet完成了初始化,并且还初始化了一些组件,关于这些组件,我们在后面慢慢的了解。
既然我们知道了DispatcherServlet对象是由Spring容器创建,DispatcherServlet对象的初始化是在第一期请求到来后在Tomcat中完成初始化,那么我想让DispatcherServlet对象在伴随着Tomcat服务器的启动而完成初始化工作可以吗?当然可以,我们可以在注册DispatcherServlet到Tomcat容器中
中手动设置:
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){
DispatcherServletRegistrationBean dispatcherServletRegistrationBean =
new DispatcherServletRegistrationBean(dispatcherServlet, "/");
dispatcherServletRegistrationBean.setLoadOnStartup(1);
return dispatcherServletRegistrationBean;
}
18:39:17.859 [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)
八月 06, 2024 6:39:17 下午 org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
八月 06, 2024 6:39:17 下午 org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
八月 06, 2024 6:39:17 下午 org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.53]
八月 06, 2024 6:39:17 下午 org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring embedded WebApplicationContext
18:39:17.975 [main] DEBUG org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext - Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
18:39:17.975 [main] INFO org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1531 ms
18:39:17.979 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dispatcherServlet'
18:39:18.005 [main] DEBUG org.springframework.boot.web.servlet.ServletContextInitializerBeans - Mapping filters:
18:39:18.005 [main] DEBUG org.springframework.boot.web.servlet.ServletContextInitializerBeans - Mapping servlets: dispatcherServlet urls=[/]
18:39:18.033 [main] DEBUG org.springframework.context.support.DefaultLifecycleProcessor - Starting beans in phase 2147483646
八月 06, 2024 6:39:18 下午 org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
18:39:18.077 [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''
18:39:18.079 [main] DEBUG org.springframework.context.support.DefaultLifecycleProcessor - Successfully started bean 'webServerStartStop'
我们发现设置后DispatcherServlet在Tomcat启动的时候就完成了初始化工作。
2. DispatcherServlet在初始化时都做了哪些事情?
接下来我们看看DispatcherServlet在初始化的时候做了哪些事?我们点击源码进去看看:
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);
}
我们发现DispatcherServlet时会初始化很多组件,例如上面的注释。接下来我们分析一下这些重要的组件的相关原理。
3. RequestMappingHandlerMapping(处理器映射器)组件
RequestMappingHandlerMapping主要作用就是建立请求路径到控制器之间的映射,如果有请求来了,就可以快速找到该请求由哪个控制器的哪个方法来处理该请求。在这里面,我们发现了一个单词RequestMapping,其实,说白了就是根据@RequestMapping
注解来实现路径映射的。
RequestMappingHandlerMapping组件运行的大致流程:
- 首先扫描所有被@Controlelr及其派生注解被标注的类
- 查看这些控制器中哪些方法上加了@RequestMapping及其派生注解
- 筛选出来之后记录路径以及对应的控制器方法,存储在RequestMappingHandlerMapping中
我们首先在配置类中加入RequestMappingHandlerMapping
组件:
// 加入`RequestMappingHandlerMapping`组件
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(){
return new RequestMappingHandlerMapping();
}
我们在容器中获取到我们加入的RequestMappingHandlerMapping组件,并且获取映射结果:
// RequestMappingHandlerMapping作用:解析我们的@RequestMapping以及派生注解,生成路径与控制器方法的映射关系
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
//获取映射结果,其中的key时路径信息,value是控制器方法信息
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k,v)->{
System.out.println(k+"="+v);
});
编写一个控制器:
package com.cherry.a20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.yaml.snakeyaml.Yaml;
@Controller
public class Controller1 {
private static final Logger log = LoggerFactory.getLogger(Controller1.class);
@GetMapping("/test1")
public ModelAndView test1() throws Exception {
log.debug("test1()");
return null;
}
@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
log.debug("test2({})", name);
return null;
}
@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
log.debug("test3({})", token);
return null;
}
@RequestMapping("/test4")
// @ResponseBody
@Yml
public User test4() {
log.debug("test4");
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 int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
String str = new Yaml().dump(new User("张三", 18));
System.out.println(str);
}
}
测试结果如下:
{PUT [/test3]}=com.cherry.a20.Controller1#test3(String)
{GET [/test1]}=com.cherry.a20.Controller1#test1()
{ [/test4]}=com.cherry.a20.Controller1#test4()
{POST [/test2]}=com.cherry.a20.Controller1#test2(String)
我们看到了请求路径到控制方法之间的映射!当请求来的时候,SpringMVC是怎么根据请求信息获取到对应的方法信息呢?看下面的代码:
// 请求来了,会返回一个处理器执行链对象,这个处理器执行链中不仅包含该路径对应的处理器方法,还会包括一些拦截器
HandlerExecutionChain handlerExecutionChain =
handlerMapping.getHandler(new MockHttpServletRequest("GET", "/test1"));
System.out.println(handlerExecutionChain);
我们发现,在根据请求路由获取处理器方法的过程中,会返回一个处理器执行链,这个处理器执行链不仅包括了处理器执行方法,还包括一些拦截器等(不过我们现在没有定义拦截器,因此现在没有拦截器):
{PUT [/test3]}=com.cherry.a20.Controller1#test3(String)
{GET [/test1]}=com.cherry.a20.Controller1#test1()
{ [/test4]}=com.cherry.a20.Controller1#test4()
{POST [/test2]}=com.cherry.a20.Controller1#test2(String)
19:58:16.657 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.cherry.a20.Controller1#test1()
HandlerExecutionChain with [com.cherry.a20.Controller1#test1()] and 0 interceptors
4. RequstMappingHandlerAdapter
RequstMappingHandlerAdapter处理器适配器,处理器适配器的作用就是调用控制器方法。下面呢,我们通过几个例子来演示一下处理器适配器的用法
在配置文件中加入处理器适配器组件
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
return new RequestMappingHandlerAdapter();
}
由于RequstMappingHandlerAdapter中的核心方法invokeHandlerMethod
是受保护的,不能直接调用。我们可以定义它的子类,并将该方法的访问修饰符改为public:
package com.cherry.a20;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
@Override
public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
return super.invokeHandlerMethod(request, response, handlerMethod);
}
}
在配置类中就不用RequstMappingHandlerAdapter
了,就使用MyRequestMappingHandlerAdapter
:
// 加入处理器适配器组件
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
return new MyRequestMappingHandlerAdapter();
}
编写主方法测试,调用test1
的方法
// 请求来了,会返回一个处理器执行链对象,这个处理器执行链中不仅包含该路径对应的处理器方法,还会包括一些拦截器
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain handlerExecutionChain =
handlerMapping.getHandler(request);
System.out.println(handlerExecutionChain);
System.out.println(">>>================>>>");
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) handlerExecutionChain.getHandler());
运行结果如下:
HandlerExecutionChain with [com.cherry.a20.Controller1#test1()] and 0 interceptors
>>>================>>>
20:44:41.517 [main] DEBUG com.cherry.a20.Controller1 - test1()
我们发现,`test``路由对应的控制器方法被调用了。
我们总结一下RequestMappingHandlerAdapter
的作用:调用控制器方法
刚才我们演示了一个比较近简单的控制器方法,但是有些控制器比较复杂,控制器方法中还包含的有参数,一些参数注解等,对于这些参数,SpringMVC是怎么处理的呢?原来SpringMVC还会为这些参数提供一些参数解析器专门用于解析这些参数,现在我们来看一下这个参数解析器。
我们可以从HandlerAdapter
中获取参数解析器,然后我们输出打印一下:
System.out.println("===================<<<");
List<HandlerMethodArgumentResolver> argumentResolvers = handlerAdapter.getArgumentResolvers();
argumentResolvers.forEach(handlerMethodArgumentResolver -> {
System.out.println(handlerMethodArgumentResolver);
});
===================<<<
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@3f6db3fb 解析@RequestParam
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@52de51b6
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@18c5069b 解析@PathVariable
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@3a0d172f
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@68ad99fe
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@485e36bc
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@781f10f2
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@2a79d4b1
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@2e9fda69
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@17cdf2d0
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@1755e85b
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@736d6a5c
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@2371aaca
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@5b529706
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@63fdab07
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@7b5a12ae
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@5553d0f5
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@1af687fe
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@14dda234
org.springframework.web.method.annotation.ModelMethodProcessor@3f390d63
org.springframework.web.method.annotation.MapMethodProcessor@74a6a609
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@5a411614
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2374d36a
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@54d18072
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@1506f20f
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@47a5b70d
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@424fd310
我们发现SpringMVC默认提供了很多参数解析器,这里面有很多我们看着很面熟的一些参数解析器,对于这些解析器,我们在后面会慢慢的了解!
除了参数解析器以外,还有一类解析器,叫做返回值解析器,因为在Spring中控制器的返回值类型非常的灵活,可以返回ModelAndView, Model,字符串类型,对象类型,对于这些返回值,它页需要一些相应的解析器对返回值进行解析,最终呢,返回值都会统一转为ModelAndView对象,同样的,我们可以使用HandlerAdapter获取返回值解析器:
System.out.println("++++++++++++++++++++++++++");
List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
returnValueHandlers.forEach(handlerMethodReturnValueHandler->{
System.out.println(handlerMethodReturnValueHandler);
});
++++++++++++++++++++++++++
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@5286c33a // ModelAndView
org.springframework.web.method.annotation.ModelMethodProcessor@6e6d5d29
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@5c530d1e
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@6c25e6c4
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@85e6769
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@c5ee75e
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@48a12036
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@bf1ec20
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@70efb718
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@b70da4c
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@4a11eb84
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@4e858e0a
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@435fb7b5
org.springframework.web.method.annotation.MapMethodProcessor@4e70a728
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@b7838a9
我们发现,返回值解析器也是特别多的,里面也有我们熟悉的返回值。对于这些返回值解析器,我们后面也会了解。
接下来,我们自定义一个参数解析器,如前面控制器中的方法:
@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
log.debug("test3({})", token);
return null;
}
这个@Token
注解不是Spring提供的,而是我们自己定义的。接下来我们来为@Token编写一个参数解析器。
首先编写该注解的定义
// 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
// token=令牌
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}
接下来我们编写这个解析器:
package com.cherry.a20;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
@Override
// 是否支持某个参数:检查这个方法参数中是否有@Token注解,有则返回true,反则返回false
public boolean supportsParameter(MethodParameter parameter) {
Token token = parameter.getParameterAnnotation(Token.class);
return token != null;
}
@Override
// 解析参数,如果方法参数中由@Token注解,则执行该方法,否则不执行该方法
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 从请求头中获取token值并返回
return webRequest.getHeader("token");
}
}
将我们写好的参数解析器加入到我们自定义的MyRequestMappingHandlerAdapter
中:
// 加入处理器适配器组件
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
MyRequestMappingHandlerAdapter myRequestMappingHandlerAdapter
= new MyRequestMappingHandlerAdapter();
List myRequestMappingHandlerAdapters = new ArrayList();
myRequestMappingHandlerAdapters.add(myRequestMappingHandlerAdapter);
myRequestMappingHandlerAdapter.setCustomArgumentResolvers(myRequestMappingHandlerAdapters);
return myRequestMappingHandlerAdapter;
}
我们写个测试类测试一下:
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
request.addHeader("token", "123456");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>");
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) handlerExecutionChain.getHandler());
>>>>>>>>>>>>>>>>>>>>>>>>
-test3(123456)
接下来我们自定义一个返回值处理器,前面我们了解到,返回值处理器会根据控制器的返回值类型而做出不同的选择,除此以外,还会根据控制器方法上是否有某些注解,例如@ResponseBody
,做出一些不同的处理。下面呢,我们模拟注解对返回值的影响。如下面的控制器:
@RequestMapping("/test4")
// @ResponseBody
@Yml
public User test4() {
log.debug("test4");
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 int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
对@Yml注解的定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}
那么我们怎么将JAVA对象转为YML呢?这里我们使用第三方工具。我们稍后再解释。我们定义一个返回值处理器:
package com.cherry.a20;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.yaml.snakeyaml.Yaml;
import javax.servlet.http.HttpServletResponse;
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 判断返回值上的方法上有没有加@Yml注解
Yml yml = returnType.getMethodAnnotation(Yml.class);
return yml != null;
}
// 如果上一个方法返沪true,则执行该方法,否则不执行该方法
@Override // 返回值
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 1. 转换返回结果为 yaml 字符串
String str = new Yaml().dump(returnValue);
// 2. 将 yaml 字符串写入响应
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType("text/plain;charset=utf-8");
response.getWriter().print(str);
// 3. 设置请求已经处理完毕
mavContainer.setRequestHandled(true);
}
}
将我们写好的返回值处理器写入配置类中:
// 加入处理器适配器组件
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
handlerAdapter.setCustomArgumentResolvers((List<HandlerMethodArgumentResolver>) tokenArgumentResolver);
handlerAdapter.setCustomReturnValueHandlers((List<HandlerMethodReturnValueHandler>) ymlReturnValueHandler);
return handlerAdapter;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构