今天來嘗試一下搭建基于SpringMVC的RESTful标準的後端服務。
首先,什麼是MVC?M-model,模型,指業務資料層,具體一點就是業務邏輯與資料庫的互動;V-view,視圖,用來展示資料,傳統SpringMVC中控制器有傳回類型ModelAndView,即傳回含有資料模型與頁面視圖的jsp檔案;C-controller,控制器,通常負責處理與使用者間的互動,控制從資料庫取數與傳回結果到使用者等。
那麼,什麼是REST風格的服務呢?REST(Representational State Transfer),中文可以叫表述性狀态轉移,有以下幾個特點
- Resources expose easily understood directory structure URIs.資源通過URI結構直接對外展示
- Representations transfer JSON or XML to represent data objects and attributes.通過JSON或者XML來傳遞資料對象或者屬性
- Messages use HTTP methods explicitly (for example, GET, POST, PUT, and DELETE).資訊通過HTTP請求傳遞
- Stateless interactions store no client context on the server between requests. State dependencies limit and restrict scalability. The client holds session state.無狀态的互動,伺服器在互動過程中不存儲任何用戶端有關的上下文。session的狀态通過用戶端來存儲。
簡單來說,傳統的MVC架構,後端和前端是部署在一起的,伺服器接受請求,去資料庫(緩存實質也是非關系型的鍵值對資料庫)查詢然後計算,然後和jsp進行組裝渲染,直接傳回一個完整的html給浏覽器。而RESTful的結構下,前端和後端是可以分離部署的,浏覽器通路一個頁面,前端根據需要向後端發送HTTP請求,後端接收請求然後同樣查詢資料庫再計算,把結果封裝為JSON封包後傳回給前端,前端把資料組裝到靜态頁面中,生成最終展示給使用者的頁面。
從個人在開發中的了解,RESTful相比之下有以下幾個優點:
- 前後端分離解耦合,尤其是在現在前端一天一個樣,動不動就啥啥啥的已經不适合這個版本了的背景下,後端需要輸出一個标準化的結果,給前端開發留下足夠的靈活性。你要什麼,我給什麼,這是面向服務的設計方式,在企業開發中這樣子的靈活性較好,不容易造成前後端互相之間成為開發瓶頸。
- 部署與性能上的可擴充性更好。我們都知道在使用者量增大的時候,最容易産生壓力的資源在資料庫,而前端伺服器是較容易通過負載均衡增加機器的方式來解決壓力問題。是以,從後端到資料庫所需的資源數量和前端是不同的,前後端的分離部署與面向服務的分布式系統對于提升系統承載并發量是有利的。同時,要擴充系統功能時,可以直接通過新增一個相關服務的伺服器或叢集,讓前端新增該請求,不需要修改其他後端伺服器。
- 開發測試更加友善。更加便于子產品的劃分,容易進行接口測試,不同子產品之間的耦合度非常低。
下面正式開始環境的搭建,因為主要是講環境搭建,一下配置内容分析先不寫了,留到深入研究spring之後
首先編輯pom.xml,把需要的jar包先弄進來,如果沒有自動下載下傳的話,手動在工程項目上右鍵maven->update project,注意要把forcre那行勾上。
清單可以參考下面這個,這裡建議spring這樣一個系列的包的版本号統一在最上方以參數寫入,便于集體版本替換,這點在企業開發中也是非常常用的。
<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>com.graywind</groupId>
<artifactId>webdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<!-- springframe 版本控制 -->
<spring.version>4.1.6.RELEASE</spring.version>
</properties>
<dependencies>
<!-- springframe start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- springframe end -->
<!--log4j日志包 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- JUnit單元測試工具 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!-- aspectJ AOP 織入器 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- JSON: jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jr</groupId>
<artifactId>jackson-jr-all</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- define the project compile level -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
然後修改些項目屬性,右鍵項目properties->prject facts确定設定如下。若web module不是3.0可把勾去掉,應用後再修改到3.0.
然後到deployment assembly,設定如下
修改web.xml如下,這裡有兩個配置檔案的路徑是以classpath開頭,根據上方的設定classpath會從WEB-INF/classes開始尋找,而src/main/java和src/main/resources下的内容會被裝配至該路徑下。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>mybatis</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<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>
<!-- springmvc配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>spring mvc 配置檔案</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<description>字元集過濾器</description>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>字元集編碼</description>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--設定log4j的配置檔案位置 -->
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<!--使用監聽加載log4j的配置檔案 -->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
</web-app>
現在添加application.xml和springmvc.xml以及log4j.properties至resources目錄下
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 啟用注解掃描,并定義元件查找規則 ,除了@controller,掃描所有的Bean -->
<context:component-scan base-package="webdemo.*">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
</beans>
<?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-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
<!-- 啟用注解掃描,并定義元件查找規則 ,mvc層隻負責掃描@Controller、@ControllerAdvice -->
<!-- base-package 如果多個,用“,”分隔 -->
<context:component-scan base-package="webdemo.*"
use-default-filters="false">
<!-- 掃描@Controller -->
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
<!-- 控制器增強,使一個Contoller成為全局的異常處理類,類中用@ExceptionHandler方法注解的方法可以處理所有Controller發生的異常 -->
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>
<!-- 會自動注冊RequestMappingHandlerMapping與RequestMappingHandlerAdapter兩個Bean,
這是SpringMVC為@Controllers分發請求所必需的,并提供了資料綁定支援、@NumberFormatannotation支援、 @DateTimeFormat支援、@Valid支援、讀寫XML的支援和讀寫JSON的支援等功能。 -->
<mvc:annotation-driven />
<!-- 使用預設的Servlet來響應靜态檔案 -->
<mvc:default-servlet-handler />
<!-- 視圖解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 字首 -->
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<!-- 字尾 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<bean id="stringConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
<!-- 輸出對象轉JSON支援 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>text/plain;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
</beans>
### set log levels ###
log4j.rootLogger = debug,stdout,D,E
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ${catalina.home}/logs/webdemo-info.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = INFO
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =${catalina.home}/logs/webdemo-error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
${catalina.home}表示日志檔案會産生到tomcat的檔案目錄下
然後添加controller與bean
package webdemo.hello;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class UserController {
private static final Logger log = Logger.getLogger(UserController.class);
@RequestMapping("/hello")
public String hello(){
return "hello";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = "application/json")
public User getUser(@PathVariable("id") int id){
log.info("get user " + id);
User user = new User();
user.setId(id);
user.setName("Hello");
return user;
}
@RequestMapping(value = "/userlist", method = RequestMethod.GET, produces = "application/json")
public List<User> getUserlist(){
System.out.println("get userlist");
List<User> list = new ArrayList<User>();
for(int i=1;i<10;i++){
User user = new User();
user.setId(i);
user.setName("Hello");
list.add(user);
}
return list;
}
}
package webdemo.hello;
public class User {
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
現在啟動伺服器,測試下接口,因為我把server設定那裡的path去掉了,是以直接輸入IP與端口
現在網頁上顯示的就是json封包了,基本的環境也就搭建完成了。但是以網站開發角度來說,後續還有很多需要擴充的地方,比如權限控制過濾器,ajax封包的組裝,錯誤控制,日志切面等等,後續會進一步在這個基礎上擴充。
個人GitHub位址: https://github.com/GrayWind33