Java:112-SpringMVC的底层原理(下篇)

这里继续续写上一章博客(111章博客):
Spring MVC 源码深度剖析:
既然我们自行写出了一个,那么我们可以选择看看mvc源码:
前端控制器 DispatcherServlet 继承结构:

在这里插入图片描述

前面我们知道mvc是操作同一个方法,也就是processRequest,只是当时我们没有细说,现在我们开始细说,如果前面自己仔细学习过,那么这个图就能非常明白,并且如果手动的去看看结构,自然也会明白的
为了源码的解析,我们还需要创建一个项目来进行看看:

在这里插入图片描述

对应的依赖(也就是配置):
 <packaging>war</packaging><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.1.5.RELEASE</version></dependency><!--servlet坐标,若不使用对应的类,如HttpServletRequest的话,可以不加--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version></dependency><!--jsp坐标--><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.2</version><scope>provided</scope></dependency><!--对应操作json需要的包--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.8</version><!--这个必须要,后面的可以不写,但最好写上,防止其他情况--></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.9.8</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.9.0</version></dependency></dependencies>
对应的web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value></init-param><load-on-startup>2</load-on-startup><!--先初始化,在67章博客有说明--></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>
对应的spring-mvc.xml:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd"><context:component-scan base-package="com"/></beans>
对应的Demo接口及其实现类:
package com.service;public interface Demo {void fa(String name);
}
package com.service.impl;import com.service.Demo;
import org.springframework.stereotype.Service;@Service
public class DemoImpl implements Demo {@Overridepublic void fa(String name) {System.out.println("你好:"+name);}
}
对应的DemoController类:
package com.controller;import com.service.Demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("/demo")
public class DemoController {@Autowiredprivate Demo dome;@RequestMapping("/name")public void fa(String name) {dome.fa(name);}
}
现在自行配置服务器,然后启动后,访问http://localhost:8080/mvcdome/demo/name?name=2(mvcdome是设置的项目名),如果打印了"你好:2",说明操作成功(视图没有处理,所以只需要看打印即可),即项目创建完毕,现在我们就以这个项目来学习源码或者阅读解析一下源码,当然,这样的处理并不非常好,因为我们并不能随时修改细节,所以与spring一样需要下载源码当然,在前面说明spring时,我们已经下载好源码了,具体可以到107章博客进行下载(对应的spring源码是包括mvc的),然后进行编译操作,当然,这里会给出步骤,虽然我已经操作一次了(107章博客),但是对应的只是叫我们自己去博客中操作,所以这里我们自己也来给出一个步骤:
首先就是拿取对应的spring源码:
下载地址:
相关文件的下载地址:
链接:https://pan.baidu.com/s/1I1S_vut_-VRhsNj_hH7e1A
提取码:alsk
我们需要学习gradle,看这个地址:
https://blog.csdn.net/qq_59609098/article/details/135358948
拿取里面的低版本,使用idea打开对应的项目,刷新即可(前提选择gradle一下,jdk选择1.8吧,可以试着选择其他高版本)
学习好后,配置一下gradle作用与这个项目(刷新),虽然在107章博客中,我们手动的进行了编译,但是其实并不需要的,我们直接对父项目进行编译即可(虽然会使得里面的所有都进行编译(build自然是包括测试的),甚至可能你不用的,一般来说spring的对应源码只需要操作测试即可(使用的过程中,其实也是操作的测试,在107章博客就是如此)),然后我们在对应的项目中,创建子项目(模块)spring-grTest(依赖对应maven的三个地方即可(groupId,artifactId,version)):
直接给出依赖吧:
plugins {id 'java'id 'war'
}group 'org.springframework'
version '5.1.21.BUILD-SNAPSHOT'repositories {mavenCentral()
}dependencies {//变成gradle其实很简单,对应maven的三个地方即可(groupId,artifactId,version)testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'implementation 'org.springframework:spring-webmvc:5.1.5.RELEASE'implementation 'javax.servlet:javax.servlet-api:3.1.0'providedCompile 'javax.servlet.jsp:jsp-api:2.2'implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.0'
}test {useJUnitPlatform()
}
在这个项目中,我们先不使用本地的东西,然后创建上面的项目案例,也就是前面的DemoController,然后再该gradle中进行部署,然后访问,若出现了信息,说明处理成功
当你处理成功后,我们准备进行源码的分析:
首先还是前端控制器DispatcherServlet的分析,这个分析首先需要说明前面的图,按照源码来看:
一般,我们操作的原生基本都是处理service方法,在HttpServlet 中重写了他,他里面调用了this.service(request, response);,他区分了get和post来进行调用get方法和post方法,而前端控制器DispatcherServlet所继承的类就重写了这两个方法
前端控制器具体操作关系如下(全部的流程关系,建议看完):
前面我们知道有这些主要的组件:
/*
1:HandlerMapping(处理器映射器):创建对象,在DispatcherServlet的doDispatch中存在(只是由HandlerExecutionChain里面的变量保存(通常用Object来接收,所以一般难以看到)),但是他的得到却是mappedHandler = this.getHandler(processedRequest);得到的(本质上是HandlerMappin来得到,具体可以看源码),而他this.getHandler里面就存在HandlerMapping2:HandlerAdapter(处理器适配器):使用创建的对象的方法,也操作好了参数,在DispatcherServlet的doDispatch中存在3:HandlerExceptionResolver:处理异常的,在DispatcherServlet的doDispatch中存在mv = this.processHandlerException(request, response, handler, exception);,他里面就存在这个处理4:ViewResolver:操作视图的解析,即视图解析器,需要两个参数:视图名和Locale,在DispatcherServlet的render中存在,一般在view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);中就存在这个5:RequestToViewNameTranslator:从请求中拿取视图名,也就是默认的视图名,在DispatcherServlet的doDispatch中存在,其中this.applyDefaultViewName(processedRequest, mv);就有他6:LocaleResolver:给视图需要的Locale,一般存在默认,所以通常不处理,在DispatcherServlet的render中存在7:ThemeResolver:主题,了解即可,在DispatcherServlet的doService中存在8:MultipartResolver:文件上传的处理,在DispatcherServlet的doDispatch中存在processedRequest = this.checkMultipart(request);,他里面就有这个9:FlashMapManager:给重定向时的(参数)数据,DispatcherServlet的doService中存在
*/
可以在后面的代码中搜索看看是哪个,很明显,大多数中doService定义初始,doDispatch操作映射调用关系,render操作视图,其他的基本都是中间数据转移过程
public class DispatcherServlet extends FrameworkServlet {//..protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {// 记录请求日志this.logRequest(request);// 如果是包含请求(include request),则备份一份请求属性的快照Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap();Enumeration attrNames = request.getAttributeNames();
// 遍历请求属性,并进行备份label95:while(true) {String attrName;do {if (!attrNames.hasMoreElements()) {break label95;}
// 如果不需要在包含请求后清理属性,或者属性名以"org.springframework.web.servlet"开头,就将属性备份attrName = (String)attrNames.nextElement();} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));attributesSnapshot.put(attrName, request.getAttribute(attrName));}}// 设置一些常用的请求属性request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());// 处理FlashMap(用于在重定向时传递数据)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);}try {//到这里,调用doDispatch方法进行实际的请求分发处理this.doDispatch(request, response);} finally {// 在包含请求中,如果异步处理没有启动,并且备份了请求属性快照,则恢复属性if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {this.restoreAttributesAfterInclude(request, attributesSnapshot);}}}//..protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 处理后的请求对象,初始值为原始请求对象HttpServletRequest processedRequest = request;// 用于保存映射的处理器(Handler)和拦截器链(HandlerExecutionChain,如操作前置,后置,最终等等)HandlerExecutionChain mappedHandler = null;// 标志是否已解析multipart请求boolean multipartRequestParsed = false;// 获取WebAsyncManager,用于处理异步请求WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {// ModelAndView用于保存处理结果视图和模型数据ModelAndView mv = null;// 用于保存异常信息Object dispatchException = null;try {// 检查是否为multipart请求,如果是则返回一个新的请求对象(可能是一个mvc的子类,包含了某些信息,这些信息通常会在后面进行某些处理,这里了解即可),否则还是原来的样子processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 获取映射的处理器和拦截器链(操作注解等等处理),具体操作可以参照模拟的,虽然他可能更加复杂(这里相当于一个对应的封装处理(主要是除了spring外的对应的保存映射器的操作))mappedHandler = this.getHandler(processedRequest);// 如果没有找到合适的处理器,则调用noHandlerFound方法进行处理(默认的)if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}// 获取处理器适配器,用于执行处理器的逻辑,所以需要传递对应的映射器来进行处理(这里也是对应的一个封装处理,参照前面的模拟,主要是操作调用对应方法的相关处理,所以模拟可能只是单纯的调用)HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());// 获取请求的HTTP方法(看看什么请求)String method = request.getMethod();boolean isGet = "GET".equals(method);// 如果是GET或HEAD请求,检查是否需要返回304状态码//这段代码的目的是处理对于 GET 或 HEAD 请求的特殊情况,主要涉及HTTP协议中的缓存控制if (isGet || "HEAD".equals(method)) {//通过HandlerAdapter的getLastModified方法获取处理器的最后修改时间long lastModified = ha.getLastModified(request, mappedHandler.getHandler());//创建一个ServletWebRequest对象,并使用其checkNotModified方法,该方法检查请求中的If-Modified-Since头信息,如果指定的最后修改时间(lastModified)早于或等于实际的最后修改时间,则返回true,表示可以返回304 Not Modified响应//仅在请求是GET方法的情况下才继续执行,对于HEAD请求,实际上也可以使用GET的逻辑进行处理,但在返回响应时,只返回响应头而不返回响应体,所以这里明显没有进行处理该HEAD请求,并且并没有具体必要if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}// 应用处理器前置拦截器(调用preHandle,应该知道吧)//这个里面也存在triggerAfterCompletion,里面也会存在afterCompletion(也是为什么前面打印时,明明其中一个没有放行,但是他的afterCompletion会调用的原因,并且之所以后面两个会反序,是因为for循环的原因,可以看看这里的for循环,和其他两个的for循环你就明白了,即i++和--i的数据量变化,自然你就明白了)//看到这里,你应该明白,结合源码来看,是可以知道对方是为什么会那样做的,也是为什么往顶级走是需要看源码的,虽然我们在阅读源码之前,是靠想象的,但是虽然单纯的靠想象并不现实,但是也能够更加的接近源码,和理解源码甚至出现新的源码(创新,或者强于该源码)if (!mappedHandler.applyPreHandle(processedRequest, response)) {//如果对应的位false,那么这里就直接返回了(现在知道为什么对应的true是放行的意思吧)return;}// 调用处理器适配器处理请求,获取处理结果// 正好拦截器就是在他旁边mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 如果是异步处理,直接返回,后续处理交由异步处理机制if (asyncManager.isConcurrentHandlingStarted()) {return;}// 应用默认的视图名称(前面说过由默认的视图名称),如果上面得到的mv中返回有视图,那么这个默认自然不会进行处理this.applyDefaultViewName(processedRequest, mv);// 应用处理器后置拦截器(调用postHandle,应该知道吧)mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}// 处理分发(拦截到的调用方法,也就是分发)结果(简单来说就是处理返回值),又或者说需要到视图解析器了,也就是操作视图的地方this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {//调用最终拦截器,里面存在afterCompletion,应该知道吧this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {// 在异步处理已启动的情况下,调用处理器的afterConcurrentHandlingStarted方法if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {// 如果是multipart请求,清理相关资源this.cleanupMultipart(processedRequest);}}}//..//到这里private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {// 标志是否发生了异常导致的错误视图boolean errorView = false;// 处理异常情况if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {// 如果异常是ModelAndViewDefiningException类型,从异常中获取ModelAndView对象this.logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException)exception).getModelAndView();} else {// 处理一般异常情况,通过处理器适配器处理异常,获取处理结果ModelAndViewObject handler = mappedHandler != null ? mappedHandler.getHandler() : null;mv = this.processHandlerException(request, response, handler, exception);errorView = mv != null;}}// 渲染视图if (mv != null && !mv.wasCleared()) {// 如果ModelAndView不为null且未被清除,渲染视图this.render(mv, request, response);// 如果是因为异常导致的错误视图,清除错误相关的请求属性if (errorView) {WebUtils.clearErrorRequestAttributes(request);}} else if (this.logger.isTraceEnabled()) {// 如果ModelAndView为null,记录跟踪日志this.logger.trace("No view rendering, null ModelAndView returned.");}// 在非异步处理情况下,调用处理器的afterCompletion方法if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, (Exception)null);}}}//..//到这里protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {//获取请求的Locale,如果存在LocaleResolver则使用它解析,否则使用请求的默认LocaleLocale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();//将解析得到的Locale设置到响应中,确保后续的内容能够按照正确的地区信息进行渲染response.setLocale(locale);// 获取 ModelAndView 中的视图名,默认情况下,只有操作默认时,这个才会存在,但是一般若存在返回的视图名称,那么操作返回的String viewName = mv.getViewName();View view;// 如果视图名不为null,根据视图名和Locale解析出对应的视图对象if (viewName != null) {//这个视图对象就是根据我们的视图名来找到的,现在还是返回的视图名称,如果返回的没有,那么操作默认的,在后置拦截器的上面的代码中就处理了默认的view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {// 如果解析的视图对象为null,抛出异常throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");}} else {//直接得到对象,不操作默认的视图名,一般就是这个,如果对象存在,说明他正常返回//说明,在处理响应结果时,其实就已经生成了对应的视图对象view = mv.getView();if (view == null) {// 如果视图对象也为null,抛出异常throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");}}// 如果日志级别是TRACE,输出渲染视图的信息if (this.logger.isTraceEnabled()) {this.logger.trace("Rendering view [" + view + "] ");}try {// 如果 ModelAndView 中设置了响应状态码,则将响应状态码设置为对应的值if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}// 调用视图的 render 方法,进行实际的渲染工作(最后的响应给前端的响应体,操作视图路径,拿取页面,操作一系列的替换处理,这里面的处理省略了,因为处理太多,有编译方面的关系(如jsp中的哪些表达式处理,如EL和JSTL核心技术),这里可以到52章博客里去查看)view.render(mv.getModelInternal(), request, response);} catch (Exception var8) {// 如果在渲染过程中出现异常,记录错误日志并将异常重新抛出if (this.logger.isDebugEnabled()) {this.logger.debug("Error rendering view [" + view + "]", var8);}throw var8;}}//..//到这里@Nullableprotected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {// 如果存在视图解析器if (this.viewResolvers != null) {// 遍历所有视图解析器Iterator var5 = this.viewResolvers.iterator();while(var5.hasNext()) {// 获取当前的视图解析器ViewResolver viewResolver = (ViewResolver)var5.next();// 尝试使用当前视图解析器解析视图名字View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {// 如果找到合适的视图,立即返回该视图return view;}}}return null;}//..
}public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {//..protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod != HttpMethod.PATCH && httpMethod != null) {//调用其父类的这个方法,使得调用对应的doGet或者doPostsuper.service(request, response);} else {this.processRequest(request, response);}}protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.processRequest(request, response);}//重写的对应的方法,所以前端控制器调用的最终是这里protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.processRequest(request, response);}//..//主要的这里protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 记录请求开始时间long startTime = System.currentTimeMillis();// 用于记录处理过程中的异常Throwable failureCause = null;// 保存当前LocaleContext,以便后续恢复LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 构建新的LocaleContextLocaleContext localeContext = this.buildLocaleContext(request);// 保存当前RequestAttributes,以便后续恢复RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 构建新的RequestAttributesServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);// 获取WebAsyncManager,用于异步请求处理WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);// 注册请求拦截器,用于处理请求绑定(注册这个拦截器的目的是在异步请求的处理流程中,确保请求的上下文信息能够正确传递和维护,以便在异步处理期间能够访问到与请求相关的数据)asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());// 初始化上下文持有者(存在将requestAttributes设置到localeContext里面的处理)this.initContextHolders(request, localeContext, requestAttributes);//相关不重要的进行跳过(如果不跳过,其实就算你读代码,你一个人也需要非常久才能全部读完代码,所以我们是不需要浪费时间的,这对任何阅读源码时所需要注意的,除非你需要在这个方面更加深入,而不是大致的阅读源码)try {// 执行服务方法,处理请求(前端控制器的处理,虽然FrameworkServlet也属于mvc依赖中的,与前端控制器是一样的地方,HttpServletBean也是)this.doService(request, response); //do开头通常是干活的,或者说是比较重要的(这里就是mvc的基本处理了)} catch (IOException | ServletException var16) {failureCause = var16;throw var16;} catch (Throwable var17) {failureCause = var17;throw new NestedServletException("Request processing failed", var17);} finally {// 重置上下文持有者,恢复之前保存的LocaleContext和RequestAttributesthis.resetContextHolders(request, previousLocaleContext, previousAttributes);// 如果RequestAttributes不为null,则表示请求已完成if (requestAttributes != null) {requestAttributes.requestCompleted();}// 记录请求处理结果和时间,发布请求处理完成事件this.logResult(request, response, (Throwable)failureCause, asyncManager);this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);}}//..protected abstract void doService(HttpServletRequest var1, HttpServletResponse var2) throws Exception;//..}//HttpServlet在这里
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
//..//什么也没有操作,一般没写doGet或者doPost或者service,说明对应的的确没有,具体可以看看源码就知道了// ..}public abstract class HttpServlet extends GenericServlet {//..protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//获取请求协议版本,如:HTTP/1.1String protocol = req.getProtocol();//从某个资源文件(或者硬编码在代码中)中获取一个字符串,用于表示HTTP GET方法不受支持的错误消息String msg = lStrings.getString("http.method_get_not_supported");//根据协议版本判断,如果是HTTP/1.1,则返回状态码为405(Method Not Allowed)的错误响应,否则返回状态码为400(Bad Request)的错误响应,都包含上述获取的错误消息if (protocol.endsWith("1.1")) {resp.sendError(405, msg); //405状态码表示客户端使用的HTTP方法在目标资源上不被允许} else {resp.sendError(400, msg); //400状态码通常表示客户端发出的请求无效,服务器无法理解}//很明显,对应的sendError是处理的结果,里面进行处理servlet规定的路径资源,默认情况下,资源自然是找不到的(如果配置的话,但是通常需要指定,具体可以百度,我们对这个忽略即可)}//..protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String protocol = req.getProtocol();String msg = lStrings.getString("http.method_post_not_supported");if (protocol.endsWith("1.1")) {resp.sendError(405, msg);} else {resp.sendError(400, msg);}}/..//被下面的public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {中所调用protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod();long lastModified;if (method.equals("GET")) {lastModified = this.getLastModified(req);if (lastModified == -1L) {//看这里this.doGet(req, resp);} else {long ifModifiedSince = req.getDateHeader("If-Modified-Since");if (ifModifiedSince < lastModified) {this.maybeSetLastModified(resp, lastModified);this.doGet(req, resp);} else {resp.setStatus(304);}}} else if (method.equals("HEAD")) {lastModified = this.getLastModified(req);this.maybeSetLastModified(resp, lastModified);this.doHead(req, resp);} else if (method.equals("POST")) {//看这里this.doPost(req, resp);} else if (method.equals("PUT")) {this.doPut(req, resp);} else if (method.equals("DELETE")) {this.doDelete(req, resp);} else if (method.equals("OPTIONS")) {this.doOptions(req, resp);} else if (method.equals("TRACE")) {this.doTrace(req, resp);} else {String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[]{method};errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(501, errMsg);}}//到这里开始public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;this.service(request, response);} else {throw new ServletException("non-HTTP request or response");}}//..
}//Servlet在这里
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {//..//需要被使用public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;//..
}//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package javax.servlet;import java.io.IOException;public interface Servlet {void init(ServletConfig var1) throws ServletException;ServletConfig getServletConfig();//这个void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;String getServletInfo();void destroy();
}//所以拿取前端控制器,当操作service方法时,会最终调用get或者post的规定判断的对应方法,最终调用processRequest方法,里面调用了前端控制器的doService方法,而该方法里面调用了doDispatch方法
最终被doDispatch进行处理(并且,get和post都是调用同一个方法,也就是说,或者验证了,get和post之前请求方式的一种区别,最终操作的地方是一致的),也由于调用相同的,所以考虑了只用一个servlet来完成,即载一个servlet中判断请求和请求方式,而不是分开,简单来说就是聚合了,当然了,tomcat其实是让继承了Servlet相关类或者他这个接口来接收请求的,所以前端控制器的父必然是Servlet,而具体的路径除了配置文件外,也可以通过注解,这些都是设置某些东西来的,而mvc内部自然也是如此处理,只不过是否是自定义的就不清楚了(如前面我们自定义的找路径操作,而不是使用servlet的注解相关路径的操作,自带的,具体可以参照50章博客),那么我们需要进行调试才会明白
上面基本说明了mvc的执行流程了,自己看看吧
当然,我们通常是需要用调试来确定调用的,而不是单纯的分析(虽然分析也基本正确),所以为了操作跳转,我们也需要操作一下视图,然后验证上面的说明:
在spring-mvc.xml中加上如下:
	<bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/jsp/"></property><property name="suffix" value=".jsp"></property></bean><mvc:annotation-driven></mvc:annotation-driven>
然后在WEB-INF中创建jsp目录,然后创建index.jsp文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<%System.out.println("跳转到页面");%>
跳转成功,${date}
</body>
</html>
然后修改DemoController里面的fa方法:
@RequestMapping("/name")public String fa(String name, Map<String, Object> map) {dome.fa(name);Date date = new Date();map.put("date", date);return "index";}
然后,我们启动,访问http://localhost:8080/gradle/demo/name?name=2,项目名设置一般人都会知道的,当然这里还是提一下:

在这里插入图片描述

当然,他是本质的,而他的变化会导致这里的变化:

在这里插入图片描述

当然,他只是定义一个初始,其实前面那个图才是本质,也要注意,如果前面哪个图中,有多个项目部署的,虽然这个初始可能只能操作一个,但是其实都可以操作的,因为他们最终还是在tomcat上处理,而tomcat本身就可以操作多个项目(或者说web项目)
看看页面的变化吧,若有数据了,说明操作成功,那么我们给这个地方打上断点:
@RequestMapping("/name")public String fa(String name, Map<String, Object> map) {//给下面这一行打上断点dome.fa(name);Date date = new Date();map.put("date", date);return "index";}
进行调试,我们看看调用栈:

在这里插入图片描述

与前面注释说明是一样的,是用来得到返回值的
我们可以看看他是什么时候进行渲染,又或者说,是什么时候开始解析或者编译对应的jsp:
所以我们给这里进行打上断点:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<!--下面一行打上断点-->
<%System.out.println("跳转到页面");%>
跳转成功,${date}
</body>
</html>
直接访问,可以看到这里(与前面注释说明几乎类似,后面就不提示了):

在这里插入图片描述

前面注释基本只是一个大致流程,首先,我们看这里:
// 获取映射的处理器和拦截器链(初始化映射器有操作注解等等处理)mappedHandler = this.getHandler(processedRequest);
虽然我们操作过模拟,但是始终是自己操作的,具体源码怎么样我们并不知道,那么他是怎么获取的呢,我们给他打上断点
进入到这里:
//还是DispatcherServlet类的@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}
this.handlerMappings一般会存在两个对象(可能随着版本而改变):

在这里插入图片描述

看名称我们可以知道,基本上我们主要操作第一个,或者说,是第一个最后会使得进行返回
这里我们可能需要修改一下源码,所以我们修改依赖,来使用本地的东西:
dependencies {    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'    implementation project(":spring-webmvc")  implementation 'javax.servlet:javax.servlet-api:3.1.0'  providedCompile 'javax.servlet.jsp:jsp-api:2.2'   implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'   implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'  implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.0'
}
然后我们重新启动,继续到上面的断点,然后进入,在HandlerExecutionChain handler = mapping.getHandler(request);的这个方法前面加上如下:
System.out.println(mapping);
一般我得到的是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping@653d1c3c
我们进入到第一个类里面查看,也就是进入上面的类中:
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappingimplements MatchableHandlerMapping, EmbeddedValueResolverAware {//..}public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
//..}public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {//..   
}//list集合里面就是:HandlerMapping
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupportimplements HandlerMapping, Ordered, BeanNameAware {//..//直接到这里(上面他的子类,基本没有这个getHandler方法,所以自然到这里来)@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 获取处理器对象,然后通过请求刷选出了(注意,对应的处理器映射器可能是初始化时就操作注解得到了,这里只是单纯的得到对应的处理器对象而已,然后交给适配器进行执行,也就是说,这里只是操作前面我们模拟的通过请求拿取对应的对象的标识(如前面我们创建的Handler对象,更加细节的说,就是:Handler handler = getHandler(req);))//他的对象一般是:HandlerMethod类型Object handler = getHandlerInternal(request);if (handler == null) {// 如果处理器对象为空,则尝试获取默认的处理器对象handler = getDefaultHandler();}if (handler == null) {// 如果获取的处理器对象仍然为空,则表示没有找到合适的处理器,返回nullreturn null;}// Bean name or resolved handler?// 如果处理器对象是字符串,可能表示Bean的名称,需要解析为实际的Bean对象if (handler instanceof String) {String handlerName = (String) handler;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);}// 返回最终的处理器执行链(虽然不是对应的HandlerMethod,但是里面包括了这个)return executionChain;}//..
}
这样我们基本说明完毕,对应的映射器是怎么拿取的了,那么适配器呢,也就是这里:
// 获取处理器适配器,用于执行处理器的逻辑(自然需要传递对应的映射器)HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//他并没有对应的参数位置的处理,那么参数位置是保存在对应的映射器中,还是在是适配器中进行处理呢
//我们可以看看他里面的代码
//还是DispatcherServlet类的// 从一组HandlerAdapter中选择适配器,用于处理特定的处理器对象
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {// 检查是否存在HandlerAdapterif (this.handlerAdapters != null) {// 遍历所有的HandlerAdapterfor (HandlerAdapter adapter : this.handlerAdapters) {// 判断当前适配器是否支持给定的处理器对象//操作后,返回对应的值,也就是对应的适配器,具体底层是如何处理,通常情况下适配器只是一个规定的处理,他的作用是:/*
1:多样化的控制器类型: Spring MVC允许开发者使用不同的控制器类型来处理请求,例如普通的Java对象、实现了Controller接口的类、注解了@Controller的类、以及RESTful风格的控制器等,这些控制器有不同的方法签名和处理逻辑
2:框架与处理器的解耦: Spring MVC框架需要处理多样性的处理器,但为了框架的灵活性,它不希望与特定类型的处理器强耦合,适配器的引入使得框架能够与不同类型的处理器解耦,使得框架可以支持更多的处理器类型而不影响框架本身的设计
3:标准化的处理器接口: 适配器提供了一个标准化的接口,使得框架能够使用统一的方式调用不同类型的处理器,这有助于框架的统一处理流程,使其更加灵活和可扩展
4:支持不同的返回类型: 不同类型的控制器可能采用不同的返回类型,例如返回视图、字符串、JSON等,适配器也负责将处理器的输出转化为框架期望的结果
5:支持不同的方法签名: 不同类型的处理器可能使用不同的方法签名,适配器负责将框架传递的请求映射到处理器的具体方法上总的来说,适配器的作用是使得框架能够与各种类型的处理器协同工作,实现了处理器的多样性和框架的统一性之间的平衡,通过适配器,Spring MVC框架能够更加灵活地支持各种处理器类型,而不需要对框架的核心进行大规模修改
所以说,对应的参数位置应该是适配器中进行处理的(不是这里,而是最后获得结果的方法里面进行处理的,甚至是初始化中进行的统一处理,因为对应的处理,通常是根据映射器来的,模拟的时候是一个,自然可以根据集合来得到一个对应的集合),然后返回这个规范,从而在最后获取结果时,需要传递这个适配器以及映射器(其中适配器在对应的处理中就会操作参数位置),当然,这个适配器通常会判断是否支持(默认情况下他基本是true,即支持的,根据里面的操作(可以自己看源码),由于对应的映射器基本都是HandlerMethod类型,所以基本返回true,即支持),而决定是否保存参数位置,否则通常会出现异常所以在前面模拟时,如果说,得到适配器的Handler handler = getHandler(req);是对应与这里的: mappedHandler = this.getHandler(processedRequest);,那么后面的最终执行之前的操作,也就是handler.getMethod().invoke(handler.getController(), objects);之前的操作(甚至包括他),就是这个适配器的处理(所以也需要传递映射器),然而这个处理也只是封装,具体执行,在后面得到结果的方法中,当然了,他们的映射器还是适配器由于初始化了,自然都会操作对应的映射器集合(这在我们模拟中的也基本是类似的,当然,基本上对应得到的映射器就已经存在所有信息了,所以大多数情况下,并不需要对应的全部集合的哪个变量(在模拟中基本也是如此,也是只是操作得到的映射器),这也是为什么后面得到返回结果中,关于映射器,只传递了对应的一个映射器的(一个)原因)*/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");}
操作的适配器是这三个:

在这里插入图片描述

在这里插入图片描述

一般我得到的是这一个(与前面一样,操作打印即可):org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter@6cacb95e
他一般是如下的:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {//..   @Overrideprotected boolean supportsInternal(HandlerMethod handlerMethod) {return true;}//..}public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
//..//也就是到这里@Overridepublic final boolean supports(Object handler) {//HandlerMethod这个我们前面提到了,是映射器的类型,而supportsInternal,在上面的子类中,默认返回true//所以这里基本返回true,也就是支持return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}//..   
}
至此基本说明完毕,一般在对应的类中:
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {//..@Nullableprivate MultipartResolver multipartResolver;@Nullableprivate LocaleResolver localeResolver;@Nullableprivate ThemeResolver themeResolver;@Nullableprivate List<HandlerMapping> handlerMappings;@Nullableprivate List<HandlerAdapter> handlerAdapters;@Nullableprivate List<HandlerExceptionResolver> handlerExceptionResolvers;@Nullableprivate RequestToViewNameTranslator viewNameTranslator;@Nullableprivate FlashMapManager flashMapManager;@Nullableprivate List<ViewResolver> viewResolvers;/*简化:multipartResolver(MultipartResolver)localeResolver(LocaleResolver)themeResolver(ThemeResolver)handlerMappings(List<HandlerMapping>,HandlerMapping)handlerAdapters(List<HandlerAdapter>,HandlerAdapter)handlerExceptionResolvers(List<HandlerExceptionResolver>,HandlerExceptionResolver)viewNameTranslator(RequestToViewNameTranslator)flashMapManager(FlashMapManager)viewResolvers(List<ViewResolver>,ViewResolver)*///..}
对比一下这个:
/*
1:HandlerMapping(处理器映射器):创建对象,在DispatcherServlet的doDispatch中存在(只是由HandlerExecutionChain里面的变量保存(通常用Object来接收,所以一般难以看到)),但是他的得到却是mappedHandler = this.getHandler(processedRequest);得到的(本质上是HandlerMappin来得到,具体可以看源码),而他this.getHandler里面就存在HandlerMapping2:HandlerAdapter(处理器适配器):使用创建的对象的方法,也操作好了参数,在DispatcherServlet的doDispatch中存在3:HandlerExceptionResolver:处理异常的,在DispatcherServlet的doDispatch中存在mv = this.processHandlerException(request, response, handler, exception);,他里面就存在这个处理4:ViewResolver:操作视图的解析,即视图解析器,需要两个参数:视图名和Locale,在DispatcherServlet的render中存在,一般在view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);中就存在这个5:RequestToViewNameTranslator:从请求中拿取视图名,也就是默认的视图名,在DispatcherServlet的doDispatch中存在,其中this.applyDefaultViewName(processedRequest, mv);就有他6:LocaleResolver:给视图需要的Locale,一般存在默认,所以通常不处理,在DispatcherServlet的render中存在7:ThemeResolver:主题,了解即可,在DispatcherServlet的doService中存在8:MultipartResolver:文件上传的处理,在DispatcherServlet的doDispatch中存在processedRequest = this.checkMultipart(request);,他里面就有这个9:FlashMapManager:给重定向时的(参数)数据,DispatcherServlet的doService中存在简化:
HandlerMapping
HandlerAdapter
HandlerExceptionResolver
ViewResolver
RequestToViewNameTranslator
LocaleResolver
ThemeResolver
MultipartResolver
FlashMapManager
*/
对比:

/*multipartResolver(MultipartResolver)localeResolver(LocaleResolver)themeResolver(ThemeResolver)handlerMappings(List<HandlerMapping>,HandlerMapping)handlerAdapters(List<HandlerAdapter>,HandlerAdapter)handlerExceptionResolvers(List<HandlerExceptionResolver>,HandlerExceptionResolver)viewNameTranslator(RequestToViewNameTranslator)flashMapManager(FlashMapManager)viewResolvers(List<ViewResolver>,ViewResolver)HandlerMapping
HandlerAdapter
HandlerExceptionResolver
ViewResolver
RequestToViewNameTranslator
LocaleResolver
ThemeResolver
MultipartResolver
FlashMapManager*//*移动一下:handlerMappings(List<HandlerMapping>,HandlerMapping)handlerAdapters(List<HandlerAdapter>,HandlerAdapter)handlerExceptionResolvers(List<HandlerExceptionResolver>,HandlerExceptionResolver)viewResolvers(List<ViewResolver>,ViewResolver)viewNameTranslator(RequestToViewNameTranslator)localeResolver(LocaleResolver)themeResolver(ThemeResolver)multipartResolver(MultipartResolver)flashMapManager(FlashMapManager)HandlerMapping
HandlerAdapter
HandlerExceptionResolver
ViewResolver
RequestToViewNameTranslator
LocaleResolver
ThemeResolver
MultipartResolver
FlashMapManager*/
正好与九大组件完全一致,这里知道就可以了(因为九大组件的说明,我们基本说明了两个,映射器和适配器,当然,并非要将所有组件都进行说明)
他们基本都是接口,而接口定义规范,自然是有好处的(基本上,框架中都会要这样处理),他们的初始化通常在这里:
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {//..//在FrameworkServlet的父类的HttpServletBean的init()进行处理(servlet的初始化)//里面操作了initServletBean(),而他是FrameworkServlet里面的,改方法里面会操作initWebApplicationContext()方法,改方法也是FrameworkServlet里面的,而改方法会操作onRefresh方法,最终到这里来@Overrideprotected 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);//初始化FlashMap处理器initFlashMapManager(context);}//..}
我们可以给initStrategies(context);加上断点:
启动应该可以看到这里:

在这里插入图片描述

左下角是调用栈,我们往下滑,是可以看到这个的:

在这里插入图片描述

在Spring中(107章博客)的IOC容器初始化中就存在这个
所以在操作之前,Spring的相关扫描已经操作完毕,正好与我们模拟的一样,在对应的映射器操作之前,首先需要操作Spring的容器初始化
我们就打开映射器的初始化操作:
/*
进入这里:initHandlerMappings(context);*///还是DispatcherServlet里面的
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;//检测所有的处理器映射if (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.// 从ApplicationContext中查找所有的HandlerMapping(扫描并获取,这里就是对应映射器生成的关键操作),包括祖先上下文//这里说明到这里就可以了,如果还需要深入,是没有必要的,因为本质上我们手写的一个就已经很接近了(mybatis,spring基本都是这样,springmvc自然也不例外)Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);// 如果找到了HandlerMapping,则将其放入列表中,并按照排序顺序进行排序if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());// We keep HandlerMappings in sorted order.// 对HandlerMappings进行排序AnnotationAwareOrderComparator.sort(this.handlerMappings);}}// 如果不需要检测所有的HandlerMappingselse {try {// 尝试从ApplicationContext中获取名为HANDLER_MAPPING_BEAN_NAME的HandlerMappingHandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);// 如果获取成功,将其设置为唯一的HandlerMappingthis.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {// 如果获取失败,忽略异常,稍后将添加一个默认的HandlerMapping// 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.// 确保至少有一个HandlerMapping,如果没有其他映射,则注册默认的HandlerMappingif (this.handlerMappings == null) {// 获取默认的HandlerMapping策略this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);// 如果日志启用了trace级别,记录一条日志if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
我们也可进入initMultipartResolver(context);:
private void initMultipartResolver(ApplicationContext context) {try {this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.multipartResolver);}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());}}catch (NoSuchBeanDefinitionException ex) {// Default is no multipart resolver.this.multipartResolver = null;if (logger.isTraceEnabled()) {logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");}}}
这里只有一个关键代码:this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
这也是为什么操作文件时,mvc基本上必须需要设置文件的解析器(通常不设置就会报错),并且名称也基本需要multipartResolver,这也是为什么有些地方的名称必须固定的原因(在DispatcherServlet中也有其他写死的,具体可以看看)
我们继续对原来的DispatcherServlet类的doDispatch方法里面进行分析,前面既然我们得到了适配器,现在需要进行获得结果了,所以我们到这里,这里对适配器进行操作:
 // 调用处理器适配器处理请求,获取处理结果
//当然了,前面模拟时,我们直接操作返回结果,很多细节并没有进行考虑(对响应进行处理等等),所以这里面的操作必然是超级复杂的//ha则是前面的:RequestMappingHandlerAdapter类型mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
适配器只是判断是否支持,并没有保存映射器,所以这里需要传递映射器,所以是操作对应适配器的方法(甚至包括了执行方法处理,而不只是操作参数位置(当然,可能还有其他处理,如参数存在注解操作等等)),他传递了请求和响应(这个请求是处理了对应的文件操作的,响应是为了判断是否直接的返回,因为没有操作视图的话,可能是需要直接处理响应的内容的,而不用操作视图转化的响应了,因为视图最终也是需要给响应的,响应是根本),这样处理后,自然会根据这个结果来判断是否操作视图,如果没有,响应里面自然有信息,自然显示给前端,如果有,自然操作视图,最后再给响应,简单来说,他在里面确定的返回值保证是直接操作响应,或者是操作返回值,操作视图,然后操作响应,都可以根据这个返回值是否为空来判断的
调试时还是访问:http://localhost:8080/gradle/demo/name?name=2
那么我们进去看看他做了什么:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {//..//到这里@Overrideprotected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {//有处理的请求,还有响应,this是适配器自身,handlerMethod是映射器//操作的返回值就决定他里面有没有值而操作视图(里面还有其他数据,通常是给里面的某个信息的,来给前端,或者说jsp展示)ModelAndView mav; // 用于保存处理结果的ModelAndView对象(因为返回的mv就是他)checkRequest(request); // 检查请求,确保它满足要求// Execute invokeHandlerMethod in synchronized block if required.// 如果需要在会话上同步执行,进入同步块//也就是判断是否需要支持在同一个session中只能线性的处理请求if (this.synchronizeOnSession) {HttpSession session = request.getSession(false); // 获取当前请求的HttpSessionif (session != null) {//为当前session生成一个唯一的可以用于锁定的keyObject mutex = WebUtils.getSessionMutex(session); // 获取用于同步的锁对象// 在同步块中执行invokeHandlerMethod方法synchronized (mutex) {//对HandlerMethod进行参数等的适配处理,并调用目标handlermav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available -> no mutex necessary// 如果没有HttpSession可用,则无需同步(上面的同步,是保证一人得到对应的session的资源)mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...// 如果不需要在会话上同步,直接执行invokeHandlerMethod方法//这个方法是对映射器的参数适配和一些中间处理操作,从而调用对应的方法,然后返回对应的值mav = invokeHandlerMethod(request, response, handlerMethod);}// 如果响应头中不包含缓存控制信息if (!response.containsHeader(HEADER_CACHE_CONTROL)) {// 如果处理方法有会话属性,应用缓存控制if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {// 否则,准备响应prepareResponse(response);}}// 返回处理结果的ModelAndView对象return mav;}//..}public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {//..@Override@Nullablepublic final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);}//..}
很明显,得到结果的方法在于:mav = invokeHandlerMethod(request, response, handlerMethod);,我们进入:

//RequestMappingHandlerAdapter里面的@Nullable // 表示方法的返回值可以为null,他只是一个意图,代表了可以为null,虽然一个返回值也的确可以为null,但是有些时候,可能内部不能存在为null的情况,那么这个标志就能为开发者提供他可以为null,认为其内部也绝对允许为null
//即他的意义在于提醒开发者,虽然他并没有实际代码方面的影响意义,也就是说他写不写无所谓,只是一种一个方法返回值可以为null时,为了记忆他可以为null,而写上的注解protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {// 创建ServletWebRequest对象,用于封装HttpServletRequest和HttpServletResponseServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 获取数据绑定工厂//也就是获取容器中,全局配置的InitBinder(一般是注解)和当前HandlerMethod所对应的Controller的InitBinder,用于参数绑定WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// 获取模型工厂//获取容器中,全局配置的ModelAttribute和当前HandlerMethod所对应的Controller中配置的ModelAttribute,这些配置的方法会在目标方法调用之前进行处理ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 创建可调用的处理器方法对象//将handlerMethod封装为一个ServletInvocableHandlerMethod对象(handlerMethod是他父类,所以自然可以)/*protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {return new ServletInvocableHandlerMethod(handlerMethod);}*/ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);// 设置处理器方法参数解析器、返回值处理器、数据绑定工厂和参数名发现器等属性if (this.argumentResolvers != null) {//设置当前容器中配置的所有ArgumentResolverinvocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {//设置当前容器中配置的所有ReturnValueHandlerinvocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}//将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);// 创建ModelAndViewContainer对象ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));// 初始化模型//这里initModel()方法主要作用是调用前面获取到的@ModelAttribute注解标识的方法//从而达到@ModelAttribute标注的方法能够在目标Handler调用之前进行处理//当然Springmvc中@ModelAttribute和InitBinder的操作相比于其他相关操作,是通常很少见的modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);// 创建异步的Web请求对象AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);// 获取Web异步管理器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);}// 调用处理器方法并处理结果//也就是对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象//主要是给了mavContainer进行处理,然后后面才真正的进行返回invocableMethod.invokeAndHandle(webRequest, mavContainer);// 如果已经启动了异步处理,返回nullif (asyncManager.isConcurrentHandlingStarted()) {return null;}// 返回处理结果的ModelAndView对象,传递了mavContainer(前面操作过程中,对返回值进行了处理,而对应的处理最后考虑返回ModelAndView对象,至此具体操作说明完毕)//这里对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果重定向//还会判断是否需要将FlashAttributes封装到新的请求中//其中视图名称由mavContainer里面进行赋值,而他在后面的/*this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);中,接收返回的视图名称,即给mavContainer,然后在这个方法里面给ModelAndView*/return getModelAndView(mavContainer, modelFactory, webRequest);}finally {// 请求完成后的清理工作webRequest.requestCompleted();}}
上面操作了数据的开始就是invocableMethod.invokeAndHandle(webRequest, mavContainer);,他基本是本质上的处理结果,我们进入他:
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {//..public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 执行请求处理逻辑,获取处理后的返回值(providedArgs通常是空数组,这是很明显的,因为前面没有对应的第三个参数(可变长参数,当没有被赋值时,默认是空数组,可变长参数本质就是(当成)数组的))Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);// 设置相关的返回状态setResponseStatus(webRequest);// 如果返回值为 nullif (returnValue == null) {// 检查请求是否未修改,或者已经设置响应状态,或者已经处理了请求,如果是,则禁用内容缓存并标记请求已处理if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}// 如果返回值不为 null,同时设置了响应状态的原因else if (StringUtils.hasText(getResponseStatusReason())) {// 标记请求已处理mavContainer.setRequestHandled(true);return;}// 标记请求未处理mavContainer.setRequestHandled(false);// 断言确保存在返回值处理器Assert.state(this.returnValueHandlers != null, "No return value handlers");try {// 使用返回值处理器处理返回值//将视图名称设置给mavContainerthis.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);是对应的处理,我们继续进入:
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {//..   
}public class InvocableHandlerMethod extends HandlerMethod {//..@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//将request中的参数转换为当前handler的参数形式(这个里面就比较复杂,他们知道即可)Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}//这里的方法主要是结合处理后的参数(这个参数很明显就是来进行判断我们前面模拟的参数与位置的关系的数组,我们前面也说了,对应的是调用适配器的方法的东西,这里也正好是这样处理的)//简单来说,这里的doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用return doInvoke(args);}//return doInvoke(args);@Nullableprotected Object doInvoke(Object... args) throws Exception {ReflectionUtils.makeAccessible(getBridgedMethod());try {//操作代理(最终的地方,也就调用了对应的方法)return getBridgedMethod().invoke(getBean(), args);}catch (IllegalArgumentException ex) {assertTargetBean(getBridgedMethod(), getBean(), args);String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");throw new IllegalStateException(formatInvokeError(text, args), ex);}catch (InvocationTargetException ex) {// Unwrap for HandlerExceptionResolvers ...Throwable targetException = ex.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(formatInvokeError("Invocation failure", args), targetException);}}}//我们也可以选择看一看protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//这里我建议启动一下,然后看看这个数组,一般就可以看到对应的参数信息是对应方法的参数信息/*比如你访问:http://localhost:8080/gradle/demo/name?name=2这个数组结果中,他并不看我们的请求参数,而是看方法的参数,由于方法参数是这样的public String fa(String name, Map<String, Object> map) {,那么你看看对于的结果是否符合吧,一般是符合的*///获取当前handler所声明的所有参数,主要包括参数名,参数类型,参数位置,所标注的注解等等属性MethodParameter[] parameters = getMethodParameters(); //HandlerMethod里面的方法,得到的自然是映射器里面的东西(在前面我们模拟时也是保存的,如前面的private Map<String, Integer> paramIndexMapping; //参数顺序,key是参数名,value是代表第几个参数)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);//providedArgs是调用方提供的参数,这里主要是判断这些参数中是否有当前类型,如果有,则直接使用调用方提供的参数,对于请求处理而言,默认情况下,调用方提供的参数都是长度为0的数组args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}//如果在调用方提供的参数中,不能找到当前类型的参数值,则遍历Spring容器中所有的ArgumentResolver,判断哪种类型的Resolver支持对当前参数的解析(当然,具体操作随着版本而变化,通常是直接报错的),这里的判断方式比较简单,比如RequestParamMethodArgumentResolver就是判断当前参数是否使用@RequestParam注解进行了标注(他的父类是HandlerMethodArgumentResolver,而该supportsParameter里面就有得到HandlerMethodArgumentResolver的处理,所以存在,并且对于的是一个集合,所以是遍历的处理,那么RequestParamMethodArgumentResolver应该是其中一个)//其中this.resolvers里面存在this.argumentResolvers和this.argumentResolverCache,本质上是操作this.argumentResolvers,通常会存在26个(数组里面26个,遍历的也是他)(所以这里基本不会出现报错)if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {//如果能够找到对当前参数进行处理的ArgumentResolver,则调用其resolveArgument()方法从request中获取对应的参数值,并且进行转换,然后给对应的数组进行赋值(这里是通过对象来封装处理的,因为会考虑注解的处理,除了注解,其他的基本操作一致,上面的遍历主要是为了注解的处理),至此向这样一路操作,最终生成对应的参数值数组,然后返回,最后进行调用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;}//..//前面也给了一次,这里再来一次看看吧(因为是最后的了)@Nullableprotected Object doInvoke(Object... args) throws Exception {ReflectionUtils.makeAccessible(getBridgedMethod());try {//到这里,则进行真正的处理,会生成对应的map,最终给视图对象//也就是mavContainer对应由map了,然后在对应方法getModelAndView里面操作如下:/*ModelMap model = mavContainer.getModel();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());这里了解即可*/return getBridgedMethod().invoke(getBean(), args);}catch (IllegalArgumentException ex) {assertTargetBean(getBridgedMethod(), getBean(), args);String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");throw new IllegalStateException(formatInvokeError(text, args), ex);}catch (InvocationTargetException ex) {// Unwrap for HandlerExceptionResolvers ...Throwable targetException = ex.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(formatInvokeError("Invocation failure", args), targetException);}}}//..}
上面我们继续说明args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);,也就是赋值的处理,这里说完基本上对应方法调用的过程基本说明完毕,我们进入:
//他的父类也是HandlerMethodArgumentResolver {
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {//..@Override@Nullablepublic 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.");}//进入这里了return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);}//..}public interface HandlerMethodArgumentResolver {//..   
}
//这里我们遍历时,一般是RequestParamMethodArgumentResolver的处理
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {//..//到这里(通常是AbstractNamedValueMethodArgumentResolver子类)@Override@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {//给这里打上断点,首先我们的请求是:http://localhost:8080/gradle/demo/name?name=2//对应方法的参数是:	public String fa(String name, Map<String, Object> map) {//那么后面的结果中以这个为例子://根据方法的参数信息得到结果,这里是参数列表的其中一个,如果你访问了上面,那么这里里面应该是从name开始,自己看看里面的信息吧//当然,RequestParamMethodArgumentResolver也会操作没有对应注解的参数,因为会考虑默认行为,也就是这里NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);//获取嵌套的方法参数(考虑对象的里面的参数),如果该参数是Optional类型的,那么进行处理(通常是考虑相同的),否则就是parameterMethodParameter nestedParameter = parameter.nestedIfOptional();// 解析参数的名称,将其转换为实际的值,考虑第一次调用,那么这里应该是nameObject resolvedName = resolveStringValue(namedValueInfo.name);// 如果解析后的名称为 null,抛出异常if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}// 通过调用 resolveName 方法解析参数的值,对这个名称进行检查,发现请求中也有这个名称,这里就是2//很明显,是从请求中检查对应的参数列表(因为这里是主体,在前面模拟时基本也是如此,都在位置确定,然后赋值,虽然这里的实现与我们模拟的不一样,但是本质上都是位置确定然后赋值,只是时机不同而已)Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); // 2//当然,上面arg存在这样的:arg = (paramValues.length == 1 ? paramValues[0] : paramValues);//也就是说,如果有多个相同的,会直接返回对应的数组(基本都是字符串),数组在打印的时候,通常不会直接操作对应的值出来,而是对应的地址,所以后面需要进行一些处理,来进行拼接(我们前面已经处理过了),注意,你可能会想到操作并发怎么办,但是你要知道读取信息在并发是不会产生冲突的,这里基本没有修改的处理,所以通常不需要考虑,而修改添加在初始化时,就已经操作了,而初始化只会操作一次,所以不考虑并发//现在我们看看后面哪个地方是处理拼接的,说明了这个,那么返回的arg就算说明完成了,也就是返回值操作完毕,也就是对应位置的参数的值也就确定了,然后最后调用对应的反射处理的方法,所以这里是最后一步//开始查看:// 如果参数值为 null,且有默认值,则使用默认值if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}// 如果参数值为 null,且该参数是必需的且非 Optional 类型,则处理缺失值else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}//..}
//当然,还存在其他的处理,比如操作map(正好前面我们操作了,可以继续调试,就知道了),一般是如下://对应的类就是MapMethodProcessor,this.argumentResolvers里面的
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {//..@Override@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");return mavContainer.getModel(); //ModelAndViewContainer 是 Spring MVC 中用于处理模型和视图的容器类,它的主要作用是存储控制器处理方法(例如使用 @RequestMapping 注解的方法)生成的模型数据和视图名称,方便后续在视图解析阶段使用(这里明显是处理模型的,且是直接的返回)}//..}public class ModelAndViewContainer {//..public ModelMap getModel() {if (useDefaultModel()) {return this.defaultModel; //这个private final ModelMap defaultModel = new BindingAwareModelMap(); //最上层的ModelMap(BindingAwareModelMap是ModelMap的子类)//public class ModelMap extends LinkedHashMap<String, Object> {}else {if (this.redirectModel == null) {this.redirectModel = new ModelMap();}return this.redirectModel;}}//..}
上面我们也大致明白了具体返回值的操作,实际上关于spring的,基本都是建立在spring上开发的,mvc自然也是
这里了解即可,因为如果还要深入,需要对很多类进行说明,学习框架不应如此,当然,如果需要写框架,那么可以建议深入(比如对应的参数中对应的注解操作过程是如何的等等),实际上没有很长时间,几乎不可能将一个框架的每个细节的每个代码都说明完毕,现在也基本没有,就算是开发人员,也基本不会记得,所以有些时候是只需要知道他做了什么即可,深入也是一样
既然方法调用了,我们回到之前的这里:
//执行方法后得到的结果
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
我们回到上面,继续调试,会到这里:
// 处理分发(拦截到的调用方法,也就是分发)结果(简单来说就是处理返回值),又或者说需要到视图解析器了,也就是操作视图的地方this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
给他打上断点,开始调试:
我们进入:
public class DispatcherServlet extends FrameworkServlet {//..//到这里private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {// 标志是否发生了异常导致的错误视图boolean errorView = false;// 处理异常情况if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {// 如果异常是ModelAndViewDefiningException类型,从异常中获取ModelAndView对象this.logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException)exception).getModelAndView();} else {// 处理一般异常情况,通过处理器适配器处理异常,获取处理结果ModelAndViewObject handler = mappedHandler != null ? mappedHandler.getHandler() : null;mv = this.processHandlerException(request, response, handler, exception);errorView = mv != null;}}// 渲染视图if (mv != null && !mv.wasCleared()) {// 如果ModelAndView不为null且未被清除,渲染视图this.render(mv, request, response);// 如果是因为异常导致的错误视图,清除错误相关的请求属性if (errorView) {WebUtils.clearErrorRequestAttributes(request);}} else if (this.logger.isTraceEnabled()) {// 如果ModelAndView为null,记录跟踪日志this.logger.trace("No view rendering, null ModelAndView returned.");}// 在非异步处理情况下,调用处理器的afterCompletion方法if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, (Exception)null);}}}//..//到这里protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {//获取请求的Locale,如果存在LocaleResolver则使用它解析,否则使用请求的默认LocaleLocale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();//将解析得到的Locale设置到响应中,确保后续的内容能够按照正确的地区信息进行渲染response.setLocale(locale);// 获取 ModelAndView 中的视图名,默认情况下,只有操作默认时,这个才会存在,当然,如果返回了视图名,那么操作返回的String viewName = mv.getViewName();View view;// 如果视图名不为null,根据视图名和Locale解析出对应的视图对象if (viewName != null) {//这个视图对象就是根据我们的视图名来找到的,现在还是返回的视图名称,如果返回的没有,会在前面说过的后置拦截器的上面的代码中操作默认的了(具体可以过去看看)view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {// 如果解析的视图对象为null,抛出异常throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");}} else {//直接得到对象,不操作默认的视图名,一般就是这个,如果对象存在,说明他正常返回//说明,在处理响应结果时,其实就已经生成了对应的视图对象(虽然没有对应的视图名称来对应,相当于直接操作响应)view = mv.getView();if (view == null) {// 如果视图对象也为null,抛出异常throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");}}// 如果日志级别是TRACE,输出渲染视图的信息if (this.logger.isTraceEnabled()) {this.logger.trace("Rendering view [" + view + "] ");}try {// 如果 ModelAndView 中设置了响应状态码,则将响应状态码设置为对应的值if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}// 调用视图的 render 方法,进行实际的渲染工作(最后的响应给前端的响应体,操作视图路径,拿取页面,操作一系列的替换处理,这里面的处理省略了,因为处理太多,有编译方面的关系(如jsp中的哪些表达式处理,如EL和JSTL核心技术),这里可以到52章博客里去查看)view.render(mv.getModelInternal(), request, response);} catch (Exception var8) {// 如果在渲染过程中出现异常,记录错误日志并将异常重新抛出if (this.logger.isDebugEnabled()) {this.logger.debug("Error rendering view [" + view + "]", var8);}throw var8;}}//..//到这里@Nullableprotected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {// 如果存在视图解析器if (this.viewResolvers != null) {// 遍历所有视图解析器Iterator var5 = this.viewResolvers.iterator();while(var5.hasNext()) {// 获取当前的视图解析器ViewResolver viewResolver = (ViewResolver)var5.next();// 尝试使用当前视图解析器解析视图名字View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {// 如果找到合适的视图,立即返回该视图return view;}}}return null;}//..}//到这里
//尝试使用当前视图解析器解析视图名字(this是InternalResourceViewResolver)//              View view = viewResolver.resolveViewName(viewName, locale);
public class InternalResourceViewResolver extends UrlBasedViewResolver {//..   
}
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
//..
}public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {//..@Override@Nullablepublic View resolveViewName(String viewName, Locale locale) throws Exception {// 判断是否启用缓存if (!isCache()) {// 如果没有启用缓存,直接创建视图并返回return createView(viewName, locale);}else {// 生成缓存键Object cacheKey = getCacheKey(viewName, locale);// 尝试从访问缓存中获取视图View view = this.viewAccessCache.get(cacheKey);// 如果访问缓存中没有,从创建缓存中获取视图if (view == null) {synchronized (this.viewCreationCache) {view = this.viewCreationCache.get(cacheKey);if (view == null) {// 如果创建缓存中也没有,让子类创建视图对象// Ask the subclass to create the View object.//进入这里view = createView(viewName, locale);if (view == null && this.cacheUnresolved) {// 如果视图为 null 并且缓存未解析的视图,则标记为未解析视图view = UNRESOLVED_VIEW;}if (view != null) {// 将视图放入访问缓存和创建缓存中this.viewAccessCache.put(cacheKey, view);this.viewCreationCache.put(cacheKey, view);}}}}else {// 如果视图在访问缓存中,记录调试信息if (logger.isTraceEnabled()) {logger.trace(formatKey(cacheKey) + "served from cache");}}// 返回视图,如果视图为未解析视图,返回 nullreturn (view != UNRESOLVED_VIEW ? view : null);}}//..}//this是InternalResourceViewResolver
public class InternalResourceViewResolver extends UrlBasedViewResolver {//..   
}public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {//..   
}public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {//..@Overrideprotected View createView(String viewName, Locale locale) throws Exception {// If this resolver is not supposed to handle the given view,// return null to pass on to the next resolver in the chain.// 如果 viewName 和 locale 不适合当前 ViewResolver,则返回 nullif (!canHandle(viewName, locale)) {return null;}
// 检查视图名称是否以特殊的 "redirect:" 前缀开头(考虑重定向,这个时候就知道为什么操作mvc时重定向需要对应的前缀了吧)// Check for special "redirect:" prefix.if (viewName.startsWith(REDIRECT_URL_PREFIX)) {// 提取重定向 URLString redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());// 创建 RedirectView 对象RedirectView view = new RedirectView(redirectUrl,isRedirectContextRelative(), isRedirectHttp10Compatible());// 获取允许的重定向主机String[] hosts = getRedirectHosts();if (hosts != null) {view.setHosts(hosts);}// 应用生命周期方法并返回视图对象return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);}// Check for special "forward:" prefix.// 检查视图名称是否以特殊的 "forward:" 前缀开头(与前面的是同理的)if (viewName.startsWith(FORWARD_URL_PREFIX)) {// 提取转发 URLString forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());// 创建 InternalResourceView 对象InternalResourceView view = new InternalResourceView(forwardUrl);// 应用生命周期方法并返回视图对象return applyLifecycleMethods(FORWARD_URL_PREFIX, view);}//还需要注意一点,比如重定向是需要设置响应信息才能让浏览器或者前端进行重定向的,这里的并没有,通常是在渲染时(对应的render方法里面),或者最后才会进行处理,即:/*// 调用视图的 render 方法,进行实际的渲染工作(最后的响应给前端的响应体,操作视图路径,拿取页面,操作一系列的替换处理,这里面的处理省略了,因为处理太多,有编译方面的关系(如jsp中的哪些表达式处理,如EL和JSTL核心技术),这里可以到52章博客里去查看)view.render(mv.getModelInternal(), request, response); //这个view就是考虑这里的*///当然转发是内部处理的,通常不需要像重定向一样的考虑响应状态,了解即可/*也就是前面的这里:// 调用视图的 render 方法,进行实际的渲染工作(最后的响应给前端的响应体,操作视图路径,拿取页面,操作一系列的替换处理,这里面的处理省略了,因为处理太多,有编译方面的关系(如jsp中的哪些表达式处理,如EL和JSTL核心技术),这里可以到52章博客里去查看)view.render(mv.getModelInternal(), request, response); //不同的视图(对象)是不同的render方法*/// 否则,回退到超类的实现:调用 loadView 方法// Else fall back to superclass implementation: calling loadView.return super.createView(viewName, locale);}//..
}public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
//..
@Nullableprotected View createView(String viewName, Locale locale) throws Exception {return loadView(viewName, locale);}//..
}public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {//..private String prefix = "";private String suffix = "";//..protected String getPrefix() {return this.prefix;}//..protected String getSuffix() {return this.suffix;}//..@Overrideprotected View loadView(String viewName, Locale locale) throws Exception {// 根据视图名称构建一个 AbstractUrlBasedView 对象(进入这里)AbstractUrlBasedView view = buildView(viewName); //默认this来调用(基础知识)//等价于:this.buildView(viewName)// 应用视图生命周期的方法,例如初始化和属性设置等View result = applyLifecycleMethods(viewName, view);// 检查视图资源是否存在并可用,如果存在则返回结果视图,否则返回 nullreturn (view.checkResource(locale) ? result : null);}protected AbstractUrlBasedView buildView(String viewName) throws Exception {// 获取视图类的Class对象,该类应该扩展自AbstractUrlBasedViewClass<?> viewClass = getViewClass();// 断言确保视图类不为空,如果为空则抛出异常Assert.state(viewClass != null, "No view class");// 使用视图类的Class对象实例化一个AbstractUrlBasedView对象AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);// 设置视图的URL,由前缀、视图名和后缀组成,这个是否有点眼熟,我们看这个:/*<bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/jsp/"></property><property name="suffix" value=".jsp"></property></bean>很明显就是前缀和后缀*/view.setUrl(getPrefix() + viewName + getSuffix());// 获取视图的内容类型String contentType = getContentType();// 如果内容类型不为空,则设置视图的内容类型if (contentType != null) {view.setContentType(contentType);}// 设置请求上下文属性view.setRequestContextAttribute(getRequestContextAttribute());// 设置视图的属性映射view.setAttributesMap(getAttributesMap());// 获取是否暴露路径变量的布尔值Boolean exposePathVariables = getExposePathVariables();// 如果暴露路径变量不为空,则设置是否暴露路径变量if (exposePathVariables != null) {view.setExposePathVariables(exposePathVariables);}// 获取是否将上下文中的Beans作为属性暴露的布尔值Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();// 如果此选项不为空,则设置是否将上下文中的Beans作为属性暴露if (exposeContextBeansAsAttributes != null) {view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);}// 获取需要暴露的上下文Bean名称数组String[] exposedContextBeanNames = getExposedContextBeanNames();// 如果数组不为空,则设置需要暴露的上下文Bean名称if (exposedContextBeanNames != null) {view.setExposedContextBeanNames(exposedContextBeanNames);}// 返回配置好的视图对象,至此,我们根据视图名称得到了对应的视图对象了,最后,我们通过视图对象进行渲染,最终可以得到一个具体的页面(如url地址的jsp等等)return view;}//..
}public class InternalResourceViewResolver extends UrlBasedViewResolver {
//..@Overrideprotected AbstractUrlBasedView buildView(String viewName) throws Exception {//进入这里InternalResourceView view = (InternalResourceView) super.buildView(viewName);if (this.alwaysInclude != null) {view.setAlwaysInclude(this.alwaysInclude);}view.setPreventDispatchLoop(true);return view;}
}
ok,我们继续回到之前的:
//这个视图对象就是根据我们的视图名来找到的,现在还是返回的视图名称,如果返回的没有,会在前面说过的后置拦截器的上面的代码中操作默认的了(具体可以过去看看)view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
在他后面可以找到:
// 调用视图的 render 方法,进行实际的渲染工作(最后的响应给前端的响应体,操作视图路径,拿取页面,操作一系列的替换处理,这里面的处理省略了,因为处理太多,有编译方面的关系(如jsp中的哪些表达式处理,如EL和JSTL核心技术),这里可以到52章博客里去查看)view.render(mv.getModelInternal(), request, response);
我们进入:
//在前面对应的返回是:
/*
// 使用视图类的Class对象实例化一个AbstractUrlBasedView对象
//本质得到的是InternalResourceViewAbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);//最终由View接收,当然,下面的implements View是实现的,所以不用担心(通常是应该某些处理直接赋值的)
*/public class InternalResourceView extends AbstractUrlBasedView {//..   
}
public abstract class AbstractUrlBasedView extends AbstractView implements InitializingBean {//..   
}//我们就以普通的为主,不考虑重定向和转发了
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {//..@Overridepublic void render(@Nullable Map<String, ?> model, HttpServletRequest request,HttpServletResponse response) throws Exception {// 如果日志级别设置为调试模式,则记录相关的调试信息if (logger.isDebugEnabled()) {// 打印当前视图的名称、传入的模型数据,以及静态属性(如果有的话)logger.debug("View " + formatViewName() +", model " + (model != null ? model : Collections.emptyMap()) +(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));}//model是之前的map,也就是/*view.render(mv.getModelInternal(), request, response);//public class ModelAndView 里面的@Nullableprotected Map<String, Object> getModelInternal() {return this.model;}*/// 合并传入的模型与静态属性到一个新的模型map中,这将用于渲染输出(里面创建了对应的map,接收model,使用putAll,将指定映射中的所有键值对插入到当前映射中,如果当前映射中已经存在某些键,这些键对应的值将被更新为指定映射中的值)Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);// 准备HTTP响应,可能设置一些响应头或其他响应级别的属性prepareResponse(request, response);// 使用合并后的模型、请求对象以及响应对象来渲染输出,具体渲染逻辑在此方法实现renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);}//..}//前面的this基本都是InternalResourceView,因为InternalResourceView是返回的视图对象
public class InternalResourceView extends AbstractUrlBasedView {//..@Overrideprotected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// 将模型对象作为请求属性暴露// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);/*protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value); //将model数据放入request,在前面我们就知道了,最终给到request域中,就是这里}else {request.removeAttribute(name);}});}*/
// 将辅助对象(如果有的话)作为请求属性暴露// Expose helpers as request attributes, if any.exposeHelpers(request);
// 确定请求调度器的路径(前面获得过了,即view.setUrl(getPrefix() + viewName + getSuffix());)// Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(request, response);// 为目标资源(通常是JSP)获取一个RequestDispatcher// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// 如果已经包含或响应已经提交,则执行include操作,否则执行forward操作// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {// 设置响应的内容类型response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}// 包含目标资源rd.include(request, response);}else {// 注意:被转发的资源应该自己确定内容类型// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}// 转发到目标资源(最终的资源了,也就是拿取页面,解析页面,返回页面(即响应给用户))rd.forward(request, response);}}//..
}
至此,可以说基本上mvc的源码说明完毕,那么mybatis,spring,springmvc的源码一游到此结束
当然,如果某些源码说明是错误的,那么你可以选择忽略,但是基本都是正确的(在以后深入研究时,通常会更正),其中某些地方或者某些方法会多次的给出,因为是比较重要的(大多是为了一个过程顺序,而非类里面方法中的顺序,这里基本都是按照类方法顺序的,所以有时候是后面的方法先看,具体可以在上一章博客中,即spring源码的说明中可以看到,即107章博客(记得看前缀Java的哦,后面和前面的博客就不说明了)),在最开始已经处理了mvc的基本流程,且都给出了,后面的是一个验证(在此期间可能会修改某些注释,但是意思基本相同,如上面的默认视图名称那里),当然,并不是所有的方法都会深入的,只是一个大概,如前面的return getModelAndView(mavContainer, modelFactory, webRequest);,这些了解即可(看看注释即可,这些说明也同样适用于mybatis的源码,以及spring的源码)
我们还需要进行将他们一起的处理
操作ssm整合(当然了,具体关键的需要的依赖的源码就不说了,本质上是一起操作的,即虽然整合可能需要其他依赖,但是该依赖是一起处理的,这里就不说明这个依赖的源码了,具体可以自行了解,总不能将所有依赖的源码都说明吧,而且也要注意:一个大框架的编写通常是多人开发,而非一个(很少而已),所以我们也通常只需要使用,而非完全的知道所有原理(个人开发的可以去搞一搞,当然,如果可以,多人的也行,只是不建议)):
SSM = Spring + SpringMVC + Mybatis = (Spring + Mybatis)+ SpringMVC
先整合 Spring + Mybatis,然后再整合 SpringMVC
Mybatis整合Spring:
整合目标:
数据库连接池以及事务管理都交给Spring容器来完成
SqlSessionFactory对象应该放到Spring容器中作为单例对象管理
Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象
当然,这里在69章博客就已经说明了,只是一个复习而已
我们创建一个maven项目,如下:

在这里插入图片描述

对应的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>mvc</artifactId> <!--自己的名称,虽然修改也没有问题(使用依赖时,写对即可,这里我们不考虑这样的影响)--><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><packaging>war</packaging> <!--写上这个,会自动将webapp变成可以操作的web文件,当然,在某些版本上,可能也会将web进行处理,但一般都是webapp,这里了解即可--><dependencies><!--junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><!--Spring整合@Test,使得可以使用注解指定配置类或者配置文件--><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.1.5.RELEASE</version></dependency><!--mybatis--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><!--spring相关--><dependency><!--Spring容器,即IOC容器的使用需要这个,比如ApplicationContext--><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.12.RELEASE</version></dependency><dependency><!--Spring自带的使用连接池的,这个可以用到了下面的tx,所以可以一起导入当然由于自带有对应事务,且不需要一些特殊的事务操作时,即下面的tx可以不加由于也包括一些tx功能,即配置文件也可以使用tx,而不用导入下面的tx(该依赖里面有tx,所以下面的可以不加)--><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.12.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.1.12.RELEASE</version></dependency><dependency><!-- aspectj的织入 切点表达式需要这个jar包,进行解析--><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version></dependency><!--mybatis与spring的整合包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.3</version></dependency><!--数据库驱动jar--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency><!--druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency></dependencies>
</project>
有时候一个版本的依赖,其对应的里面的依赖(如子依赖),通常版本也会与他相同,但是有些不会(基本不是同源的不会),如:
 <!--dubbo --><dependency><groupId>com.alibaba</groupId><artifactId>dubbo</artifactId><version>2.5.7</version></dependency>
在maven中,进去看看就行了这样的了解即可,只是说明一下版本并不是绝对相同而已,如图:

在这里插入图片描述

的确有相同的和不同的(通常同源的相同,具体是否有例子,可能需要百度查看,反正是人为的)
创建jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bank?characterEncoding=utf8&useSSL=false
jdbc.username=root
jdbc.password=123456
创建数据库:
CREATE DATABASE bank CHARACTER SET utf8;
USE bank;
CREATE TABLE test(
id INT(2),
NAME VARCHAR(10)
);
INSERT INTO test VALUE(1,'张三');
INSERT INTO test VALUE(2,'李四');
INSERT INTO test VALUE(3,'王五');
INSERT INTO test VALUE(4,'赵六');
创建applicationContext-dao.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
"><!--包扫描--><context:component-scan base-package="com.mapper"/><!--引⼊外部资源⽂件--><context:property-placeholder location="classpath:jdbc.properties"/><!--第三⽅jar中的bean定义在xml中--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--数据库连接池以及事务管理都交给Spring容器来完成--><!--SqlSessionFactory对象应该放到Spring容器中作为单例对象管理原来mybaits中sqlSessionFactory的构建是需要素材的:SqlMapConfig.xml中的内容--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--别名映射扫描--><property name="typeAliasesPackage" value="com.pojo"/><!--数据源dataSource--><property name="dataSource" ref="dataSource"/></bean><!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象--><!--扫描mapper接⼝,⽣成代理对象,⽣成的代理对象会存储在ioc容器中--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--mapper接⼝包路径配置--><property name="basePackage" value="com.mapper"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean>
</beans>
写一个配置(只需要在测试时,读取多个即可):applicationContext-service.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd
"><!--引⼊外部资源⽂件--><context:property-placeholderlocation="classpath:jdbc.properties"/><!--第三⽅jar中的bean定义在xml中--><bean id="dataSource"class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--包扫描--><context:component-scan base-package="com.service"/><!--事务管理--><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--事务管理注解驱动--><tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
当然,通常我们建议都写在一个xml中(除非需要进行区分,要不然会有相同的代码的,如上面的数据库的配置)
编写mapper层:
创建com.mapper包,然后创建AccountMapper接口:
package com.mapper;public interface AccountMapper {// 定义dao层接⼝方法--> 查询account表所有数据List<Account> queryAccountList() throws Exception;
}
在com包下,创建pojo包,然后创建Account类:
package com.pojo;public class Account {int id;String name;public Account() {}public Account(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "Account{" +"id=" + id +", name='" + name + '\'' +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
然后创建文件,在资源文件夹下,创建com.mapper包(注意层级,在这个地方可能需要分开创建,因为可能不受idea或者maven的影响,一般是maven,idea可能有),然后创建AccountMapper.xml:
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.AccountMapper"><select id="queryAccountList" resultType="com.pojo.Account">select *from test</select>
</mapper>
然后在com包下创建service包,在创建AccountService接口及其实现类:
package com.service;import com.pojo.Account;import java.util.List;public interface AccountService {List<Account> queryAccountList() throws Exception;
}
package com.service.impl;import com.mapper.AccountMapper;
import com.pojo.Account;
import com.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;import java.util.List;@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountMapper accountMapper;@Overridepublic List<Account> queryAccountList() throws Exception {return accountMapper.queryAccountList();}
}
然后在测试文件夹的java下,创建test包,然后创建Test1类:
package test;import com.pojo.Account;
import com.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;@RunWith(SpringJUnit4ClassRunner.class)
//指定多个
@ContextConfiguration(locations = {"classpath*:application*.xml"})
public class Test1 {// 希望测试ioc容器中的哪个对象你注⼊即可。@Autowiredprivate AccountService accountService;@Testpublic void testMybatisSpring() throws Exception {List<Account> accounts = accountService.queryAccountList();for (int i = 0; i < accounts.size(); i++) {Account account = accounts.get(i);System.out.println(account);}}
}
启动后,若有数据,说明操作成功,至此整合完毕,现在我们来整合mvc
在pom.xml中加上如下的依赖:
<!--SpringMVC--><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.1.12.RELEASE</version></dependency><!--jsp-api--><dependency><groupId>javax.servlet</groupId><artifactId>jsp-api</artifactId><version>2.0</version><scope>provided</scope></dependency><dependency><!--servlet-api--><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!--⻚⾯使⽤jstl表达式,包括后面的standard--><dependency><!--对应标签需要的库--><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><!--可以操作标签--><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency><!--注意:上面两个通常需要一起,当然,可能随着时间的发展,可能只需要一个就够了,并且这个意思可能会改变,了解即可--><!--json数据交互所需jar,start,68章博客有他们的说明--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.9.0</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.0</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.9.0</version></dependency>
在资源文件夹下创建springmvc.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--扫描controller--><context:component-scan base-package="com.controller"/> <!--
可以一起扫描,不操作先后(但是建议分开,因为需要比较的模块化和清晰)<context:component-scan base-package="com.controller"/><context:component-scan base-package="com.mapper"/><context:component-scan base-package="com.service"/>
--><mvc:annotation-driven/>
</beans>
在com包下,创建controller包,然后创建AccountController类:
package com.controller;import com.pojo.Account;
import com.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.List;@Controller
@RequestMapping("/account")
public class AccountController {@Autowiredprivate AccountService accountService;@RequestMapping("/queryAll")@ResponseBody//参数中,有些是可以直接获取的,有些不行,具体看版本(但是一般请求和响应基本都可以)public List<Account> queryAll(HttpServletRequest request) throws Exception {ServletContext context = request.getSession().getServletContext();String displayName = context.getServletContextName();//Display Name: Archetype Created Web Application//<display-name>Archetype Created Web Application</display-name>System.out.println("Display Name: " + displayName);return accountService.queryAccountList();}
}
然后在web.xml文件下加上这个:
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" >
<!--考虑一些约束,建议使用其他方式(因为顺序不好看),可以看看67章博客的操作-->
<web-app><!--通常代表给管理界面来进行区分的,某些操作可以获取的,了解即可--><display-name>Archetype Created Web Application</display-name><context-param><param-name>contextConfigLocation</param-name><param-value>classpath*:applicationContext*.xml</param-value></context-param><!-- 解决post乱码问题 --><filter><filter-name>encoding</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><!-- 设置编码参是UTF8 --><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encoding</filter-name><url-pattern>/*</url-pattern></filter-mapping><!--spring框架启动--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--springmvc启动--><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><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
配置tomcat,然后访问http://localhost:8080/account/queryAll,若出现数据,那么我们mvc也整合完毕
至此,全部整合完毕,当然了,乱码问题,在前面的博客中都有,并且关于:
<?xml version="1.0" encoding="UTF-8"?>
这个部分的说明,在前面的注释中可能存在(具体可以找找,其实我自己也不知道在那个注释里面的),他在某些情况下,需要与idea设置的编码一致(如文件读取的编码,这里了解即可,通常解决项目编码问题也需要到对应的idea编码中处理)
比如:

在这里插入图片描述

global设置是指应用于所有项目的通用设置,而 project设置是指特定项目的设置,当你在 global设置中更改编码时,它会影响所有项目,而在 project设置中更改编码只会影响当前项目,即当前项目的是GBK,下面的是配置文件的,我们可以看到默认的是对应的ISO开头的编码,在某些时候,我们需要设置这个,当然,后面的选项是操作ascii的,在某些时候也需要用到,他们都是对配置文件的编码处理,只需要编码一致,那么自然就可以解决编码问题(后面的选择ascii,通常考虑更大的跨项目,考虑国际化,虽然有时候考虑跨项目的处理,因为他的统一是更多的,即更加的底层,谁基本上都可以看懂(就如英语一样))
在pom.xml中也可以加上一些插件,操作如这样的标签:
<build><plugins><plugin><!--省略了,具体可以百度,.....--></plugin></plugins></build>
这个时候,在右边对应的maven的插件选项中,会多出来一些你配置的东西,也就可以进行操作处理,这里了解即可
设计模式:
策略模式:策略模式(Strategy),就是一个问题有多种解决方案,选择其中的一种使用,这种情况下我们使用策略模式来实现灵活地选择,也能够方便地增加新的解决方案,比如做数学题,一个问题的 解法可能有多种,再比如商场的打折促销活动,打折方案也有很多种,有些商品是不参与折扣活 动要按照原价销售,有些商品打8.5折,有些打6折,有些是返现5元等
结构:
策略(Strategy): 定义所有支持算法的公共接口,Context 使用这个接口来调用某 ConcreteStrategy 定义的算法
策略实现(ConcreteStrategy) :实现了Strategy 接口的具体算法
上下文(Context): 维护一个 Strategy 对象的引用,用一个 ConcreteStrategy 对象来装配,可定义一个接口方法让 Strategy 访问它的数据
案例:
假如现在有一个商场优惠活动,有的商品原价售卖,有的商品打8.5折,有的商品打6折,有的返现 5元
我们创建项目,然后创建一个com包,在里面创建BuyGoods类:
package com;import java.text.MessageFormat;public class BuyGoods {private String goods;private double price;private double finalPrice;private String desc;public BuyGoods(String goods, double price) {this.goods = goods;this.price = price;}public double calculate(String discountType) {if ("discount85".equals(discountType)) {finalPrice = price * 0.85;desc = "该商品可享受8.5折优惠";} else if ("discount6".equals(discountType)) {finalPrice = price * 0.6;desc = "该商品可享受6折优惠";} else if ("return5".equals(discountType)) {finalPrice = price >= 5 ? price - 5 : 0;desc = "该商品可返现5元";} else {finalPrice = price;desc = "对不起,该商品不参与优惠活动";}//一种打印,一般都会明白的System.out.println(MessageFormat.format("您购买的商品为:{0},原价为: {1},{2},最终售卖价格为:{3}", goods, price, desc, finalPrice));return finalPrice;}
}
然后在com包里面创建Test类:
package com;public class Test {public static void main(String[] args) {BuyGoods buyGoods1 = new BuyGoods("Java编程思想", 99.00);buyGoods1.calculate("discount85");BuyGoods buyGoods2 = new BuyGoods("罗技⿏标", 66);buyGoods2.calculate("discount6");BuyGoods buyGoods3 = new BuyGoods("苹果笔记本", 15000.00);buyGoods3.calculate("return5");BuyGoods buyGoods4 = new BuyGoods("佳能相机", 1900);buyGoods4.calculate(null);}
}
上述代码可以解决问题,但是从代码设计的角度还是存在一些问题
增加或者修改打折方案时必须修改 BuyGoods 类源代码,违反了面向对象设计的 “开闭原则”,代码的灵活性和扩展性较差
打折方案代码聚合在一起,如果其他项目需要重用某个打折方案的代码,只能复制粘贴对应代码,无法以类组件的方式进行重用,代码的复用性差
BuyGoods 类的 calculate() 方法随着优惠方案的增多会非常庞大,代码中会出现很多if分支,可维护性差
这个时候,我们可以使用策略模式了:
在com包下,创建AbstractDiscount类,他是所有打折方案的父类:
package com;public abstract class AbstractDiscount {protected double finalPrice;protected String desc;public AbstractDiscount(String desc) {this.desc = desc;}public abstract double discount(double price);public double getFinalPrice() {return finalPrice;}public void setFinalPrice(double finalPrice) {this.finalPrice = finalPrice;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}}
策略(Strategy): 定义所有支持算法的公共接口,Context 使用这个接口来调用某 ConcreteStrategy 定义的算法
上面也就是策略(Strategy)
创建四种打折方案的类:
package com;public class Discount85 extends AbstractDiscount {public Discount85() {super("该商品可享受8.5折优惠");}@Overridepublic double discount(double price) {finalPrice = price * 0.85;return finalPrice;}
}
package com;public class Discount6 extends AbstractDiscount {public Discount6() {super("该商品可享受6折优惠");}@Overridepublic double discount(double price) {finalPrice = price * 0.6;return finalPrice;}
}
package com;public class Return5 extends AbstractDiscount {public Return5() {super("该商品可返现5元");}@Overridepublic double discount(double price) {this.finalPrice = price >= 5 ? price - 5 : 0;return finalPrice;}
}
package com;public class NoDiscount extends AbstractDiscount {public NoDiscount() {super("对不起,该商品不参与优惠活动");}@Overridepublic double discount(double price) {finalPrice = price;return finalPrice;}
}
策略实现(ConcreteStrategy) :实现了Strategy 接口的具体算法,也就是上面的操作
修改对应的BuyGoods类:
package com;import java.text.MessageFormat;public class BuyGoods {private String goods;private double price;private AbstractDiscount abstractDiscount;public BuyGoods(String goods, double price, AbstractDiscount abstractDiscount) {this.goods = goods;this.price = price;this.abstractDiscount = abstractDiscount;}public double calculate() {double finalPrice = abstractDiscount.discount(this.price);String desc = abstractDiscount.getDesc();System.out.println(MessageFormat.format("商品:{0},原价:{1},{2},最 终价格为:{3}", goods, price, desc, finalPrice));return finalPrice;}
}
上下文(Context): 维护一个 Strategy 对象的引用,用一个 ConcreteStrategy 对象来装配,可定义一个接口方法让 Strategy 访问它的数据,就是上面的操作
进行测试:
package com;public class Test {public static void main(String[] args) {BuyGoods buyGoods1 = new BuyGoods("Java编程思想", 99.00, new Discount85());buyGoods1.calculate();BuyGoods buyGoods2 = new BuyGoods("罗技⿏标", 66, new Discount6());buyGoods2.calculate();BuyGoods buyGoods3 = new BuyGoods("苹果笔记本", 15000.00, new Return5());buyGoods3.calculate();BuyGoods buyGoods4 = new BuyGoods("佳能相机", 1900, new NoDiscount());buyGoods4.calculate();}
}
这样,我们以后,只需要补充类即可,但是你是否会发现,他是用空间换时间或者维护的,基本上所有的设计模式,都会使用一点空间来交换的,这基本是必然的,因为原来不使用设计模式的处理是绝对使用最少的类或者接口等等的操作的,所以使用设计模式基本必然会多用一点空间,但是这点空间交换的便利性足够可以忽略空间所带来的影响(所以建议使用设计模式,但是有些时候,因为空间,可能并不会使用,而且这个空间,在一定程度上,会导致不好维护(比如:太多的类,不好看的情况))
空间分成两种:一种是内存和硬盘,一种是显示,通常来说,我们说明的基本都是内存和硬盘,但是在设计模式中,两种都说明
当然,在mybatis,spring,springmvc中都使用了很多设计模式,他们基本都会使用工厂模式,具体在那里就不给出了,因为太多了
模板方法模式:
模板方法模式是指定义一个算法的骨架,并允许子类为一个或者多个步骤提供实现,模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为型设计模式
采用模板方法模式的核心思路是处理某个流程的代码已经具备,但其中某些节点的代码暂时不能确定,此时可以使用模板方法
案例:
在com包下创建moban包,然后创建Interview类(类可以称为抽象类和接口的):
package com.moban;public abstract class Interview {private final void register() {System.out.println("⾯试登记");}protected abstract void communicate();private final void notifyResult() {System.out.println("HR⼩姐姐通知⾯试结果");}protected final void process() {this.register();this.communicate(); //可以这样,抽象类不能创建实例,所以必然是其他的,即可以写上的this.notifyResult();}
}
然后继续创建如下的类:
package com.moban;public class Interviewee1 extends Interview {public void communicate() {System.out.println("我是⾯试⼈员1,来⾯试Java⼯程师,我们聊的是Java相关内容");}
}
package com.moban;public class Interviewee2 extends Interview {public void communicate() {System.out.println("我是⾯试⼈员2,来⾯试前端工程师,我们聊的是前端相关内容");}
}
创建InterviewTest类:
package com.moban;public class InterviewTest {public static void main(String[] args) {// ⾯试Java⼯程师Interview interviewee1 = new Interviewee1();interviewee1.process();// ⾯试前端工程师Interview interviewee2 = new Interviewee2();interviewee2.process();}
}
调用子类的版本,这样就是模板方法模式,当然,可以将protected abstract void communicate();修改成如下,也是调用子类的(考虑默认的执行顺序,也就相当于完成了修改其中一个步骤了):
  protected  void communicate(){System.out.println(1);}
适配器模式:
使得原本由于接口不兼容而不能一起工作、不能统一管理的那些类可以一起工作、可以进行统一管理
解决接口不兼容而不能一起工作的问题,看下面一个非常经典的案例
在中国,民用电都是220v交流电,但是手机锂电池用的都是5v直流电,因此,我们给手机充电时,就需要使用电源适配器来进行转换,使用代码还原这个生活场景
在com包下,创建shipei包,然后创建AC220类,表示220v交流电:
package com.shipei;public class AC220 {public int outputAC220V() {int output = 220;System.out.println("输出交流电" + output + "V");return output;}
}
创建DC5接口,表示5V直流电:
package com.shipei;public interface DC5 {int outputDC5V();
}
创建电源适配器类 PowerAdapter:
package com.shipei;public class PowerAdapter implements DC5 {private AC220 ac220;public PowerAdapter(AC220 ac220) {this.ac220 = ac220;}public int outputDC5V() {int adapterInput = ac220.outputAC220V();// 变压器...int adapterOutput = adapterInput / 44;System.out.println("使用 PowerAdapter 输⼊AC:" + adapterInput + "V 输出DC:" + adapterOutput + "V");return adapterOutput;}
}
创建测试类Test:
package com.shipei;public class Test {public static void main(String[] args) {DC5 dc5 = new PowerAdapter(new AC220());dc5.outputDC5V();}
}
很显然,适配器使得原来的操作可以适配,即实现了二者兼容,在前面说明源码时,这个处理:if (adapter.supports(handler)) {,其实就是判断是否支持兼容,支持就使用对应的适配器,上面的代码可以实现某种判断,可以多一个方法,就可以认为是兼容的
比如:
package com.shipei;public class PowerAdapter implements DC5 {private AC220 ac220;public PowerAdapter(AC220 ac220) {this.ac220 = ac220;}public PowerAdapter() {}public boolean is(Object o) {//isAssignableFrom() 方法用于判断一个类 Class 对象是否可以被赋值给另一个类 Class 对象//如果参数的类型是当前this的父类,那么调用方的确可以进行赋值给他,也就是说,调用方是否是你的子类或者你的类型return AC220.class.isAssignableFrom(o.getClass());}public int outputDC5V() {int adapterInput = ac220.outputAC220V();// 变压器...int adapterOutput = adapterInput / 44;System.out.println("使用 PowerAdapter 输⼊AC:" + adapterInput + "V 输出DC:" + adapterOutput + "V");return adapterOutput;}
}
修改测试类:
package com.shipei;public class Test {public static void main(String[] args) {PowerAdapter powerAdapter = new PowerAdapter();AC220 ac220 = new AC220();boolean b = powerAdapter.is(ac220);//可以适配对应的类型,相当于if (adapter.supports(handler)) {if (b == true) {DC5 dc5 = new PowerAdapter(ac220);dc5.outputDC5V();}}
}
这样就适配成功了,设计模式也说明完成
我们现在操作一个注解,先将前面的手写的部分拿过来(可以全局搜索"手写MVC框架之注解开发"),操作好后,我们创建一个注解:
package com.mvc.framework.annotations;import java.lang.annotation.*;@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Security {String[] value() default {};
}
我们最后操作这个注解来完成一些事来结束这篇博客,完成的事情有:
该注解用于添加在Controller类或者Handler方法(或者对应的方法)上,表明哪些用户拥有访问该Handler方法的权限(注解配置用户名)
/*
比如:@Security("zhansan")
*/
当请求url中,如存在username=zhansan时,就可以进行访问,否则不行
那么怎么处理,首先我们可以找到对RequestMapping注解的处理的地方,因为该注解放的位置与该Security基本一样,他是匹配路径的,那么我们同样的可以匹配权限,那么我们可以修改这个地方(给该类加上对应的变量):
public class Handler {//..//定义权限数组,来认为允许有谁可以访问private String[] Security;public String[] getSecurity() {return Security;}public void setSecurity(String[] security) {Security = security;}//..
}
然后回到这里:
 if (aClass.isAnnotationPresent(RequestMapping.class)) {//拿取对应的值String value = aClass.getAnnotation(RequestMapping.class).value();//判断是否存在,不存在加上,否则不做处理if ("/".equals(value.substring(0, 1)) == false) {//默认加上/value = "/" + value;}//按照对应已经写好的DemoController,那么这里的value相当于拿到了@RequestMapping("/demo")中的"/demo"这个字符串值baseUrl += value; //拿取前缀}//在这个后面的地方加上如下的代码://如果存在权限(在类上)//那么定义全局权限String[] SecurityAll = new String[0]; //防止null出现的问题if(aClass.isAnnotationPresent(Security.class)){//拿取对应的值SecurityAll = aClass.getAnnotation(Security.class).value();}
然后再在这里进行补充:
//开始封装路径和方法信息,参数Pattern.compile(url),直接保存对应的正则表达式赋值后的对象Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));//在这个后面加上如下的代码://定义临时权限String[] Secu = SecurityAll;if(method.isAnnotationPresent(Security.class)){//拿取在方法上的对应权限String[] Seculin = method.getAnnotation(Security.class).value();//然后补充到定义的临时权限中,如果有相同,那么跳过// 使用 LinkedHashSet 来保持插入顺序并去除重复项Set<String> mergedSet = new LinkedHashSet<>(Arrays.asList(Secu));mergedSet.addAll(Arrays.asList(Seculin));// 将 Set 转换回数组(new String[0]是指定类型的)Secu = mergedSet.toArray(new String[0]);}//加上权限handler.setSecurity(Secu);
然后再这个地方进行判断:
 String requestURI = req.getRequestURI();String contextPath = req.getContextPath();String substring = requestURI.substring(contextPath.length(), requestURI.length());//这里加上
Map<String, String[]> parameterMap = req.getParameterMap(); //一个键是可以对应多个值的Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();//然后到如下:Matcher matcher = handler.getPattern().matcher(substring);//如果不匹配,那么找下一个是否匹配,否则直接返回匹配的信息if (!matcher.matches()) {continue;}//再这个后面加上代码://判断权限//先拿取权限String[] security = handler.getSecurity();//定义是否访问boolean contains = false;//遍历所以从请求中拿取的参数for (Map.Entry<String, String[]> param : entries) {if ("username".equals(param.getKey())){//只需要第一个即可:param.getValue()[0],因为我们当前肯定只有一个用户的(这里不能传递数组对象,因为是对象(也刚好我们只需要一个))contains = Arrays.asList(security).contains(param.getValue()[0]);}}if (contains) {//修改一下哦return handler;}
然后我们再DemoController类上加上@Security(“zhangsan”)注解,访问:http://localhost:8080/demo/query?name=1234&username=zhangsan,看看效果,如果可以访问,将username=zhangsan修改成username=zhang,如果访问失败,返回404,说明操作成功,可以选择在方法上加上这个注解,或者都加上来进行测试,如果都成功,那么我们操作完毕
但是呢,返回的错误信息并不好,所以我们还需要进行修改(加上如下):
public class Handler {//..public Handler() {}//定义错误信息private String error;public String getError() {return error;}public void setError(String error) {this.error = error;}//..}
然后对getHandler方法全部改变一下:
package com.mvc.framework.servlet;import com.mvc.framework.annotations.*;
import com.mvc.framework.config.ParameterNameExtractor;
import com.mvc.framework.pojo.Handler;
import com.mvc.framework.util.UtilGetClassInterfaces;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;//唯一的一个servlet
//@WebServlet(name = "1", urlPatterns = "/a")
public class DispatcherServlet extends HttpServlet {//这里也可以选择不加静态,具体看你自己//存放每个类对应的全限定名(这里其实可以不用静态的,因为他只是在这里处理而已,而几乎不会给其他对象或者说类来使用),最后统一操作他来创建对象,如果是同一个容器,那么这个应该不是同一个,他只是记录当前扫描的,也就是说,对应的容器应该是更加上层的,当然//这里是以mvc为住,所以对应的容器,或者说map就放在这里了private static List<String> classNames = new ArrayList<>();//需要一个bean的存放(ioc容器)private static Map<String, Object> map = new HashMap<>();//缓存已经进行过依赖注入的信息private static List<String> fieldsAlreayProcessed = new ArrayList<>();//url和method的映射关系private List<Handler> handlerMapping = new ArrayList<>();@Overridepublic void init(ServletConfig config) {//加载配置文件//手写获取对应的指定的值(如果说mvc中这个名称不可改变,那么说明这里是写死的)//这个是读取web.xml的,虽然web.xml是固定的,但是里面的数据是mvc进行处理的,比如这里//因为我们需要时刻记住,mvc是建立在servlet之上的,而不是单独的框架,所以需要servlet,否则mvc是自然处理不了的String contextConfigLocation = config.getInitParameter("contextConfigLocation");//得到之后,需要根据这个来确定路径,使得加载配置文件String s = doLoadconfig(contextConfigLocation);//扫描相关的类doScan(s);//初始化bean对象(ioc容器),也就是根据扫描得到的全限定名创建对象并保存doInstance();//实现依赖注入//维护和处理(操作)依赖注入关系doAutoWired();//上面三个在上一章博客中,或者说108章博客中就有操作,当然,这里可以选择操作一样的,也可以不一样,只需要实现即可(虽然基本存在多种方式,但是一般是108章那一种,还有spring的那一种(三级缓存))//构造一个HandlerMapping处理器映射器,来完成映射关系initHandlerMapping(config);//当然,在上一章博客时,对应的map是一个全局的,所以都是同一个容器的(也说明了是同一个,只是顺序问题而已)System.out.println("初始化完成...,等待请求与映射匹配了");//映射关系创建好,那么根据请求查看映射,来决定调用谁}//方法的位置,也建议从上到下,这里却反过来了,当然,这只是模拟mvc框架的操作,并不会有很大影响,以后注意即可//构造一个HandlerMapping处理器映射器private void initHandlerMapping(ServletConfig config) {//这里基本上是比spring的依赖操作更加的有难度(在前一章博客中,我们几乎学习过spring的源码,所以后面的依赖操作我们几乎可以明白)//但是这里还是第一次,所以需要多次的理解(虽然对只看前一章来说也是第一次,但是对比这里的第一次,难度还是较小的,这里的难道比较大)//因为依赖的处理主要是死循环的解决,在这里的,我们判断当前类全限定名,加上变量的总名称,判断是否赋值过即可,赋值过,那么不赋值,直接退出,然后根据递归,自然都会处理完毕//递归在中途也会顺便的解决过程中的对象,这样也会使得后续更加的方便(当然,那基本也是类似三级缓存的处理,都是找到对应的类型,赋值后结束)//其中spring在三级缓存拿取结束,而这里也是在对应的map拿取结束,只是spring的是当时创建然后赋值后删除的,所以并不需要考虑是否可以赋值的问题(赋值就结束了,也不会到里面去的,因为当时创建,他们必然只需要赋值即可),而是直接的结束//而spring没有,所以需要考虑是否可以赋值,即判断名称来结束//上面的看看就行,这是说明为什么spring是较小难道的原因,因为只是一个循环依赖的问题的处理(其实也正是他,所以是较小难度,否则是没有难度的)//现在我们开始操作//最关键的环节//将url和method建立关联//这里首先需要大致吃透前面的mvc的知识的细节,并且我们也需要观看27章博客的最后一部分,这样才基本可以明白,甚至更加的可以看懂//当然了,我也会写上注释的,让你更加的看得懂//首先我们需要思路,也就是我们现在已经有了对于的对象实例了,我们自然可以通过该实例得到里面的方法上面的相应注解//然后根据当前类实例的上面的注解进行拼接路径,得到需要的url的路径拦截,然后我们可以创建map保存拦截(路径)和方法的对应的映射关系//这样在真正操作请求时,判断请求路径与路径拦截,而进行执行哪个方法,具体思路是这样的,但是实现需要看后面的处理//当然,这些是初始化操作,所以前面说明组件时,第一步是调用处理器映射器,而不是创建(因为已经初始化了)if (map.isEmpty()) { //如果map为空,自然直接的退出return;}for (Map.Entry<String, Object> entry : map.entrySet()) {//获取这个实例的ClassClass<?> aClass = entry.getValue().getClass();//如果包含这个注解,那么操作他,否则不会(只有这个注解才操作路径),当然,我们一般建议先操作不存在时直接的结束当前循环//这样其实更加的好维护,防止后续的代码执行不对(这是一个写法),当然,只要合理都可,只是建议而已//所以我们以后需要这样的规范,即所有需要结束的判断写在前面,正确的处理写在后面,尽量将可以操作结束的判断(比如这里的不存在)提取出来写在前面//因为这样就可以很明显的,知道一个方法里面需要满足什么条件,而不是需要去在后面翻找(正常情况下,对应后面代码也是较好处理的,所以不考虑代码无效定义的说明,只考虑找到的问题),这里由于比较少的代码,所以我们并不是特别在意//但是在以后开发大代码时,建议这样哦(在任何的作用域都希望如此)if (aClass.isAnnotationPresent(Controller.class)) {String baseUrl = "";//看看有没有对应的路径的注解if (aClass.isAnnotationPresent(RequestMapping.class)) {//拿取对应的值String value = aClass.getAnnotation(RequestMapping.class).value();//判断是否存在,不存在加上,否则不做处理if ("/".equals(value.substring(0, 1)) == false) {//默认加上/value = "/" + value;}//按照对应已经写好的DemoController,那么这里的value相当于拿到了@RequestMapping("/demo")中的"/demo"这个字符串值baseUrl += value; //拿取前缀}//如果存在权限(在类上)//那么定义全局权限String[] SecurityAll = new String[0];if (aClass.isAnnotationPresent(Security.class)) {//拿取对应的值SecurityAll = aClass.getAnnotation(Security.class).value();}//当然,如果上面的没有拿取前缀,自然还是""//现在我们拿取方法//输入itar,一般会出现下面的,相当于快捷键吧(具体可能是自定义的,且根据的上面的第一个数组来处理的)Method[] methods = aClass.getMethods();for (int j = 0; j < methods.length; j++) {Method method = methods[j];//大多数的Class操作的,基本都可以操作isAnnotationPresent以及getAnnotationif (method.isAnnotationPresent(RequestMapping.class)) {RequestMapping annotation = method.getAnnotation(RequestMapping.class);String value = annotation.value(); //按照之前写好的,这里相当于拿到了"/query"//同样的也会操作默认加上/if ("/".equals(value.substring(0, 1)) == false) {//默认加上/value = "/" + value;}//定义临时url,因为前面的baseUrl可能存在值,而他需要为所有的方法进行拼接的,所以不能给他进行赋值String url = baseUrl;url += value;//开始封装路径和方法信息,参数Pattern.compile(url),直接保存对应的正则表达式赋值后的对象Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));//定义临时权限String[] Secu = SecurityAll;if (method.isAnnotationPresent(Security.class)) {//拿取在方法上的对应权限String[] Seculin = method.getAnnotation(Security.class).value();//然后补充到定义的临时权限中,如果有相同,那么跳过// 使用 LinkedHashSet 来保持插入顺序并去除重复项Set<String> mergedSet = new LinkedHashSet<>(Arrays.asList(Secu));mergedSet.addAll(Arrays.asList(Seculin));// 将 Set 转换回数组(new String[0]是指定类型的)Secu = mergedSet.toArray(new String[0]);}//加上权限handler.setSecurity(Secu);//处理参数位置信息//比如:query(HttpServletRequest request, HttpServletResponse response, String name)//获取该方法的参数列表信息,自然包括名称或者类型或者位置(从左到右,起始下标为0)Map<String, String> prmap = null;try {String ba = aClass.getName().replace('.', '/') + ".class";//对应得到的基本都是String,所以上面的可以这样处理(Map<String,String>)prmap = ParameterNameExtractor.getParameterNames(config.getServletContext().getRealPath("/") + "WEB-INF\\classes\\" + ba, method.getName());} catch (Exception e) {e.printStackTrace();}Parameter[] parameters = method.getParameters();for (int i = 0; i < parameters.length; i++) {Parameter parameter = parameters[i];//parameter.getType()得到参数类型if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {//如果是这两个,建议名称就是他们,这样就能保证赋值是对应的,而不会被其他参数名称所影响,具体保存名称干什么在后面就会知道的handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);} else {//其他的类型,保存其名称,但是这里是使用prmap,这个循环是确认位置的以及之所以使用Parameter,也是为了得到一下类型或者其他的,比如parameter.getType().getSimpleName()//他们的参数都是从0开始,所以可以这样做,且对应handler.getParamIndexMapping().put(prmap.get(i), i);}}//保存映射关系handlerMapping.add(handler);//你会发现并能没有难度,难在哪里,实际上难在后面最终到的doPost请求方法的处理//我们到后面去看看吧}}}}}//实现依赖注入private void doAutoWired() {if (map.isEmpty()) { //如果map为空,自然直接的退出return;}// 遍历map中所有对象,查看对象中的字段,是否有@Autowired注解,如果有需要操作依赖注入关系for (Map.Entry<String, Object> entry : map.entrySet()) {try {//将对象作为参数传递doObjectDependancy(entry.getValue());} catch (Exception e) {e.printStackTrace();}}}//开始操作依赖注入关系,传递实例对象private static void doObjectDependancy(Object object) {//Field[] getDeclaredFields(),用于获取此Class对象所表示类中所有成员变量信息Field[] declaredFields = object.getClass().getDeclaredFields();//没有成员,那么退出if (declaredFields == null || declaredFields.length == 0) {return;}for (int i = 0; i < declaredFields.length; i++) {//拿取第一个成员变量的信息,如成员变量是public int i = 0;,那么得到public int com.she.factory.bb.i//其中值不会操作,在结构中,我们并不能操作其值,最多只能通过这个结构去设置创建的对象的值Field declaredField = declaredFields[i];//判断是否存在对应的注解,如果不存在,那么结束当前循环,而不是结束循环,看看下一个成员变量if (!declaredField.isAnnotationPresent(Autowired.class)) {continue;}//判断当前字段是否处理过,如果已经处理过则continue,避免嵌套处理死循环,这里我们的实现是与spring是不同的//在spring中,我们创建一个类的实例时,会顺便在注入时判断对方是否存在而创建对方的实例(是否可以创建实例,而不是直接创建,我们基本都处理了判断的(如配置或者注解)),从而考虑循环依赖//但是这里我们首先统一创建实例了,然后在得到对应的实例后,继续看看该实例里面是否也存在对应的注解,以此类推,直到都进行设置//但是如果存在对应的实例中,操作时是自身或者已经操作的实例(这个自身代表的是自己,而不是其他类的自己类的类型),那么就会出现死循环(如果我中有你,你中有我,且你已经操作的我不退出,这不是死循环是什么),所以就会直接的退出(退出当前循环,即continue;)//这里与Spring是极为不同的,Spring是报错,而这里是不会赋值,即退出,使得不会出现死循环(虽然我们也可以使得报错),即也就不会报错了,所以这里不会出现循环依赖的问题,因为我们对应的类就已经创建好了,就与你的三级缓存一样,虽然三级缓存是在需要的时候也保存了对应的实例,使得赋值,只是我首先统一操作的,那么我这里的缓存与三级缓存的本质还是一样的,都是保存实例,只是这里是保存id,因为实例都是创建好的,就不需要三级缓存将实例移动了//但是由于我们是统一处理的,而不是与spring一样,所以在一定程度上需要更多的空间,如果项目非常的大,那么这可能是一个不好的情况,要不然为什么spring是使用时创建呢,而由于这里是测试,所以我们使用这种方式,就不用考虑三级缓存的处理了//boolean contains(Object o),判断是否包含指定对象//判断当前类的全限定名加上该变量名称组成的字符串是否存在//如果存在,说明已经注入了if (fieldsAlreayProcessed.contains(object.getClass().getName() + "." + declaredField.getName())) {continue;}//这里也会操作技巧,一般情况下,我们都会判断是否可行才会操作真正的代码,而不是先操作真正的代码然后处理是否可行(所以上面的结束循环先处理)//当然我们基本都会意识到这样的问题的,这里只是提醒一下Object dependObject = null;Autowired annotation = declaredField.getAnnotation(Autowired.class);String value = annotation.value();if ("".equals(value.trim())) { //清空两边空格,防止你加上空格来混淆//先按照声明的是接口去获取,如果获取不到再按照首字母小写//拿取对应变量类型的全限定名,若是基本类型,那么就是本身,如int就是int//然而int基本操作的全限定名中,不可能作为类使用,所以在int中处理该注解是没有意义的,在spring中基本也是如此,除非是以后的版本可能会有dependObject = map.get(declaredField.getType().getName());//如果没有获取,那么根据当前变量类型的首字母小写去获取(上面是默认处理接口的)//然而,大多数按照规范的话,基本都有接口,但是是按照规范,防止没有规范的,所以使用下面的处理if (dependObject == null) {//getType是得到了Field的ClassdependObject = map.get(lowerFirst(declaredField.getType().getSimpleName()));}} else {//如果是指定了名称,那么我们选择操作拼接全限定名来处理dependObject = map.get(value + declaredField.getType().getName());}//正好,如果不匹配的话,为null,那么防止递归出现问题,所以操作跳过//在spring中是操作报错的,而导致不能运行程序,这里我们跳过,顺便设置null吧(虽然在成员变量中,默认的对象基本都是null)//上面正好对应之前操作的接口和类的操作,当然,在Spring中,是查询全部//来找到对应类型的实例(getBean的),这里我们是自定义的,自然不会相同//一般来说Class的getName是全限定名,而其他的就是对应的类名称//而Class的getSimpleName则是类名称,其他的并没有getSimpleName方法// 记录下给哪个对象的哪个属性设置过,避免死循环(递归的死循环)fieldsAlreayProcessed.add(object.getClass().getName() + "." + declaredField.getName());//递归if (dependObject != null) {doObjectDependancy(dependObject);}//全部设置好后,我们进行设置//设置可以访问private变量的变量值,在jdk8之前可能不用设置//但是之后(包括jdk8)不能直接的访问私有属性了(可能随着时间的推移,也会改变),因为需要进行设置这个,所以不能直接访问私有属性了declaredField.setAccessible(true);//给对应的对象的该成员变量设置这个值try {declaredField.set(object, dependObject);} catch (Exception e) {e.printStackTrace();}}}//初始化类private void doInstance() {//如果,没有全限定名,说明对应的指定扫描的地方,不存在任何的文件处理if (classNames.size() == 0) return;if (classNames.size() <= 0) return; //其实,就算一个程序中,其可能不存在负数,但是加个意外的条件还是比较好的,所以上面的代码可以注释掉,虽然他也没有错(因为也基本不会出现负数)try {for (int i = 0; i < classNames.size(); i++) {//拿取对应的全限定名(称)String className = classNames.get(i);// 通过对应的全限定名称,拿取其Class对象Class<?> aClass = Class.forName(className);//接下来判断Controller和Service的注解的区别,一般情况下,mvc的这里通常只会判断Controller以及依赖注入的情况,而不会处理Service//其实这也是顺序出现的底层原因,但是由于这里是统一处理的,所以这里的ioc容器,是mvc和spring共有的,这里需要注意一下//判断对应的类上是否存在对应的注解if (aClass.isAnnotationPresent(Controller.class)) {//既然存在对应的注解,那么进入//Controller一般并不操作value,所以不考虑//获取类名称String simpleName = aClass.getSimpleName();String s = lowerFirst(simpleName);//创建实例,实际上这个实例由于是根据对应的Class(他也有具体的类来得到或者全限定名,所以创建的实例相当于我们在main中操作创建(实际上在main创建也是需要导入或者当前目录下完成))//所以就是合理的Object o = aClass.newInstance();map.put(s, o);}//这里就有一个想法,好像使用if-else也是可行的,的确,他就是可行的,但是这样的好处是,在中间可以继续处理,并且也可以自定义结束方案//而不是利用if-else中最后的处理才进行,如果比灵活性,那么多个if就是好的,如果比稳定,那么if-else是好的,但是在代码逻辑非常正确的情况下,那么多个if就是好的//也就是多个if上限高,下限低,当然,多个if由于下限低,所以在某些情况下,可能并不好处理,比如存在两个判断,但是后面都需要他们的数据,而两个if一般基本只能重复了(比如这里的依赖注入的方法(即指定名称并不友好这里))if (aClass.isAnnotationPresent(Service.class)) {String beanName = aClass.getAnnotation(Service.class).value();//创建实例Object o = aClass.newInstance();int ju = 0;if ("".equals(beanName.trim())) {//如进入这里,那么说明对应注解我们没有进行设置value,那么默认是""(我们注解设置的默认的),操作首字母小写//Class的getSimpleName方法是获取类名称beanName = lowerFirst(aClass.getSimpleName());} else {ju = 1;}//放入map,因为id(beanName)有了,对应的实例也有了,自然放入//id作为类名称首字母小写或者指定的类名称id//然而指定名称并不友好,因为可能存在多个不同的类是相同的名称,所以这个名称需要与全限定名进行拼接(也就是上面的beanName += aClass.getName();,这里操作两个if并不友好,因为需要前面的数据),但是这个全限定名可能也是对应的接口而不是类(到这里你是否理解了,为什么spring在如果容器中存在多个相同的实例时,会报错了吧,因为判断这个的话,非常麻烦,所以spring只能存在一个实例(当然,也可以存在多个,只是他需要对应一些信息,比如也操作这样的名称,或者变量名称等等),且他是利用遍历来赋值的,这样就非常简单了,当然,如果你考虑了所有情况,那么在获取时,自然比spring快,只是存在没有必要的空间,所以互有好处,spring,节省空间,但是获取时需要性能,而这里需要空间,但是获取时性能更快)if (ju == 1) {UtilGetClassInterfaces.getkeyClass(beanName, aClass, map, o);} else {map.put(beanName, o);}//当然,你也可以这样,由于我们的类通常有接口(在controller中通常没有接口,所以不考虑他),所以在一定程度上是可以给接口id的,虽然spring并没有这样的操作,但并不意味着我们不能//操作如下://Class<?>[] getInterfaces(),获取实现的所有接口Class<?>[] interfaces = aClass.getInterfaces();if (interfaces != null && interfaces.length > 0) {for (int j = 0; j < interfaces.length; j++) {//如果你实现的是java.io.Serializable接口,那么打印这个anInterface时,结果是:interface java.io.Serializable//如果接口是java.io.Serializable接口,那么对应的getName就是java.io.SerializableClass<?> anInterface = interfaces[j];// 以接口的全限定类名作为id放入(如果想要名称,那么可以通过其他api,具体可以百度,一般getSimpleName好像可以),这里我们继续创建一个吧,在map中我们通常是不会指向同一个的map.put(anInterface.getName(), aClass.newInstance());//为什么全限定名是对应的整个路径呢,解释如下://"全限定名"这个术语的名称来自于它的作用:它提供了完整的、唯一的类名标识,以避免命名冲突,"全"表示完整性,"限定"表示唯一性,因此全限定名是一个完整且唯一的标识//也的确,对应的全限定名,比如com.mvc.framework.servlet.DispatcherServlet,在项目中也的确是完整且唯一的,要不然也不会作为生成Class对象的参数}}}}} catch (Exception e) {e.printStackTrace();}}//将str首字母进行小写private static String lowerFirst(String str) {char[] chars = str.toCharArray();if ('A' <= chars[0] && chars[0] <= 'Z') {chars[0] += 32; //在ASCII中a是97,A是65,相差32}return String.valueOf(chars); //将字符变成String}//扫描类,或者扫描到注解,参数是在那里扫描private void doScan(String scanPackage) {try {//获取对应包所在的绝对路径String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");scanPackagePath = URLDecoder.decode(scanPackagePath, StandardCharsets.UTF_8.toString());File pack = new File(scanPackagePath); //参数可以是以"/"开头的File[] files = pack.listFiles();for (File file : files) {//如果是一个目录,那么为了保证是下一个目录,所以我们以当前路径加上这个目录名称if (file.isDirectory()) {//这样可以使得继续处理,知道找到里面的文件doScan(scanPackage + "." + file.getName());//对应的目录处理完了,那么应该要下一个文件或者目录了continue;}//找到是一个文件,且是class,那么进行处理if (file.getName().endsWith(".class")) {//当前的包名,加上文件名,就是全限定名String className = scanPackage + "." + file.getName().replaceAll(".class", "");classNames.add(className); //保存好}}} catch (Exception e) {e.printStackTrace();}}//加载配置文件,得到信息,这里比较简单,只需要得到地址即可private String doLoadconfig(String contextConfigLocation) {//加载xmlInputStream resourceAsStream = DispatcherServlet.class.getClassLoader().getResourceAsStream(contextConfigLocation);//使用读取xml的依赖来进行处理//获取XML解析对象SAXReader saxReader = new SAXReader();try {//解析XML,获取文档对象documentDocument document = saxReader.read(resourceAsStream);//getRootElement():获得根元素Element rootElement = document.getRootElement();//直接找到这个标签,得到他的信息对象,但是其父类只能操作标签自身信息,所以这里需要强转Element element = (Element) rootElement.selectSingleNode("//component-scan");//获得对应属性的值String attribute = element.attributeValue("base-package");return attribute;} catch (Exception e) {e.printStackTrace();}return "";}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Handler handler = getHandler(req);if (handler == null) {resp.getWriter().write("404 not found");return;}//参数绑定//先获取所有的参数类型,因为对应的map只能保存其固定的两个值//如名称,和位置,所以我们这里给出类型吧,就不额外创建类了Class<?>[] parameterTypes = handler.getMethod().getParameterTypes(); //之前操作名称的下标就是为了这里,也顺便为了map本身,所以大多数情况下,如果需要保存位置,建议从0开始//因为大多数的集合或者数组操作都是从0开始的(除非是手写的,当然系统提供的基本都是从0开始,以后不确定),从而进行对应//创建一个数组,用来保存参数Object[] objects = new Object[parameterTypes.length];//保存已经操作的位置int[] ii = new int[parameterTypes.length];//给数组参数值,并操作顺序,先拿取参数集合(这里我们只是模拟,就不考虑文件的处理了,所以这里一般我们认为考虑键值对的参数传递)Map<String, String[]> parameterMap = req.getParameterMap(); //一个键是可以对应多个值的Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();//遍历所以从请求中拿取的参数for (Map.Entry<String, String[]> param : entries) {String value = "";//如果是多个的话,那么合并吧,比如name=1&name=2,那么这里的结果就是1,2(这里考虑在mvc中如果存在多个相同的,那么会处理合并)for (int i = 0; i < param.getValue().length; i++) {if (i >= param.getValue().length - 1) {value = param.getValue()[i];continue;}value = param.getValue()[i] + ",";}//上面的就考虑了value处理合并,当然,如果没有,自然不会处理,因为value = param.getValue()[i];//现在我们拿取了传递的参数,我们考虑将参数进行存放//但是考虑到类型是否正确,以及名称是否对应,所以需要判断存放在objects中//这两个考虑我们都有存放信息(也就是位置,名称,类型)//这里就要利用到我们之前保存的handler.getParamIndexMapping()了//getKey是键值对的键,自然对应与参数列表中的名称,所以这里的判断即可//当然,有些是不看名称的,但是我们只用名称定义了位置,所以每次设置完值,建议用数组保存对应的已经操作的位置,从而来判断其他位置(因为类型在外面定义)if (!handler.getParamIndexMapping().containsKey(param.getKey())) {//如果不包含,那么结束当前循环,那么考虑其他的参数了,那么对应的数组中的值自然就是null了//在67章博客中,我们说明了类型的变化,其实就在这里,可以思考一下就行continue;}//拿取位置,前提是对应的名称对应才能拿取Integer integer = handler.getParamIndexMapping().get(param.getKey());//如果对应了,那么看看对应的类型是否可行,并且由于我们保存了位置(且大多数获取列表的操作都是从0开始),且下标为0,那么正好对应与类型//进行赋值,在赋值之前需要考虑一个问题,我们需要看看他的类型是什么,来进行其他的处理if ("String".equals(parameterTypes[integer].getSimpleName())) {objects[integer] = value;}//默认情况下,前端传给后端的键值对基本都是字符串if ("Integer".equals(parameterTypes[integer].getSimpleName()) || "int".equals(parameterTypes[integer].getSimpleName())) {//在mvc中Integer在考虑多个时,只会拿取第一个,而int与Integer可以互相转换,所以也可以是同一个代码value = value.split(",")[0];Integer i = null;try {i = Integer.parseInt(value);} catch (Exception e) {e.printStackTrace();throw new RuntimeException("String转换Integet报错,参数名称是:" + param.getKey());}objects[integer] = i;}//其他的类型我们就不判断了,因为只是模拟而已//保存位置ii[integer] = 1;}//当然,有些是不看名称的,但是我们只用名称定义了位置,所以我们需要所以我们需要考虑如下://用来先考虑默认赋值的,而不是先处理请求参数,默认赋值的,一般不会考虑名称是否一致(看前面的规定的名称就知道了,他一般只是保证不会影响其他名称,自然的,我们也不可能直接写出,我们只需要判断类型即可)//这里我们就需要前面的保存的位置来排除了//当然,由于有些名称是固定的,所以并不需要对应的数组,直接给出得到位置即可,否则的话,需要循环找位置Integer integer = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());objects[integer] = req;ii[integer] = 1;integer = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());objects[integer] = resp;ii[integer] = 1;//解决其他的默认赋值的,并且没有保存名称的(也就是没有保存对应的位置的)//当然,也可以不这样做,只是有些默认的赋值null会报错,这也是mvc的处理方式,那么删除这个for循环和不删除就算处理与不处理的区别了,后面可以选择测试一下for (int i = 0; i < ii.length; i++) {if (ii[i] == 0) {if ("int".equals(parameterTypes[i].getSimpleName())) {//用来解决int在mvc中会报错的情况objects[i] = 0;}//后面还可以定义double等等其他的默认值,反正可以在这里处理默认值(赋值)为null会报错的情况}}//调用对应的方法,invoke后面的参数是可变长参数,所以可以操作数组//如果没有赋值,那么对应的下标的值,自然是null,自然也就操作了默认的赋值,但是如果对应的是int,那么自然会报错,这在mvc中是如此//但是这里我们处理了,即:/*if("int".equals(parameterTypes[i].getSimpleName())){//用来解决int在mvc中会报错的情况objects[i] = 0;}*/try {handler.getMethod().invoke(handler.getController(), objects); //一般这里由处理器适配器来完成的,得到的结果通常用于视图解析器,这里我们没有写这个,所以忽略了,这里只是调用一下,所以称为处理器适配器也行吧} catch (Exception e) {e.printStackTrace();}//至此,我们总算执行了对应的方法了,当然,在mvc中,是存在非常多的自带的组件,这里我们只是模拟一部分而已//当然,这里的两个请求HttpServletRequest req, HttpServletResponse resp是在这里得到的,所以对应的mvc得到的也是这个//我们也在前面说明了这个得到了,如果没有看这里即可}private Handler getHandler(HttpServletRequest req) {if (handlerMapping.isEmpty()) {return null;}String requestURI = req.getRequestURI();String contextPath = req.getContextPath();String substring = requestURI.substring(contextPath.length(), requestURI.length());Map<String, String[]> parameterMap = req.getParameterMap(); //一个键是可以对应多个值的Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();//定义路径是否可以访问boolean ur = false;//定义权限是否可以访问boolean contains = false;//访问人String pe = "";for (Handler handler : handlerMapping) {//在这里你可能会有以为,好像我们保存url也行,为什么非要操作Pattern呢//这是因为正则存在更多的操作,而不只是路径,所以加上可以进行更好的扩展//具体扩展可以考虑到在RequestMapping注解中加上正则表达式//从而匹配请求路径Matcher matcher = handler.getPattern().matcher(substring);//如果不匹配,那么找下一个是否匹配,否则直接返回匹配的信息if (!matcher.matches()) {continue;}ur = true;//判断权限//先拿取权限String[] security = handler.getSecurity();//遍历所以从请求中拿取的参数for (Map.Entry<String, String[]> param : entries) {if ("username".equals(param.getKey())) {pe = param.getValue()[0];//只需要第一个即可:param.getValue()[0],因为我们当前肯定只有一个用户的(这里不能传递数组对象,因为是对象(也刚好我们只需要一个))contains = Arrays.asList(security).contains(pe);}}if (contains) {return handler;}}Handler handler = new Handler();if (!ur) {handler.setError("路径错误");return handler;}if (!contains) {handler.setError(pe + "没有访问权限");return handler;}//如果没有,自然返回nullreturn null;}
}
然后将这个部分进行修改:
Handler handler = getHandler(req);if (handler == null) {resp.getWriter().write("404 not found");return;}//变成如下:Handler handler = getHandler(req);if (handler.getError() != null) {resp.setContentType("text/html;charset=UTF-8");resp.getWriter().write(handler.getError());return;}
继续测试,如果都成功,那么这篇博客到此结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/27184.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【后端开发】服务开发场景之高可用(冗余设计,服务限流,降级熔断,超时重试,性能测试)

【后端开发】服务开发场景之高可用&#xff08;冗余设计&#xff0c;服务限流&#xff0c;降级熔断&#xff0c;超时重试&#xff0c;性能测试&#xff09; 文章目录 序&#xff1a;如何设计一个高可用的系统&#xff1f;可用性的判断指标是什么&#xff1f;哪些情况会导致系统…

陪诊小程序开发,陪诊师在线接单

近几年&#xff0c;陪诊师成为了一个新兴行业&#xff0c;在科技时代中&#xff0c;陪诊小程序作为互联网下的产物&#xff0c;为陪诊市场带来了更多的便利。 当下生活压力大&#xff0c;老龄化逐渐严重&#xff0c;年轻人很难做到陪同家属看病。此外&#xff0c;就诊中出现了…

!力扣46. 全排列

给定一个不含重复数字的数组 nums &#xff0c;返回其所有可能的全排列 。你可以按任意顺序返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例 2&#xff1a; 输入&#xff1a;nu…

摄影构图:如何处理对焦、快门、光圈、ISO 以及拍摄方式

写在前面 博文内容涉及摄影对焦模式、快门速度、光圈、ISO以及拍摄方式的简单介绍《高品质摄影全流程解析》 读书笔记整理理解不足小伙伴帮忙指正 &#x1f603; 生活加油 99%的焦虑都来自于虚度时间和没有好好做事&#xff0c;所以唯一的解决办法就是行动起来&#xff0c;认真…

【VS】尚未配置为Web项目XXXX指定的本地IIS URL HTTP://localhost

报错原因&#xff1a; 我们在Web项目的属性配置中勾选了“使用本地IIS Web服务器”&#xff1b; 本来嘛&#xff0c;这也没啥&#xff0c;问题是当我们的电脑IP改变时&#xff0c;将会导致程序找不到原来的IP地址了&#xff0c;那么当然会报错啦。 解决办法&#xff1a; 其实…

Windows中LoadLibrary加载动态库失败,详细解释(解决思路)

今天在开发的过程中&#xff0c;需要用到动态库里的一些接口&#xff0c;又不希望全部载入&#xff0c;在这过程中使用LoadLibrary加载dll时&#xff0c;出现问题&#xff0c;特此记录一下自己怎么解决的思路。 目录 先介绍一下这几个函数为以下错误分析做准备 GetProcAddres…

数据结构错题答案汇总

王道学习 第一章 绪论 1.1 3.A 数据的逻辑结构是从面向实际问题的角度出发的&#xff0c;只采用抽象表达方式&#xff0c;独立于存储结构&#xff0c;数据的存储方式有多种不同的选择;而数据的存储结构是逻辑结构在计算机上的映射&#xff0c;它不能独立于逻辑结构而存在。数…

Mac | 崩溃分析

一、dump分析 1. 导入符号&#xff1a; ./import_pdb.sh libmedia_stream_ext.dylib.dSYM ./import_pdb.sh libowcr.framework.dSYM 2. 分析dump&#xff1a; ./analyze_dump.sh AE59D64F-0E1D-4A18-8DAF-C2C4D22F9FA6.dmp 3. 第 2 步骤 中会输出崩溃模块、崩溃线程及堆栈…

区间分割求解方程

本文实现了基于mpi4py的多进程算法 mpi不过多介绍&#xff0c;某些函数的用法也不是介绍范围&#xff0c;这里只给出怎么实现多进程的方程求根算法。区间划分求解方程&#xff0c;在串行程序里&#xff0c;二分法是非常经典的算法&#xff0c;现在对其进行拓展&#xff0c;实现…

Java聚合快递系统对接云洋系统快递小程序APP公众号系统源码

聚合快递对接云洋系统小程序&#xff1a;一键解决物流难题 一、引言&#xff1a;为何选择聚合快递对接&#xff1f; 在电商日益繁荣的今天&#xff0c;物流成为了连接卖家与买家的关键桥梁。然而&#xff0c;面对市场上琳琅满目的快递公司&#xff0c;如何高效、便捷地进行快…

教师人才引进需要什么条件

在这个竞争激烈的时代&#xff0c;学校和教育机构都在寻求优秀的教育工作者&#xff0c;以提升教学口碑和学生的学习体验。而引进教师人才可并非易事&#xff0c;涉及到多方面的考量。 专业素养。一个优秀的教师需要具备扎实的专业知识和教育理论&#xff0c;能够不断更新自己的…

MySQL的增删查改(CRUD)

目录 一.CRUD 1.什么是CRUD 2.CRUD的特点 二.新增&#xff08;Create&#xff09; 单列插入全行数据 表的复制 额外小知识 三.阅读(Read) 1.全表查询指定列查询 2.查询字段为表达式 3.别名 ​编辑 4.去重 5.排序 1.根据列名进行排序 2.使用表达式及别名进行排序…

Vue编程式路由导航

编程式路由导航 作用&#xff1a;不借助<router-link>实现路由跳转&#xff0c;让路由跳转更加灵活 以下放在你配置路由的页面代码&#xff1a; <template><div><h1>页面</h1><hr /><button click"btn1()">前进</bu…

复分析——第2章——Cauchy定理及其应用(E.M. Stein R. Shakarchi)

第2章 Cauchy定理及其应用 The solution of a large number of problems can be reduced, in the last analysis, to the evaluation of definite integrals; thus mathematicians have been much occupied with this task... However, among many results obtained, a n…

AlertManager解析:构建高效告警系统

一、AlertManager简介 AlertManager是一个开源的告警管理工具&#xff0c;主要用于处理来自于监控系统&#xff08;如Prometheus&#xff09;的告警。它的设计目标是提供一个统一的告警处理平台&#xff0c;能够集中管理告警的路由、去重、分组和通知等操作。在现代云服务架构中…

C++面向对象程序设计 - 命名空间

命名空间是ANSI C引入的可以由用户命名的作用域&#xff0c;用来处理程序中常见的同名冲突。 在C语言中定义了三个层次的作用域&#xff0c;即文件&#xff08;编译单元&#xff09;、函数和复合语句。C又引入了类作用域&#xff0c;类是出现在文件内的。在不同的作用域中可以定…

Electron无感打印 静默打印(vue3 + ts + vite)

&#xff08;electron vue3 项目搭建部分 自行查找其他资源 本文只讲解Electronvue3 如何实现静默打印&#xff09; 第一步获取打印机资源 渲染端代码&#xff08;vue里面&#xff09; // 因使用了vite所以在浏览器中打开 require会报错 只能在electron中 const { ipcRender…

统计信号处理基础 习题解答10-11

题目 我们希望根据一个人的身高来估计他的体重。为了判断其可行性,对N100个人取数据&#xff0c;产生有序的数据对(h,w),其中h代表身高,w代表体重。得到的数据如图10.9(a)所示的。解释你如何利用MMSE估计量根据一个人的身高来猜测他的体重。对于这些数据的建模有些什么样的假设…

RT-thread内核对象的基础应用

RT-thread的内核对象基础应用 启动流程&#xff1a; 以RT-Thread Studio为例&#xff0c;用户程序入口为位于 main.c 文件中 的main 函数。系统启动后先运行startup_stm32f103xe.s文件中的汇编代码&#xff0c;运行“bl entry”指令后跳转到components.c 文件中调用entry函数&a…

WebMvcConfigurer配置不当导致鉴权失败

最近同事说他们有个新需求&#xff0c;需要对接口进行加解密&#xff0c;所以他给项目配置了一个拦截器&#xff0c;但这个拦截器直接导致了每个接口鉴权失败&#xff0c;每次调用接口都是提示没有session信息。 公司内的所有java项目是公用同一套基础依赖&#xff0c;所以我也…