MVC简介
-
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
-
是将业务逻辑、数据、显示分离的方法来组织代码。
-
MVC主要作用是降低了视图与业务逻辑间的双向偶合。
-
MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。
MVC实现
如下代码是使用servlet 写的一个简单例子,可以看出,在servlet 下这边所有逻辑基本在servlet的service 方法中,做完逻辑交给jsp 做数据渲染,在当前项目中,假如一个方法逻辑较为复杂,并且存在较多的sql 的时候,我们的servlet+javaBean 代码是否会较为庞大,臃肿。而且在servlet 映射下,当servlet较多的时候,url 重映射又如何处理? 另外看如下servlet 代码,request 对象,response 对象,如何和客户端交互,是我们需要手写代码去操作的事情,我们给他们数据,是json 格式还是给映射jsp,或者其他数据格式,都是我们需要代码来编写。正是有了这一系列问题,所以人们才引出了相对的解决方案,框架。
web.xml,
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>servlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/helloWorld</url-pattern>
</servlet-mapping>
servlet 代码
package servlet;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class MyServlet implements Servlet {
public MyServlet() {
super();
System.out.println("实例化servlet ");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("执行servlet init()");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest request, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("执行servlet 的 service()");
HttpServletResponse response = (HttpServletResponse) servletResponse;
PrintWriter out = response.getWriter();
response.setContentType("text/html;charset=UTF-8");
String title = "使用 GET 方法读取表单数据";
// 处理中文
String name =new String("小王".getBytes("ISO-8859-1"),"UTF-8");
String docType = "<!DOCTYPE html> \n";
out.println(docType +
"<html>\n" +
"<head><title>" + title + "</title></head>\n" +
"<body bgcolor=\"#f0f0f0\">\n" +
"<h1 align=\"center\">" + title + "</h1>\n" +
"<ul>\n" +
" <li><b>站点名</b>:"
+ name + "\n" +
" <li><b>网址</b>:"
+ request.getParameter("url") + "\n" +
"</ul>\n" +
"</body></html>");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("执行servlet destroy() ");
}
}
springMVC 解析
下图是一个springMVC 的执行流程,接下来我们通过该图,分析springMVC 的执行流程
众所周知,servlet 在处理一个请求时,是请求到达servlet, 然后执行servlet 的doget/dopost 方法,然后返回给客户端。springMVC 同样如此。接下来我们分析下springMVC 的调用流程
我调用的路径为 http://localhost:8081/login post 请求,调用了 FrameworkServlet dopost方法,我们发现不管是dopost,还是doget ,请求都交由processRequest() 这个方法来处理了,里面有很多代码,相信还是有很多朋友看不大懂的,不过没关系,先看看这个方法名 processRequest 意思是 加工请求/处理请求,那我们看看他是如何处理请求的呢?(下边源码分析参照一大神博客,限于题主水平有限,无法将springMVC 所有流程全理解,看官们可直接阅读原博客)地址为 https://cloud.tencent.com/developer/article/1497797
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上下文(因为可能在Filter里已经设置过了)
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
以当前的request创建一个Local的上下文,后面会继续处理
LocaleContext localeContext = this.buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 这里面build逻辑注意:previousAttributes若为null,或者就是ServletRequestAttributes类型,那就new ServletRequestAttributes(request, response);
// 若不为null,就保持之前的绑定结果,不再做重复绑定了(尊重原创)
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
// 拿到异步管理器。这里是首次获取,会new WebAsyncManager(),然后放到request的attr里面
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//这里需要注意:给异步上下文恒定注册了RequestBindingInterceptor这个拦截器(作用:绑定当前的request、response、local等)
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor(null));
//这句话很明显,就是吧request和Local上下文、RequestContext绑定
this.initContextHolders(request, localeContext, requestAttributes);
try {
this.doService(request, response);
} catch (IOException | ServletException var16) {
failureCause = var16;
throw var16;
} catch (Throwable var17) {
failureCause = var17;
throw new NestedServletException("Request processing failed", var17);
} finally {
this.resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
this.logResult(request, response, (Throwable)failureCause, asyncManager);
this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
}
}
publishRequestHandledEvent()发布请求处理完后的事件源码
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
//当publishEvents设置为true和 webApplicationContext 不为空就会处理这个事件的发布
if (this.publishEvents && this.webApplicationContext != null) {
// 计算出处理该请求花费的时间
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
//ServletRequestHandledEvent这个事件:目前来说只有这里会发布
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, response.getStatus()));
}
}
接下来就分析我们的核心方法了
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 如果该请求是include的请求(请求包含) 那么就把request域中的数据保存一份快照版本
// 等doDispatch结束之后,会把这个快照版本的数据覆盖到新的request里面去
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
// 说得很清楚,把一些常用对象放进请求域 方便Handler里面可以随意获取
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); //这个是web子容器哦
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// 如果是重定向,放置得更多一些~~~~
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// DispatcherServlet最重要的方法,交给他去分发请求你、找到handler处理等等
doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
//如果是include请求 会上上面的数据快照,重新放置到request里面去
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
下面到我们的doDispatch() ,这里就是分发到我们的controller 了,关于下图所示代码,相关地方还有些深奥,这里我重点分析我所理解的部分:
1. 先判断请求是否是文件请求,然后找打该次请求的执行器链 HandlerExecutionChain ,执行器链里边除了执行器,还有拦截器,然后根据执行器链找到执行器,现在问题来了,执行器里边存在很多种执行器,每种类型的执行器器的执行方法都不一样,比方说servlet 执行器,执行是调用service() 方法,而我们的controller 执行器 执行是调用 handleRequest 方法,这种情况看官们想想该如何处理呢? 下面题主贴一下我当初的思路代码如下,这是题主个人思路,与springMVC 无关
这个是springMVC 的思路
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
这个是题主个人的思路,写下伪代码
//取到所有执行器类型,比如controller类型,servlet 类型(还有其他类型,这里以这两种类型为例)
for(执行器 handler:handerList){
if(handler instance servlet){
// servlet 的执行方法
handler.service()
}else if(handler instance controller){
//controller 的执行方法
handler.handlerMapping()
}
}
看官们是否认可上面题主的思路呢? 好像也没什么问题哈, 但是现在问题来了我们springMVC 的handler 类型是Object 类型所有的handler 并不能用同一个list 放在一起呀,那如果是List<Object> 呢?
list<Object> 如何调用呢?我们的controller,servlet 都是接口,两者没有什么联系。那个我们的spring 又是怎么处理的呢? 接下来我们就引入我们的适配器模式
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
//这是controller
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
}
适配器模式
顾名思义,即做适配的,在两种不同的类型的类之间做适配,使其在api 调用方需要那个的时候,提供方就给他那个。那么如何实现一个适配器呢? 比如有A,B两个接口,B要适配A, 那么写一个B 的实现类,引入A 的实例,重写B接口的方法,使用A 的实现,这些只是两个之间的适配,如果说涉及多个之间的适配,则需要写一个适配器接口,在适配器内部持有需要适配的引用,把对目标的调用转换成对适配器的调用。有设计模式的看官们,这种是否也有点类似我们的代理呀?
如springMvc 这个例子,controller,servlet都是不同的接口,调用方具体给什么类型只有在运行期才能知道。怎么办呢?我们看看spring怎么处理的
1. 一个通用的适配器接口
public interface HandlerAdapter {
boolean supports(Object var1);
@Nullable
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
long getLastModified(HttpServletRequest var1, Object var2);
}
2. 不同的适配器实现
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public SimpleControllerHandlerAdapter() {
}
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return ((Controller)handler).handleRequest(request, response);
}
public long getLastModified(HttpServletRequest request, Object handler) {
return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;
}
}
3. 获取适配器类型
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
while(var2.hasNext()) {
HandlerAdapter adapter = (HandlerAdapter)var2.next();
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");
}
4. 适配器执行方法
适配器的优缺点
1. 更好的复用性,如果功能是已有了的,只是接口不兼容,那么通过适配器模式可以让这些功能得到更好的复用
2. 更好的扩展性,在实现适配器功能的时候,可以调用自己开发的功能
缺点
1. 过多的使用适配器,会使得系统非常凌乱,比如说明明看到的是调用A 接口,却在内部被换成了B 实现,这种代码过多,无异于一场灾难。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
//检查是否是文件请求
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//找到其执行器链
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//找到适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//执行方法,返回moduleAndView 对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
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) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}