JavaWeb开发常用模式——MVC以及SpringMVC源码分析

1.MVC的介绍

介绍的是 MVC 模型,它包含了 Model(模型),View(视图)和 Controller(控制器)。
其中 Model,通常指的就是 JavaBean。
View,通常指的是 JSP 或者 HTML(即用于展示数据的资源,包括静态资源和动态资源)。
Controller,通常指的是 Servlet 或者 Filter,以及框架中封装的各类控制器。

1.1表现层模型MVC的由来

1.1.1 Model1模型

Model1 模型是很早以前项目开发的一种常见模型,只有 jsp 和 JavaBean 两部分组成。
它的优点是:结构简单。开发小型项目时效率高。
他的缺点也同样明显的:
1.JSP的职责兼顾于展示数据和处理数据(也就是干了控制器和视图的事)
2.所有的逻辑代码都是写在JSP中的,导致代码的重用性很低。
3.由于展示数据的代码和部分的业务代码交织在一起,维护非常不便。
所以,结论是此种设计模型已经被淘汰没人使用了。
下图展示了Model1模型的执行过程:
在这里插入图片描述

1.1.2 Model2模型

Model2 模型是在 Model1 的基础上进行改良,它是 MVC 的模型的一个经典应用。它把处理请求和展示数据进
行分离
,让每个部分各司其职。此时的 JSP 已经就是纯粹的展示数据了,而处理请求的事情交由控制器来完成,使
每个组件充分独立,提高了代码可重用性和易维护性。下图展示的就是 Model2 模型:
在这里插入图片描述

1.2MVC模型的优劣分析

1.2.1 MVC模型的优势

第一:清晰的职责划分。
第二:每个组件作用独立。有利于代码的重用。
第三:由于可重用性强,所以后期维护起来方便。
第四:任何项目都适用(现在没必要照本宣科的说什么适用于大型项目,其实中小型项目的表现层也可以采用
此种模型来开发,并且更易于后期的扩展,即二次开发)

1.2.2 MVC模型的弊端

任何事情都是有其两面性,MVC 模型也并不是全方位优秀的设计模型。它的弊端体现在:
第一:展示数据响应速度慢(这里讨论的是 JSP。因为 jsp 要经过翻译成 java,编译成 class,然后展示)
第二:对开发人员的要求高,需要合理的设计和严谨的架构。
第三:异步交互并不方便(因为响应回 ajax 引擎之后的数据处理,需要有dom的功底)

1.2.3 基于异步请求的MVVM模式

它全称是 Model View VielModel。是针对 mvc 模型的再次改良,不过只改良了展示数据的部分。(Controller 的再次优化交给了框架,Model 部分已经无需优化了)。
在上一小节,我们提出了异步交互不变的弊端,这主要是在异步展示数据时,javascript 的逻辑处理和数据
显示交织在了一起,当我们想进行调整时,需要阅读大量的代码,给后期维护造成了影响。而 MVVM 它把 javascript
的逻辑处理和数据展示分开,可以让使用者在后期维护时,针对不同的需求进行调整。例如:如果是逻辑部分需要
处理,则修改逻辑部分代码。如果是数据显示位置需要调整,则修改展示部分的代码,使前端展示更加灵活,也更
加合理。

2.基于MVC模型的框架:SpringMVC

2.1 SpringMVC源码分析

2.1.1 SpringMVC执行过程分析

下边是一张SpringMVC官方提供的框架执行图:

在这里插入图片描述
通过此图,我们可以看到其实都是由前端控制器负责找到要执行的控制器方法。这个前端控制器就是
SpringMVC 的核心控制器 DispatcherServlet
接下来,我们通过一个图来看一下SpringMVC请求的全流程:

在这里插入图片描述
代码的实现:

1.代码:导入maven支持

包括spring容器的支持,springmvc的支持,以及servlet和jsp的支持.

<?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>com.zhou</groupId>
  <artifactId>SpringMVC_detail</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.14.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.14.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
    </dependency>
  </dependencies>
</project>

2.配置前端控制器

在web.xml中配置DispatcherServlet前端控制器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

  <!--配置前端控制器 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <!--配置读取SpringMVC文件的位置 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置初始参数,启动应用时创建servlet对象-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

3.配置视图解析器

在springmvc配置文件中配置InternalViewResolver视图解析器.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置创建时要扫描的包路径-->
    <context:component-scan base-package="com.zhou"></context:component-scan>
    <!-- 配置视图解析器 -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!-- 开启注解支持 -->
    <mvc:annotation-driven enable-matrix-variables="true"/>
</beans>

4.编写控制器类

@Controller
public class ControllerDemo1 {
    @RequestMapping("/hello")
    public String helloSpringMVC(){
        System.out.println("mvc:success!");
        return "success";
    }
}

5.编写响应页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功页面</title>
</head>
<body>
页面成功开启!
</body>
</html>

成功跳转:
在这里插入图片描述

实现流程代码解析

1) 请求被前端控制器的doService方法捕获,并执行其中的doDispatch()方法

在这里插入图片描述

2)doDispatch()方法调用控制器方法handle()

在这里插入图片描述

3)控制器类RequestMappingHandlerAdapter中的方法执行

在这里插入图片描述

4) RequestMappingHandlerAdapter中的invokeHandlerMethod方法调用ServletInvokableHandlerMethod中的方法

在这里插入图片描述

在这里插入图片描述

5)InvocableHandlerMethod类中的方法最终使用反射调用

在这里插入图片描述

2.1.2 SpringMVC 中三大组件详解

2.1.2.1 处理器映射器

它指的是:RequestMappingHandlerMapping
是在 Spring 的 3.1 版本之后加入的。它的出现,可以让使用者更加轻松的去配置 SpringMVC 的请求路径映
射。去掉了早期繁琐的 xml 的配置(关于这个内容,请见 2.1.8 小节)。
它的配置有两种方式:都是在 springmvc.xml 中加入配置。
第一种方式:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

第二种方式:

   <mvc:annotation-driven></mvc:annotation-driven>

在这两种方式中,第二种方式更加的完善,它可以帮我们在容器中添加很多的 bean(更多说明请看 2.1.9 小节)。
它 起 的 作 用 是 为我们 建立起 @RequestMapping 注 解 和 控 制 器 方 法 的 对 应 关 系 。 并 且 存 在 于MappingRegistry 对象中的 mappingLookup 的 Map 中,该 Map 是个 LinkedHashMap。对应关系的建立时机是在应用加载的时候,也就是当服务器启动完成后,这些对应关系已经建立完成了。从而做到在我们访问的时候,只是从 Map 中获取对应的类和方法的信息,并调用执行。
在这里插入图片描述

2.1.2.2 处理器适配器

要清晰的认识 SpringMVC 的处理器适配器,就先必须知道适配器以及它的作用。我们先通过下图,直观的了解一下:
在这里插入图片描述
通过上面三张图,我们可以直观的感受到,它是把不同的接口都转换成了 USB 接口。
带入到我们 SpringMVC 中,就是把不同的控制器,最终都可以看成是适配器类型,从而执行适配器中定义的方法。更深层次的是,我们可以把公共的功能都定义在适配器中,从而减少每种控制器中都有的重复性代码。就如上图中所显示的,像公共数据交换都可以在 USB 接口中定义,而无需在三种不同接口中重复定义。
我们通过 2.1.1 小节,学习了 SpringMVC 的执行过程,最终调用的是前端控制器 DispatcherServlet 的doDispatch 方法,而该方法中的 HandlerAdapter 的 handle 方法实际调用了我们自己写的控制器方法。而我们写的控制方法名称各不一样,它是通过 handle 方法反射调用的。但是我们不知道的是,其实 SpringMVC 中处理器适配器也有多个。

第一个:org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter

使用此适配器,适用的控制器写法:要求实现 Controller 接口:

public class ControllerDemo2 implements Controller {


    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("success");
        return mv;
    }
}

同时要求我们在 springmvc.xml 中添加:

<bean id="simpleControllerHandlerAdapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter">
    </bean>

    <bean name="/sayhello2"
          class="com.zhou.controller.ControllerDemo2">

启动:
在这里插入图片描述

第二个:org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter

使用此适配器的控制器写法:要求实现 HttpRequestHandler 接口

public class ControllerDemo3 implements HttpRequestHandler {
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
    }
}
<bean name="/sayhello3"
          class="com.zhou.controller.ControllerDemo3"></bean>
    <bean id=" httpRequestHandlerAdapter "
          class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter">
    </bean>

启动:
在这里插入图片描述

第三个:org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

这种方式也是我们实际开发中采用最多的。它的要求是我们用注解@Controller 配置控制器

@Controller
public class ControllerDemo1 {

    @RequestMapping("/hello")
    public String helloSpringMVC(){
        System.out.println("mvc:success!");
        return "success";
    }
}

同时要求我们在 springmvc.xml 中配置:

<bean id="requestMappingHandlerAdapter" 
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingH
andlerAdapter">
</bean>

不过通常情况下我们都是直接配置:

<mvc:annotation-driven></mvc:annotation-driven>

2.1.2.3 视图解析器

首先,我们得先了解一下 SpringMVC 中的视图。视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
为了实现视图模型和具体实现技术的解耦,Spring 在org.springframework .web.servlet 包中定义了一个高度抽象的 View 接口。
我们的视图是无状态的,所以他们不会有线程安全的问题。无状态是指对于每一个请求,都会创建一个 View对象。
在 SpringMVC 中常用的视图类型:
在这里插入图片描述
接下来就是了解视图解析器的作用。View Resolver负责将处理结果生成View视图,View Resolver首 先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。视图对象是由视图解析器负责实例化。
视图解析器的作用是将逻辑视图转为物理视图,所有的视图解析器都必须实现ViewResolver接口。
SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring WEB上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。程序员可以选择一种视图解析器 或混用多种视图解析器。可以通过order属性指定解析器的优先顺序,order越小优先级越高,SpringMVC会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出ServletException 异常。
在这里插入图片描述

2.1.3 不需要视图解析器的场景分析

在分析之前,我们先需要回顾下控制器方法的返回值,此处我们都是以注解@Controller 配置控制器为例,
控制器的方法返回值其实支持三种方式:

  • 第一种:String 类型。借助视图解析器,可以在指定位置为我们找到指定扩展名的视图。视图可以是 JSP,HTML 或者其他的控制器方法上的 RequestMapping 映射地址。前往指定视图的方式,默认是请求转发,可以通过redirect:前缀控制其使用重定向。
  • 第二种:void,即没有返回值。因为我们在控制器方法的参数中可以直接使用原始 SerlvetAPI 对象HttpServletRequest 和 HttpServletResponse 对象,所以无论是转发还是重定向都可以轻松实现,而无需使用返回值。
  • 第三种:ModelAndView 类型。其实我们跟踪源码可以发现在 DispatcherServlet 中的 doDispatch 方
    法执行时,HandlerAdapter 的 handle 方法的返回值就是 ModelAndView,只有我们的控制器方法定义为 void
    时,才不会返回此类型。当返回值是 String 的时候也会创建 ModelAndView 并返回。
    通过上面三种控制器方法返回值,我们可以再深入的剖析一下我们请求之后接收响应的方式,其实无外乎就三种。
    第一种:请求转发
    第二种:重定向
    第三种:直接使用 Response 对象获取流对象输入。可以是字节流也可以是字符流。
    接下来我们就分析,这三种方式的本质区别。
    其中请求转发和重定向的区别相信大家已经很熟悉了。但是它们的共同点呢?就是都会引发页面的跳转。
    在我们的实际开发中,如果我们不需要页面跳转,即基于 ajax 的异步请求用 json 数据交互时,即可不配置任何视图解析器前后端交互是通过 json 数据的,利用@RequestBody 和@ResponseBody 实现数据到 java对象的绑定(当然还要借助 Jackson 开源框架)。

2.1.4 请求参数封装的实现原理

在使用 SpringMVC 实现请求参数封装时,它支持基本类型,POJO 类型和集合类型。其封装原理其实就是使用我们原始的 ServletAPI 中的方法,并且配合反射实现的封装。
此处先以最简单的 String 和 Integer 两个方法为例,带着大家把整个执行过程走一圈。
先来看控制器的方法:

@Controller
public class HelloControler {
	@RequestMapping("hello")
	public String sayHello(String name,Integer age) {
		System.out.println("控制器方法执行了"+name+","+age);
		return "success";
	}
}

它的执行过程如下图所示:
在这里插入图片描述
他调用的是当前类的方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1.5 常用注解的使用场景及实现思路分析

2.1.5.1 RequestParam

首先我们要明确,我们的请求参数体现形式是什么样的。
在请求体的 MIME 类型为 application/x-www-form-urlencoded 或者 application/json 的情况下,无论 get/post/put/delete 请求方式,参数的体现形式都是 key=value。
再来,通过上一小节我们知道,SpringMVC 是使用我们控制器方法的形参作为参数名称,再使用 request 的getParameterValues 方法获取的参数。所以才会有请求参数的 key 必须和方法形参变量名称保持一致的要求。
但是如果形参变量名称和请求参数的 key 不一致呢?此时,参数将无法封装成功。
此时 RequestParam 注解就会起到作用,它会把该注解 value 属性的值作为请求参数的 key 来获取请求参数的值,并传递给控制器方法。

@Controller
public class ParamController1 {
/**
* 处理请求的控制器方法
* @return
*/
@RequestMapping("hello")
	public String sayHello(@RequestParam("username")String name,Integer age) {
		System.out.println("控制器方法执行了"+name+","+age);
		return "success";
	}

在这里插入图片描述

2.1.5.2 RequestBody

在 2.1.4 小节,我们通过源码分析得知,SpringMVC 在封装请求参数的时候,默认只会获取参数的值,而不会把参数名称一同获取出来,这在我们使用表单提交的时候没有任何问题。因为我们的表单提交,请求参数是key=value 的。但是当我们使用 ajax 进行提交时,请求参数可能是 json 格式的:{key:value},在此种情况下,要想实现封装以我们前面的内容是无法实现的。此时需要我们使用@RequestBody 注解。
JSP 代码片段:

<script src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
	$("#ajaxBtn").click(function(){
		$.ajax({
				type:"POST",
				url:"${pageContext.request.contextPath}/hello2",
				dataType:"text",
				data:"{'name':'test','age':18}",
				contentType:"application/json",
				success:function(data){
				alert(data);
			}
		});
	
	});
})
</script>
<title>SpringMVC</title>
</head>
<body>
<button id="ajaxBtn">异步请求</button>
</body>
</html>

控制器代码片段:

@Controller
public class ParamController {
/**
* 处理请求的控制器方法
* @return
*/
@RequestMapping("hello2")
	public String sayHello2(@RequestBody String body) {
		System.out.println("控制器方法执行了 2"+body);
		return "success";
	}
}

它的执行过程如下图:首先前面的执行和 2.1.4 小节是一致的,在下图红框中进行参数解析时:
在这里插入图片描述
在这里插入图片描述
接下来执行的是:
在这里插入图片描述
通过最后这个方法,我们可以看出,它是先获取的请求参数 MIME 类型MediaType,然后再把整个内容获取出来,并传递给我们的控制器方法。
需要注意的是,此注解并不能为我们提供封装到 pojo 的操作,它只能把请求体中全部内容获取出来,仅此而已,要想实现封装,需要借助 jackson 开源框架。

2.1.6 拦截器的 AOP 思想

AOP 思想是 Spring 框架的两大核心之一,是解决方法调用依赖以及提高方便后期代码维护的重要思想。它是把我们代码中高度重复的部分抽取出来,并在适当的时机,通过代理机制来执行,从而做到不修改源码对已经写好的方法进行增强。
而拦截器正式这种思想的具体实现。
拦截器代码:

public class MyInterceptor1 implements HandlerInterceptor{
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse 
	response, Object handler)throws Exception {
		System.out.println("拦截器执行了");
		return false;
	}
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
		System.out.println("执行了 postHandle 方法");
	}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {
		System.out.println("执行了 afterCompletion 方法");
	}
}

它是在 DispatcherServlet 的:
在这里插入图片描述
去执行了 HandlerExecutionChain 类中的:
在这里插入图片描述
而此时还没有执行我们的控制器方法,所以此时为前置增强。
在执行完调用控制方法,
在这里插入图片描述

2.1.8 为什么不使用 XML 配置 SpringMVC

我们先来看基于 XML 的 SpringMVC 配置:
第一步:配置 web.xml
第二步:编写控制器
第三步:编写 springmvc.xml
第四步:配置控制器
第五步:配置处理器映射器,处理器适配器。
第六步:配置视图解析器。
其中,前 3 步和第六步基于注解配置时也都有,而第四第五步注解配置只需:

<mvc:annotation-driven></mvc:annotation-driven>

而 XML 配置则需:

<!-- 实现 Controller 接口-->
 <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<bean name="/sayhello2" class="com.itheima.web.controller.HelloController2"/>
<!-- 继承 HttpRequestHandler 类-->
<bean name="/sayhello3" class="com.itheima.web.controller.HelloController3"/>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>

而对比注解配置只需一个 Controller 注解和一个 RequestMapping 注解来比,显然注解来的更方便。

2.1.9 mvc:annotation-driven 的说明

它就相当于在 xml 中配置了:
<!-- Begin -->
<!-- HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerM
apping"></bean>
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerA
dapter"></bean>
<bean
class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- HadnlerExceptionResolvers -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExcept
ionResolver"></bean>
<bean
class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolv
er"></bean>
<bean
class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"
></bean>
<!-- End -->
发布了122 篇原创文章 · 获赞 1 · 访问量 7336

猜你喜欢

转载自blog.csdn.net/qq_36079912/article/details/104377723