Spring WebMvc基础-SpringWebMvc概述与简单实例

从 0 开始深入学习 Spring 专栏目录总览

接下来咱进入 WebMvc 的部分了。首先,按照之前的惯例,咱先了解一下 SpringWebMvc 的由来,这里面还有一段历史。了解完它的发展,接下来咱就可以来学习整合和搭建了。

1. Spring的Web模块【了解】

之前,我从一篇文章中看到一个还蛮不错的介绍,但是我已经不记得当时的出处了,只能模糊的凭记忆,按照我自己的思路给大家伙讲一下 Spring 中的 web 模块的演变和发展历程了。当然,我会尽可能通俗的讲述的。

1.1 SpringMVC阶段的历史

还记得咱在最开始,介绍 SpringFramework 整体的时候,那个年代其实就已经有 SpringMVC 了,不过当时 SpringMVC 的出现,是为了为 J2EE 的那些要钱的东西(对,就是 EJB 那一套)提供更佳的替代方案。

当时的 SpringFramework 看到市面上流行的那些笨重的家伙实在是看不下去,于是自己研究了一番,最终搞出了一个 SpringMVC 框架,不过在那个时候就已经叫 SpringWebMvc 了(可从 SpringFramework 的官方文档中找到当时很早期的称呼:docs.spring.io/spring-fram… )。SpringWebMvc 是基于 Servlet 规范之上实现的,所以要运行基于 SpringWebMvc 的项目,需要 Servlet 容器服务器支撑(Tomcat 、Jetty 等)。

当时除了 SpringWebMvc 之外,当时还有一个比较流行的是 Struts2 ,它们两个共同占据了表现层框架的绝大部分。不过他俩最大的区别是,SpringWebMvc 的核心是 Servlet ,而 Struts2 的核心是 Filter 。不过后来随着 SpringWebMvc 的强大,以及 Struts2 被曝出好多漏洞,在 2012 年 SpringWebMvc 已经全面赶超了 Struts2 ,成为了最流行的 JavaEE 表现层框架。

1.2 WebMvc与WebFlux

时间来到 2017 年,当时微服务、响应式的概念已经闹得很火了,加上互联网应用的并发量要求越来越高,Spring 家族的开发者自然能看得清楚局势,于是在 SpringFramework 5.0 正式推出了 WebMvc 的兄弟 WebFlux 。

WebFlux 不同于 WebMvc ,它不是基于 Servlet 规范设计的,而是基于响应式、异步非阻塞等理念设计。响应式、非阻塞代表着更高的并发承载,所以基于 WebFlux 的项目,可以扛得住单位时间更多的请求。

因为它不基于 Servlet 规范,所以它也就不再强依赖于 Servlet 环境,而是必须运行在非阻塞环境的 Web 容器(Netty 、Undertow 等)。另外,WebFlux 是基于响应式的,所以它也需要找一套响应式框架作为支撑。在 Reactor 和 RxJava 中,Spring 选择了 Reactor ,所以这就意味着使用 WebFlux 之前,还要先会响应式、Reactor 编程。

当然,也正是因为这些原因,加上市面上目前 WebFlux 的实践还是太少,所以小册并不打算展开讲解 WebFlux ,只是这里提一下。如果小伙伴对 WebFlux 的确有需求,可以参照官方文档、借助搜索引擎来学习。

2. SpringWebMvc快速使用【掌握】

接下来,咱直接进入正题吧,通过一个简单的示例,来快速体会使用 SpringWebMvc 的方便。

2.1 工程环境创建

首先,咱先用 web.xml 的方式,搭建 WebMvc 的简单示例。

依然,使用 Maven 创建工程,工程名就叫 spring-04-webmvc 吧。这里面的依赖,就不能只导入 spring-web 了,要一起导 spring-webmvc :

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>

</dependencies>

除了这些还不够,servlet 和 jsp 、jstl 的 API 也要导进来,不然支持不了 jsp 页面的:

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

哦对了,最后一点,不要忘记 <packaging>war</packaging> 。

2.2 基础代码

然后咱来准备一些基础代码,对于 SpringWebMvc 而言,它认为只要标注了 @Controller 注解的类,扫描到后都算控制器(类似于 Servlet 的概念),所以我们可以编写这样一个 DemoController 的类:

@Controller
public class DemoController {
    
}

里面干什么活呢?这样吧,我们就先来一个最简单的,当浏览器请求 /demo 时,能给你跳转到 demo.jsp 页面

那在 SpringWebMvc 中,就可以这样来编写:

    @RequestMapping("/demo")
    public String demo() {
        return "demo";
    }

解释一下这段代码的含义:@RequestMapping 注解表示了它要监听的请求 uri ,方法的返回值是 String 代表要跳转的页面,返回值就是页面的相对路径(无需加 .jsp 的后缀)

2.3 xml配置文件

有了 Controller ,没有组件扫描,那标注再多的模式注解也不好使,所以咱来写一个 spring-mvc.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: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/context 
                           https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.linkedbear.spring.a_quickstart"/>
</beans>

另外,这里面需要额外引入一个新的组件,叫视图解析器,它的作用就是将上面 Controller 中返回的那个字符串,解析为 jsp 页面。

如果小伙伴刚开始学的话,那直接照着小册抄过来就完全 OK :

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- jsp文件路径的前缀 -->
    <property name="prefix" value="/WEB-INF/pages/" />
    <!-- jsp文件的后缀 -->
    <property name="suffix" value=".jsp" />
</bean>

这样 spring-mvc.xml 就算配置完成了。

2.4 jsp页面

接下来创建 demo.jsp ,一般我们都会把 jsp 文件放在 WEB-INF 里面,以保证不会被外界用 url 的方式访问到,所以咱放到 WEB-INF/pages 中吧。里面的内容不用太复杂,就一个 h1 标签得了:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>demo</title>
</head>
<body>
    <h1>这里是demo页面</h1>
</body>
</html>

2.5 配置web.xml

最后该配置 web.xml 了,这里面的内容不需要我们先去折腾 Spring 的 IOC 容器,那是下一章的事了。现在的内容相对简单,咱直接说。SpringWebMvc 的核心控制器是一个 Servlet ,它的名称叫 DispatcherServlet 。所以我们需要在 web.xml 中配置一个 DispatcherServlet :

<?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_3_1.xsd"
         version="3.1">
    
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

注意这个地方,servlet-mapping 小册写的是拦截 / ,它代表的含义是:拦截所有请求、静态资源,但不拦截 .jsp 。

与之比较相似的一种写法是 /* ,它的含义是:拦截所有请求、静态资源、.jsp 页面。所以简单的来说,/* 会额外拦截 .jsp 页面

还有一种之前常见的写法:*.do 或者 *.action 等等,这种写法代表:只会拦截所有结尾为 .do 或者 .action 的请求,一般企业内部系统、或者前后端分离的情况下后端提供接口会用,现在用的已经不算很多了(大多都换用 RESTful 风格的 API 了,后面咱会讲的)。

写到这里,可能有些小伙伴察觉到一个问题:之前在 web.xml 中还配置 Spring 的配置文件加载了,咋这次没了呢?哎,如果小伙伴能意识到这个问题就非常好非常优秀哈,这个 DispatcherServlet 的初始化,就需要传入一个 SpringWebMvc 的配置文件,那就是刚才在上面写的那个 spring-mvc.xml 。所以我们还要给这个 DispatcherServlet 配置初始化参数:

    <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>
    </servlet>

嚯,属性名还是叫 contextConfigLocation 啊,那正好不用额外记了,下面的配置文件路径,根据自己的项目工程路径写就行。

当然,也可以不配置 contextConfigLocation ,只不过如果不配的话,它会用一种特殊的规则去找 xml 配置文件:classpath:{servlet-name}-servlet.xml ,中间的花括号替换为上面配置的 servlet-name 。以这种规则去找的话,那上面的配置方法,就会去找 classpath:dispatcherServlet-servlet.xml 文件。你看这个文件名就很不友好。。。所以我们都会在 web.xml 中老老实实的配置好,这样也省心。

2.6 测试运行

启动 Tomcat ,之后在浏览器访问 http://localhost:8080/spring-webmvc/demo ,浏览器可以成功跳转到 demo.jsp 页面:

Spring WebMvc基础-SpringWebMvc概述与简单实例

这样最简单的示例工程就搞定了。

3. 基于Servlet3.0规范的WebMvc搭建【掌握】

上面是基于 web.xml 的方式搭建的 SpringWebMvc 工程,接下来咱继续讲解如何借助 Servlet 3.0 规范,来构建基于 SpringWebMvc 的项目工程。

3.1 修改web.xml

基于 Servlet 3.0 规范的搭建,与 web.xml 是冲突的,所以咱要先把 web.xml 的东西拿走,所以在这里咱直接把 web.xml 删掉也行,全注释掉也行,看小伙伴的喜好了。

当注释掉 web.xml 的内容后,此时启动 Tomcat ,并访问 /demo 路径,便无法得到任何响应了。

3.2 编写WebApplicationInitializer的实现

上一章我们已经了解了如何借助 Servlet 3.0 规范来搞定 SpringFramework 与 web 整合,还记得吧,编写 WebApplicationInitializer 就可以了。上一章我们用的是借助 SpringWeb 已经提供的一个抽象类叫 AbstractContextLoaderInitializer ,在引入 SpringWebMvc 后,这里面又多了一些新的抽象类。在整合 SpringWebMvc 实现注解驱动开发时,我们通常是用 AbstractAnnotationConfigDispatcherServletInitializer 这个类来搞定,如果要 xml 配置文件与注解驱动混合使用,则用 AbstractDispatcherServletInitializer 即可。下面咱先介绍一下这两个类的设计,然后再编码。

3.2.1 AbstractDispatcherServletInitializer

这个类保留的核心模板方法,是用来创建 WebApplicationContext 的:

protected abstract WebApplicationContext createServletApplicationContext();

至于具体怎么创建 IOC 容器,那就是我们自己说了算了。所以这种方式适合用于 xml 配置文件驱动,或者混合驱动 IOC 容器的方式搭建工程。

注意:这个方法名,与上一章中提到的 createRootApplicationContext 不是一回事!!!所以我们会发现,引入 SpringWebMvc 之后,我们要分别创建两个不同的 ApplicationContext !!!至于为什么这么设计,咱下面会简单的解释。

除此之外,还有一个方法,用来声明 DispatcherServlet 拦截的路径,它就类比于 web.xml 中,配置 Servlet 的 url-pattern 。

protected abstract String[] getServletMappings();

上面咱也说了,通常情况下咱用 / 即可。

3.2.2 AbstractAnnotationConfigDispatcherServletInitializer

从类名都能看出来,继承这个抽象类,那就是点明了要用注解驱动了。所以上面的 createServletApplicationContext 方法也就由它实现了,不过具体的配置类还需要我们自己来搞定,所以它保留的核心模板方法,是获取配置类的:

protected abstract Class<?>[] getRootConfigClasses();
protected abstract Class<?>[] getServletConfigClasses();

对嘛,基于注解驱动的话,都是以配置类的方式,所以这里只需要定义好两组不同的配置类,返回出去,AbstractAnnotationConfigDispatcherServletInitializer 就会自动的将这些配置类分别注册进两个 IOC 容器中了。

使用它,整合 SpringWebMvc 也会更加容易,所以我们选用它作为编码的实现。

3.2.3 编码实现

好,下面咱来实现。

3.2.3.1 配置类

首先我们要分别对这两个不同的 IOC 容器,提供两组配置类。简单工程我们就不搞那么复杂了,一样一个就得了:

@Configuration
public class RootConfiguration {
    
}
@Configuration
@ComponentScan("com.linkedbear.spring.b_anno.controller")
public class WebMvcConfiguration {
    
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/pages/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

上面的 RootConfiguration 代表根容器,既然我们还不知道根容器是啥,也不知道它的作用,那就先空着吧;下面的 WebMvcConfiguration ,里面的内容就跟上面基于 xml 配置文件中,里面干的事一模一样就得了。

3.2.3.2 Controller

这个 Controller 我们就不再重复写了,直接把上面的那个 DemoController 拿过来得了。

3.2.3.3 Initializer的编写

最后,我们来编写一个 AbstractAnnotationConfigDispatcherServletInitializer 的实现类,并指定对应的配置类、url-pattern :

public class SpringWebMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {RootConfiguration.class};
    }
    
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {WebMvcConfiguration.class};
    }
    
    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }
}

OK ,这样一切就大功告成了。

3.3 测试运行

重新启动 Tomcat ,之后浏览器依然是访问 http://localhost:8080/spring-webmvc/demo ,发现浏览器仍然可以成功跳转到 demo.jsp 页面,说明基于 Servlet 3.0 规范的整合 WebMvc 的方式也是编写的木有问题。

3.4 根容器与Servlet子容器?

为什么 SpringWebMvc 会设计一个根容器和 Servlet 的子容器呢?这种具有父子关系的层次性容器,设计之初的目的是什么呢?想必这是不少小伙伴的疑惑。而这个疑惑,我们可以从 SpringWebMvc 的官方文档上找到一些解释。

翻开官方文档 docs.spring.io/spring-fram… ,

可以发现这样一张图:

Spring WebMvc基础-SpringWebMvc概述与简单实例

从这上面可以很明显的看出 ApplicationContext 的层次性特征吧!对于一个基于 SpringWebMvc 的应用,它希望把 Service 、Dao 等类都放到根容器,把表现层的 Controller 及相关的组件都放到 Servlet 的子容器中,以此形成一个层级关系。

其实在上面,使用 web.xml 的方式整合 SpringWebMvc 的时候,也可以形成父子容器的,只需要把之前写的那个 ContextLoaderListener 配置上,就可以形成父子容器,只不过小册一开始并没有那么做罢了。

这种父子容器的设计,好处有两个:第一,形成层级关系后,Controller 可以拿到 Service ,而 Service 拿不到 Controller ,可以以此形成一道隔离;第二,如果真的出现特殊情况,需要注册多个 DispatcherServlet 的时候,不必注册多套 Service 和 Dao ,每个 Servlet 子容器都从这一个根容器中取 Service 和 Dao 即可。

但这样也会伴随一个小小的坑:在进行包扫描的时候,Servlet 子容器只能扫描 @Controller 注解,而不能扫描 @Service 、@Repository 等注解,否则会导致子容器中存在 Service 而不会去父容器中寻找,从而引发一些问题(如事务失效、AOP 增强失效等)。

好,到这里咱先热热身,搭建了一个 SpringWebMvc 的工程,下一章开始咱就开始实际的向实际业务开发进军了。

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Spring WebMvc基础-SpringWebMvc概述与简单实例