笔记参考B站黑马程序员视频:spring原理
1.DispatcherServlet 初始化
加入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>AnnotationConfigServletWebServerApplicationContext 是 Spring Framework 中用于基于注解的配置和 Servlet 环境的应用上下文。这种上下文通常用于 Spring Web 应用中,通过 Java 配置类来启动和配置 Spring 应用。
也就是这个支持内嵌tomcat
package com.dreams.demo20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
public class Demo {
private static final Logger log = LoggerFactory.getLogger(Demo.class);
public static void main(String[] args) throws Exception {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}WebConfig 类是一个配置类,用于配置 Spring Web 应用。
package com.dreams.demo20;
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 {
// 内嵌 web 容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
// 创建 DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册 DispatcherServlet, Spring MVC 的入口
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
}它包含了以下几个关键的配置:
- TomcatServletWebServerFactory Bean:用于创建内嵌的 Tomcat Web 服务器工厂。通过注入 ServerProperties 对象获取端口号,并将其传递给 TomcatServletWebServerFactory 的构造函数。
- DispatcherServlet Bean:用于创建 DispatcherServlet 实例。
- DispatcherServletRegistrationBean Bean:用于注册 DispatcherServlet 到 Spring MVC 中。这是 Spring MVC 的入口。并指定 URL 映射为 “/”,表示所有的请求都将由这个 DispatcherServlet 处理。
输出如下:

那DispatcherServlet什么时候被初始化
当内嵌的 Servlet 容器(例如 Tomcat)启动时,它会初始化所有注册的 Servlet,包括通过 DispatcherServletRegistrationBean 注册的 DispatcherServlet。
在 Servlet 容器初始化过程中,DispatcherServlet 的 init 方法会被调用。
这里我们访问8080端口

可以看到初始化了

DispatcherServletRegistrationBean 用来注册 DispatcherServlet 到 Servlet 容器,并配置其属性。配置registrationBean.setLoadOnStartup(1),就是设置 DispatcherServlet 的 load-on-startup 属性为 1。这表示在应用启动时就加载 DispatcherServlet。
// 注册 DispatcherServlet, Spring MVC 的入口
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}可以看到一起启动就加载。

registrationBean.setLoadOnStartup(1)应该读取配置文件来配置

默认值是-1.

@EnableConfigurationProperties 注解指定了两个参数:WebMvcProperties.class 和 ServerProperties.class。自动装配并使用这两个类中定义的配置属性。
WebMvcProperties 类通常是用来配置 Spring MVC 的属性,也就是以spring.mvc打头的配置,如:spring.mvc.servlet.load-on-startup=1
ServerProperties 类则通常包含了配置服务器(如内嵌的 Tomcat)的属性,例如端口号、上下文路径等。也就是以server打头的配置,如:server.port=8848
使用代码如下:
package com.dreams.demo20;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.DispatcherServlet;
@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {
// 内嵌 web 容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
return new TomcatServletWebServerFactory(serverProperties.getPort());
}
// 创建 DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册 DispatcherServlet, Spring MVC 的入口
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registrationBean;
}
}
DispatcherServlet类的真正逻辑在onRefresh方法


用于初始化 Servlet 使用的各种策略对象:
- initMultipartResolver(context)
初始化用于处理文件上传的 MultipartResolver 对象。 - initLocaleResolver(context)
初始化用于解析客户端请求的区域信息(Locale)的 LocaleResolver 对象。 - initThemeResolver(context)
初始化用于解析主题(Theme)的 ThemeResolver 对象。 - initHandlerMappings(context)
初始化用于映射请求到处理器(Controller)的 HandlerMapping 对象。 - initHandlerAdapters(context)
初始化用于执行处理器的 HandlerAdapter 对象。 - initHandlerExceptionResolvers(context)
初始化用于处理异常的 HandlerExceptionResolver 对象。 - initRequestToViewNameTranslator(context)
初始化用于将请求映射为视图名称的 RequestToViewNameTranslator 对象。 - initViewResolvers(context)
初始化用于解析视图的 ViewResolver 对象。 - initFlashMapManager(context)
初始化用于管理 Flash Map 的 FlashMapManager 对象。
这些方法的实现大同小异,比如initHandlerMappings方法,如下:
detectAllHandlerMappings 为 true 时获取所有 HandlerMapping 实例,也会从父容器中查找,反之则不会

如果没有就调用getDefaultStrategies方法

这里会默认读取DispatcherServlet.properties


都会看到默认配置

可以看到,没有配置默认实现的就是配置文件的配置

2.RequestMappingHandlerMapping基本用途
RequestMappingHandlerMapping 是 Spring MVC 中的一个特定类型的 HandlerMapping 实现,它用于映射 @RequestMapping 注解的方法到具体的处理器(Handler)。也就是路径到控制器方法的映射。
上面的代码没有配置,虽然会默认读取DispatcherServlet.properties,会创建RequestMappingHandlerMapping,但是并不会创建对应Bean到容器中,所以我们在webConfig文件加入以下代码
// 如果用 DispatcherServlet 初始化时默认添加的组件, 并不会作为 bean, 给测试带来困扰
// 1. 加入RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}在当前目录下controller类
package com.dreams.demo20;
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 TestController {
private static final Logger log = LoggerFactory.getLogger(TestController.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 static void main(String[] args) {
String str = new Yaml().dump(new User("张三", 18));
System.out.println(str);
}
}
然后启动类就可以通过getBean获取该Bean了
package com.dreams.demo20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.Map;
public class Demo {
private static final Logger log = LoggerFactory.getLogger(Demo.class);
public static void main(String[] args) throws Exception {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取映射结果
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k, v) -> {
System.out.println("key= " + k + " ; value= " + v);
});
}
}调用 handlerMapping.getHandlerMethods() 方法,可以得到一个 Map<RequestMappingInfo, HandlerMethod> 对象,该对象存储了所有请求路径到处理器方法的映射关系。

上面已经拿到了所有请求路径到处理器方法的映射关系,那么如何获取每个呢
使用 MockHttpServletRequest 来模拟一个 HTTP GET 请求,指定请求方法为 “GET” 和请求路径为 “/test1″。然后就可以使用 handlerMapping.getHandler(request) 方法来获取与请求对应的 HandlerExecutionChain 对象。
// 请求来了,获取控制器方法 返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
3.RequestMappingHandlerAdapter基本用途
RequestMappingHandlerAdapter 是 Spring MVC 框架中的一个核心组件之一,用于将处理器方法映射成可执行的对象,并处理请求和响应的转换。也就是调用控制器方法。
同上默认并不会创建对应Bean到容器中,所以我们需要在webConfig文件加入@Bean,不过因为我们需要的方法是私有的,所以我们实现个子类来扩大范围
package com.dreams.demo20;
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);
}
}
然后在webConfig配置该Bean
// 2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
return new MyRequestMappingHandlerAdapter();
}
Demo类调用
// 请求来了,获取控制器方法 返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler()) 这行代码调用了 invokeHandlerMethod 方法来执行控制器方法。这里传入的chain.getHandler()就是上面的RequestMappingHandlerMapping处理的所有映射了。
可以看到成功调用了该方法。

然后如果要传递参数
@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
log.debug("test2({})", name);
return null;
}代码:
// 请求来了,获取控制器方法 返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name", "张三");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
那么他是如何获取参数解析器的呢?
导入
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
handlerAdapter.getArgumentResolvers() 返回一个包含所有参数解析器的列表。
System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有参数解析器");
for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) {
System.out.println(resolver);
}这里就是他用到的参数解析器

每一个请求的返回值也要处理
导入
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
handlerAdapter.getReturnValueHandlers() 返回一个包含所有返回值解析器的列表
System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有返回值解析器");
for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) {
System.out.println(handler);
}
4.参数解析器
Spring Framework 中的参数解析器(Argument Resolvers)是用于将 HTTP 请求中的参数映射到控制器方法的参数的组件。
常见的解析器:
- RequestParamMethodArgumentResolver:
处理 @RequestParam 注解的方法参数,允许参数的必要性为可选(false)或必需(true)。 - PathVariableMethodArgumentResolver:
处理 @PathVariable 注解的方法参数,用于从 URI 中获取路径变量的值。 - RequestHeaderMethodArgumentResolver:
处理 @RequestHeader 注解的方法参数,用于从 HTTP 请求头中获取数据。 - ServletCookieValueMethodArgumentResolver:
处理 @CookieValue 注解的方法参数,用于从 HTTP 请求的 Cookie 中获取数据。 - ExpressionValueMethodArgumentResolver:
处理 SpEL 表达式作为方法参数的情况。 - ServletRequestMethodArgumentResolver:
处理 HttpServletRequest 和 HttpServletResponse 类型的方法参数。 - ServletModelAttributeMethodProcessor (false):
处理带有 @ModelAttribute 注解的方法参数,这里配置为不允许省略 @ModelAttribute 注解。 - RequestResponseBodyMethodProcessor:
处理带有 @RequestBody 和 @ResponseBody 注解的方法参数和返回值。此处指定使用 MappingJackson2HttpMessageConverter 来处理 JSON 数据的转换。 - ServletModelAttributeMethodProcessor (true):
处理带有 @ModelAttribute 注解的方法参数,这里配置为允许省略 @ModelAttribute 注解。 - RequestParamMethodArgumentResolver (true):
处理 @RequestParam 注解的方法参数,允许参数的必要性为可选(false)或省略(true)@RequestParam 注解。
解析器都有对应的注解来配置
在TestController类中我们定义一个请求方法
@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
log.debug("test3({})", token);
return null;
}上面的用到我们定义一个的注解
package com.dreams.demo20;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
// token=令牌
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}
要处理上面的注解,我们定义一个解析器
这个参数解析器(HandlerMethodArgumentResolver),用于从 HTTP 请求中获取特定的参数(在这里是从请求头中获取 token)并注入到控制器方法的参数中。
需要实现HandlerMethodArgumentResolver 接口,这是 Spring MVC 用来解析控制器方法参数的接口。
supportsParameter 方法:supportsParameter 方法决定了是否支持解析某个参数。在这里,通过检查 parameter 参数上是否存在 Token 注解来确定是否支持该参数的解析。如果存在 Token 注解,则返回 true,表示支持解析。
resolveArgument 方法:resolveArgument 方法负责实际的参数解析工作。在这里,通过 webRequest.getHeader(“token”) 方法从 HTTP 请求的头部获取名为 “token” 的值,并将其作为方法参数的值返回。
package com.dreams.demo20;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
@Override
// 是否支持某个参数
public boolean supportsParameter(MethodParameter parameter) {
Token token = parameter.getParameterAnnotation(Token.class);
return token != null;
}
@Override
// 解析参数
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getHeader("token");
}
}
对于这个解析器我们需要加入RequestMappingHandlerAdapter中,所以在webConfig.java修改Bean,设置自定义的参数解析器,传入。
// 2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(tokenArgumentResolver));
return handlerAdapter;
}
接下来在启动类Demo加入请求test3方法
// 请求来了,获取控制器方法 返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
request.setParameter("name", "张三");
request.addHeader("token", "某个令牌");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());可以看到成功了

对于不同的注解,有不同的解析器去处理
static class Controller {
public void test(
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") MultipartFile file, // 上传文件
@PathVariable("id") int id, // /test/124 /test/{id}
@RequestHeader("Content-Type") String header,
@CookieValue("token") String token,
@Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}
HttpServletRequest request, // request, response, session ...
@ModelAttribute("abc") User user1, // name=zhang&age=18
User user2, // name=zhang&age=18
@RequestBody User user3 // json
) {
}
}
全部代码:
package com.dreams.demo21;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
public class Demo {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();
// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
// 要点2. 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
// 多个解析器组合
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
);
String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
if (composite.supportsParameter(parameter)) {
// 支持此参数
Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
// System.out.println(v.getClass());
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
System.out.println("模型数据为:" + container.getModel());
} else {
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
}
}
}
private static HttpServletRequest mockRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name1", "zhangsan");
request.setParameter("name2", "lisi");
request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
System.out.println(map);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
request.setContentType("application/json");
request.setCookies(new Cookie("token", "123456"));
request.setParameter("name", "张三");
request.setParameter("age", "18");
// 设置请求内容为 JSON 字符串
String jsonContent = "{\n" +
" \"name\": \"李四\",\n" +
" \"age\": 20\n" +
"}";
request.setContent(jsonContent.getBytes(StandardCharsets.UTF_8));
return new StandardServletMultipartResolver().resolveMultipart(request);
}
static class Controller {
public void test(
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") MultipartFile file, // 上传文件
@PathVariable("id") int id, // /test/124 /test/{id}
@RequestHeader("Content-Type") String header,
@CookieValue("token") String token,
@Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}
HttpServletRequest request, // request, response, session ...
@ModelAttribute("abc") User user1, // name=zhang&age=18
User user2, // name=zhang&age=18
@RequestBody User user3 // json
) {
}
}
static class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}解析代码:
这里我们不需要web环境,使用AnnotationConfigWebApplicationContex即可
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); // 准备测试 Request HttpServletRequest request = mockRequest();
这里我们需要准备一个控制器方法,之前我们使用RequestMappingHandlerMapping,他会帮我们封装成控制器方法,这里直接创建一个。传入controller类和对应的方法即可。
// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
对于解析出来的参数比如age=8,解析出来的是String类型,所以我们需要准备对象绑定与类型转换
// 要点2. 准备对象绑定与类型转换 ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
ModelAndViewContainer 的主要作用是存储控制器方法处理后的模型数据。它充当了一个容器,可以暂时保存控制器方法处理中间结果的数据。在请求处理过程中,ModelAndViewContainer 通常会与视图解析器(ViewResolver)协作。最终,视图解析器将根据 ModelAndViewContainer 中的数据选择合适的视图来呈现最终的响应结果。后面会用到
// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果 ModelAndViewContainer container = new ModelAndViewContainer();
通过 handlerMethod.getMethodParameters() 获取了控制器方法的参数列表,对于每个参数对象 parameter,调用 initParameterNameDiscovery() 方法,并传入 DefaultParameterNameDiscoverer 的实例。就是设置一个参数名的解析器
然后获取:
- parameter.getParameterIndex():获取参数在方法参数列表中的索引。
- parameter.getParameterType().getSimpleName():获取参数的类型的简单名称,比如 String、int 等。
- parameter.getParameterName():获取参数的名称,如果编译时启用了参数名称保存,将显示原始的参数名称;否则可能显示为默认的形式,比如 arg0 等。
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
System.out.println("[" + parameter.getParameterIndex() + "] " + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
}
加上注解,使用parameter.getParameterAnnotations() 返回参数上的所有注解数组。再拼接上参数名。
String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() );
RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver 是 Spring Framework 中用于解析处理请求参数的类之一, 可以处理 @RequestParam 注解
比如处理@RequestParam:
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") MultipartFile file, // 上传文件
这里使用RequestParamMethodArgumentResolver去处理
创建了一个 HandlerMethodArgumentResolverComposite 对象 composite,并向其中添加了一个 RequestParamMethodArgumentResolver 实例。这个 composite 组合了多个参数解析器,以支持不同类型的方法参数处理。
composite.supportsParameter(parameter) 检查当前的参数是否被 composite 支持解析。
如果支持,通过 composite.resolveArgument() 方法解析参数,并打印参数类型、参数名及其解析后的值。
Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);我们传入了factory,就是我们上面准备的对象绑定与类型转换的ServletRequestDataBinderFactory,还有传入container,也就是我们上面的用来存储中间 Model 结果的ModelAndViewContainer。
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
// 多个解析器组合
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(null, false)
);
String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
if (composite.supportsParameter(parameter)) {
// 支持此参数
Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
System.out.println("模型数据为:" + container.getModel());
} else {
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
}
}其他正常输出,但是defaultValue = “${JAVA_HOME}”直接输出了

只要在new RequestParamMethodArgumentResolver(beanFactory, false)传入beanFactory,即DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();我们需要传入一个工厂,他才能处理defaultValue = “${JAVA_HOME}”

PathVariableMethodArgumentResolver
PathVariableMethodArgumentResolver()处理@PathVariable注解
这里我们对参数需要进行处理,使用 Spring Framework 提供的 AntPathMatcher 类来解析 REST 风格的 URL,将 URL 中的路径变量提取出来,并将其存储在 request 对象的属性中,属性名为 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE。


这个步骤是RequestMappingHandlerMapping完成的,这里没有使用RequestMappingHandlerMapping,所以我们需要以上配置
这样加入composite即可
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver()
);
RequestHeaderMethodArgumentResolver
RequestHeaderMethodArgumentResolver 是 Spring MVC 中负责解析使用 @RequestHeader 注解标记的方法参数的类。
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory)
);

ServletCookieValueMethodArgumentResolver
ServletCookieValueMethodArgumentResolver是负责处理 @CookieValue 注解的方法参数解析器
当调用带有 @CookieValue 注解的控制器方法时,Spring MVC 会使用 ServletCookieValueMethodArgumentResolver 来获取指定 Cookie 的值,并将其传递给方法的参数。
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory)
);

ExpressionValueMethodArgumentResolver
ExpressionValueMethodArgumentResolver 是 Spring MVC 中负责处理 @Value 注解的方法参数解析器,当调用带有 @Value 注解的控制器方法时,Spring MVC 会使用 ExpressionValueMethodArgumentResolver 来计算 SpEL 表达式,并将其结果作为方法的参数。


ServletRequestMethodArgumentResolver
ServletRequestMethodArgumentResolver 是 Spring MVC 中负责处理直接从 Servlet API(如 HttpServletRequest 或 HttpServletResponse)中获取参数的方法参数解析器之一。它的作用是允许控制器方法直接声明 HttpServletRequest 或 HttpServletResponse 参数,从而直接访问底层的 HTTP 请求或响应对象。当调用控制器方法时,Spring MVC 会使用 ServletRequestMethodArgumentResolver 来判断参数类型是否是 HttpServletRequest 或 HttpServletResponse,并将对应的实例传递给方法。
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver()
);

打开这个类的supportsParameter,可以看到也不止支持HttpServletRequest 或 HttpServletResponse

ServletModelAttributeMethodProcessor
ServletModelAttributeMethodProcessor 是 Spring MVC 中负责处理带有 @ModelAttribute 注解的方法参数的方法参数解析器之一。它的作用是在处理控制器方法时,解析和处理 @ModelAttribute 注解,并将其注入到方法的参数中。当调用带有 @ModelAttribute 注解的控制器方法时,Spring MVC 使用 ServletModelAttributeMethodProcessor 来解析该注解,并创建或更新模型对象。
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false) // 必须有 @ModelAttribute
);

这里看到模型数据输出了,也就是我们上面配置的
ModelAndViewContainer container = new ModelAndViewContainer()的用处了。
RequestResponseBodyMethodProcessor
RequestResponseBodyMethodProcessor 是 Spring MVC 中负责处理带有 @ResponseBody 和 @RequestBody 注解的方法参数和返回值的关键组件之一。它的作用是将请求的数据(如 JSON、XML 等格式的请求体)转换为方法参数,并将方法返回的数据转换为响应体。当控制器方法的参数或返回值被 @RequestBody 注解修饰时,RequestResponseBodyMethodProcessor 负责将请求体中的数据(通常是 JSON 或 XML 格式)转换为对应的 Java 对象。它通过消息转换器(Message Converters)实现请求体和响应体的转换。
composite.addResolvers(
// false 表示必须有 @RequestParam,否则可以省略
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter()))
);


5.返回值处理器
返回值处理器(Return Value Handler)指的是处理控制器方法返回结果的组件。这些处理器负责将控制器方法的返回值转换为适当的响应格式,比如将对象转换为 JSON 格式或者渲染模板视图等。
比如要处理返回yml格式
@RequestMapping("/test4")
@Yml
public User test4() {
log.debug("test4");
return new User("张三", 18);
}自定义注解
package com.dreams.demo20;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}同样的我们需要自定义一个处理器
package com.dreams.demo20;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.yaml.snakeyaml.Yaml;
import javax.servlet.http.HttpServletResponse;
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Yml yml = returnType.getMethodAnnotation(Yml.class);
return yml != null;
}
@Override // 返回值
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 1. 转换返回结果为 yaml 字符串
String str = new Yaml().dump(returnValue);
// 2. 将 yaml 字符串写入响应
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType("text/plain;charset=utf-8");
response.getWriter().print(str);
// 3. 设置请求已经处理完毕
mavContainer.setRequestHandled(true);
}
}supportsReturnType 方法:supportsReturnType 方法用于确定是否支持处理特定类型的返回值。在这里,它通过检查 MethodParameter 对象中的方法注解 @Yml 来判断是否支持处理该返回类型。如果方法被 @Yml 注解修饰,则返回 true,表示当前的处理器可以处理该返回值类型。
handleReturnValue 方法:handleReturnValue 方法实际上执行了将返回值转换为 YAML 格式并写入 HTTP 响应的操作。首先,它使用 SnakeYAML 库将 returnValue 对象转换为 YAML 格式的字符串。
然后,从 NativeWebRequest 中获取 HttpServletResponse 对象,设置响应的内容类型为 text/plain;charset=utf-8,并将转换后的 YAML 字符串写入响应的输出流中。不过,springMVC默认还会视图解析,所以通过 ModelAndViewContainer 的 setRequestHandled(true) 方法标记请求处理完成,告知 Spring MVC 框架已经处理了响应。
同样需要在WebConfig.java配置
// 2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(tokenArgumentResolver));
handlerAdapter.setCustomReturnValueHandlers(Collections.singletonList(ymlReturnValueHandler));
return handlerAdapter;
}
启动类里调用test4方法
// 请求来了,获取控制器方法 返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test4");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
// 检查响应
byte[] content = response.getContentAsByteArray();
System.out.println(new String(content, StandardCharsets.UTF_8));运行如下:
!! YAML 库中用于标识数据类型,也就是成功了

6.获取方法的参数名称
如果我们想要获取方法参数名
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
System.out.println(parameter.getParameterName());
}
可以看到为null
在前面,我们获取方法的参数名称时,是这样获取的
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
System.out.println(parameter.getParameterName());
}
对于每个方法参数 parameter,通过调用 initParameterNameDiscovery(new DefaultParameterNameDiscoverer()) 方法来初始化参数名的发现器。才能获取到参数名,在这里使用的是 DefaultParameterNameDiscoverer,它是 Spring 提供的默认实现,用于发现方法参数的名称。
可以看到参数名的获取不是直接拿到的
我们新建一个普通类
package com.dreams.demo;
public class Bean {
public void test(String name, int age){
}
}javac命令编译
javac .\Bean.java
idea的src目录下默认不显示class文件,我们直接打开该目录,然后使用idea打开编译的class文件,可以看到是没有参数名的。

如果想要参数的话,就要添加参数去编译
javac -parameters .\Bean.java
这样就有了

原理是编译在在class文件添加了个了个MethodParameters
反编译一下:
javap -c -v .\Bean.class


还有一种方法就是使用以下方式去编译
javac -g .\Bean.java
而原理就是编译在class文件上添加了一个LocalVariableTable。


idea的src目录下自带的编译会影响实验,所以新建个demo目录,与src目录平级

添加到java中,让idea能够被检测到,但是不会被自动编译



Bean类代码
package com.dreams.demo;
public class Bean {
public void test(String name, int age) {
}
}对他进行编译

可以看到这里编译出来并没有保留参数名

在src目录下获取上面编译出来的class类,注意这个启动类要与Bean的目录结构一致。
package com.dreams.demo;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class Demo {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
// 1. 反射获取参数名
Method foo = com.dreams.demo.Bean.class.getMethod("test", String.class, int.class);
for (Parameter parameter : foo.getParameters()) {
System.out.println(parameter.getName());
}
}
}
加上-g编译参数

可以看到class里有了参数名

但是Demo类是获取不到的,因为-g参数这种方法无法通过参数获取

使用-parameters 参数可以通过反射获取


java中有对-g参数生成的局部变量表的获取的类
package com.dreams.demo;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import java.lang.reflect.Method;
import java.util.Arrays;
public class Demo {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
Method foo = Bean.class.getMethod("test", String.class, int.class);
// 2. 基于 LocalVariableTable 本地变量表
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));
}
}
解释:
- LocalVariableTableParameterNameDiscoverer 类: 这个类利用 Java 的反射能力和额外的字节码分析,从已编译的类中获取参数名。这基于编译时是否包含了调试信息(例如在 Java 编译时使用了 -g 选项)。
- getParameterNames(Method method) 方法: 这个方法尝试从给定的 Method 对象中获取参数名。如果字节码中包含了调试信息(例如编译时启用了调试信息),它可以将字节码索引映射到源代码中定义的参数名。
- -parameters参数没有生成局部变量表,就获取不了
而在spring中-g参数和-parameters参数都是支持的

7.参考
黑马程序员:spring


