SSM源码分析之Spring09-SpringMVC核心原理与手写实现

前言

在上一节,我们实现了Spring的IOC与DI《SSM源码分析之Spring08-手写Spring源码V2.0》

本节,我们要做的就是将SpringMVC整合其中。

SpringMVC核心原理与手写实现

如何理解MVC?

MVC:基于项目开发的设计模式,用来解决用户和后台交互的问题

  • Model:将传输数据封装成一个完整的载体
  • View:视图,用来展示或者输出的模块(HTML、JSP、JSON、String、Swing、xml)
  • Control:控制交互一个中间组件,由他来根据用户请求分发不同人物从而得到不同的结果

J2EE标准,JSP页面是一个万能的组件,可以写HTML、js、java、sql
缺点:不容易维护

因此,MVC框架应运而生:struts1、struts2、webwork、SpringMVC

Spring MVC请求处理流程

引用 Spring in Action 上的一张图来说明了 SpringMVC 的核心组件和请求处理流程:
在这里插入图片描述
①:DispatcherServlet 是 SpringMVC 中的前端控制器(Front Controller),负责接收 Request 并将 Request 转发给对应的处理组件.

②:HanlerMapping 是 SpringMVC 中完成 url 到 Controller 映射的组件.DispatcherServlet接收 Request,然后从 HandlerMapping 查找处理 Request 的 Controller.

③:Cntroller 处理 Request,并返回 ModelAndView 对象,Controller 是 SpringMVC 中负责处

理Request 的组件(类似于 Struts2 中的 Action),ModelAndView 是封装结果视图的组件.

④ ⑤ ⑥:视图解析器解析 ModelAndView 对象并返回对应的视图给客户端.

SpringMVC原理时序图

SpringMVC:只是MVC设计模式的应用典范,给MVC的实现制定了一套标准

  • M:支持将URL参数自动封装成一个Object或者Map
  • V:自己只有一个默认的template、支持扩展、自定义View、而且能够自定义解析
  • C:做到把限制放宽了、任何一个类、都有可能是一个Controller

在这里插入图片描述

Spring MVC 的工作机制

在容器初始化时会建立所有 url 和 Controller 的对应关系 ,保存到 Map<url,Controller> 中.Tomcat 启动时会通知 Spring 初始化容器(加载 Bean 的定义信息和初始化所有单例 Bean),然后 SpringMVC 会遍历容器中的 Bean,获取每一个 Controller 中的所有方法访问的 url,然后将 url 和 Controller 保存到一个 Map 中;

这样就可以根据 Request 快速定位到 Controller,因为最终处理 Request 的是 Controller 中的方法,Map 中只保留了 url 和 Controller 中的对应关系,所以要根据 Request 的 url 进一步确认 Controller 中 的 Method, 这 一 步 工 作 的 原 理 就 是 拼 接 Controller 的 url(Controller 上 @RequestMapping 的值)和方法的 url(Method 上@RequestMapping 的值),与 request 的 url 进行匹配,找到匹配的那个方法;

确定处理请求的 Method 后,接下来的任务就是参数绑定,把 Request 中参数绑定到方法的形式参数上,这一步是整个请求处理过程中最复杂的一个步骤。SpringMVC 提供了两种 Request 参数与方法形参的绑定方法:

① 通过注解进行绑定

@RequestParam

② 通过参数名称进行绑定

使用注解进行绑定,我们只要在方法参数前面声明@RequestParam(“a”),就可以将 Request 中参数a 的值绑定到方法的该参数上.使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法.SpringMVC 解决这个问题的方法是用 asm 框架读取字节码文件,来获取方法的参数名称.asm 框架是一个字节码操作框架,关于 asm 更多介绍可以参考它的官网。个人建议,使用注解来完成参数绑定,这样就可以省去 asm 框架的读取字节码的操作。

Spring MVC源码分析

我们根据工作机制中三部分来分析 SpringMVC 的源代码.。

  • 其一,ApplicationContext 初始化时建立所有 url 和 Controller 类的对应关系(用 Map 保存);

  • 其二,根据请求 url 找到对应的 Controller,并从 Controller 中找到处理请求的方法;

  • 其三,request 参数绑定到方法的形参,执行方法处理请求,并返回结果视图.

第一步、建立 Map<urls,Controller>的关系

我们首先看第一个步骤 , 也就是建立 Map<url,Controller> 关系的部分 . 第一部分的入口类为 ApplicationObjectSupport 的 setApplicationContext 方法.setApplicationContext 方法中核心 部 分 就 是 初 始 化 容 器 initApplicationContext(context), 子 类 AbstractDetectingUrlHandlerMapping 实现了该方法,所以我们直接看子类中的初始化容器方法。

@Override

public void initApplicationContext() throws ApplicationContextException {

super.initApplicationContext();

detectHandlers();

}
/**

*建立当前 ApplicationContext 中的所有 Controller 和 url 的对应关系 */

protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); if (logger.isDebugEnabled()) {

logger.debug("Looking for URL mappings in application context: " + applicationContext);

}

// 获取 ApplicationContext 容器中所有 bean 的 Name

String[] beanNames = (this.detectHandlersInAncestorContexts ?

BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :

applicationContext.getBeanNamesForType(Object.class));


//遍历 beanNames,并找到这些 bean 对应的 url 
for (String beanName : beanNames) {

//找 bean 上的所有 url(Controller 上的 url+方法上的 url),该方法由对应的子类实现 String[] urls = determineUrlsForHandler(beanName);

if (!ObjectUtils.isEmpty(urls)) {

// 保存 urls 和 beanName 的对应关系,put it to Map<urls,beanName>,该方法在父类 AbstractUrlHandlerMapping 中

实现

registerHandler(urls, beanName);

}

else {

if (logger.isDebugEnabled()) {

logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");

}

}

}

}


/** 获取 Controller 中所有方法的 url,由子类实现,典型的模板模式 **/
 protected abstract String[] determineUrlsForHandler(String beanName);

determineUrlsForHandler(String beanName)方法的作用是获取每个 Controller 中的 url,不同

的子类有不同的实现,这是一个典型的模板设计模式.因为开发中我们用的最多的就是用注解来配置

Controller 中的 url,BeanNameUrlHandlerMapping 是 AbstractDetectingUrlHandlerMapping 的子类,处理注解形式的 url 映射.所以我们这里以 BeanNameUrlHandlerMapping 来进行分析.我们看BeanNameUrlHandlerMapping 是如何查 beanName 上所有映射的 url.

/**

*获取 Controller 中所有的 url */

@Override

protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();

if (beanName.startsWith("/")) {

urls.add(beanName);

}

String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) {
if (alias.startsWith("/")) {

urls.add(alias);

}

}

return StringUtils.toStringArray(urls);

}

到这里 HandlerMapping 组件就已经建立所有 url 和 Controller 的对应关系。

第二步、根据访问 url 找到对应的 Controller 中处理请求的方法

下面我们开始分析第二个步骤,第二个步骤是由请求触发的,所以入口为 DispatcherServlet 的核心方法为 doService(),doService()中的核心逻辑由 doDispatch()实现,我们查看 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;

Exception dispatchException = null;


try {

//1.检查是否是文件上传的请求 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request);

//2.取得处理当前请求的 Controller,这里也称为 hanlder,处理器,

//第一个步骤的意义就在这里体现了.这里并不是直接返回 Controller,

//而是返回的 HandlerExecutionChain 请求处理器链对象,

//该对象封装了 handler 和 interceptors.

mappedHandler = getHandler(processedRequest);

//如果 handler 为空,则返回 404 if (mappedHandler == null) {

noHandlerFound(processedRequest, response); return;

}


//3. 获取处理 request 的处理器适配器 handler adapter

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());


// 处理 last-modified 请求头

String Method = request.getMethod();

boolean isGet = "GET".equals(Method);

if (isGet || "HEAD".equals(Method)) {

long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);

}

if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return;
}

}


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


//4.实际的处理器处理请求,返回结果视图对象

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) {

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()) {

if (mappedHandler != null) {

//请求成功响应之后的方法 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

}

}

else {

if (multipartRequestParsed) {

cleanupMultipart(processedRequest);

}

}

}

}

第2 步 :getHandler(processedRequest) 方法实际上就是从 HandlerMapping 中找到 url 和 Controller 的对应关系.这也就是第一个步骤:建立 Map<url,Controller>的意义.我们知道,最终处理 Request 的是 Controller 中的方法 , 我们现在只是知道了 Controller, 还要进一步确认

Controller 中处理 Request 的方法.由于下面的步骤和第三个步骤关系更加紧密,直接转到第三个步骤.

第三步、反射调用处理请求的方法,返回结果视图

上面的方法中,第 2 步其实就是从第一个步骤中的 Map<urls,beanName>中取得 Controller,然后经过拦截器的预处理方法,到最核心的部分–第 5 步调用 Controller 的方法处理请求.在第 2 步中我们可以知道处理 Request 的 Controller,第 5 步就是要根据 url 确定 Controller 中处理请求的方法,然后通过反射获取该方法上的注解和参数,解析方法和参数上的注解,最后反射调用方法获取 ModelAndView结果视图。因为上面采用注解 url 形式说明的.第 5 步调用的就是 RequestMappingHandlerAdapter的handle()中的核心逻辑由 handleInternal(request, response, handler)实现。

@Override

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

ModelAndView mav;

checkRequest(request);


if (this.synchronizeOnSession) {

HttpSession session = request.getSession(false);

if (session != null) {

Object mutex = WebUtils.getSessionMutex(session);

synchronized (mutex) {

mav = invokeHandlerMethod(request, response, handlerMethod);

}

}

else {

mav = invokeHandlerMethod(request, response, handlerMethod);

}

}

else {

mav = invokeHandlerMethod(request, response, handlerMethod);

}


if (!response.containsHeader(HEADER_CACHE_CONTROL)) {

if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}

else {

prepareResponse(response);

}

}


return mav;

}

这一部分的核心就在 2 和 4 了.先看第 2 步,通过 Request 找 Controller 的处理方法.实际上就是拼接 Controller 的 url 和方法的 url,与 Request 的 url 进行匹配,找到匹配的方法.

/**根据 url 获取处理请求的方法**/

@Override

protectedHandlerMethodgetHandlerInternal(HttpServletRequestrequest)throwsException{

//如果请求 url 为,http://localhost:8080/web/hello.json,则 lookupPath=web/hello.json

StringlookupPath=getUrlPathHelper().getLookupPathForRequest(request);

if(logger.isDebugEnabled()){

logger.debug("Lookinguphandlermethodforpath"+lookupPath);

}

this.mappingRegistry.acquireReadLock();

try{

//遍历 Controller 上的所有方法,获取 url 匹配的方法

HandlerMethodhandlerMethod=lookupHandlerMethod(lookupPath,request);

if(logger.isDebugEnabled()){

if(handlerMethod!=null){

logger.debug("Returninghandlermethod["+handlerMethod+"]");

}

else{

logger.debug("Didnotfindhandlermethodfor["+lookupPath+"]");

}
}

return(handlerMethod!=null?handlerMethod.createWithResolvedBean():null);

}

finally{

this.mappingRegistry.releaseReadLock();

}

}

通过上面的代码,已经可以找到处理 Request 的 Controller 中的方法了,现在看如何解析该方法上的参数,并调用该方法。也就是执行方法这一步。执行方法这一步最重要的就是获取方法的参数,然后我们就可以反射调用方法了。

/**获取处理请求的方法,执行并返回结果视图**/

@Nullable

protectedModelAndViewinvokeHandlerMethod(HttpServletRequestrequest,

HttpServletResponseresponse,HandlerMethodhandlerMethod)throwsException{

ServletWebRequestwebRequest=newServletWebRequest(request,response);

try{

WebDataBinderFactorybinderFactory=getDataBinderFactory(handlerMethod); ModelFactorymodelFactory=getModelFactory(handlerMethod,binderFactory);

ServletInvocableHandlerMethodinvocableMethod=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);

ModelAndViewContainermavContainer=newModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest,mavContainer,invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

AsyncWebRequestasyncWebRequest=WebAsyncUtils.createAsyncWebRequest(request,response); asyncWebRequest.setTimeout(this.asyncRequestTimeout);

WebAsyncManagerasyncManager=WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

if(asyncManager.hasConcurrentResult()){

Objectresult=asyncManager.getConcurrentResult();

mavContainer=(ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];

asyncManager.clearConcurrentResult();

if(logger.isDebugEnabled()){

logger.debug("Foundconcurrentresultvalue["+result+"]");

}

invocableMethod=invocableMethod.wrapConcurrentResult(result);

}

invocableMethod.invokeAndHandle(webRequest,mavContainer);

if(asyncManager.isConcurrentHandlingStarted()){

returnnull;

}

returngetModelAndView(mavContainer,modelFactory,webRequest);

}

finally{

webRequest.requestCompleted();

}

}

invocableMethod.invokeAndHandle 最终要实现的目的就是:完成 Request 中的参数和方法参数上数

据的绑定。

SpringMVC 中提供两种 Request 参数到方法中参数的绑定方式:

① 通过注解进行绑定,@RequestParam

② 通过参数名称进行绑定.

使用注解进行绑定,我们只要在方法参数前面声明@RequestParam(“a”),就可以将 request 中参数a 的值绑定到方法的该参数上.使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法.SpringMVC 解决这个问题的方法是用 asm 框架读取字节码文件,来获取方法的参数名称.asm 框架是一个字节码操作框架,关于 asm 更多介绍可以参考它的官网.个人建议,使用注解来完成参数绑定,这样就可以省去 asm 框架的读取字节码的操作.

@Nullable

publicObjectinvokeForRequest(NativeWebRequestrequest,@NullableModelAndViewContainermavContainer,

Object...providedArgs)throwsException{

Object[]args=getMethodArgumentValues(request,mavContainer,providedArgs);

if(logger.isTraceEnabled()){

logger.trace("Invoking'"+ClassUtils.getQualifiedMethodName(getMethod(),getBeanType())+

"'witharguments"+Arrays.toString(args));

}

ObjectreturnValue=doInvoke(args);

if(logger.isTraceEnabled()){

logger.trace("Method["+ClassUtils.getQualifiedMethodName(getMethod(),getBeanType())+

"]returned["+returnValue+"]");

}

returnreturnValue;

}

privateObject[]getMethodArgumentValues(NativeWebRequestrequest,@NullableModelAndViewContainermavContainer,

Object...providedArgs)throwsException{

MethodParameter[]parameters=getMethodParameters();

Object[]args=newObject[parameters.length];

for(inti=0;i<parameters.length;i++){ MethodParameterparameter=parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i]=resolveProvidedArgument(parameter,providedArgs); if(args[i]!=null){

continue;

}

if(this.argumentResolvers.supportsParameter(parameter)){ try{

args[i]=this.argumentResolvers.resolveArgument(

parameter,mavContainer,request,this.dataBinderFactory);

continue;

}

catch(Exceptionex){

if(logger.isDebugEnabled()){

logger.debug(getArgumentResolutionErrorMessage("Failedtoresolve",i),ex);

}

throwex;

}

}

if(args[i]==null){

thrownewIllegalStateException("Couldnotresolvemethodparameteratindex"+

parameter.getParameterIndex()+"in"+parameter.getExecutable().toGenericString()+

":"+getArgumentResolutionErrorMessage("Nosuitableresolverfor",i));

}

}

returnargs;

}

关于 asm 框架获取方法参数的部分,这里就不再进行分析了.感兴趣的话自己去就能看到这个过程.到这里,方法的参数值列表也获取到了,就可以直接进行方法的调用了.整个请求过程中最复杂的一步就是在这里了.ok,到这里整个请求处理过程的关键步骤都分析完了.理解了 SpringMVC 中的请求处理流程,整个代码还是比较清晰的.

SpringMVC实现原理

DispatcherServlet作为SpringMVC的逻辑口,他是怎么被Spring管理的呢?
在这里插入图片描述
DispatcherServlet实现了FrameworkServlet,而FrameworkServlet又实现了ApplicationContextAware,这个类很熟悉吧!就是Spring里核心的上下文接口。
在DispatcherServlet里我们发现:
在这里插入图片描述
这个onRefresh方法不就是SpringMVC上下文的源头嘛!
接下来我们看看里面发生了什么?

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

这就是SpringMVC的九大组件
在这里插入图片描述
接下来,我们看DispatcherServlet的父类–>FrameworkServlet
在这里插入图片描述
doGet和doPost调用的方法:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		try {
		//CORE
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
		//...
		}
	}

接着调用了doService()方法,我们发现抽象方法FrameworkServlet交给子类DispatcherServlet去实现 -->模板方法模式

然后看doService方法调用了:doDispatch

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	

		try {
			doDispatch(request, response);
		}
		finally {
		//...
			}
	}

进入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;
			Exception dispatchException = null;

			try {
				// 1.检查是否是文件上传的请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 2.取得处理当前请求的controller,这里也称为hanlder,处理器,
				// 	 第一个步骤的意义就在这里体现了.这里并不是直接返回controller,
				//	 而是返回的HandlerExecutionChain请求处理器链对象,
				//	 该对象封装了handler和interceptors.
				mappedHandler = getHandler(processedRequest);
				// 如果handler为空,则返回404
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				//3. 获取处理request的处理器适配器handler adapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				// 处理 last-modified 请求头
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

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

				// Actually invoke the handler.
				// 4.实际的处理器处理请求,返回结果视图对象
				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);
				}
			}
		}
	}

手写SpringMVC

首先配置servlet:
在这里插入图片描述
初始化上下文:

//Servlet只是作为一个MVC的启动入口
public class GPDispatcherServlet extends HttpServlet {


    private  final String LOCATION = "contextConfigLocation";

//    private Map<String,GPHandlerMapping> handlerMapping = new HashMap<String,GPHandlerMapping>();

    //课后再去思考一下这样设计的经典之处
    //GPHandlerMapping最核心的设计,也是最经典的
    //它牛B到直接干掉了Struts、Webwork等MVC框架
    private List<GPHandlerMapping> handlerMappings = new ArrayList<GPHandlerMapping>();

    private Map<GPHandlerMapping,GPHandlerAdapter> handlerAdapters = new HashMap<GPHandlerMapping, GPHandlerAdapter>();

    private List<GPViewResolver> viewResolvers = new ArrayList<GPViewResolver>();

    @Override
    public void init(ServletConfig config) throws ServletException {

        //相当于把IOC容器初始化了
        GPApplicationContext context = new GPApplicationContext(config.getInitParameter(LOCATION));


        initStrategies(context);

    }


    protected void initStrategies(GPApplicationContext context) {

        //有九种策略
        // 针对于每个用户请求,都会经过一些处理的策略之后,最终才能有结果输出
        // 每种策略可以自定义干预,但是最终的结果都是一致
        // ModelAndView

        // =============  这里说的就是传说中的九大组件 ================
        initMultipartResolver(context);//文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析
        initLocaleResolver(context);//本地化解析
        initThemeResolver(context);//主题解析

        /** 我们自己会实现 */
        //GPHandlerMapping 用来保存Controller中配置的RequestMapping和Method的一个对应关系
        initHandlerMappings(context);//通过HandlerMapping,将请求映射到处理器
        /** 我们自己会实现 */
        //HandlerAdapters 用来动态匹配Method参数,包括类转换,动态赋值
        initHandlerAdapters(context);//通过HandlerAdapter进行多类型的参数动态匹配

        initHandlerExceptionResolvers(context);//如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析
        initRequestToViewNameTranslator(context);//直接解析请求到视图名

        /** 我们自己会实现 */
        //通过ViewResolvers实现动态模板的解析
        //自己解析一套模板语言
        initViewResolvers(context);//通过viewResolver解析逻辑视图到具体视图实现

        initFlashMapManager(context);//flash映射管理器
    }

    private void initFlashMapManager(GPApplicationContext context) {}
    private void initRequestToViewNameTranslator(GPApplicationContext context) {}
    private void initHandlerExceptionResolvers(GPApplicationContext context) {}
    private void initThemeResolver(GPApplicationContext context) {}
    private void initLocaleResolver(GPApplicationContext context) {}
    private void initMultipartResolver(GPApplicationContext context) {}




    //将Controller中配置的RequestMapping和Method进行一一对应
    private void initHandlerMappings(GPApplicationContext context) {
        //按照我们通常的理解应该是一个Map
        //Map<String,Method> map;
        //map.put(url,Method)

        //首先从容器中取到所有的实例
        String [] beanNames = context.getBeanDefinitionNames();
        try {
            for (String beanName : beanNames) {
                //到了MVC层,对外提供的方法只有一个getBean方法
                //返回的对象不是BeanWrapper,怎么办?
                Object proxy = context.getBean(beanName);
                Object controller = GPAopProxyUtils.getTargetObject(proxy);
                Class<?> clazz = controller.getClass();
                //但是不是所有的牛奶都叫特仑苏
                if (!clazz.isAnnotationPresent(GPController.class)) {
                    continue;
                }

                String baseUrl = "";

                if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                    GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
                    baseUrl = requestMapping.value();
                }

                //扫描所有的public方法
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(GPRequestMapping.class)) {
                        continue;
                    }

                    GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
                    String regex = ("/" + baseUrl + requestMapping.value().replaceAll("\\*", ".*")).replaceAll("/+", "/");
                    Pattern pattern = Pattern.compile(regex);
                    this.handlerMappings.add(new GPHandlerMapping(pattern, controller, method));
                    System.out.println("Mapping: " + regex + " , " + method);

                }


            }
        }catch (Exception e){
            e.printStackTrace();
        }


    }

    private void initHandlerAdapters(GPApplicationContext context) {
        //在初始化阶段,我们能做的就是,将这些参数的名字或者类型按一定的顺序保存下来
        //因为后面用反射调用的时候,传的形参是一个数组
        //可以通过记录这些参数的位置index,挨个从数组中填值,这样的话,就和参数的顺序无关了

        for (GPHandlerMapping handlerMapping : this.handlerMappings){

            //每一个方法有一个参数列表,那么这里保存的是形参列表
            Map<String,Integer> paramMapping = new HashMap<String, Integer>();


            //这里只是出来了命名参数
            Annotation[][] pa = handlerMapping.getMethod().getParameterAnnotations();
            for (int i = 0; i < pa.length ; i ++) {
                for (Annotation a : pa[i]) {
                    if(a instanceof GPRequestParam){
                        String paramName = ((GPRequestParam) a).value();
                        if(!"".equals(paramName.trim())){
                            paramMapping.put(paramName,i);
                        }
                    }
                }
            }

            //接下来,我们处理非命名参数
            //只处理Request和Response
            Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes();
            for (int i = 0;i < paramTypes.length; i ++) {
                Class<?> type = paramTypes[i];
                if(type == HttpServletRequest.class ||
                        type == HttpServletResponse.class){
                    paramMapping.put(type.getName(),i);
                }
            }


            this.handlerAdapters.put(handlerMapping,new GPHandlerAdapter(paramMapping));
        }

    }

    private void initViewResolvers(GPApplicationContext context) {
        //在页面敲一个 http://localhost/first.html
        //解决页面名字和模板文件关联的问题
        String templateRoot = context.getConfig().getProperty("templateRoot");
        String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();

        File templateRootDir = new File(templateRootPath);

        for (File template : templateRootDir.listFiles()) {
            this.viewResolvers.add(new GPViewResolver(template.getName(),template));
        }

    }



    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        String url = req.getRequestURI();
//        String contextPath = req.getContextPath();
//        url = url.replace(contextPath,"").replaceAll("/+","/");
//        GPHandlerMapping handler = handlerMapping.get(url);

//        try {
//            GPModelAndView mv = (GPModelAndView)handler.getMethod().invoke(handler.getController());
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//        } catch (InvocationTargetException e) {
//            e.printStackTrace();
//        }


        //对象.方法名才能调用
        //对象要从IOC容器中获取
//        method.invoke(context.);
        try {
            doDispatch(req, resp);
        }catch (Exception e){
            resp.getWriter().write("<font size='25' color='blue'>500 Exception</font><br/>Details:<br/>" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]","")
                    .replaceAll("\\s","\r\n") +  "<font color='green'><i>Copyright@GupaoEDU</i></font>");
            e.printStackTrace();
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws  Exception{

        //根据用户请求的URL来获得一个Handler
            GPHandlerMapping handler = getHandler(req);
            if(handler == null){
                resp.getWriter().write("<font size='25' color='red'>404 Not Found</font><br/><font color='green'><i>Copyright@GupaoEDU</i></font>");
                return;
            }

            GPHandlerAdapter ha = getHandlerAdapter(handler);


            //这一步只是调用方法,得到返回值
            GPModelAndView mv = ha.handle(req, resp, handler);


            //这一步才是真的输出
            processDispatchResult(resp, mv);


    }

    private void processDispatchResult(HttpServletResponse resp, GPModelAndView mv) throws Exception {
        //调用viewResolver的resolveView方法
        if(null == mv){ return;}

        if(this.viewResolvers.isEmpty()){ return;}

        for (GPViewResolver viewResolver: this.viewResolvers) {

            if(!mv.getViewName().equals(viewResolver.getViewName())){ continue; }
            String out = viewResolver.viewResolver(mv);
            if(out != null){
                resp.getWriter().write(out);
                break;
            }
        }

    }

    private GPHandlerAdapter getHandlerAdapter(GPHandlerMapping handler) {
        if(this.handlerAdapters.isEmpty()){return  null;}
        return this.handlerAdapters.get(handler);
    }

    private GPHandlerMapping getHandler(HttpServletRequest req) {

        if(this.handlerMappings.isEmpty()){ return  null;}


        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");

        for (GPHandlerMapping handler : this.handlerMappings) {
            Matcher matcher = handler.getPattern().matcher(url);
            if(!matcher.matches()){ continue;}
            return handler;
        }

        return null;
    }


}

在刚才的SpringMVC源码里我们不难发现,核心就是doService->doDispatch->九大组件,所以我们在写完DispatchServlet后,补充HandlerAdapter:

//专人干专事
public class GPHandlerAdapter {

    private Map<String,Integer> paramMapping;

    public GPHandlerAdapter(Map<String,Integer> paramMapping){
        this.paramMapping = paramMapping;
    }

    /**
     *
     * @param req
     * @param resp
     * @param handler 为什么要把handler传进来
     *                因为handler中包含了controller、method、url信息
     * @return
     */
    public GPModelAndView handle(HttpServletRequest req, HttpServletResponse resp, GPHandlerMapping handler) throws  Exception {
        //根据用户请求的参数信息,跟method中的参数信息进行动态匹配
        //resp 传进来的目的只有一个:只是为了将其赋值给方法参数,仅此而已

        //只有当用户传过来的ModelAndView为空的时候,才会new一个默认的

        //1、要准备好这个方法的形参列表
        //方法重载:形参的决定因素:参数的个数、参数的类型、参数顺序、方法的名字
        Class<?>[] paramTypes = handler.getMethod().getParameterTypes();

        //2、拿到自定义命名参数所在的位置
        //用户通过URL传过来的参数列表
        Map<String,String[]> reqParameterMap = req.getParameterMap();

        //3、构造实参列表
        Object [] paramValues = new Object[paramTypes.length];
        for (Map.Entry<String,String[]> param : reqParameterMap.entrySet()) {
            String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").replaceAll("\\s","");

            if(!this.paramMapping.containsKey(param.getKey())){continue;}

            int index = this.paramMapping.get(param.getKey());

            //因为页面上传过来的值都是String类型的,而在方法中定义的类型是千变万化的
            //要针对我们传过来的参数进行类型转换
            paramValues[index] = caseStringValue(value,paramTypes[index]);
        }

        if(this.paramMapping.containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = this.paramMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }

        if(this.paramMapping.containsKey(HttpServletResponse.class.getName())) {
            int respIndex = this.paramMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }

        //4、从handler中取出controller、method,然后利用反射机制进行调用

        Object result = handler.getMethod().invoke(handler.getController(),paramValues);

        if(result == null){ return  null; }

        boolean isModelAndView = handler.getMethod().getReturnType() == GPModelAndView.class;
        if(isModelAndView){
            return (GPModelAndView)result;
        }else{
            return null;
        }
    }

    private Object caseStringValue(String value,Class<?> clazz){
        if(clazz == String.class){
            return value;
        }else if(clazz == Integer.class){
            return  Integer.valueOf(value);
        }else if(clazz == int.class){
            return Integer.valueOf(value).intValue();
        }else {
            return null;
        }
    }

}

HaddlerMapping:


public class GPHandlerMapping {
    private Object controller;
    private Method method;
    private Pattern pattern;  //url的封装

    public GPHandlerMapping(Pattern pattern,Object controller, Method method) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }
}

ModelAndView:

public class GPModelAndView {

    private String viewName;
    private Map<String,?> model;

    public GPModelAndView(String viewName, Map<String, ?> model) {
        this.viewName = viewName;
        this.model = model;
    }

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    public Map<String, ?> getModel() {
        return model;
    }

    public void setModel(Map<String, ?> model) {
        this.model = model;
    }
}

ViewResolver:

//设计这个类的主要目的是:
//1、讲一个静态文件变为一个动态文件
//2、根据用户传送参数不同,产生不同的结果
//最终输出字符串,交给Response输出
public class GPViewResolver {

    private String viewName;
    private File templateFile;

    public GPViewResolver(String viewName,File templateFile){
        this.viewName = viewName;
        this.templateFile = templateFile;
    }

    public String viewResolver(GPModelAndView mv) throws Exception{
        StringBuffer sb = new StringBuffer();

        RandomAccessFile ra = new RandomAccessFile(this.templateFile,"r");

        try {
            String line = null;
            while (null != (line = ra.readLine())) {
                line = new String(line.getBytes("ISO-8859-1"), "utf-8");
                Matcher m = matcher(line);
                while (m.find()) {
                    for (int i = 1; i <= m.groupCount(); i++) {

                        //要把¥{}中间的这个字符串给取出来
                        String paramName = m.group(i);
                        Object paramValue = mv.getModel().get(paramName);
                        if (null == paramValue) {
                            continue;
                        }
                        line = line.replaceAll("¥\\{" + paramName + "\\}", paramValue.toString());
                        line = new String(line.getBytes("utf-8"), "ISO-8859-1");
                    }
                }
                sb.append(line);
            }
        }finally {
            ra.close();
        }

        return sb.toString();
    }

    private Matcher matcher(String str){
        Pattern pattern = Pattern.compile("¥\\{(.+?)\\}",Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return  matcher;
    }


    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

}

补充上AOP的工具类:

public class GPAopProxyUtils {


    public static  Object getTargetObject(Object proxy) throws Exception{
        //先判断一下,这个传进来的这个对象是不是一个代理过的对象
        //如果不是一个代理对象,就直接返回
        if(!isAopProxy(proxy)){ return proxy; }
        return getProxyTargetObject(proxy);
    }

    private static boolean isAopProxy(Object object){
        return Proxy.isProxyClass(object.getClass());
    }


    private static Object getProxyTargetObject(Object proxy) throws Exception{
        Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
        h.setAccessible(true);
        GPAopProxy aopProxy = (GPAopProxy) h.get(proxy);
        Field target = aopProxy.getClass().getDeclaredField("target");
        target.setAccessible(true);
        return  target.get(aopProxy);
    }

}

SpringMVC测试

我们这里写一个demo,用来测试SpringMVC:
在这里插入图片描述
增删改的业务接口:

public interface IModifyService {

	/**
	 * 增加
	 */
	public String add(String name, String addr);
	
	/**
	 * 修改
	 */
	public String edit(Integer id, String name);
	
	/**
	 * 删除
	 */
	public String remove(Integer id);
	
}

增删改实现类:

@GPService
public class ModifyService implements IModifyService {

	/**
	 * 增加
	 */
	public String add(String name,String addr) {
		return "modifyService add,name=" + name + ",addr=" + addr;
	}

	/**
	 * 修改
	 */
	public String edit(Integer id,String name) {
		return "modifyService edit,id=" + id + ",name=" + name;
	}

	/**
	 * 删除
	 */
	public String remove(Integer id) {
		return "modifyService id=" + id;
	}
	
}

查询接口:

public interface IQueryService {
	
	/**
	 * 查询
	 */
	public String query(String name);
}

查询接口实现类:

@GPService
public class QueryService implements IQueryService {

	/**
	 * 查询
	 */
	public String query(String name) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String time = sdf.format(new Date());
		String json = "{name:\"" + name + "\",time:\"" + time + "\"}";
		return json;
	}

}

Controller:

@GPController
@GPRequestMapping("/web")
public class MyAction {

	@GPAutowired IQueryService queryService;
	@GPAutowired IModifyService modifyService;
	
	@GPRequestMapping("/query.json")
	public GPModelAndView query(HttpServletRequest request, HttpServletResponse response,
								@GPRequestParam("name") String name){
		String result = queryService.query(name);
		System.out.println(result);
		return out(response,result);
	}
	
	@GPRequestMapping("/add*.json")
	public GPModelAndView add(HttpServletRequest request,HttpServletResponse response,
			   @GPRequestParam("name") String name,@GPRequestParam("addr") String addr){
		String result = modifyService.add(name,addr);
		return out(response,result);
	}
	
	@GPRequestMapping("/remove.json")
	public GPModelAndView remove(HttpServletRequest request,HttpServletResponse response,
		   @GPRequestParam("id") Integer id){
		String result = modifyService.remove(id);
		return out(response,result);
	}
	
	@GPRequestMapping("/edit.json")
	public GPModelAndView edit(HttpServletRequest request,HttpServletResponse response,
			@GPRequestParam("id") Integer id,
			@GPRequestParam("name") String name){
		String result = modifyService.edit(id,name);
		return out(response,result);
	}
	
	
	
	private GPModelAndView out(HttpServletResponse resp,String str){
		try {
			resp.getWriter().write(str);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
}

分页controller:

@GPController
@GPRequestMapping("/")
public class PageAction {

	@GPAutowired IQueryService queryService;
	
	@GPRequestMapping("/first.html")
	public GPModelAndView query(@GPRequestParam("teacher") String teacher){
		String result = queryService.query(teacher);
		Map<String,Object> model = new HashMap<String,Object>();
		model.put("teacher", teacher);
		model.put("data", result);
		model.put("token", "123456");
		return new GPModelAndView("first.html",model);
	}
	
}

最后补充需要的注解:
Controller

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPController {
	String value() default "";
}

RequestMapping :

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestMapping {
	String value() default "";
}

Autowired :

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPAutowired {
	String value() default "";
}

RequestParam:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestParam {
	
	String value() default "";
	
	boolean required() default true;

}

Service:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPService {
	String value() default "";
}

SpringMVC的优化

上面我们已经对 SpringMVC 的工作原理和源码进行了分析,在这个过程发现了几个优化点:

1.Controller 如果能保持单例,尽量使用单例,这样可以减少创建对象和回收对象的开销.也就是说,如果 Controller 的类变量和实例变量可以以方法形参声明的尽量以方法的形参声明,不要以类变量和实例变量声明,这样可以避免线程安全问题.

2.处理 Request 的方法中的形参务必加上@RequestParam 注解,这样可以避免 SpringMVC 使用 asm 框架读取 class 文件获取方法参数名的过程.即便 SpringMVC 对读取出的方法参数名进行了缓存,如果不要读取 class 文件当然是更加好.

3.阅读源码的过程中,发现 SpringMVC 并没有对处理 url 的方法进行缓存,也就是说每次都要根据请求 url 去匹配 Controller 中的方法 url,如果把 url 和 Method 的关系缓存起来,会不会带来性能上的提升呢?有点恶心的是,负责解析 url 和 Method 对应关系的 ServletHandlerMethodResolver 是一

个private 的内部类,不能直接继承该类增强代码,必须要该代码后重新编译.当然,如果缓存起来,必须要考虑缓存的线程安全问题。

后记

SpringMVC github地址

发布了47 篇原创文章 · 获赞 5 · 访问量 1861

猜你喜欢

转载自blog.csdn.net/qq_34361283/article/details/104083844