Spring MVC 执行原理和源码分析

简介

​ Spring MVC 基于 Servlet,提供核心控制器DispatcherServlet,结构松散,以至于能适应各种灵活的需求

初始化

IoC初始化

ServletContextListener接口可以在web容器的初始化和结束期中执行一定的逻辑。所以,通过下面这个类,可以在ServletContextListener初始化之前初始化Ioc,也可以在结束期完成对Ioc容器的销毁

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }//初始化

    public void contextDestroyed(ServletContextEvent event) {
        //关闭应用上下文
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
        //清除属性
    }//销毁
}

映射请求上下文 初始化

​ 这个是由DispatcherServlet初始化的

FrameworkServlet

@Override
protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
    	//上面都是日志相关的,其实不用看,但是我们可以知道,原来日志可以这么用,所以我打算好好了解一下日志,因为平时里做的日志确实不是很多,而且很难把这些日志利用起来
    
    	//原来启动时间是这里来的
		long startTime = System.currentTimeMillis();

		try {
            //初始化操作
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

    	//下面都是日志,没啥意思,我直接删了
	}

	/**
	 * 初始化和注册WebApplicationContext为这个Servlet.
	 * 这个是能够被子类重写的,所以用的是protected方法
	 */
	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
		//判断是否已经被初始化
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
				//如果Ioc容器没有刷新,那么就刷新父容器的上下文
					//如果父容器为空
					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);
				}
			}
		}
        //下面逻辑判断好奇怪,如果是我写会判断是否初始化,是否有web Ioc容器,但是它这样的写法,怎么说,还行,毕竟操作的是一个对象,就是可读性不好
        
        //如果没有被初始化
		if (wac == null) {
			// 查找是否有存在web Ioc容器
			wac = findWebApplicationContext();
		}
        //如果没有初始化,而且没有找到存在的ioc容器
		if (wac == null) {
			//则我自己创建一个
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// 如果没有执行过onRefresh方法,就执行,注意一下,spring在这个版本加了锁,因为是发现并发的问题了
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// 作为Servlet的上下文属性发布Ioc容器
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

DispatcherServlet

​ onRefresh会跳转到这些方法执行,这个将初始化mvc的各个组件,是一个值得十分关注的方法

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

MultipartResolver

文件解析器,支持服务器上传文件的功能,不过现在公司一般用阿里云的OSS来完成文件服务器

LocaleResovler

国际化解析器,提供不同语言不同的返回语言结果,这个一般交给前端处理,写多套前端,比写后端,然后配置语言要方便的多,而且前端很多UI都支持国际化

ThemeResolver

主题解析器,也是交给前端,不用管

HandlerMapping

非常重要,它会包装用户提供一个控制器的方法和对它的一些拦截器,通过调用它就能运行控制器

handlerAdaptr

处理器适配器,因为处理器会在不同的上下文中运行,所以spring会找到合适的适配器,然后运行处理器服务方法,比如对于控制器的SimpleControllerHandlerAdapter,对于普通请求的HttpRequestHandlerAdapter

HandlerExceptionResolver

全局异常处理器,而且可以指定前端跳转到指定页面

RequestToViewNameTranlator

可以在控制器中返回一个视图的名称,通过它可以找到实际的视图,如果没有就根据url寻找,我猜是静态资源解析

ViewResolver

视图解析器,解析名称,定位实际视图


实际上DispatcherServlet会通过DispatcherServlet.properties

注解配置方式初始化

​ 由于Servlet3以后支持了注解配置,所以spring在3以后也做了支持,只需要我们继承一个AbstractAnnotationConfigDispatcherSerletInitializer然后实现它定义的方法,为什么MVC可以做到我们继承一个类,就能代替它原有的支持呢,而且我们在MVC开发的时候,也遇到了好多这样的情况,都是继承一个类,然后就能实现对应的方法
​ 因为Servlet3容许动态加载Servlet,所以spring提供了SpringSerletContainerInitializer,它继承了Serlet提供的接口,这样利用Servlet实现了动态配置加载

SpringServletContainerInitializer

spring的抽象类层级是不止一层的

  • getRootConfigClasses 获取Ioc容器的java配置类,用以装载各类的bean
  • getServletConfigClasses 获取各类MVC的URI控制器的配置关系类,用以生成web请求的上下文
  • getServletMappings 定义DispatchServlet拦截的请求

开发流程补充

​ 在这里记录一下,我平时开发没有注意到的地方
​ 解析的大体流程,用Controller标注一个类,而且只能用这个标注,restController一样,我们使用后者,然后spring会扫描这些配置,然后结合@XXXMapping,它的作用是把uri和方法绑定在一起,然后mvc结合配置的拦截器,组成了多个拦截器和一个控制器的形式,放到一个HandlerMapping中。当请求来服务器的时候,首先通过请求的信息找到对应的HandlerMapping,然后找到拦截器和处理的函数
​ spring mvc竟然还提供了@SessionAttibute来直接获取session中的值,所以,我在想,能不能自定义一个注解,把token里的信息取出来,如果能把这个加到我的权限框架中,我觉得会很好用

@RequestAttribute

​ 我们可以在request里面设置属性,它一次请求的作用内有效,其实本质就是同一个request对象,我们可以通过拦截器和web上下文设置额外的参数

多个拦截器的执行顺序

​ 多个拦截器是根据责任链的顺序执行的,但是如果其中有一个preHandler返回false,那么整个链就直接停止了

视图

​ 对于我们返回的对象,spring可以做任意的处理,可以把我们的对象转换为excel,pdf还有我们常见的json,这些就是视图,并不仅仅是指jsp页面
​ 视图实现了ViewResoler的接口,而且是支持国际化的,因为这个接口的唯一方法resolveViewName有Locale参数
​ 举个例子,我们一开始在spring中返回一个字符串,spring就能把他对应成html,是通过InternalResourceViewResolver来实现的

导出excel

​ 我们用视图来实现这个功能,spring推荐我们使用AbstractXlsView来实现(我发现框架的抽象类都是这个前缀)这个用到了ModelView
​ spring提供的这个我觉得有一点不好,因为他限制了只能用poi,他的方法入参应该是一个文件流,也就是说,我们方法参数的接受范围要尽可能的大,这样,我们的函数功能才会更加强大,但是有一点,如果你的方法里出现了大量的关于参数的判断,那么是完全没有必要的

文件上传

​ 这里我记录一些spring的设计思想,它的MultipartRequest类,它继承了HttpServletRequest然后扩充了它对文件操作的方法,利用这种继承,使一个类的功能得到了增强

数据转换和格式化

​ 首先,当一个请求到达了DispatchServlet的时候,需要找到对应的HandlerMapping,然后通过HandlerMapping找到对应的HandlerAdapter处理器,处理器在调用控制器(就是我们定义的controller)之前,会先获取HTTP发送过来的信息然后将其转变为控制器的各种参数,这就是我们能通过注解获取参数的方法,一句话概括的话,就是spring处理了我们的http请求。

​ spring用HttpMessageConverter消息转换器实现了消息的装换,但这只是一个比较简单的转换,所以spring提供了转换器和格式化器,然后就会对这些参数进行验证,只是我个人习惯自己写验证,因为一般涉及到增的地方,就会设计到改,如果用框架,需要写的代码比较多,而且有些验证逻辑很复杂,需要自定义复杂的类,所以我一般是让对象自己完成验证的

​ 然后,处理我们的业务逻辑,最后在返回结果的时候,spring会用HttpMessageConverter进行转换,比如我们的@ResponeBody

​ Http请求的参数会给HttpMessageConverter然后会给转换器(转换器又分为普通转换器Converter和集合转换器GenericConverter)和格式化器(Formatter),然后就变成了我们的java对象,接下来,spring会对它进行验证。然后就是调用我们编写的业务逻辑,接下来,又把交回了HttpMessageConverter然后进行比如Json之类的转换最后返回给我们的客户端
所以这个能够完成json和对象的双向转换,而且这些转换器是需要注册的,如果我们自己的写的话,这个类型转换器确实是很有必要的,我整理了一下spring boot的注册,因为spring的太繁琐了, 我还发现spring boot采用的方式是继承一个类,然后里面有一个参数,我们只要操作这个参数就能完成各种配置和注册

源码分析

​ 不过事到如今,我觉得一个方法,含boolen参数也是合理的,因为对于用户来说,只要我们bool参数的变量名起的比较好,那么是完全没有问题的,而且idea会帮助我们把参数名显示出来,这是非常好的,所以有些代码规则在编译器日新月异的改变中,代码规范是可能会发生改变的,又比如现在重构一个很长的类,很长的方法,也是很方便的

mvc执行流程图

mvc执行流程图

我根据这个图的顺序来自己分析一下源码,如果有不对的地方,希望大家指出来

从一个http请求进来出发

DispatchServlet

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {//这个就是我们在写servlet时常见的哪些参数
		logRequest(request);//spring自己也有为请求参数做日志的方法

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;//属性快照
		if (WebUtils.isIncludeRequest(request)) {
            //这个方法应该是把request里面的属性放到了自己的map里面
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		if (this.flashMapManager != null) {
			FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
			if (inputFlashMap != null) {
				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
			}
			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
		}

		RequestPath previousRequestPath = null;
		if (this.parseRequestPath) {
			previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
			ServletRequestPathUtils.parseAndCache(request);
		}
        //上面我们可以看到,spring在request的属性里面没少放东西

		try {
			doDispatch(request, response);
		}//这个可以不处理异常,我当时竟然没有试过不写这个会怎么样,这个其实就相当于,我执行一个方法,然后无论方法执行如何我都要保证后面的代码被执行
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
			if (this.parseRequestPath) {
				ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
			}
		}
	}

logRequest

	private void logRequest(HttpServletRequest request) {
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String params;
            //如何判断上传的是否为文件类型
			if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {
				params = "multipart";
			}
			else if (isEnableLoggingRequestDetails()) {//如果容许答应请求参数细节
                //我发现这个默认是false,怎么开启?
                //我看到参数的注释上说可以通过debug日志级别开启
                //不得不说,一次请求的debug日志数量就超出了我的想象
				params = request.getParameterMap().entrySet().stream()
						.map(entry -> entry.getKey() + ":" + Arrays.toString(entry.getValue()))
						.collect(Collectors.joining(", "));
                //map怎么用Java8的流,用entrySet(),从形式上看就是一个List
                //collect提供了用,拼接字符串的方法,这个说实话挺常见的,所以你也要学会使用这个方法
			}
			else {
				params = (request.getParameterMap().isEmpty() ? "" : "masked");
			}

			String queryString = request.getQueryString();
			String queryClause = (StringUtils.hasLength(queryString) ? "?" + queryString : "");
			String dispatchType = (!DispatcherType.REQUEST.equals(request.getDispatcherType()) ?
					"\"" + request.getDispatcherType() + "\" dispatch for " : "");
			String message = (dispatchType + request.getMethod() + " \"" + getRequestUri(request) +
					queryClause + "\", parameters={" + params + "}");

			if (traceOn) {
				List<String> values = Collections.list(request.getHeaderNames());
				String headers = values.size() > 0 ? "masked" : "";
				if (isEnableLoggingRequestDetails()) {
					headers = values.stream().map(name -> name + ":" + Collections.list(request.getHeaders(name)))
							.collect(Collectors.joining(", "));//从日志上来看,就是打印了header的信息
				}
				return message + ", headers={" + headers + "} in DispatcherServlet '" + getServletName() + "'";
			}
			else {
				return message;
			}
		});
	}

​ 又有了一个想法,我可以设置如果发生线上异常情况,就报警,报警的内容要尽可能详细一点,然后只在上线的项目要通过报警,所以要设置环境参数,然后报警的内容,要尽可能的让我快速定位到问题,并且解决它

spring boot 设置日志级别

logging:
  level:
    root: debug

​ 记得要加上那个root,不然会出现报错,无法把字符串转换成枚举类

doDispatch

	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;//这个是spring无论如何都要初始化的
			Exception dispatchException = null;//转发异常

			try {
				processedRequest = checkMultipart(request);//检测是否为文件上传
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);//选择handler
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//选择适配器

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {//这里特殊处理了get和head方法
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.实际上执行的是这个处理器
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {//目前的handler开始了吗
					return;
				}
				//这个明显就是上面那个if不成立
				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);
				}
			}
		}
	}

getHandler

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {//controller的url映射都放在了这个里面
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;//明显这个处理逻辑就是,如果我找到一个匹配的就返回
				}
			}
		}
		return null;
	}

​ 我发现这个handlerMapping竟然是一个接口,但是实现类特别多,我不知道具体用的是哪一个,怎么办?我目前知道最好的办法就是debug打断点去看了,不得不说,我发现依托于idea强大的debug能力,能让我发现好多东西,因为他显示的值都是实际项目的值,依托于这个值,我可以很容易的猜到这个属性是干什么的,看来以后得经常用了,而且我觉得尽量用,说不定能发现好多意想不到的收获呢

HandlerMapping

​ spring确实初始化了不少,真的不止一个接口实现类,不过我最后确定是这个SimpleUrlHandlerMapping,再次赞叹idea的debug强大

SimpleUrlHandlerMapping

下面这个类我删除了很多无用的代码,就是对分析代码没什么用的
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {

	//这个urlMap直接给人一种通透的感觉,这里就是把url和某个对象结合起来,然后根据这个url找到对应的Object来执行具体的操作,同时我还注意到,这是一个LinkedHashMap,说明是想保证一定顺序的
	private final Map<String, Object> urlMap = new LinkedHashMap<>();
	//我这里产生了一些疑惑的地方,如果我们的url是restFul风格,spring怎么来找呢?
	//不过自己分析源码还是很有意思的,当然这都建立在别人告诉我入口在哪里,不然我肯定不知道怎么找,然后就是我用过这个,可以根据自己平时的使用去猜测


	/**
	 * Register all handlers specified in the URL map for the corresponding paths.
	 * 注册全部的处理器
	 */
	protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.trace("No patterns in " + formatMappingName());
		}
		else {
			urlMap.forEach((url, handler) -> {
				// Prepend with slash if not already present.
				if (!url.startsWith("/")) {//你看这里,明确的告诉我们,前面的url是可以不加/的,但是spring肯定会给你补上这个
					url = "/" + url;
				}
				// Remove whitespace from handler bean name.
				if (handler instanceof String) {//而且会帮助我们去除字符串首尾的空格
					handler = ((String) handler).trim();
				}
				registerHandler(url, handler);//注册
			});
			logMappings();//打印日志的方法
		}
	}

}

registerHandler

	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;

		// Eagerly resolve handler if referencing singleton via name.
		// 如果通过名称引用单例,则优先解析处理程序,这个是字符串,可能是视图解析优先吧
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			ApplicationContext applicationContext = obtainApplicationContext();
			if (applicationContext.isSingleton(handlerName)) {
				resolvedHandler = applicationContext.getBean(handlerName);
			}
		}

		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {//如果你不小心定义了重复的urlPath,spring是会帮助你处理的
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {//这种会被当成Root来处理
				if (logger.isTraceEnabled()) {
					logger.trace("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {//通配符号
				if (logger.isTraceEnabled()) {
					logger.trace("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {//这个应该就是一般的
				this.handlerMap.put(urlPath, resolvedHandler);
				if (getPatternParser() != null) {
					this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);//这个方法来了,spring是怎么处理url的
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

parse

    public PathPattern parse(String pathPattern) throws PatternParseException {
        Assert.notNull(pathPattern, "Path pattern must not be null");
        this.pathPatternData = pathPattern.toCharArray();
        this.pathPatternLength = this.pathPatternData.length;
        this.headPE = null;
        this.currentPE = null;
        this.capturedVariableNames = null;
        this.pathElementStart = -1;
        this.pos = 0;
        this.resetPathElementState();

        for(; this.pos < this.pathPatternLength; ++this.pos) {//这是逐个遍历,有点惊讶,不过好像确实没有什么好的办法
            char ch = this.pathPatternData[this.pos];
            char separator = this.parser.getPathOptions().separator();//分割符号
            if (ch == separator) {//如果你是分割符号
                if (this.pathElementStart != -1) {
                    this.pushPathElement(this.createPathElement());
                }

                if (this.peekDoubleWildcard()) {
                    this.pushPathElement(new WildcardTheRestPathElement(this.pos, separator));
                    this.pos += 2;
                } else {
                    this.pushPathElement(new SeparatorPathElement(this.pos, separator));
                }
            } else {
                if (this.pathElementStart == -1) {
                    this.pathElementStart = this.pos;
                }

                if (ch == '?') {
                    ++this.singleCharWildcardCount;
                } else if (ch == '{') {//这个地方应该就是restFul风格的解析
                    if (this.insideVariableCapture) {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_NESTED_CAPTURE, new Object[0]);
                    }

                    this.insideVariableCapture = true;
                    this.variableCaptureStart = this.pos;
                } else if (ch == '}') {
                    if (!this.insideVariableCapture) {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.MISSING_OPEN_CAPTURE, new Object[0]);
                    }

                    this.insideVariableCapture = false;
                    if (this.isCaptureTheRestVariable && this.pos + 1 < this.pathPatternLength) {
                        throw new PatternParseException(this.pos + 1, this.pathPatternData, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST, new Object[0]);
                    }

                    ++this.variableCaptureCount;
                } else if (ch == ':') {//这个我好像没有遇到过
                    if (this.insideVariableCapture && !this.isCaptureTheRestVariable) {
                        this.skipCaptureRegex();
                        this.insideVariableCapture = false;
                        ++this.variableCaptureCount;
                    }
                } else if (ch == '*') {//这个应该是通配符号
                    if (this.insideVariableCapture && this.variableCaptureStart == this.pos - 1) {
                        this.isCaptureTheRestVariable = true;
                    }

                    this.wildcard = true;
                }

                if (this.insideVariableCapture) {
                    if (this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0) == this.pos && !Character.isJavaIdentifierStart(ch)) {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, new Object[]{Character.toString(ch)});
                    }

                    if (this.pos > this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0) && !Character.isJavaIdentifierPart(ch) && ch != '-') {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR, new Object[]{Character.toString(ch)});
                    }
                }
            }
        }

        if (this.pathElementStart != -1) {
            this.pushPathElement(this.createPathElement());
        }

        return new PathPattern(pathPattern, this.parser, this.headPE);
    }

​ 说实话,上面这个把我劝退了,很难,就像是算法题一样,不过,还有一点,就是我不知道这个handler是什么时候生成的,从代码里面,我只能看到string类型的hander生成,怎么办,我试着把端点放在了那个类的属性上,好像不行,所以我在下面补充一些我学习到的debug技巧

debug

步出:向上指的蓝色箭头。如果我们点了步入,发现它执行了我们不想要的代码,那么我们就可以点击这个步出,而且不是直接关掉再重新调试一遍
运行到关标处:蓝色箭头指着一个关标,我们完全没有必要一步一步的点着它去执行
计算表达式的值:我发现这个其实可以执行java的代码,所以很多时候,我们不需要,为了debug专门的计算某些值
最好的用的是绿色箭头,它可以直接走到下一个断点
​ 点击debug的两个红点,选择添加,可以发现idea有那种被动的debug机制,可以给字段和异常打上断点,这个时候我就发现了,如果我们想看某个字段的变化,可以在这里用,然后就是如果我们触发了某个异常,但是不知道异常触发的原因,我们也可以通过debug的方法查看,比如最头疼的空指针异常,而且这个是可以支持多线程调试的
​ 说实话,这个处理的结果和我想象的不太一样,那就先继续分析其他的流程了,不过我至少知道了,如果是外部的项目,我们可以通过Ctrl+Alt+F7来决定,到底是哪些类用了这个类的方法

getHandlerAdapter

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {//判断方法同上
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

SimpleServletHandlerAdapter

public class SimpleServletHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Servlet);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((Servlet) handler).service(request, response);
		//原来handler竟然是一个Servlet,不过想想确实很合理,看来经过spring的层层包装,最后还是我们的Servlet执行了最后的方法
		return null;
	}

	@Override
	@SuppressWarnings("deprecation")
	public long getLastModified(HttpServletRequest request, Object handler) {
		return -1;
	}

}

下面这个是doDispatch方法,这个就执行了我们定义的前置处理器,而我们定义的早就在mapperChain这条处理责任链里面

	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;//如果你判断假的话,一个!把这个变成真,然后停止方法的执行
	}
	//不过不得不说的是,这个!还是有点饶人的

AbstractHandlerMethodAdapter

这个好像就做了一个参数类的转换然后就丢给具体的实现类了,只有一个实现类

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {

	return handleInternal(request, response, (HandlerMethod) handler);
}

RequestMappingHandlerAdapter

这个类还是很复杂的,所以我只弄了断点跑过来的这个方法

	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		//完成了session的判断,不过现在已经都是jwt,session很少见了
		if (this.synchronizeOnSession) {//注意到这里还有并发的处理
			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 {
			// 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;
	}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);//设置参数
		}
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//设置返回类型
		}
		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);
		}
		//上面对请求过来的参数做了相当多的处理,看名字能猜出一些,但是我不是很敢写这些注释,不过也不是什么很重要的方法

		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}

ServletInvocableHandlerMethod

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {

	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);//设置404,500,200这种状态码

	if (returnValue == null) {//如果你返回了一个null,spring也会帮你处理
		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);//我猜这是把对象处理成json的方法
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}

InvocableHandlerMethod

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);//方法参数
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}

	return this.doInvoke(args);
}

InvocableHandlerMethod

@Nullable
    protected Object doInvoke(Object... args) throws Exception {
        Method method = this.getBridgedMethod();//终于走到真正的执行方法了

        try {
            return KotlinDetector.isSuspendingFunction(method) ? CoroutinesUtils.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);
        } catch (IllegalArgumentException var5) {
            this.assertTargetBean(method, this.getBean(), args);
            String text = var5.getMessage() != null ? var5.getMessage() : "Illegal argument";
            throw new IllegalStateException(this.formatInvokeError(text, args), var5);
        } catch (InvocationTargetException var6) {
            Throwable targetException = var6.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException)targetException;
            } else if (targetException instanceof Error) {
                throw (Error)targetException;
            } else if (targetException instanceof Exception) {
                throw (Exception)targetException;
            } else {
                throw new IllegalStateException(this.formatInvokeError("Invocation failure", args), targetException);
            }
        }
    }

!现在方法执行完成,开始处理返回结果,我们只关注到json对象的生成,然后一直交给servlet方法

HandlerMethodReturnValueHandlerComposite

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	} else {
		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);
	ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
	ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

	// Try even with null return value. ResponseBodyAdvice could get involved.
	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

AbstractMessageConverterMethodProcessor

你看下面那个方法名,用MessageConverters来实现了,这样就和我们之前学的结合起来了

	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {//字符类型,字符串也是,因为它实现了这个接口,所以我们定义的类,不要乱实现这个,根据我的经验,这个应该是处理视图的
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

		MediaType selectedMediaType = null;
		MediaType contentType = outputMessage.getHeaders().getContentType();
		boolean isContentTypePreset = contentType != null && contentType.isConcrete();
		if (isContentTypePreset) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> acceptableTypes;
			try {
				acceptableTypes = getAcceptableMediaTypes(request);
			}
			catch (HttpMediaTypeNotAcceptableException ex) {
				int series = outputMessage.getServletResponse().getStatus() / 100;
				if (body == null || series == 4 || series == 5) {
					if (logger.isDebugEnabled()) {
						logger.debug("Ignoring error response content (if any). " + ex);
					}
					return;
				}
				throw ex;
			}
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				return;
			}

			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
			//你会发现上面的方法在根据http协议定义的接受类型来决定自己要做什么事情,当然这些是通过debug看出来的

			for (MediaType mediaType : mediaTypesToUse) {
				if (mediaType.isConcrete()) {
					selectedMediaType = mediaType;
					break;//这里选择了Json类型然后break了
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}
		

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			//没想到spring在决定用哪个类型处理器的时候,用的是for循环,我也是在for了几次之后,发现用的处理器实际上是MappingJackson2CborHttpMessageConverter
			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;
				}
			}
		}

		if (body != null) {
			Set<MediaType> producibleMediaTypes =
					(Set<MediaType>) inputMessage.getServletRequest()
							.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

			if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
				throw new HttpMessageNotWritableException(
						"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
			}
			throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
		}
	}

下面的比较重复,今天看的太多了,而且返回其实不很重要,这个环节是很少会出现错误的。

其他

​ 其他的地方,书上讲的不太全,而且不是很常用,我打算从网上找一些常用的东西,然后补充一下,其他的部分,我会在之后看更深入的书籍来补充自己的知识

如何获取request

ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

​ 通过上面这个方法,我们就可以在AOP中获取对应的参数,然后做我们想做的处理,我发现,可以把用户的id,用户的常用信息设置在request的属性里面,然后我们可以通过Controller的相关注解获取

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值