Spring RE 13 Spring MVC -  基础

Spring RE 13 Spring MVC - 基础

时间过得真是飞快, 12月19号看完异步任务之后, 深感自己的持久化技术方面还是短板, 因此去看了SQL基础理论, PgSQL的操作以及Hibernate. 功夫不负有心人, 今天2月10号, 50天的时间里把上边的内容都过了一遍, 总算是补上了数据库操作这块短板, 之后不管是SQL语句还是用JPA

时间过得真是飞快, 12月19号看完异步任务之后, 深感自己的持久化技术方面还是短板, 因此去看了SQL基础理论, PgSQL的操作以及Hibernate. 功夫不负有心人, 今天2月10号, 50天的时间里把上边的内容都过了一遍, 总算是补上了数据库操作这块短板, 之后不管是SQL语句还是用JPA, 心里不会再有丝毫恐惧了, 这也说明, 害怕一个玩意逃避是没有用的, 唯有下决心堂堂正正面对, 彻底搞清楚才行. 现在把持久化层的内容搞定, 终于又可以回头再来好好的看看Spring MVC了.
  1. 什么是MVC
  2. DispatcherServlet的配置
  3. DispatcherServlet的工作原理
  4. 一个简单的GET和POST控制器的例子
  5. Servlet 3.0方式配置DispatcherServlet
  6. Spring MVC简单的类配置

什么是MVC

这个问题对于此时的我来说就简单多了, 不过这里还是有一些新内容. MVC是经典的Model, View, Control模型, 在Web中的MVC还不是原始的MVC, 而是MVC在Web应用中的变体, 这一变体被称为Model 2. Java的工业开发历史悠久, 尤其是EJB, 所以很多玩意我都没有听说过. Model 1和Model 2都来自EJB历史上的开发模型, Model 1就是内嵌JSP的开发模型, JSP页面是独立的实体, 逻辑被包含在JSP页面中. 1999年12月,JavaWorld发表了Govind Seshadri的文章,标题为“理解JavaServer Pages Model 2体系结构”, 将Model 2作为一种构建模式确定下来. 其实Model 1就是没有单独的控制器, 而Model 2有单独的控制器. 这里有一篇文章描述了Model 1和Model 2的区别. Spring MVC就是基于Model 2构建的框架, 所以可以知道其很多技术, 都是集中在控制器层面. Spring MVC的工作流程是:
  1. 在Web容器初始化的时候插入一个DispatcherServlet, 用这个Servlet拦截符合要求的所有Web请求, 一般是拦截全部请求.
  2. DispatcherServlet解析Http请求信息, 根据其中的内容和HandlerMapping类, 确定对应的Handler. 所谓Handler, 就是处理请求的对象, Spring MVC并没有一个Handler接口, 任意类只要符合要求, 都可以当成处理请求的对象
  3. Spring采取反射技术, 使用一个HandlerAdapter接口来适配所有的Handler, 并且来调用相关的方法.
  4. Handler完成处理请求之后, 会返回一个ModelAndView对象给DispatcherServlet, 即使你的Handler的代码中没有显式返回ModelAndView对象, 框架最后也会让你的Handler实际上返回ModelAndView对象.
  5. DispatcherServlet拿到ModelAndView之后, 从里边获取逻辑视图名称, 然后使用ViewResolver将逻辑视图名称解析成真正的视图对象(文件)路径.
  6. 得到视图对象后, 使用Model中的数据, 渲染视图对象. 将渲染结果写入到Http响应中.
  7. 将Http响应发回给客户端, 根据渲染的结果, 客户端会得到不同的media-type对应的内容.

DispatcherServlet

Spring 由于支持Servlet 3.0, 除了XML方式, 也可以使用编程的方式进行配置, 还是先来看看XML配置的方式. 之前已经知道, Spring会有自己的容器, DispatcherServlet也会有IOC容器, 到底这些容器是怎么启动的呢, 看一个配置文件:
<?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">

    //第一部分
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/applicationContext.xml</param-value>
    </context-param>

    //第二部分
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    //第三部分
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>WEB-INF/spring-mvc-crm.xml</param-value>
        </init-param>

        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
详细的启动顺序如下:
  1. 首先这是一个web.xml, 所以最先启动的并不是Spring, 而是Web容器, 就像IDEA里如果创建Spring, 启动的是Tomcat, 而不是某个具体的Spring IOC容器. Web容器启动的时候最先干什么呢, 当然就是生成一个ServletContext, 即Web容器的上下文. 在这个时候Web容器还没有完全启动, 因为Servlet都还没有创建.
  2. Web容器之后会继续启动, 来读取web.xml文件的第一部分, 可以知道, 这些是给Web容器看的, 实际上就是给Web容器配置上了一个属性和对应的值.
  3. Web容器启动的是第二部分的配置, 也就是监听器, 还记得之前学JSP的时候, 监听器是比Servlet要早启动的. 这个监听器是Spring框架里的监听器, 既然是Spring框架的, 很显然就是要做点事情了.
  4. ContextLoaderListener监听器启动的时候, 在JSP中可以知道, 监听器是可以获取ServletContext对象的, 这个监听器就获取了当前Web容器的上下文, 然后去找这个contextConfigLocation属性对应的配置文件的路径, 然后使用这个配置文件, 启动一个Spring IOC容器, 通过之前的学习, 知道这其实是一个XmlWebApplicationContext对象.
  5. 监听器创建了Spring 容器之后, 将这个容器以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为key, 将其存储到ServletContext里, 这样Web容器就有了对这个Spring IOC容器的引用.
  6. 监听器工作完毕. 带有了Spring IOC容器引用的Web容器继续向下创建Servlet对象, 这里的Servlet对象是DispatcherServlet.
  7. DispatcherServlet创建过程中, 会从ServletContext里获取刚刚的Spring IOC容器, 将其设置成为自己的父容器.
  8. DispatcherServlet有了父容器对象后, 再初始化自己的上下文, 也是一个IOC容器. 这个上下文中就存放了视图解析, 映射等一系列所需要的服务Bean. 最后会将自己的这个子IOC容器, 也设置到ServletContext中.
  9. 全部的Servlet创建完毕之后, Web容器成功启动. 此时Web容器中有一个父IOC容器, 还有一个子IOC容器(也可能有多个). 父IOC容器中的Bean被所有的子IOC容器共享. 每个子IOC容器中的Bean是属于自己的.
所以实际上我们编写的业务类和DAO层, 可以看到是放在父IOC容器中的, 被所有的子IOC容器共享. 而子IOC容器中的视图解析器, HandlerMapping等, 仅仅只为子IOC容器使用. 这个层次体系就非常合理. 在创建DispathcerServlet过程中, 除了web.xml中对于servlet的标准写法, 还有一些额外的配置, 都写在<init-param>中:
  1. contextConfigLocation, 表示子IOC容器创建的时候对应的配置文件路径, 可以使用完整路径, classpath:等写法, 还可以使用逗号分割多个xml文件, 会一并使用这些配置文件来创建子IOC.
  2. nameSpace, 表示这个DispatcherServlet对应的命名空间, 如果显示配置了该属性, 配置文件就变成WEB-INF/<nameSpace>.xml. 如果不配置, 默认配置文件是WEB-INF/<servlet-name>-servlet.xml.
  3. publishContext, 布尔值, 表示是否将子IOC容器设置到Web容器的上下文里, 默认是true, 也就是上边的标准启动流程. 如果设置的话, key属性名是.getServeltContextAttributeName()的返回值.
  4. publishEvents, 布尔值, 默认为true. 表示每次DispatcherServlet处理完一个请求之后, 是否向Web容器内发布一个ServletRequestHandlerEvent事件. 如果不需要监听此事件, 可以将该属性设置为false.
编程方式配置DispatcherServlet也很方便. 支持Servlet 3.0的容器(比如Tomcat 7.0之后的容器), 会在启动的时候, 在类路径下边查找一个实现了javax.servlet.ServletContainerInitializer的类, 调用这个类来配置Servlet容器. Spring也提供了一个org.springframework.web.SpringSerlvetContainerinitializer类, 实现了该接口. 编程方式配置到最后看.

DispatcherServlet的工作原理

DispatcherServlet会组装一个IOC容器, 那么这个容器中到底有什么玩意呢. 首先能想到的是, 会去组装指定的spring-mvc-crm.xml文件中的Bean. 确实如此. 不过还有很多上边提到的其他Bean, 都是怎么组装的呢, 这要看具体的工作: DispatcherServlet的源码中有这样一个方法(spring-webmvc 5.1.5版):
protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }
每个方法其实都会初始化一个Bean, 随便看一个其中的源码:
private void initMultipartResolver(ApplicationContext context) {
    try {
        this.multipartResolver = (MultipartResolver)context.getBean("multipartResolver", MultipartResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Detected " + this.multipartResolver);
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
        }
    } catch (NoSuchBeanDefinitionException var3) {
        this.multipartResolver = null;
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No MultipartResolver 'multipartResolver' declared");
        }
    }

}
可以看到其实就会去获取一个Bean, 当然容器在这个过程中就会创建Bean. 但是在xml文件中没有定义这个Bean怎么办, 在org.springframework.web.servlet目录下边有一个文件叫做DispatcherServlet.properties, 其中内容如下:
//默认的DispatcherServlet生成的实现类, 用于在没有子IOC容器中找到对应的Bean使用, 不要更改此文件
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
这里边就是所有子IOC容器中必须具备的Bean,来看一下简介:
  1. MultipartResolver, 这个没有写在properties文件中, 是通过方法来定义的. 如果没有显式定义, 不会自行创建, 不存在该Bean
  2. LocaleResolver, 这个i18n之后的, 是本地化解析器. 如果没有显式定义, 不会自行创建, 不存在该Bean
  3. ThemeResolver, 主题解析器, 如果没有显式定义, 不会自行创建, 不存在该Bean
  4. HandlerMapping, 非常重要的请求处理器的映射, 如果没有显式定义, 会创建文件里的两个Bean
  5. HandlerAdapter, 也是非常重要的, 如果没有显式定义, 会创建三个适配器类, 添加到适配器列表中
  6. HandlerExceptionResolver, 异常处理器, 如果没有显式定义, 也会自行创建.
  7. RequestToViewNameTranslator, 视图名称翻译器, 查找名称为viewNameTranslator, 类型是RequestToViewNameTranslator的Bean, 如果找不到, 就会用上边的默认实现类创建Bean.
  8. ViewResolver, 非常重要的视图解析器, 不显式配置也会创建.
这其中的1,2,3项可以不存在, 如果存在也就只有一个Bean. 第4,5,6,8项必须存在, 而且可以有多个相同的Bean. 第7项必须存在, 而且只有一个Bean. 看到这里, 我就尝试了一下, 将原来写在\WEB-INF\spring-mvc-crm.xml中的数据库连接的内容, 放到了另外一个随便起的base.xml文件中, 然后在web.xml中指定监听器以及将父IOC容器的配置文件路径指向base.xml. 之后再启动服务器, 发现完全OK, 这时候数据框相关的Bean从原来的子IOC容器中改成了在父IOC容器中, 而不是子IOC容器中, 但是对于依赖注入毫无影响, 确实方便.

一个简单的GET和POST控制器的例子

可以看到, Spring与Web容器发生直接关系, 其实就是通过DispatcherServlet, 其后容器只负责将请求送给DispatcherServlet, 再把响应拿回来发给客户, 也就是Web容器只认识DispatcherServlet. 至于DispatcherServlet之后的容器里边怎么来, 就是要按照Spring的要求来了, Spring MVC最核心的其实也就是控制器. 什么是控制器, 简单的说, 就是一个Bean, 使用特殊的标注, 让容器认出来这是一个用于处理请求的Bean. 现在基本上编写全是靠注解驱动的, 就来一个一个看注解吧.

@Controller与@RequestMapping

这个注解用于标识一个控制器, 标注之后, 这个类就可以接受Http请求, Spring MVC框架会检测到这个标准的类, 然后检测其中的方法, 将方法参数与Http请求和其他注解标记的类关联起来, 给方法传入适当的参数. Controller首先要解决的问题就是处理哪些Http请求, 这就需要搭配@RequestMapping注解来使用, 可以注解在类上和方法上, 表示两层逻辑关系.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/register")
    public String register() {
        return "user/register";
    }
}
@RequestMapping的路径, 表示的是相对于web应用根路径的路径, 这点一定要记清楚, 前边要加上/, 不能不加. 因为会在这之前自动拼接一个根路径, 假如没有设置根路径, 还不会出问题. 如果设置了根路径, 没有加/,就会出问题. 这就是一个最简单的控制器了, 整个控制器对应/user路径下的Http请求, 然后register()方法对应/user/register, 这就可以看出层级关系. 不过这个返回字符串的方法是什么呢, 之前说过返回的是ModelAndView对象, 这里编写了返回字符串, 实际上这个字符串会被当做视图文件来解析. 之前的spring配置文件里有一个bean:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean>
这就是自定义的视图解析器, 会在这个user/register之前加上/WEB-INF/view/, 在之后加上.jsp. 最后实际的视图就是/WEB-INF/view/user/register.jsp. 最前边的/表示web容器的根路径, 这个要注意, 所以在对应的地方编写一个jsp文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>用户注册</title>
</head>
<body>
<form method="post" action="<c:url value="/user"/>">
    <table>
        <tr>
            <td>用户名: </td>
            <td><input type="text" name="userName"></td>
        </tr>
        <tr>
            <td>密码: </td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td>姓名: </td>
            <td><input type="text" name="realName"></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" name="提交"></td>
        </tr>
    </table>
</form>
</body>
</html>
JSP和JSTL已经知道是怎么回事了, 这里要注意, 表单提交到哪里呢, 很显然, 又是一个控制器需要获取表单的数据. 实际上, 如果是一个静态页面的话, 我们现在就编写完了最简单的返回一个静态页面的Spring MVC控制器, ModelAndView以及实际渲染了页面,并返回了请求.

处理POST请求

很显然, 现在没有对应/user路径的控制器方法, 只有一个控制器类对应这个路径, 而且是POST方法, 很显然需要编写一个新的接受POST方法的控制器:
    @RequestMapping(path = "/user",method = RequestMethod.POST)
    public ModelAndView createUser(User user) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("user/createSuccess");
        modelAndView.addObject("user", user);
        return modelAndView;
    }
@RequestMapping没有加路径, 则就是对应类上的"/user"路径, 然后设置了接受POST方法. 这里利用了JSP的一大特点, 就是自动组装Post的对象, 所以有一个User类可以对应到Post进来的属性的时候, 可以自动组装一个User对象. 这里注意, 如果设置了@RequestMapping的consumes为x-www-form-urlencoded时需要去掉方法参数中的@RequestBody注解, 这是因为JSP自动组装, 而不使用JSON转换. 此外还发现POST中文数据是乱码. 这是标准的返回了一个ModelAndView对象, 可以看到设置了视图名称, 然后添加了模型数据, 之后编写一个简单的createSuccess.jsp, 依然注意最终解析的路径.
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>成功创建用户</title>
</head>
<body>
<p>${user.userName}创建成功.</p>
<p>详细信息是:</p>
<ol>
    <li>${user}</li>
</ol>

</body>
</html>
注意, 这里POST进来发现是乱码, 这是由于Tomcat默认使用ISO-8859编码方式, Spring提供了过滤器来强制进行请求的转换,比较方便, 可以在web.xml中配置如下:

<filter>
    <description>字符编码过滤器</description>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <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>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
麻雀虽小, 五脏俱全, 经过这么费劲, 终于完成了一个最简单的GET和POST的页面编写, 包含了通过表单获取数据, 然后生成数据对象, 将数据对象设置到模型上, 然后用模型数据来渲染页面等过程.

Servlet 3.0方式配置DispatcherServlet

在Servlet 3.0容器启动的时候, 比如Tomcat, Tomcat会在类路径之下查找javax.servlet.ServletContainerInitializer接口类. 如果找到, 就用这个类来配置容器, 找不到, 就会去寻找web.xml文件. 似乎我们只需要编写一个这个接口类就可以来配置, 不过Spring已经提供了该接口的实现类, 叫做SpringServletContainerInitializer接口类, 这个类会查找WebApplicationInitializer类的实现并用其进行配置任务. Spring对WebApplicationInitializer又提供了一个实现, 就是AbstractAnnotationConfigDispatcherServletInitializer. 简单的说, 如果在Spring框架下, 不想使用web.xml文件, 就需要编写一个类来继承AbstractAnnotationConfigDispatcherServletInitializer. 继承之后, 有三个方法需要实现, 编写一个看看:
package cc.conyli.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //这个方法指定配置父IOC容器的类, 返回配置类的class列表
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    //这个方法指定配置子IOC容器的配置类, 返回配置类的class列表
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    //ServletMapping, 也就是DispatcherServlet对应的路径, 一般设置成"/". 因为是一个列表, 实际上可以设置多个.
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
显然, 这里我们需要编写两个配置类, RootConfig用来配置业务端的类, 是父容器对应的配置类. 而WebConfig用来配置那些Spring MVC的Bean, 是子容器对应的配置类. 这里将两个配置类先留空. 这个项目只有这三个类, 然后运行Tomcat, 然后看日志:
//Web容器启动完毕, 在部署项目
[2020-02-12 03:40:37,450] Artifact securitydemo:war: Artifact is being deployed, please wait...
//容器在扫描ServletContainerInitializer接口类
12-Feb-2020 15:40:42.350 淇℃伅 [RMI TCP Connection(3)-127.0.0.1] org.apache.jasper.servlet.TldScanner.scanJars
//配置父容器
12-Feb-2020 15:40:42.443 淇℃伅 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization started
12-Feb-2020 15:40:42.976 淇℃伅 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext initialized in 533 ms
//创建DispatcherServlet, 同时会创建子容器
12-Feb-2020 15:40:43.593 淇℃伅 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.FrameworkServlet.initServletBean Initializing Servlet 'dispatcher'
12-Feb-2020 15:40:44.040 淇℃伅 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.FrameworkServlet.initServletBean Completed initialization in 446 ms
可见用这类配置, 比如web.xml文件要方便很多.

Spring MVC简单的类配置

除了XML配置之外, 可以用类来配置, 搭配不需要使用web.xml的方式, 非常方便. 此时我们还没有什么东西打算放在RootConfig中, 都是配置子容器, 所以先来编写WebConfig:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
public class WebConfig {
}
所有的Spring配置类, 都必须加上@Configuration注解 , 然后@EnableWebMvc表示启用Spring MVC,这两个都必须要加上. 虽然这可以启动了, 但是相比web.xml, 我们知道还有很多东西没有配置, 比如开启组件扫描, 配置视图解析器, 此外还有一个功能, 就是静态文件需要转交给Web容器本身的处理. 所以来编写一个今后常常用到的配置类:
package cc.conyli.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
//启用组件扫描并配置扫描的目录. 准备将所有Web相关的内容放置到web包内
@ComponentScan("cc.conyli.web")
public class WebConfig extends WebMvcConfigurationSupport {

    @Bean
    @Override
    public ViewResolver mvcViewResolver() {
        System.out.println("执行了解析器");
        InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
        resourceViewResolver.setPrefix("/WEB-INF/views/");
        resourceViewResolver.setSuffix(".jsp");
        resourceViewResolver.setExposeContextBeansAsAttributes(true);
        return resourceViewResolver;
    }

    @Override
    protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}
Spring实战4这本书里, 继承的配置类是WebMvcConfigurerAdapater类, 在现在的Spring 5中, 这个类已经过期. 现在的新类就是例子中的WebMvcConfigurationSupport.再次启动项目, 也没有问题. 剩下就要来看一下控制器的详细编写了. 说白了,控制器本身最大的作用, 就是要将请求携带的所有数据都要能拿到, 组装和合理的对象, 然后交给业务单元去使用, 然后获取处理后的数据, 返回ModelAndView对象. 一个请求不外乎请求行, 请求头, 请求体. 在之前JSP中我们知道可以直接使用请求对象来获取详细数据, 接下来就要看看在Spring MVC中如何将这其中的所有内容都方便快捷的获取到.
LICENSED UNDER CC BY-NC-SA 4.0
Comment