主启动类:
- public class A20 {
- public static void main(String[] args) throws Exception {
- //支持内嵌 Tomcat 容器的 Spring 容器实现
- AnnotationConfigServletWebServerApplicationContext context =
- new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
- }
- }
配置类
- @Configuration
- @ComponentScan
- //指定资源文件读取的位置
- @PropertySource("classpath:application.properties")
- //让使用了 @ConfigurationProperties 注解的类生效,并且将该类注入到 IOC 容器中,交由 IOC 容器进行管理
- @EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
- public class WebConfig {
-
- // 内嵌 web 容器工厂
- @Bean
- public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
- return new TomcatServletWebServerFactory(serverProperties.getPort());
- }
-
- // 创建 DispatcherServlet
- @Bean
- public DispatcherServlet dispatcherServlet() {
- return new DispatcherServlet();
- }
-
- // 向 Tomcat 注册 DispatcherServlet, Spring MVC 的入口
- @Bean
- public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
- DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
- DispatcherServletRegistrationBean registrationBean =
- new DispatcherServletRegistrationBean(dispatcherServlet, "/");
- return registrationBean;
- }
- }
启动容器时:

第一次访问时:

DispatcherServlet初始化不是 Spring 管理 ,是 Tomcat 服务器在首次使用到 DispatcherServlet时,才由 Tomcat 服务器 进行初始化。
我们可以设置 LoadOnStartup ,让 Tomcat 启动时初始化 DispatcherServlet。
一旦设置大于 0 的值,就会在 Tomcat 启动时初始化。数字大小代表优先级,如果有多个 Servlet,数字小的优先级高。
- @Bean
- public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
- DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
- DispatcherServletRegistrationBean registrationBean =
- new DispatcherServletRegistrationBean(dispatcherServlet, "/");
- registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
- return registrationBean;
- }
DispatcherServlet 初始化时,会执行 onRefresh() 方法,从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化
- public class DispatcherServlet extends FrameworkServlet {
-
- protected void onRefresh(ApplicationContext context) {
- this.initStrategies(context);
- }
-
- protected void initStrategies(ApplicationContext context) {
- this.initMultipartResolver(context);
- this.initLocaleResolver(context);
- this.initThemeResolver(context);
- this.initHandlerMappings(context);
- this.initHandlerAdapters(context);
- this.initHandlerExceptionResolvers(context);
- this.initRequestToViewNameTranslator(context);
- this.initViewResolvers(context);
- this.initFlashMapManager(context);
- }
- }
RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
- private void initHandlerMappings(ApplicationContext context) {
- this.handlerMappings = null;
- // 该值默认为 true 但是可以设置为 false
- // 如果设置为 false 那 Spring MVC就只会查找名为“handlerMapping”的bean,并作为当前系统的唯一的HandlerMapping
- // 如果是 ture 则查询所有的..
- if (this.detectAllHandlerMappings) {
- //在ApplicationContext中查找所有handler映射,包括父类上下文。
- Map
matchingBeans = - BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
- //如果不为空
- if (!matchingBeans.isEmpty()) {
- // 将获取的 HandlerMapping 转换成集合..
- this.handlerMappings = new ArrayList<>(matchingBeans.values());
- // 排序
- AnnotationAwareOrderComparator.sort(this.handlerMappings);
- }
- }
- else {
- try {
- // 从容器中获取 HandlerMapping ,如果获取不到 下面则会添加默认的..
- HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
- this.handlerMappings = Collections.singletonList(hm);
- }
- catch (NoSuchBeanDefinitionException ex) {
- }
- }
-
- //通过注册,确保至少有一个HandlerMapping
- //如果找不到其他映射,则为默认的HandlerMapping。
- if (this.handlerMappings == null) {
- this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
- }
-
- for (HandlerMapping mapping : this.handlerMappings) {
- if (mapping.usesPathPatterns()) {
- this.parseRequestPath = true;
- break;
- }
- }
- }
-
-
- protected
List getDefaultStrategies(ApplicationContext context, Class strategyInterface) { - if (defaultStrategies == null) {
- try {
- // 从配置文件加载默认的 简单的说 加载 DispatcherServlet.properties 这个文件...
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
- // 默认的策略~~~
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
- }
- }
- //获取 org.springframework.web.servlet.HandlerMapping
- String key = strategyInterface.getName();
- // 配置文件中定义了三个 默认组件
- // org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
- // org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
- // org.springframework.web.servlet.function.support.RouterFunctionMapping
- String value = defaultStrategies.getProperty(key);
- if (value != null) {
- // 将 获取的 value 转换成数组
- String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
- List
strategies = new ArrayList<>(classNames.length); - // 循环
- for (String className : classNames) {
- try {
- // 根据 路径获取 class
- Class> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
- // 创建...
- Object strategy = createDefaultStrategy(context, clazz);
- // 添加到集合中
- strategies.add((T) strategy);
- }
- catch (ClassNotFoundException ex) {
- // 抛出异常
- }
- catch (LinkageError err) {
- // 抛出异常
- }
- }
- return strategies;
- }
- else {
- return new LinkedList<>();
- }
- }
-
配置类添加RequestMappingHandlerMapping,如果用 DispatcherServlet 初始化时默认添加的组件, 并不会放到 Spring 容器里,给测试带来困扰。
- @Bean
- public RequestMappingHandlerMapping requestMappingHandlerMapping(){
- return new RequestMappingHandlerMapping();
- }
Controller类
- @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(String token) {
- log.debug("test3({})", token);
- return null;
- }
-
- @RequestMapping("/test4")
- 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 class A20 {
- public static void main(String[] args) throws Exception {
- //支持内嵌 Tomcat 容器的 Spring 容器实现
- AnnotationConfigServletWebServerApplicationContext context =
- new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
-
- RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
- //收集所有 @RequestMapping 映射信息,封装为 Map
- Map
handlerMethods = handlerMapping.getHandlerMethods(); - handlerMethods.forEach((k, v) -> {
- System.out.println(k + " = " + v);
- });
- }
- }
结果:
- {GET [/test1]} = com.itheima.a20.Controller1#test1()
- {PUT [/test3]} = com.itheima.a20.Controller1#test3(String)
- { [/test4]} = com.itheima.a20.Controller1#test4()
- {POST [/test2]} = com.itheima.a20.Controller1#test2(String)
有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet。
可以使用 MockHttpServletRequest 模拟请求, Spring 提供的用于测试使用
案例一
- MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
- request.setParameter("name", "张三");
- HandlerExecutionChain chain = handlerMapping.getHandler(request);
- System.out.println(chain);
结果:
HandlerExecutionChain with [com.itheima.a20.Controller1#test2(String)] and 0 interceptors
案例二
- MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
- request.addHeader("token", "令牌");
- HandlerExecutionChain chain = handlerMapping.getHandler(request);
- System.out.println(chain);
结果:
HandlerExecutionChain with [com.itheima.a20.Controller1#test3(String)] and 0 interceptors
RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
源码和 初始化HandlerMapping 类似,默认添加的组件如下
- org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
- org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
- org.springframework.web.servlet.function.support.HandlerFunctionAdapter
继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个HandlerAdapter
- @Bean
- public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
- MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
- return handlerAdapter;
- }
使用 MyRequestMappingHandlerAdapter 原因:RequestMappingHandlerAdapter的 invokeHandlerMethod 方法作用域是 protected,反射调用太麻烦,就写了个子类,调用子类的方法即可。
- public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
- @Override
- public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
- return super.invokeHandlerMethod(request, response, handlerMethod);
- }
- }
主启动类
- public class A20 {
- public static void main(String[] args) throws Exception {
- //支持内嵌 Tomcat 容器的 Spring 容器实现
- AnnotationConfigServletWebServerApplicationContext context =
- new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
- MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
-
- System.out.println(">>>>>>>>>>>>>>所有的参数解析器");
- for (HandlerMethodArgumentResolver argumentResolver : handlerAdapter.getArgumentResolvers()) {
- System.out.println(argumentResolver);
- }
-
- System.out.println(">>>>>>>>>>>>>>所有的返回值解析器");
- for (HandlerMethodReturnValueHandler returnValueHandler : handlerAdapter.getReturnValueHandlers()) {
- System.out.println(returnValueHandler);
- }
- }
- }
结果:

调用 invokeHandlerMethod 执行 控制器方法
- public class A20 {
- public static void main(String[] args) throws Exception {
- //支持内嵌 Tomcat 容器的 Spring 容器实现
- AnnotationConfigServletWebServerApplicationContext context =
- new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
-
- RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
- MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
-
- //MockHttpServletRequest: Spring提供的模拟request对象,用于测试使用
- MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
- request.setParameter("name", "张三");
-
- MockHttpServletResponse response = new MockHttpServletResponse();
- //返回处理器执行链对象
- HandlerExecutionChain chain = handlerMapping.getHandler(request);
-
- handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
-
- }
- }
结果:
[DEBUG] 10:40:09.745 [main] com.itheima.a20.Controller1 - test2(张三)
自定义的注解:
- // 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
- @Target(ElementType.PARAMETER) // 注解位置:方法参数上
- @Retention(RetentionPolicy.RUNTIME) // 注解作用范围:运行期
- public @interface Token {
- }
- // 与 @ResponseBody 注解功能类似,不过返回的是 yml 格式的字符串
- @Target(ElementType.METHOD) // 注解位置:方法上
- @Retention(RetentionPolicy.RUNTIME) //注解作用范围:运行期
- public @interface Yml {
- }
Controller类
- @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")
- @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;
- }
-
- }
- }
实现 HandlerMethodArgumentResolver 接口
- 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");
- }
- }
将自定义的参数解析器加到handleAdapter
- @Bean
- public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
- TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
- MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
- //将自定义的参数解析器加到handleAdapter
- handlerAdapter.setCustomArgumentResolvers(Arrays.asList(tokenArgumentResolver));
- return handlerAdapter;
- }
主启动类
- public class A20 {
- public static void main(String[] args) throws Exception {
- //支持内嵌 Tomcat 容器的 Spring 容器实现
- AnnotationConfigServletWebServerApplicationContext context =
- new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
-
- RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
- MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
-
- MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
- request.addHeader("token", "令牌");
- MockHttpServletResponse response = new MockHttpServletResponse();
- //返回处理器执行链对象
- HandlerExecutionChain chain = handlerMapping.getHandler(request);
-
- handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
- }
- }
结果:
[DEBUG] 10:51:26.150 [main] com.itheima.a20.Controller1 - test3(令牌)
实现 HandlerMethodReturnValueHandler 接口
- public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
- @Override
- public boolean supportsReturnType(MethodParameter returnType) {
- Yml yml = returnType.getMethodAnnotation(Yml.class);
- return yml != null;
- }
-
- @Override
- public void handleReturnValue(Object returnValue, MethodParameter returnType,
- ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
- //returnValue : 返回值对象
- //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);
- }
- }
将自定义的返回值解析器加到 handleAdapter
- @Bean
- public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
- MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
- YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
- //将自定义的返回值解析器加到handleAdapter
- handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(ymlReturnValueHandler));
- return handlerAdapter;
- }
主启动类
- public class A20 {
- public static void main(String[] args) throws Exception {
- //支持内嵌 Tomcat 容器的 Spring 容器实现
- AnnotationConfigServletWebServerApplicationContext context =
- new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
-
- RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
- MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
-
- MockHttpServletRequest request = new MockHttpServletRequest("Get", "/test4");
- MockHttpServletResponse response = new MockHttpServletResponse();
- //返回处理器执行链对象
- HandlerExecutionChain chain = handlerMapping.getHandler(request);
-
- handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
-
- //检查响应
- byte[] content = response.getContentAsByteArray();
- System.out.println(new String(content, StandardCharsets.UTF_8));
-
- }
- }
结果:
!!com.itheima.a20.Controller1$User {age: 18, name: 张三}