16. Spring 入门 MVC
16.1. Spring MVC 在 Web 应用中的地位
让我们将 Spring MVC 置于 Web 应用程序的开发中。通常情况下,该应用程序将基于如下所示的多层架构构建:
![]() |
- [Web]层是与Web应用程序用户直接交互的层。用户通过浏览器显示的网页与Web应用程序进行交互。Spring MVC就位于这一层,且仅位于这一层;
- [métier]层实现应用程序的管理规则,例如工资或发票的计算。 该层通过 [Web] 层使用来自用户的数据,并通过 [DAO] 层使用来自 SGBD 的数据;
- [DAO] 层(数据访问对象)、[ORM] 层(对象关系映射器)以及 JDBC 驱动程序负责管理对 SGBD 层数据的访问。 [ORM] 层在 [DAO] 层处理的对象与关系型数据库表中的行和列之间架起了一座桥梁。 一项名为 JPA(Java Persistence API)的规范允许对所使用的 ORM 进行抽象,前提是该 ORM 实现了这些规范。 本教程中即属此情况,因此我们将 ORM 层称为 JPA 层;
- 各层的集成由 Spring 框架完成;
16.2. Spring MVC的开发模型
Spring MVC 以如下方式实现了所谓的 MVC 架构模型(模型 – 视图 – 控制器):
![]() |
客户端请求的处理流程如下:
- 请求 - 请求的 URL 采用 http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... 格式[Front Controller] 使用配置文件或 Java 注解将请求“路由”到正确的控制器以及该控制器内的正确操作。 为此,它会使用 URL 中的 [Action] 字段。URL 和 [/param1/param2/...] 的其余部分由可选参数组成,这些参数将传递给操作。 此处的 MVC 的 C 字段即为字符串 [Front Controller, Contrôleur, Action]。如果没有任何控制器能处理所请求的操作,Web 服务器将返回“未找到所请求的 URL”的响应。
- 处理
- (续)
- 所选操作可利用 parami 传递给它的参数。这些参数可能来自多个来源:
- 来自 URL 的路径 [/param1/param2/...],
- 来自 URL 的 [p1=v1&p2=v2] 参数
- 浏览器随请求发送的参数;
- 在处理用户请求时,该操作可能需要 [métier] 和 [2b] 层。一旦处理了客户端的请求,该操作可能会触发各种响应。一个典型的例子是:
- 如果请求无法正确处理,则返回错误页面
- 否则显示确认页面
- 该操作会要求显示某个视图 [3]。该视图将显示被称为视图模型的数据。这就是 MVC 中的 M。 该操作将创建视图模型 M [2c],并要求显示视图 V [3];
- 响应——选定的视图 V 使用操作构建的模型 M 来初始化响应 HTML 的动态部分(该响应需发送给客户端),随后发送此响应。
对于 Web 服务 / jSON,上述架构稍作修改:
![]() |
- 在 [4a] 中,作为 Java 类的模型通过 jSON 库转换为字符串 jSON;
- 在 [4b] 中,该字符串 jSON 被发送至浏览器;
现在,让我们明确MVC Web架构与分层架构之间的关系。根据对模型的定义,这两个概念可能相关,也可能无关。以一个单层的Spring Web应用程序MVC为例:
![]() |
如果我们使用 Spring MVC 实现 [Web] 层,那么虽然会形成 MVC Web 架构,但并非多层架构。 在此情况下,[web]层将包揽所有工作:展示、业务逻辑、数据访问。这些工作将由Action类来完成。
现在,让我们考虑一种多层Web架构:
![]() |
[Web]层可以在不使用框架且不遵循MVC模型的情况下实现。这样虽然确实形成了一个多层架构,但Web层并未实现MVC模型。
例如,在 .NET 环境中,上述 [Web] 层,可使用 ASP.NET 和 MVC 来实现,从而形成一种分层架构,其中 [Web] 层属于 MVC 类型。 完成上述操作后,可以将该 ASP.NET MVC 层替换为经典的 ASP.NET 层(WebForms),同时保持其余部分 (业务层、DAO、ORM)保持不变。 此时便形成了一个分层架构,其中 [Web] 层不再属于 MVC 类型。
在 MVC 中,我们提到模型 M 即视图 V(c.a.d)所展示的数据集合。这里给出 MVC 模型 M 的另一种定义:
![]() |
许多作者认为,位于 [Web] 层右侧的内容构成了 MVC 的模型 M。为避免歧义,我们可以这样表述:
- 领域模型(指 [Web] 层右侧的所有内容)
- 当指代视图 V 所显示的数据时,称为“视图模型”
下文中,“M模型”一词将专指视图V的模型。
16.3. 基于 Spring 的 Web 项目 / jSON
[http://spring.io/guides] 网站提供了入门教程,帮助您了解 Spring 生态系统。我们将参考其中一个教程,了解 Spring 项目 MVC 所需的 Maven 配置。
16.3.1. 演示项目
![]() |
- 在 [1] 中,我们导入了一个 Spring 指南;
![]() |
- 在 [2] 中,我们选择示例 [Rest Service];
- 在 [3] 中,选择 Maven 项目;
- 在 [4] 中,我们选用指南的最终版本;
- 在 [5] 中,进行确认;
- 在 [6] 中,导入该项目;
可通过标准 URL 访问并返回 jSON 文本的 Web 服务通常被称为 REST 服务(REpresentational 状态传递)。 如果一个服务遵守某些规则,则被称为 Restful 服务。
现在让我们来查看导入的项目,首先是其Maven配置。
16.3.2. Maven配置
[pom.xml] 文件内容如下:
<?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>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- 第 6-8 行:Maven 项目的属性。缺少一个 [<packaging>] 标签,该标签用于指定 Maven 编译生成的文件类型。若未指定,则默认使用 [jar] 类型。 因此,该应用程序是一个控制台类型的可执行应用程序,而非 Web 应用程序(如果是 Web 应用程序,其打包类型应为 [war]);
- 第10-14行:该Maven项目有一个父项目[spring-boot-starter-parent]。该项目定义了项目的大部分依赖项。如果这些依赖项已足够,则无需添加;否则,需补充缺失的依赖项;
- 第17-20行:[spring-boot-starter-web]构建产物包含Spring项目MVC所需的库,该项目属于Web服务类型,其中不包含生成的视图。 该工件随附了大量库,其中包括嵌入式 Tomcat 服务器的相关库。应用程序将在该服务器上运行;
此配置包含的库数量非常多:
![]() | ![]() |
上图显示了三个 Tomcat 服务器的归档文件。
16.3.3. Spring服务的架构 [web / jSON]
对于 Web 服务 / jSON,Spring MVC 通过以下方式实现 MVC 模型:
![]() |
- 在 [4a] 中,该模型(作为 Java 类)通过 jSON 库转换为字符串 jSON;
- 在 [4b] 中,该字符串 jSON 被发送至浏览器;
16.3.4. C控制器
![]() |
导入的应用程序具有以下控制器:
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
- 第 9 行:注解 [@RestController] 将类 [GreetingController] 定义为 Spring 控制器,即其方法被注册为处理 URL。 我们之前见过类似的注解 [@Controller]。该控制器方法的返回类型是 [String],即待显示视图的名称。而这里的情况有所不同。 类型为 [@RestController] 的控制器方法会返回对象,这些对象会被序列化后发送至浏览器。所采用的序列化类型取决于 Spring 的配置 MVC。 在此,它们将被序列化为 jSON。这是因为项目依赖中存在 jSON 库,导致 Spring Boot 通过自动配置以这种方式配置项目;
- 第 14 行:注解 [@RequestMapping] 指明了该方法处理的 URL,此处为 URL [/greeting];
- 第 15 行:我们已经解释过注解 [@RequestParam]。该方法返回的值是一个 [Greeting] 类型的对象。
- 第 12 行:一个原子类型的长整型。这意味着它支持并发访问。多个线程可能希望同时递增变量 [counter]。这将安全地进行。只有当正在修改计数器的线程完成修改后,其他线程才能读取该计数器的值。
16.3.5. M 模型
由前一种方法生成的 M 模型是以下对象 [Greeting]:
![]() |
package hello;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
对该对象进行 jSON 转换将生成字符串 {"id":n,"content":"文本"}。最终,控制器方法生成的 jSON 字符串将呈现为:
或
16.3.6. 执行
![]() |
类 [Application.java] 是该项目的可执行类。其代码如下:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
我们在前面的示例中已经遇到并解释过这段代码。现在运行该项目:
![]() |
我们将获得以下控制台日志:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.1.9.RELEASE)
2014-11-28 15:22:55.005 INFO 3152 --- [ main] hello.Application : Starting Application on Gportpers3 with PID 3152 (started by ST in D:\data\istia-1415\spring mvc\dvp-final\gs-rest-service)
2014-11-28 15:22:55.046 INFO 3152 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@62e136d3: startup date [Fri Nov 28 15:22:55 CET 2014]; root of context hierarchy
2014-11-28 15:22:55.762 INFO 3152 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2014-11-28 15:22:56.567 INFO 3152 --- [ main] .t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
2014-11-28 15:22:56.738 INFO 3152 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2014-11-28 15:22:56.740 INFO 3152 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.56
2014-11-28 15:22:56.869 INFO 3152 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2014-11-28 15:22:56.870 INFO 3152 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1827 ms
2014-11-28 15:22:57.478 INFO 3152 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2014-11-28 15:22:57.481 INFO 3152 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2014-11-28 15:22:57.685 INFO 3152 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.879 INFO 3152 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/greeting],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public hello.Greeting hello.GreetingController.greeting(java.lang.String)
2014-11-28 15:22:57.884 INFO 3152 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.885 INFO 3152 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.906 INFO 3152 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] 类型为 [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 的处理程序
2014-11-28 15:22:57.907 INFO 3152 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] 类型为 QZXW2HTML 的处理程序 BW2NsYXNzIG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQucmVzb3VyY2UuUmVzb3VyY2VIdHRwUmVxdWVzdEhhbmRsZXJdZQX
2014-11-28 15:22:58.231 INFO 3152 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2014-11-28 15:22:58.318 INFO 3152 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-11-28 15:22:58.319 INFO 3152 --- [ main] hello.Application : Started Application in 3.788 seconds (JVM running for 4.424)
- 第 13 行:Tomcat 服务器在 8080 端口上启动(第 12 行);
- 第 17 行:存在 [DispatcherServlet] Servlet;
- 第 20 行:已发现方法 [GreetingController.greeting];
为测试该Web应用程序,请求URL [http://localhost:8080/greeting]:
![]() | ![]() |
确实收到了预期的字符串 jSON。查看服务器发送的 HTTP 头部信息可能会很有趣。 为此,我们将使用名为 [Advanced Rest Client] 的 Chrome 扩展程序(Chrome / Ctrl-T / 菜单 [Applications] / [Advanced Rest Client] - 参见附录第 23.11 节):
![]() |
- 在 [1] 中,请求的是 URL;
- 在 [2] 中,采用 GET 方法;
- 在 [3] 中,响应为 jSON;
- 在 [4] 中,服务器表示将发送格式为 jSON 的响应;
- 在 [5] 中,请求的是相同的 URL,但这次使用的是 POST;
- 在 [7] 中,信息以 [urlencoded] 的形式发送给服务器;
- 在 [6] 中,name 参数及其值;
- 在 [8] 中,浏览器告知服务器将发送 [urlencoded] 信息;
- 在 [9] 中,服务器给出的响应 jSON;
16.3.7. 创建可执行存档
现在我们创建一个可执行存档:
![]() |
![]() |
- 生成 [1]:执行一个 Maven 目标;
- 在 [2] 中:有两个目标(goals):[clean] 用于从 Maven 项目中删除 [target] 文件夹,[package] 用于重新生成该文件夹;
- 在 [3] 中:生成的 [target] 文件夹将位于此文件夹中;
- 在 [4] 阶段:生成目标;
在控制台显示的日志中,务必确认出现插件 [spring-boot-maven-plugin]。正是它生成可执行存档(参见下文的 [pom.xml]):
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
在控制台中,进入生成的文件夹:
D:\Temp\wksSTS\gs-rest-service\target>dir
...
11/06/2014 15:30 <DIR> classes
11/06/2014 15:30 <DIR> generated-sources
11/06/2014 15:30 11 073 572 gs-rest-service-0.1.0.jar
11/06/2014 15:30 3 690 gs-rest-service-0.1.0.jar.original
11/06/2014 15:30 <DIR> maven-archiver
11/06/2014 15:30 <DIR> maven-status
...
- 第 5 行:生成的归档文件;
该存档文件的执行方式如下:
D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.1.0.RELEASE)
2014-06-11 15:32:47.088 INFO 4972 --- [ main] hello.Application
: Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...
现在 Web 应用程序已启动,可通过浏览器访问:
![]() |
16.3.8. 将应用程序部署到 Tomcat 服务器
与上一个项目一样,我们将文件 [pom.xml] 修改如下:
<?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>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<packaging>war</packaging>
...
</project>
- 第 9 行:需指定将生成一个 WAR 归档文件(Web ARchive);
此外,还需配置Web应用程序。若不存在[web.xml]文件,则需通过继承自[SpringBootServletInitializer]的类来实现:
![]() |
类 [ApplicationInitializer] 如下所示:
package hello;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ApplicationInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
- 第 6 行:类 [ApplicationInitializer] 继承自类 [SpringBootServletInitializer];
- 第 9 行:方法 [configure] 被重定义(第 8 行);
- 第 10 行:提供了配置该项目的类;
要运行该项目,可以按以下步骤操作:
![]() |
- 在 [1-2] 中,在 Eclipse 中注册的某台服务器上运行该项目;
完成上述操作后,可在浏览器中访问 URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell]:
![]() |
16.4. 结论
我们介绍了一种 Spring 项目类型,其中 Web 应用程序会向浏览器发送数据流。 接下来,我们将开发一个 Web 应用程序 / jSON,用于在 Web 上公开前几章所研究的 [dbproduitscategories] 数据库。























