SpringMVC源码解析

 2023-02-19
原文作者:女友在高考 原文地址:https://juejin.cn/post/7053629456623075358

本文已参与「新人创作礼」活动,一起开启掘金创作之路

Spring MVC源码解析

Spring MVC的使用原理其实是通过配置一个Servlet来接管所有的请求,所有的请求由这个Servlet来进行分发处理。

我们可以从web.xml里面看出这一点

    <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc.xml</param-value>
            </init-param>
        </servlet>

这个Servlet就是Spring MVC提供的DispatcherServlet,它的继承图如下:

202301012043522801.png

DispatcherServlet大致接收请求的时序图如下:

202301012043527472.png

下面我们先设定几个问题然后再去看源码。

  1. DispatcherServlet初始化的时机?

我们将断点打在DispatcherServlet中的onRefresh方法上 ,然后启动项目。

202301012043533183.png

从这个堆栈的图可以看出,最开始是HttpServletBean的init方法执行,然后启动Spring容器,Spring容器初始化的最后一步finishRefresh里面进行了事件通知。

FrameworkServlet里有个内部类ContextRefreshListener,实现了ApplicationListener接口,接收到了上面的事件通知,执行onApplicationEvent方法。

    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    
    		@Override
    		public void onApplicationEvent(ContextRefreshedEvent event) {
    			FrameworkServlet.this.onApplicationEvent(event);
    		}
    	}

继续跟踪onApplicationEvent方法

    public void onApplicationEvent(ContextRefreshedEvent event) {
    		this.refreshEventReceived = true;
    		synchronized (this.onRefreshMonitor) {
    			onRefresh(event.getApplicationContext());
    		}
    	}

发现最终是由onRefresh方法来进行具体处理的,并且这个onRefresh方法在FrameworkServlet里面是个空方法,是由它的子类DispatcherServlet来实现的。

我们来看DispatcherServlet里的onRefresh方法

    protected void onRefresh(ApplicationContext context) {
    		initStrategies(context);
    	}
    protected void initStrategies(ApplicationContext context) {
    		initMultipartResolver(context);
    		initLocaleResolver(context);
    		initThemeResolver(context);
    		initHandlerMappings(context);
    		initHandlerAdapters(context);
    		initHandlerExceptionResolvers(context);
    		initRequestToViewNameTranslator(context);
    		initViewResolvers(context);
    		initFlashMapManager(context);
    	}

这样就把SpringMVC的九大组件给初始化了。

  1. 我们的@RequestMapping注解在方法上,SpringMVC是怎么根据这个注解就能将对应的请求执行到这个方法上的?

从上面可以看到有个initHandlerMappings方法:

    private void initHandlerMappings(ApplicationContext context) {
    		this.handlerMappings = null;
    
    		if (this.detectAllHandlerMappings) {
    			// 找所有实现了HandlerMapping接口的类
    			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) {
    		//默认情况下,我们是使用的这个方法。
    			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    			if (logger.isTraceEnabled()) {
    				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
    						"': using default strategies from DispatcherServlet.properties");
    			}
    		}
    	}

继续跟踪getDefaultStrategies方法

    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    		String key = strategyInterface.getName();
    		String value = defaultStrategies.getProperty(key);
    		if (value != null) {
    			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);
    				}
    			//省略部分代码...
    			}
    			return strategies;
    		}
    		else {
    			return new LinkedList<>();
    		}
    	}

可以看出对classNames进行了遍历,这里有两个值, BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,我们一般用的是RequestMappingHandlerMapping。

RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,我们来看看它实现的afterPropertiesSet方法。

    public void afterPropertiesSet() {
    		this.config = new RequestMappingInfo.BuilderConfiguration();
    		this.config.setUrlPathHelper(getUrlPathHelper());
    		this.config.setPathMatcher(getPathMatcher());
    		this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    		this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    		this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    		this.config.setContentNegotiationManager(getContentNegotiationManager());
    
    		super.afterPropertiesSet();
    	}

前面都是一些配置,后面是调用父类的afterPropertiesSet方法,此方法里只有initHandlerMethods一个方法

    protected void initHandlerMethods() {
    		for (String beanName : getCandidateBeanNames()) {
    			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
    				processCandidateBean(beanName);
    			}
    		}
    		handlerMethodsInitialized(getHandlerMethods());
    	}

主要看processCandidateBean方法

    protected void processCandidateBean(String beanName) {
    		Class<?> beanType = null;
    		try {
    			beanType = obtainApplicationContext().getType(beanName);
    		}
    		catch (Throwable ex) {
    			// An unresolvable bean type, probably from a lazy bean - let's ignore it.
    			if (logger.isTraceEnabled()) {
    				logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
    			}
    		}
    		if (beanType != null && isHandler(beanType)) {
    			detectHandlerMethods(beanName);
    		}
    	}

继续跟踪detectHandlerMethods方法

    protected void detectHandlerMethods(Object handler) {
    		Class<?> handlerType = (handler instanceof String ?
    				obtainApplicationContext().getType((String) handler) : handler.getClass());
    
    		if (handlerType != null) {
    			Class<?> userType = ClassUtils.getUserClass(handlerType);
    			//利用工具,取出类里面的方法并组装成map
    			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
    					(MethodIntrospector.MetadataLookup<T>) method -> {
    						try {
    							return getMappingForMethod(method, userType);
    						}
    						catch (Throwable ex) {
    							throw new IllegalStateException("Invalid mapping on handler class [" +
    									userType.getName() + "]: " + method, ex);
    						}
    					});
    			if (logger.isTraceEnabled()) {
    				logger.trace(formatMappings(userType, methods));
    			}
    			//遍历map,处理里面的方法
    			methods.forEach((method, mapping) -> {
    				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
    				registerHandlerMethod(handler, invocableMethod, mapping);
    			});
    		}
    	}

继续跟踪里面的register方法

    public void register(T mapping, Object handler, Method method) {
    			this.readWriteLock.writeLock().lock();
    			try {
    				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
    				assertUniqueMethodMapping(handlerMethod, mapping);
    				this.mappingLookup.put(mapping, handlerMethod);
    
    				List<String> directUrls = getDirectUrls(mapping);
    				for (String url : directUrls) {
    					this.urlLookup.add(url, mapping);
    				}
    
    				String name = null;
    				if (getNamingStrategy() != null) {
    					name = getNamingStrategy().getName(handlerMethod, mapping);
    					addMappingName(name, handlerMethod);
    				}
    
    				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
    				if (corsConfig != null) {
    					this.corsLookup.put(handlerMethod, corsConfig);
    				}
    
    				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    			}
    			finally {
    				this.readWriteLock.writeLock().unlock();
    			}
    		}

可以看出mappingLookup、urlLookup、registry都放入了值。这时我也不知道每个的具体作用。我把断点打到这3个属性上。然后前端发起一个get请求。

  1. 请求匹配的过程?

前端发起请求,断点停在了AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl方法上,以前端发起的请求路径,从urlLookup上获取对应的值。

202301012043539564.png

最后看看请求分发的主流程,也是springMVC最核心的代码

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		boolean multipartRequestParsed = false;
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    
    			try {
    				//multipart请求处理
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				// 获取合适的HandlerExecutionChain
    				mappedHandler = getHandler(processedRequest);
    				if (mappedHandler == null) {
    					noHandlerFound(processedRequest, response);
    					return;
    				}
    
    				//获取合适的HandlerAdapter
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    				// Process last-modified header, if supported by the handler.
    				String method = request.getMethod();
    				boolean isGet = "GET".equals(method);
    				if (isGet || "HEAD".equals(method)) {
    					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    						return;
    					}
    				}
    
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    
    				// handle执行调用(核心)
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					return;
    				}
    
    				applyDefaultViewName(processedRequest, mv);
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				// As of 4.3, we're processing Errors thrown from handler methods as well,
    				// making them available for @ExceptionHandler methods and other scenarios.
    				dispatchException = new NestedServletException("Handler dispatch failed", err);
    			}
    			//视图解析并渲染到页面
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				// Instead of postHandle and afterCompletion
    				if (mappedHandler != null) {
    					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    				}
    			}
    			else {
    				// Clean up any resources used by a multipart request.
    				if (multipartRequestParsed) {
    					cleanupMultipart(processedRequest);
    				}
    			}
    		}
    	}

==总结==:

  1. tomcat的Servlet调起Spring容器启动,Spring容器启动完,事件通知到SpringMVC的DispatcherServlet。
  2. 这时会扫描所有的bean,将注解了@Controller和@RequestMapping的解析出来。
  3. 前端请求发过来,DispatcherServlet接收到(因为它是个servlet,配置在web.xml的),根据上一步处理好的映射关系,找到对应的方法来处理。

如通过/test能找到test方法

    @RequestMapping("/test")
    	public String test(String name, HttpServletRequest request,Model model){
    		System.out.println("name");
    		model.addAttribute("date",new Date());
    		return "success";
    	}
  1. 找到对应的方法后,反射调用
    method.invoke(Object obj,Object... args)
  1. 组装modelAndView渲染视图到前端