一、 DispatcherServlet 初始化
1、DispatcherServlet 初始化时机
主启动类:
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;
}
2、DispatcherServlet 初始化都做了什么
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
RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
-
key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
-
value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
-
有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
1、初始化 HandlerMapping
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 该值默认为 true 但是可以设置为 false
// 如果设置为 false 那 Spring MVC就只会查找名为“handlerMapping”的bean,并作为当前系统的唯一的HandlerMapping
// 如果是 ture 则查询所有的..
if (this.detectAllHandlerMappings) {
//在ApplicationContext中查找所有handler映射,包括父类上下文。
Map<String, HandlerMapping> 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 <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> 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<T> 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<>();
}
}
2、代码测试
配置类添加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<RequestMappingInfo, HandlerMethod> 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
RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
- HandlerMethodArgumentResolver 解析控制器方法参数
- HandlerMethodReturnValueHandler 处理控制器方法返回值
1、初始化HandlerAdapter
源码和 初始化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
2、代码测试
继续加入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;
}
}
}
1、自定义参数处理器
实现 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(令牌)
2、自定义返回值处理器
实现 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: 张三}