SpringMVC 构建 RESTful Web 服务

因工作需要和个人技术兴趣,而需要学习一下 Java Web开发。本文记录了学习如何在 IntelliJ + Tomcat + Maven + SpringMVC 环境下,开发 RESTful Web 服务的过程。

要构建的 Web 服务

在这份文档中,我们将经历一个构建简单的 “hello, world” 级的 Spring MVC RESTful Web服务的过程。我们构建的服务将可以通过如下 URL 的 HTTP GET 请求进行访问:

1
http://localhost:8080/greeting

这个接口将为我们返回 JSON 表示的问候语:

1
{"id":1,"content":"Hello, World!"}

我们可以通过 query 字符串中可选的参数 name 来定制问候语:

1
{"id":1,"content":"Hello, User!"}

创建工程

在菜单栏选择 File -> New -> Project… 打开创建工程的窗口,如下图:

然后选中左边的 Spring 一项,在右边选择 Spring,以及 Spring 下的 Spring MVC。在选中 Spring 下的 Spring MVC 时, Web Application 也会被自动选中。选中之后,需要等 IntelliJ 加载一会儿组件的版本信息,在选中的组件后面出现括号包着的灰色版本号时,说明版本信息加载完成了。然后点击 Next,进入如下界面:

输入工程名称,设置工程的根目录,并点击 Finish。此时 IntelliJ 将帮我们自动下载所需的 Jar 文件:

下载完成之后,项目就建好了。整个项目的结构如下:

lib 下存放我们下载的 jar 文件,以及其它在项目中要用到的第三方库文件。src 用于放置我们的 Java 代码,而 web 目录则存放我们的 Web 应用的配置文件等。

新建包

通过 IntelliJ 菜单的 File -> New -> Package打开创建包的窗口:

输入包名 com.hanpfei 并点 OK 键。此时,工程的整个结构如下:

包直接位于 src 目录下。

创建资源表示类

接着我们可以创建我们的 Web 服务了。首先考虑服务的接口。服务将处理 /greetingGET 请求,在 query 串中有一个可选的 name 参数。GET 请求应该返回一个 200 OK响应,而在响应体中是 JSON 格式表示的问候语。它看起来应该像这样:

1
2
3
4
{
"id": 1,
"content": "Hello, World!"
}

其中 id 字段是唯一的问候语标识符,content 是文本表示的问候语。

我们创建一个资源表示类来建模问候语表示。提供一个含有字段,构造器,和 idcontent 的访问器的 POJO 对象。
src/com/hanpfei/Greeting.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.hanpfei;
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;
}
}

如我们在下面的步骤中所见,Spring 将使用 Jackson JSON 库自动地将类型 Greeting 的实例序列化 JSON。

接着我们创建资源控制器来提供这些问候语服务。

创建资源控制器

在构建 RESTful Web服务的 Spring 方法中,HTTP 请求由一个控制器处理。这些组件简单地通过 @RestController 注解标识,下面的 GreetingController 处理对 /greetingGET 请求,并返回 Greeting 类的实例。
src/com/hanpfei/GreetingController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hanpfei;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicLong;
@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));
}
}

这个控制器是简洁而简单的,但是在底层还是有着大量的东西。让我们一步一步地分解它。

@RequestMapping 注解确保对 /greeting 的HTTP 请求被映射到 greeting() 方法。上面的例子没有指定 GETPUTPOST,等等,因为 @RequestMapping 默认映射所有的 HTTP 操作。使用 @RequestMapping(method=GET) 来缩窄这种映射。

@RequestParam 将 query 字符串参数 name 的值绑定到 greeting() 方法的 name 参数。这个 query 字符串参数被明确地标记为可选的(默认是 required=true ):如果在请求中没有,将使用 “World” 作为 defaultValue

方法体的实现创建并返回一个新的具有基于 counter 的下一个值的 idcontent 属性的 Greeting 对象,并通过使用问候语 template 将给定的 name 格式化。

传统的 MVC 控制器和上面的 RESTful Web 服务控制器之间的一个重要不同是 HTTP 响应体创建的方式。这个 RESTful Web 服务控制器不是依赖于 视图技术 来执行服务器端将问候数据呈现为HTML,而是简单地填充并返回Greeting对象。对象数据将被以 JSON 的格式直接写入 HTTP 响应。

上面的代码使用 Spring 4 的新 @RestController 注解,它将类标记为一个控制器,其中每个方法返回一个域对象而不是一个View。它是 @Controller@ResponseBody 的缩写。

Greeting 对象必须被转换为JSON。有赖于 Spring 的 HTTP 消息转换器支持,你无需手动地做这种转换。。由于 Jackson 2 在 classpath 上,Spring 的 MappingJackson2HttpMessageConverter 被自动地选择来将 Greeting 实例转换为 JSON。

使应用程序可执行

尽管将这个服务打包为传统的 WAR 文件然后部署到一个外部应用服务器上是可能的,然而下面将演示更简单的方法,创建一个独立的应用。将所有的东西打包为一个单独的,可执行的 JAR 文件,由一个老式的 Java main() 方法驱动。一路上,使用 Spring 的支持将 Tomcat servlet 容器嵌入为 HTTP 运行时,而不是部署到外部实例。
src/com/hanpfei/Application.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.hanpfei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@SpringBootApplication是一个方便的注解,它将添加下面所有的东西:

  • @Configuration 标记类为应用上下文的一个 bean 定义源。
  • @EnableAutoConfiguration 告诉 Spring Boot 基于 classpath 设置添加 beans,其它 beans 和不同的属性设置。
  • 通常你将为一个 Spring MVC app 添加 @EnableWebMvc,但 Spring Boot 会在 classpath 上看到 spring-webmvc 时自动地添加它。这将应用标记为一个 Web 应用,并激活诸如设置一个 DispatcherServlet 这样的重要行为。
  • @ComponentScan 告诉 Spring 在 com.hanpfei 包中寻找其它组件,配置,和服务,使它可以找到控制器。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法启动一个应用。你是否注意到没有一行的XML。也没有 web.xml 文件。这个 Web 应用是 100% 的纯 Java应用,你不必处理配置任何管道或基础设施。

然而此时我们在 IntelliJ 中发现,找不到 org.springframework.boot 包下的组件。我们还需要导入如下的几个 jar 包,来使这个 Web 应用运行起来:

1
2
3
4
spring-boot-1.4.1.RELEASE.jar
spring-boot-autoconfigure-1.4.1.RELEASE.jar
spring-boot-starter-1.5.1.RELEASE.jar
spring-boot-starter-web-1.5.1.RELEASE.jar

方法是在 Maven Repository 找到这几组件,并下载适当的版本到 HelloHanpfeiSpring/lib 目录下。如果本地其它项目中已经有了这些文件,或者本地 Maven 仓库(位于 ~/.m2/repository/org/springframework/boot )中下载过了这些文件,也可以直接拷贝过去。

有了需要的 Jar 包之后,还需要为项目添加对这些 Jar 包的依赖。方法是通过菜单栏 File -> Project Structure… (或使用 Ctrl + Alt + Shift + S )打开如下对话框:

选中左边的 Libraries,点击中间一栏上面的 “+” 号,选中其中的 Java,在弹出的文件选择对话框中,选中 spring-boot-1.4.1.RELEASE.jarspring-boot-autoconfigure-1.4.1.RELEASE.jar

然后以同样的方法添加 spring-boot-starter-1.5.1.RELEASE.jarspring-boot-starter-web-1.5.1.RELEASE.jar。引用的库的配置将如下图:

点击 OK 键结束添加。

新建 Run/Debug Configuration

为了使我们的 Web 应用可以跑起来,我们还需要创建一个 Run/Debug Configuration。方法为通过菜单的 Run -> Edit Configuratons … 打开编辑配置对话框:

点击右上角的 “+” 号,并在弹出的一列选项中,找到 Spring Boot 并选中,此时对话框将变成如下的样子:

我们输入 Name,配置 Main class,最终将像下面这样:

点击 OK 退出。此时我们就可以通过工具栏中的 运行 按钮来运行我们的 Web 应用了:

此时我们点击 运行 按钮,结果如下:

What a Terrible Failure!!! Web 应用进程竟然在我们的请求还没发出的时候,就直接退出了,退出了,了,了。。。

不着急。回想前文有提到,这样运行需要借助于 Tomcat 运行时。于是我们找到相关的几个 Jar 文件,像导入 SpringBoot 一样导入我们的工程(同样可以到 Maven Respository 下载):

1
2
3
tomcat-embed-core-8.5.11.jar
tomcat-embed-el-8.5.11.jar
tomcat-embed-websocket-8.5.11.jar

再次运行我们的 Web 应用。终于欢快地跑起来了:

测试服务

接着我们来测试一下我们新建的 Web 应用。在浏览器的地址栏中输入 http://localhost:8080/greeting,我们看到了:

新手定律说得好,问题总是喜欢刁难初学者,这真是一点也不错。这不又出错了么。此时在 IntelliJ 的运行控制台,我们看到了如下的错误输出:

1
2
3
4
5
6
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: No converter found for return value of type: class com.hanpfei.Greeting] with root cause
java.lang.IllegalArgumentException: No converter found for return value of type: class com.hanpfei.Greeting
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:187)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:174)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:132)

提示没有 com.hanpfei.Greeting 类的转换器,这可让我们如何是好啊。

前面不是说,只要 Jackson 在 classpath 上,转 JSON 的动作就会自动完成么。不对,那可能是在我们的环境中,Jackson 并没有在 classpath 上。像前面的 spring-boot 和 tomcat-embed 那样,我们将 Jackson 相关的几个 jar 包导入:

1
2
3
jackson-annotations-2.8.0.jar
jackson-core-2.8.6.jar
jackson-databind-2.8.6.jar

在浏览器中,刷新 Url,终于看到了:

1
{"id":1,"content":"Hello, World!"}

提供一个 name query 字符串参数,http://localhost:8080/greeting?name=hanpfei。注意,content 属性的值是如何从 “Hello, World!” 变为 “Hello, hanpfei!” 的:

1
{"id":2,"content":"Hello, hanpfei!"}

这个变动演示了 GreetingController 中安排的 @RequestParam 如预期般运行了。 name 参数已经给了一个默认的值 “World”,但总是可以被明确地通过 query 字符串覆盖。

同时注意 id 属性是如何从 1 变为 2 的。这证明了多个请求是运行于相同的 GreetingController 实例之上的,且它的 counter 字段在每次调用时如预期那样递增了。

在 Tomcat 上运行

安装配置 Tomcat 的方法可以参考 Ubuntu 16.04 Tomcat 8安装指南。这里我们主要来看在 IntelliJ 中如何将我们的 Web 应用跑在 Tomcat上。

首先我们需要创建一个Application Server。在菜单栏选择 File -> Settings … ,然后选择 Build, Execution, Deployment -> Application Servers。点击中间一栏的 “+” 加号,在弹出的选项列表中选择 Tomcat Server,在弹出的对话框中配置 Tomcat 主目录:

完成之后点击 “OK” 按钮。最终将像下面这样:

然后添加一个 Run/Debug Configuration。通过菜单栏的 Run -> Edit Configuratons … 打开编辑配置对话框,点击右上角的 “+” 号,并在弹出的一列选项中,找到 Tomcat Server -> Local 并选中。然后输入配置名称,并适当的配置 Application server 为我们之前创建的配置。结果如图所示:

最下面可以看到 Warning 行,且左方的 Tomcat-8.0.32 图标上有一个错误标记,说明还没有配置完全。我们还需要将项目部署到 Tomcat 服务器中。点击 Deployment,再点击中间上方的 ”+“ 号,添加一个 Artifact,最终将像下面这样:

再点击OK,整个Tomcat配置结束。

点击工具栏中的运行按钮,就可以启动 Tomcat 了,其控制台输出将在 IDEA 下方显示。再次遭受了新手定律的困扰:在控制台输出中,我们看到 Demo Spring MVC 应用部署失败,One or more listeners failed to start.,但需要到应用容器的 log 中寻找关于错误的更多信息。

在控制台中还可以看到如下这样的一些输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/opt/tomcat/bin/catalina.sh run
Using CATALINA_BASE: /home/hanpfei0306/.IntelliJIdea2016.3/system/tomcat/Unnamed_HelloHanpfeiSpring
Using CATALINA_HOME: /opt/tomcat
Using CATALINA_TMPDIR: /opt/tomcat/temp
Using JRE_HOME: /usr/lib/jvm/java-8-openjdk-amd64
Using CLASSPATH: /opt/tomcat/bin/bootstrap.jar:/opt/tomcat/bin/tomcat-juli.jar
[2017-02-28 03:25:20,841] Artifact HelloHanpfeiSpring:war exploded: Server is not connected. Deploy is not available.
28-Feb-2017 15:25:21.365 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version: Apache Tomcat/8.0.32
28-Feb-2017 15:25:21.366 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Feb 2 2016 19:34:53 UTC
28-Feb-2017 15:25:21.366 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server number: 8.0.32.0
28-Feb-2017 15:25:21.367 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
28-Feb-2017 15:25:21.367 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 4.4.0-62-generic
28-Feb-2017 15:25:21.367 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
28-Feb-2017 15:25:21.369 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /usr/lib/jvm/java-8-openjdk-amd64/jre
28-Feb-2017 15:25:21.369 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 1.8.0_121-8u121-b13-0ubuntu1.16.04.2-b13
28-Feb-2017 15:25:21.369 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Oracle Corporation
28-Feb-2017 15:25:21.369 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /home/hanpfei0306/.IntelliJIdea2016.3/system/tomcat/Unnamed_HelloHanpfeiSpring
28-Feb-2017 15:25:21.369 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /opt/tomcat
28-Feb-2017 15:25:21.370 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/home/hanpfei0306/.IntelliJIdea2016.3/system/tomcat/Unnamed_HelloHanpfeiSpring/conf/logging.properties
28-Feb-2017 15:25:21.370 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
28-Feb-2017 15:25:21.370 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcom.sun.management.jmxremote=

通过控制台输出中的 CATALINA_BASE 定义找到 log 文件夹的位置:

1
/home/hanpfei0306/.IntelliJIdea2016.3/system/tomcat/Unnamed_HelloHanpfeiSpring/logs

在其中的 localhost.2017-02-28.log 中可以看到如下的 error log:

1
2
3
4
28-Feb-2017 15:17:39.814 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.StandardContext.listenerStart Error configuring application listener of class org.springframework.web.context.ContextLoaderListener
java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1308)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1142)

即找不到 Spring 的一个 class org.springframework.web.context.ContextLoaderListener。在网上搜了半天,终于找到解法,ClassNotFoundException:ContextLoaderListener。解法其实简单,就是将下载的 Spring 的 lib 文件夹移到项目的 web/WEB-INF/ 下。工程的结构将像下面这样:

再次尝试在 Tomcat 中运行我们的 Web应用,结果如下:

部署终于成功了。(通过菜单栏打开 File -> Project Structure… (或使用 Ctrl + Alt + Shift + S )工程配置对话框,选中左边的 Libraries,重新配置之前我们导入的 Jar 包,将仍然可以 通过前面配置的 Spring Boot 运行配置运行我们的 Web 应用,否则将由于符号找不到而运行失败。)

而当我们通过 http://localhost:8080/greeting?name=hanpfei 访问我们的 Web 服务,却发现返回了 404:

配置 web.xml

我们的 Web 服务没能在 Tomcat 中跑起来,不急,我们还没通过 web.xml 配置 Servlet 呢。

打开 HelloHanpfeiSpring/web/WEB-INF 下的 web.xml 文件,稍微更新一下web.xml 的版本,可以支持更高级的一些语法,如下:

1
2
3
4
5
6
7
8
9
<?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">
<display-name>HelloHanpfeiSpring Web Application</display-name>
</web-app>

<web-app> 中加入一个 Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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">
<display-name>HelloHanpfeiSpring Web Application</display-name>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

Servlet 名为 dispatcher(名称可修改),用于拦截请求(url-pattern为 / ,说明拦截所有请求),并交由 Spring MVC 的后台控制器来处理。这一项配置是必须的。

为了能够处理中文的 POST 请求,再配置一个 encodingFilter,以避免 POST 请求中文出现乱码情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<filter>
<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>

至此,web.xml 配置完毕。

xxx-servlet.xml配置

在配置完 web.xml 后,需配置与 web.xml 在同级目录下的 dispatcher-servlet.xml( -servlet 前面的 xxx 是在 Servlet 里面定义的servlet名)。

首先加入 component-scan 标签,指明 controller 所在的包,并扫描其中的注解(最好不要复制,输入时按IDEA会在beans xmlns中添加相关内容):

1
2
3
4
5
6
7
8
9
10
11
12
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--指明 controller 所在包,并扫描其中的注解-->
<context:component-scan base-package="com.hanpfei"/>
</beans>

再进行js、image、css等静态资源访问的相关配置,这样,SpringMVC才能访问网站内的静态资源:

1
2
<!-- 静态资源(js、image等)的访问 -->
<mvc:default-servlet-handler/>

再开启 SpringMVC 注解模式,由于我们利用注解方法来进行相关定义,可以省去很多的配置:

1
2
<!-- 开启注解 -->
<mvc:annotation-driven/>

再进行视图解析器的相关配置:

1
2
3
4
5
6
7
<!--ViewResolver 视图解析器-->
<!--用于支持Servlet、JSP视图解析-->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>

关于Controller 如何找到视图文件,这里需要详细的说明。在 Controller 的一个方法中,返回的字符串定义了所需访问的 jsp 的名字(如 index)。在jspViewResolver 中,有两个属性,一个是 prefix,定义了所需访问的文件路径前缀,另一是 suffix,表示要访问的文件的后缀,这里为 .jsp。那么,如果返回字符串是 xxx ,SpringMVC 就会找到 /WEB-INF/pages/xxx.jsp 文件。

完成以上配置后,dispatcher-servlet.xml 文件如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--指明 controller 所在包,并扫描其中的注解-->
<context:component-scan base-package="com.hanpfei"/>
<!-- 静态资源(js、image等)的访问 -->
<mvc:default-servlet-handler/>
<!-- 开启注解 -->
<mvc:annotation-driven/>
<!--ViewResolver 视图解析器-->
<!--用于支持Servlet、JSP视图解析-->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

此时,我们再次尝试在 Tomcat 中运行我们的 Web 应用。从 IntelliJ 的控制台输出中可以看到如下的报错信息:

1
2
3
4
5
6
7
8
9
28-Feb-2017 16:09:42.078 INFO [RMI TCP Connection(2)-127.0.0.1] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
28-Feb-2017 16:09:42.129 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.springframework.web.servlet.DispatcherServlet.initServletBean Context initialization failed
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 15 in XML document from ServletContext resource [/WEB-INF/dispatcher-servlet.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 15; columnNumber: 35; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但无法找到元素 'mvc:default-servlet-handler' 的声明。
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:399)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188)

提示我们的 dispatcher-servlet.xml 配置文件无效。在网上一通搜,找到解决方法,关于springMVC转换json出现的异常,修改 dispatcher-servlet.xml ,最终如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--指明 controller 所在包,并扫描其中的注解-->
<context:component-scan base-package="com.hanpfei" />
<!-- 静态资源(js、image等)的访问 -->
<mvc:default-servlet-handler/>
<!-- 开启注解 -->
<mvc:annotation-driven/>
<!--ViewResolver 视图解析器-->
<!--用于支持Servlet、JSP视图解析-->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

然而我们再次在 Tomcat 中运行我们的 Web 应用时,再一次出错了。由控制台输出,可以看到如下的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
... 66 more
Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.determineDriverClassName(DataSourceProperties.java:229)
at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.initializeDataSourceBuilder(DataSourceProperties.java:174)
at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.createDataSource(DataSourceConfiguration.java:42)
at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat.dataSource(DataSourceConfiguration.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
... 67 more

看上去主要与 JDBC 配置不当有关。而在我们的小应用中,完全没有用到 JDBC,因而我们将 spring-jdbc 从我们的 HelloHanpfeiSpring/web/WEB-INF/lib 目录下删除。

再次在 Tomcat 中运行我们的 Web 应用,终于OK了。可以像前面那样,在浏览器的地址栏中输入如下内容:

1
http://localhost:8080/greeting?name=hanpfei

来测试我们的 Web 服务了。

Maven 改造

为了方便工程的构建管理,我们将上面创建的工程转化为一个 Maven工程。

我们首先将 HelloHanpfeiSpring/web/WEB-INF/lib 目录整个删除,因为通过 Maven,我们无需在我们的工程中再放置任何 Jar 包,我们完全可以通过 Maven 的配置文件,来管理我们依赖的组件。

然后按照 Maven 的工程结构惯例调整我们工程的工程结构,最终结果将如下面这样:

Web应用的配置文件都被放入了 HelloHanpfeiSpring/src/main/ 下面,Java 代码则被放入了 HelloHanpfeiSpring/src/main/java 下。

同时为了减少冲突,而直接将文件 HelloHanpfeiSpring/src/main/com/hanpfei/Application.java 移除。

然后在工程的根目录添加构建、依赖配置文件 pom.xml,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>HelloHanpfeiSpring</groupId>
<artifactId>HelloHanpfeiSpring</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>HelloHanpfeiSpring Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>4.2.6.RELEASE</spring.version>
<hibernate.version>5.1.0.Final</hibernate.version>
<jackson.version>2.5.4</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.10.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<finalName>HelloHanpfeiSpring</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

然后选择工作窗口最右侧的 Maven Projects,并点击弹出来的窗口左上角的刷新按钮,等待 Maven 帮我们刷新工程,下载依赖的所有包。最终整个工程的结构将如下面这样:

我们可以通过最右侧 Plugins 下的 war 下的 war:war 来为我们的 Web 应用构建 war 包。构建之后,.war 文件将位于 HelloHanpfeiSpring/target/ 目录下。

通过一个非常简单的 RESTful Web服务的构建,来了解在 IntelliJ + Tomcat + Maven + SpringMVC 环境下,开发 Web 服务的过程,至此结束。

打赏

参考文档:

Building a RESTful Web Service

使用IntelliJ IDEA开发SpringMVC网站(一)开发环境

使用IntelliJ IDEA开发SpringMVC网站(二)框架配置

关于SPRING”通配符的匹配很全面, 但无法找到元素 ‘CONTEXT:COMPONENT-SCAN’ 的声明“的错误

解决springmvc报No converter found for return value of type

关于spring”通配符的匹配很全面, 但无法找到元素 ‘context:component-scan’ 的声明“的错误

坚持原创技术分享,您的支持将鼓励我继续创作!