Spring5 全家桶 | 26 - Spring、Spring MVC 整合

561 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

一、Spring MVC 运行流程

在spring-mvc-handler项目的controller包中新增一个HalloContrller,增加hallo方法,Debug Spring MVC的运行流程,在index页面增加/hallo的超链接,断点打在 doDispatch 方法上,Debug模式启动应用并点击首页的hallo超链接

第一步:前端控制器DispatcherServlet收到请求,调用doDispatch()方法处理 image.png

第二步:根据HandlerMapping中保存的请求映射信息找到处理当前请求的处理器执行链,包含拦截器

image.png

第三步:根据当前处理器找到他的适配器HandlerAdapter

image.png

第四步:拦截器的preHandler()方法执行

image.png

第五步:适配器执行目标方法,返回ModelAndView对象

image.png

第六步:处理器的postHandler()方法执行 image.png

第七步:处理结果;页面渲染

  • 如果有异常使用异常解析器进行处理,处理之后返回ModelAndView
  • 调用render()方法进行页面渲染
    • 视图解析器根据视图名得到视图对象
    • 视图对象调用render()方法
  • 执行拦截器的afterCompletion()方法 image.png

image.png

二、Spring、Spring MVC 整合

IDEA创建Maven工程spring-mvc-spring,添加Spring、Spring MVC、Servlet、Jackson、文件上传依赖

<properties>
    <spring-version>5.3.13</spring-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp.jstl</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.13.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.13.1</version>
    </dependency>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>

</dependencies>

选中项目,点击Add Framework,选择添加 Web Application image.png

选中项目,点击顶部菜单中的File -> Project Structure -> Artifacts,在右侧WEB-INF下新建lib文件夹,将Available Element下的Jar包全部选中导入lib文件夹下 image.png 点击 Apply 并关闭该窗口。

配置WEB-INF下的 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/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
        
    <!--Spring-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>     

    <!--DispatchServlet-->
    <servlet>

        <servlet-name>dispatcherServlet</servlet-name>
        <!--
            DispatcherServlet是Spring MVC最核心的对象
            DispatcherServlet用于拦截Http请求,
            并根据请求的URL调用与之对应的Controller方法,来完成Http请求的处理
        -->
        <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>
        <!--
            在Web应用启动时自动创建Spring IOC容器,
            并初始化DispatcherServlet
        -->
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--"/" 代表拦截所有请求,/*拦截所有请求包括jsp页面这些请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--Rest支持-->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

在resources目录下新建 spring-mvc.xml 和 spring.xml 两个配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.citi">
    </context:component-scan>

    <!--配置试图解析器,自动拼接页面地址,自动在jsp页面前增加/WEB-INF/pages/-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!-- 默认前端控制器是拦截所有资源(除过jsp),js文件就404了;要js文件的请求是交给tomcat处理的
http://localhost:8080/7.SpringMVC_crud/scripts/jquery-1.9.1.min.js -->
    <!-- 告诉SpringMVC,自己映射的请求就自己处理,不能处理的请求直接交给tomcat -->
    <!-- 静态资源能访问,动态映射的请求就不行 -->
    <mvc:default-servlet-handler/>
    <!-- springmvc可以保证动态请求和静态请求都能访问 -->
    <mvc:annotation-driven conversion-service="conversionServiceFactory">

    </mvc:annotation-driven>

    <bean id="conversionServiceFactory" class="org.springframework.context.support.ConversionServiceFactoryBean">
    </bean>
</beans>

这是spring.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.citi">
    </context:component-scan>

</beans>

在java包下新建controller包和service包,增加HalloController和HalloService

@Controller
public class HalloController {

    // 添加构造方法,实例化是打印日志
    public HalloController(){
        System.out.println(this.getClass().getName() + "被实例化了...");
    }
}
@Service
public class HalloService {

    public HalloService(){
        System.out.println(this.getClass().getName() + "被实例化了...");
    }
}

在WEB-INF下新增pages目录,用来保存jsp页面,新增success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>SUCCESS</h2>
</body>
</html>

web.xml中需要添加一个Listener

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

配置Tomcat,选择顶部的Add Configuration,添加本地的Tomcat

image.png 点击Fix

image.png

点击Apply,之后启动Tomcat

image.png

根据控制台输出的日志,可以确定创建了两个容器,并且两个容器中都实例化了HalloController和HalloService组件,这会导致在Autowire的时候不知道导入的是Spring容器实例化的Bean还是Spring MVC容器实例化的Bean

Spring 和 Spring MVC能够分工明确,Spring MVC的配置文件就负责配置和网站转发逻辑以及网站功能相关的,如视图解析器,文件上传解析器,Ajax等

Spring的配置文件只负责配置和业务有关的组件,如事务控制、数据源等

所以Spring和Spring MVC配置文件中配置包扫描的时候就各自扫描自己的组件;将Spring MVC 配置文件修改为

<context:component-scan base-package="com.citi" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

将Spring 配置文件修改为

<context:component-scan base-package="com.citi">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

保存配置并重新启动应用 image.png Spring 启动时创建了 HalloService, Spring MVC 容器启动时实例化了 HalloController,并且这两个组件只实例化了一次

在HalloController中增加方法

@Autowired
private HalloService halloService;

@RequestMapping("/hallo")
public String hallo(){
    System.out.println("halloService组件为:" + halloService);
    return "success";
}

重新启动应用 image.png 页面可以正常跳转

image.png 控制台中的HalloService组件成功导入

上面提到的Spring容器和Spring MVC容器是一对父子容器,Controller中可以装配Service,Service中不能装配Controller, 有点像继承,子类可以用弗雷的,父类不能用子类的

新建一个HiController,在HalloService中注入HiController

@Service
public class HalloService {

    @Autowired
    private HiController hiController;

    public HalloService(){
        System.out.println(this.getClass().getName() + "被实例化了...");
    }
}

再次重新启动 image.png

控制台出现报错,Spring MVC 容器中的的组件不可以被带入 Spring 容器中

image.png

异常处理注解@ControllerAdvice标注的类也应该由Spring MVC容器扫描,修改Spring MVC 配置文件

<context:component-scan base-package="com.citi" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>