想要手撕Spring MVC源码?让我带你一步一步走并手绘流程图
liebian365 2024-10-19 07:57 29 浏览 0 评论
注意注意:文末有Spring全家桶的资料分享哦,资料共享,大家可以私信【spring】免费领取哦~
01 Servlet 与 MVC
什么是Spring MVC 其实应该说 什么是 MVC ?
Model 数据,View 视图,Controller 控制器。啪!三个东西合在一起,MVC就出来了。
这么简单? 没错,其实就是这么简单。
当然如果你对MVC不太熟悉的话还是乖乖往下看吧。
其实MVC就是处理Web请求的一种框架模式,我们思考一下用户请求的流程:
- 输入url
- 发送请求
- 接受响应
对于用户来说其实也就这三个步骤,但是对于服务端来说需要做很多,这里我画了一张图供大家理解。这里其实忽略了很多 Tomcat 本身已经为我们做的,而且 Tomcat 并不仅仅只有 Host,Context。
我来解释一下,用户发送请求的 url 其实对应着很多东西。
比如说 localhost ,当然这个就是 ip 地址。这个 ip 地址对应着 Tomcat 里面的 Host (站点) 层。
Context 代表着一个 web 应用,还记得当初写 Servlet 项目的时候有一个 webapp 文件夹(里面还有个WEB-INF,最里面是web.xml)吗?也可以理解为当初写的 servlet 项目就是一个 web 应用,而用户通过 ip 地址的端口映射去找到了这个应用。
这时候我们已经通过 ip 和端口寻找到了指定的 web 应用,我们知道一个 web 应用中存在多个 servlet ,而我们如何去寻找每个请求对应的 servlet 呢? 答案还是 url ,我们通过后面的 /news 去web.xml里面寻找已经注册到应用中的 Servlet 类。
具体我再配合图中解释一下: 找到了指定的 web 应用之后,通过请求的路径 /news 去 web.xml 中寻找是否有对应的 标签,其中这个标签的子标签 标签的值需要匹配到请求的路径,这个时候 标签的值为 /news 正好匹配到了,所以我们获取了上面的标签的值然后再寻找是否有 标签的子标签 和这个值相等,如果有则获取到底下的 对应的类 并通过这个类去解析请求
总结来说就是通过 url 从 web.xml 文件中寻找到匹配的 servlet 的类
其实这就是原生的 servlet ,那么 MVC 的影子在哪呢?
别急,你要记住的是 MVC 就是对 Servlet 的封装,想要理解 MVC 就必须理解 Servlet 和 MVC 与 Servlet 的关系。
02 SpringMVC中的DispatcherServlet
2.1 DispatcherServlet的继承结构
有没有发现这个 DispatcherServlet 其实就是一个 Servlet。也就是说 Spring MVC中最核心的部分其实就是一个 Servlet 。
我来简单解释一下相应的部分(先简单了解一下)
- FrameworkServlet : 是 DispatcherServlet 的一个抽象父类。其中提供了加载某个对应的 web 应用程序环境的功能,还有将 GET、POST、DELETE、PUT等方法统一交给 DispatcherServlet 处理。
- Servlet : 一个规范,用来解决 HTTP服务器和业务代码之间的耦合问题
- GenericServlet : 提升了 ServletConfig 的作用域,在init(servletConfig)方法中调用了init()无参方法,子类可以重写这个无参初始化方法来做一些初始化自定义工作(后面分析源码中会讲到)。
- HttpServletBean : 可以将 Servlet 配置信息作为 Bean 的属性 自动赋值给 Servlet 的属性。
- DispatcherServlet :整个继承链中的最后一个也是最重要的一个类,是SpringMVC 实现的核心类。MVC 通过在 web.xml 中配置 DispatcherServlet 来拦截所有请求,然后通过这个 DispatcherServlet 来进行请求的分发,并调用相应的处理器去处理请求和响应消息。
有没有想起来在 SSM 框架配置的时候在 web.xml 中的配置
<servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- 把所以请求都交给DispatcherServlet处理--> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!-- 拦截所有 --> <url-pattern>/</url-pattern> </servlet-mapping>
好的,现在我们知道了 springMVC 中使用了一个 DispatcherServlet 去处理所有请求,而我们知道真正处理的肯定不是 DispatcherServlet ,而是具体我们在 Controller 层中写的带有 @Controller @RequestMapping 注解的类和底下的方法。DispatcherServlet 只是一个为我们分发请求到具体处理器的一个分发Servlet。
那么,这个 DispatcherServlet 具体怎么工作的呢?它是如何分发请求的呢? 且听我慢慢道来。
2.2 和 DispatcherServlet 一起工作的一些组件
首先我先将这些组件简单化,并把一些不必要的先省略为了便于理解。
其实要分发请求 和 处理请求并相应,我们可以肯定的是 我们需要使用一个映射关系Mapping 来表示 url 和对应的 处理器,使用一个 处理器Handler 来处理对应的请求。这样,我们就出来了两个最根本的角色: HandlerMapping 和 Handler。
我们再来强调一下这两者的工作。
- HandlerMapping : 建立请求和处理器的映射关系,即我们可以通过请求去获取对应的 handler。
- Handler : 处理请求的处理器。
这样,我们就可以再画出一个简单的流程图了。
有没有疑惑,这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的?
如果有,那么就带着这些问题往下看。
首先,这个 handlerMapping 的集合从哪来的?甭说集合,连单个你都不知道从哪来。 那么我们就从源码中找答案吧。为了你省力,我直接告诉你,DispatcherServlet 中的 doDispatch 方法中进行了 分发的主要流程。
这里我给出了简化版的 doDispatch 方法
public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception { // 通过request在处理器映射HandlerMapping中获取相应处理器 Object handler = getHandler(req); if (handler != null) { ... 调用handler的处理方法 } }
那么这个 getHandler(request) 方法又是什么样的呢?这里我直接放 DispatcherServlet 类的源码
@Nullable // 这里返回的是 HandlerExecutionChain // 其实这是一个处理器和拦截器链的组合 // 现在你就理解为返回的是一个 handler protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { // 遍历 handlerMapping 调用它的getHanlder获取处理器 // 如果不为空直接返回 for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
我们继续追踪 HandlerMapping 的getHandler(request) 方法。
其实进入源码你会发现,HandlerMapping 是一个接口,故这里给出一个简单的 HandlerMapping 接口代码,如果有能力可以去看源码。
public interface HandlerMapping { /** * 获取请求对应的处理器 * @param request 请求 * @return 处理器 * @throws Exception 异常 */ Object getHandler(HttpServletRequest request) throws Exception; }
那么,具体的实现类又是什么呢?我们思考一下,这个mapping是一个请求和处理器的映射,它是如何存的?我们当初怎么做的?
想必,你已经有答案了,在我们使用 SSM 框架的时候我们是通过 给类和方法 配置相应的注解(@Controller,@ReqeustMapping)来建立相应的 url 和 处理器方法的映射关系的。
我们再回来看源码 在idea中 可以使用 ctrl+alt+B 来查看方法实现和类实现继承。我们查看 HandlerMapping 接口的 getHandler 方法的实现,我们会发现直接跳到了 AbstractHandlerMapping 这个抽象类的方法,我们查看该方法的源码
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 获取 handler 这其实是一个抽象方法 // 子类可以通过不同实现来获取handler // 例如通过 url 和 name的映射逻辑 // 通过 requestMapping 中的 url 和对应方法的映射逻辑 Object handler = getHandlerInternal(request); if (handler == null) { // 如果获取为null 的获取默认的处理器 // 这里子类也可以设置自己的默认处理器 handler = getDefaultHandler(); } // 如果还是没有则返回 这时候 DispatcherServlet会返回 404 if (handler == null) { return null; } // Bean name or resolved handler? // 如果返回的处理器是字符串 则认为它是一个beanName if (handler instanceof String) { String handlerName = (String) handler; // 通过beanName从IOC容器中获取相应的处理器 handler = obtainApplicationContext().getBean(handlerName); } // 下面是将处理器 和 拦截器封装成处理器执行链 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler()); } if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } // 返回处理器执行链 return executionChain; }
如果其他的你看不懂,你只要理解我注释区域的代码就行了。我们从上面得到最重要的信息就是:真正的handler获取是在子类实现的getHandlerInternal(request)中,那我们来看一下有哪些子类。
我们可以看到其中有 AbstractHandlerMethodMapping、AbstractUrlHandlerMapping、WelcomeHandlerMapping。
我们主要关注 AbstractHandlerMethodMapping (提供方法处理器) 和 AbstractUrlHandlerMapping(提供url对应处理器映射),这里为了不耽误时间,我们直接分析 AbstractHandlerMethodMapping ,它是注解方法的映射的一个抽象类。
@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求的路径 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); this.mappingRegistry.acquireReadLock(); try { // 通过 lookupPath 来从中获取 HandlerMethod // 这个HandlerMethod 又是什么? // 先不用管 我们继续看lookupHandlerMethod源码 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } // 这里的逻辑稍微有些复杂 // 你只要知道它通过请求来匹配返回处理器方法 // 如果有多个处理器方法可以处理当前Http请求 那么返回最佳匹配的处理器 @Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // No choice but to go through all mappings... addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); Match bestMatch = matches.get(0); if (matches.size() > 1) { if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); handleMatch(bestMatch.mapping, lookupPath, request); // 返回最佳匹配 return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }
到现在逻辑慢慢变得复杂起来,我们做一个小结:在 DispatcherServlet 中我们通过遍历 handlerMapping 集合并调用它的 getHandler 方法来获取handler ,这个handler 是一个Object (因为spring会整合其他框架的处理器,并使用这些处理器处理请求 所以这里选择Object)。而 HandlerMapping 仅仅是一个接口 为了方便 抽象类 AbstractHandlerMapping 实现了这个方法并且为子类提供了自定义获取handler的 getHandlerInternal(request) 方法。 对于我们通用方式注解来标识控制器方法和url请求路径的映射是通过 AbstractHandlerMethodMapping 来获取请求对应的 HandlerMethod 的。
那么,疑问又来了,HandlerMethod是什么?
还记得刚刚上面的问题么,这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的?
我们现在可以来回答一下Handler的类结构了,Handler是一个Object,为了第三方框架的处理器能够接入来处理请求,spring使用了Object,而对于注解形式来说 一个处理器是一个 HandlerMethod。这里我给出 HandlerMethod 的简单实现形式,如果有能力可以查看源码。
@Data public class HandlerMethod { // bean 其实这个是标识 Controller 注解的类的对象 private Object bean; // 该对象的类型 private Class<?> beanType; // 该类上被标识RequestMapping注解的方法 private Method method; }
在 HandlerMethod 中存放了控制器类和对应的方法。为什么要存放他们?你想一下,我们用@RequestMapping注解标识的方法不就是处理方法吗,HandlerMethod 中存放他们,到时候调用处理方法只需要通过反射调用这个bean的method就行了。如果不理解可以看一下我下面写的代码。
// 这里先不用管 ModelAndView public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ModelAndView modelAndView = null; HandlerMethod handlerMethod = ((HandlerMethod) handler); // 获取HandlerMethod的method Method method = handlerMethod.getMethod(); if (method != null) { // 通过反射调用方法并返回视图对象(这就是处理方法) modelAndView = (ModelAndView)method.invoke(BeanFactory.getBean(handlerMethod.getBeanType())); } return modelAndView; }
再来看看上面的问题。这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的
第三个问题解决了,第二个问题上面也解决了,那么第一个问题来了。
我们从一开始就只讨论了如何在HandlerMapping中取出handler 并且调用handler的处理方法,那么我们一开始遍历的这个handlerMappings集合到底从哪儿来,或者说它是什么时候被初始化的?
这个时候,我们又得回到根源。我再来放这张图,不知道你们是否还记得
你能想到什么呢?我这里假设你对servlet还是有一些了解的。
我们知道 DispatcherServlet 是一个 servlet 。一个 servlet 肯定有init()方法 (还记得我上面讲的GenericServlet的作用吗?现在来了,如果不是很懂init(),建议去了解一下servlet的生命周期)。
我们可以大胆的猜测,对 handlerMappings 的初始化就是在 servlet 的初始化方法中进行的。
很遗憾我们没有能在 DispatcherServlet 中找到 init 方法,那么就找他爹,找不到再找他爷爷,曾爷爷。我们知道因为 DispatcherServlet 继承了 GenericServlet 所以我们需要找到 实现的 init() 无参方法。所以我们找到了 HttpServletBean 中重写的 init() 方法了
@Override
// 这家伙还不允许被重写 final
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 将servlet配置信息存入bean的属性中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 重点在这里 这里子类才可以自由发挥
// 该方法不能被重写是因为 上面的步骤是必须的
// 别忘了上面的步骤是 HttpServletBean 的职责
// 接下去继续看
// Let subclasses do whatever initialization they like.
initServletBean();
}
// 进入FrameworkServlet 查看实现的initServletBean方法
@Override
protected final void initServletBean() throws ServletException {
// log不用管
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
// 重点来了
try {
// 初始化容器和上下文
// 我们要记得现在在 FrameworkServlet中执行呢
// 我们进入initWebApplicationContext方法
this.webApplicationContext = initWebApplicationContext();
// 初始化FrameworkServlet 这里没给实现 子类也没给
// 所以不用管
initFrameworkServlet();
}
// log不用管
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
// 初始化容器和上下文
protected WebApplicationContext initWebApplicationContext() {
// 查找是否有专门的根环境 先不用管
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 如果不存在专用根环境 通常我们不会走到这 先不用管
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 如果为空
if (wac == null) {
// 查看是否在servlet中已经注册
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// 自己创建一个
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
// 判断这个环境是否支持刷新 如果不支持 下面手动刷新
// 如果支持则前面已经刷新了
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// !!!!!!!!!!!!!!!!!!!!!重点
// DispacherServlet 就是在这里实现的
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
// DispatcherServlet 重写了该方法
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 一系列的初始化工作
protected void initStrategies(ApplicationContext context) {
// 前面一些不用管
initMultipartResolver(context);
// 地域
initLocaleResolver(context);
// 主题
initThemeResolver(context);
// 重点来了!!!!
// 初始化HandlerMapping
initHandlerMappings(context);
// 初始化适配器
initHandlerAdapters(context);
// 初始化异常处理
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
我们先暂停一下,理一下思路。
在 HttpServletBean 中重写了 GenericServlet 的 init() 无参方法开始初始化动作,其中HttpServletBean中先实现了 servlet 配置信息到 bean 属性信息的赋值,然后调用 initServletBean() 该方法是子类进行自定义初始化的方法。FrameworkServlet 实现了该方法并且调用了 initWebApplicationContext() 方法进行了容器和上下文的初始化工作,并且其中调用了 onRefresh(ApplicationContext context) 方法。 这里FrameworkServlet没有做任何操作而是子类 DispatcherServlet 在其中调用了 initStrategies(context) 进行初始化工作。
好了我们继续看初始化 handlerMappings方法。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 在应用上下文中寻找 handlerMappings
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
// 前面不用管 其实一般我们使用默认的
if (this.handlerMappings == null) {
// 这里是获取默认的handlerMappings
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
// 获取defaultStrategies的内容
String value = defaultStrategies.getProperty(key);
if (value != null) {
// 解析相应内容并初始化 handlerMappings
// 获取内容中的类名数组
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
//通过反射创建并加入数组中取返回给上面
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
事情马上明了了,我们现在已经知道了 handlerMapping 是怎么加入队列中了(获取到 defaultStrategies 的资源内容 遍历内容获取类名 并通过反射创建对象加入队列),所以我们可以大胆猜测 defaultStrategies 中藏着秘密,它肯定已经定义好了默认的 handlerMapping 的类名。
果不其然,我们来看代码
private static final Properties defaultStrategies; // 在静态块中已经加载了defaultStrategies static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { // 通过资源初始化defaultStrategies // 这里的资源路径 很重要!!!! 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()); } } // 在这里呀 DispatcherServlet.properties private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
我们去寻找一下 DispatcherServlet.properties 这个文件, 原来都给我们定义好了,我们可以看见默认的handlerMapping有两个。 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping。
好了,我们现在终于可以总结一下了。
在我定义的简单的 DispatcherServlet 的 “同事”中,主要有 HandlerMapping 和 Handler。HandlerMapping 会在 DispatcherServlet 初始化的时候被在加载 ,然后在 DispatcherServlet 调用到执行方法 doDispatch() 的时候,会遍历 handlerMappings 集合获取对应的 handler。handler 是一个 Object(因为需要适配其他框架的处理器),在注解方式中是一个 HandlerMethod (里面存了Controller类的实例和method,在处理方法的时候使用反射调用该实例的method方法)。获取完 handler之后通过 处理器的处理方法返回一个视图对象并渲染页面。
2.3 再来一个组件 Adapter
其实对于“正宗”的MVC流程中,在遍历 handlerMappings 获取到相应的 handler 之后,其实并不是直接通过 handler 来执行处理方法的,而是通过 HandlerAdapter 来执行处理方法的。
这里我写了一个简单的适配器接口,源码也不复杂 你可以直接看源码
public interface HandlerAdapter { ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; boolean support(Object handler); }
handleRequest 不必说,用来执行处理的,里面传进去一个 handler 肯定最终调用的 handler的执行方法。这是典型的适配器模式。
support 判断该handler是否被该适配器支持。
其实理解上面的过程了之后再加入一个适配器就不难了,我们主要思考一下 为什么要加入适配器,我们知道 handler 是一个 Object 它的处理方法是不固定的,如果我们要在 DispatcherServlet 中通过 Handler 执行处理方法,那么就要做很多类型判断,这对于 DispatcherServlet 是非常难受的,所以需要通过适配器扩展。
这样我们可以写出一个简单的 doDispatch 方法了,有能力的可以查看源码
public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception { // 通过request在处理器映射HandlerMapping中获取相应处理器 Object handler = getHandler(req); if (handler != null) { // 通过handler获取对应的适配器 HandlerAdapter handlerAdapter = getHandlerAdapter(handler); if (handlerAdapter != null) { // 通过适配器调用处理器的处理方法并返回ModelAndView视图对象 ModelAndView modelAndView = handlerAdapter.handleRequest(req, resp, handler); ... 处理视图并渲染 } } }
2.4 视图解析
我们知道在 doDispatch 方法调用完 HandlerAdapter 的处理方法后统一返回的是一个 ModelAndView 对象,那么这个 ModelAndView 对象是什么呢?
字面意思,模型和视图。也就是 MVC 的 Model 和 View。在 SpringMVC 中 ModelAndView 是给 框架本身支持的网页生成器使用的,它是用来连接后台和网页的类,而如今在前后端分离的趋势下,基本不怎么使用了,这里我只简单提一下。
我们知道在 HandlerAdapter 调用处理方法之后会返回一个视图对象 ModelAndView ,而在这之后,doDispatch方法会调用 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException) 去处理视图信息,这个方法又会调用一个 render() 方法进行真正的视图渲染。
2.5 非常有用的RequestResponseBodyMethodProcessor
还记不记得 @RequestBody @ResponseBody @RestController 这些注解。没错,现在我们大部分都用它们,那么它们是如何工作的呢?
奥秘要从 doDispatch() 方法中的
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这条语句开始。 这个语句就是调用 相应的适配器的 handle 方法并返回 ModelAndView 对象。
当然通过前面的学习,我们知道最终调用到的是 RequestMappingHandlerAdapter 类的 handleInternal方法。
// 查看这个方法的源码 你会发现 除了处理一些 session 的问题 // 最终都会调用 处理器方法 invokeHandlerMethod @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. // 如果配置了 session 同步 if (this.synchronizeOnSession) { // 则获取 session HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // 如果没有配置 session 内同步 或者还没有创建 session 直接调用处理器方法 // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
我们来看一下 invokeHandlerMethod 中干了什么事, 看上去好密密麻麻,其实我们只要关注重点就行了,关注我注释的地方。
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 构造 Web 请求 其实就是一个代理类 封装了 请求和响应
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 重点来了!!!!!
// 将handlerMethod 封装成 ServletInvocableHandlerMethod类
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 为invocableMethod 做一些配置
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 重点来了!!!!! 调用 ServletInvocableHandlerMethod 的 invokeAndHandle 方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
总结一下上面的方法就是:将 HandlerMethod 对象封装成 ServletInvocableHandlerMethod 然后做一些配置并调用它的 invokeAndHandle 方法
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 这一步很重要 执行请求并获取返回值 // 这里里面就涉及到了 RequestBody 注解了 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); // 处理返回值 if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } }
我们首先来解析一下 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs) 方法 ,这里涉及到了 @RequestBody 注解的使用。
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 方法参数的解析 这里可以做很多关于 参数和请求 的事情
// 这是我们需要深入查看源码的
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 返回调用结果 很简单 就是通过反射调用方法 这里不做赘述
return doInvoke(args);
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 很简单 获取到参数
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
// 遍历参数
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 重点来了!!!!
// 将请求中的信息通过参数解析器解析到对应的参数
// 最终遍历完之后将参数数组返回
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 获取对应的参数解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
// 通过解析器解析参数 重点就在这了 因为 @RequestBody注解的存在
// 我们会调用到 RequestResponseBodyProcessor 类的这个方法
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 主要这里面通过 MessageConverters 消息转换器 来实现了 @RequestBody 的功能
// 由于篇幅有限 这里不再深入分析 如果想找到答案 顺着往下查看源码就行
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
下面我还放了一下 readWithMessageConverters 方法的代码,其实里面主要就是遍历消息转换器,然后通过转换器执行HTTP报文到参数的转换。
for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass != null && converter.canRead(targetClass, contentType))) { if (message.hasBody()) { HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse)); body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType); } break; } }
知道了 @RequestBody 注解的原理,@ResponseBody 注解的原理也马上浮出水面了。答案就在 ServletInvocableHandlerMethod 类中的 invokeAndHandle 方法获取了 returnValue 之后的步骤
// 答案就在这里
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
上面两个方法你可以追踪源码,其实最终调用的还是在 RequestResponseBodyMethodProcessor 这个类中。
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
// 封装web请求
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// 通过消息解析器解析返回的value
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
// 这里我贴出了writeWithMessageConverters方法的主要代码 因为这个方法有点长。。
// 这里遍历 Http消息转换器集合
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
// 通过转换器来输出 重点。。。
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
我们来总结一下: 在调用 HandlerAdapter 的 处理方法的时候 会跳转调用到 RequestMappingHandlerAdapter 的 handleInternal 方法。这里面会将 原本的处理器 HandlerMethod 封装成 ServletInvocableHandlerMethod,然后会调用这个类中的 invokeAndHandle 方法,这个方法中主要进行了相应方法处理器的方法的调用,在调用之前,会将Http报文中的内容转换为对应的参数内容。在调用完成返回 returnValue 之后,会调用相应 HttpMessageConvert 的转换方法 然后返回。
2.6 最终变成什么样了呢?
现在,你理解 Spring MVC了么?
不了解没关系,再分享一波Spring全家桶资料:Spring+Spring Boot+Spring Cloud+Spring MVC提供大家继续学习,私信【spring】免费领取哦~
1.Spring(源码深度解析)
2.Spring Boot(实战)
3.Spring Cloud(参考指南)
4.Spring MVC源代码分析与实践
更有Spring全家桶的面试资料和xmind的学习笔记图,再说一遍,需要获取这些资料的朋友请私信【spring】免费领取~
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)