今天来尝试一下搭建基于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报文的组装,错误控制,日志切面等等,后续会进一步在这个基础上扩展。