- 一.概述
- 二.REST Server的實現
- 2.1 REST API設計
- 2.2 數據庫設計
- 2.3 用MyBatis實現的DAO層
- 2.4 用Jersey實現的REST API
- 2.5 用Spring AOP實現的日志功能
- 三.前端的實現
- 3.1 顯示User列表
- 3.2 顯示User詳細信息
- 3.3 修改User信息
- 3.4 增加User
- 3.5 刪除User
- 3.6 添加validate
二.REST Server的實現
Server端使用Java來實現,用到了Spring、Mybatis、c3p0、Jersey等技術。
代碼結構如下圖所示:

2.1 REST API設計
根據系統的功能設計了如下一些REST API:
| 功能 |
Method |
URL |
| 獲取User信息列表 |
GET |
/rest/user |
| 獲取某個User的詳細信息 |
GET |
/rest/user/[id] |
| 添加一個User |
POST |
/rest/user |
| 修改某個User的信息 |
PUT |
/rest/user/[id] |
| 刪除某個User |
DELETE |
/rest/user/[id] |
| 驗證Username是否合法 |
GET |
/rest/user/validate/[username] |
其中POST請求的JSON中沒有User的id,因為在數據庫中這是自增的字段,所以在insert成功之后,需要將id設置上之后再返回整個JSON對象,方便前端更新數據。
最后一個方法是用來檢測用戶名是否已經被注冊的,返回true或者false,用來進行表單驗證。
2.2 數據庫設計
數據庫使用的是SQLServer,只有一張表,字段也比較少,創建數據表的SQL如下:
CREATE TABLE [dbo].[rd_user]( [id] [int] IDENTITY(1,1) NOT NULL, [username] [varchar](50) NOT NULL, [password] [varchar](50) NOT NULL, [email] [varchar](50) NOT NULL, [phone] [varchar](50) NULL ) ON [PRIMARY];
2.3 用MyBatis實現的DAO層
連接池采用了c3p0,在Spring中的配置如下:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> <property name="minPoolSize" value="5"></property> <property name="maxPoolSize" value="30"></property> <property name="initialPoolSize" value="5"></property> <property name="maxIdleTime" value="60"></property> <property name="acquireIncrement" value="5"></property> </bean>
在Spring中配置MyBatis:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>
在Spring中注冊DAO的bean:
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.demo.register.dao.mybatis.MyBatisUserDao"></property> <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> </bean>
User的POJO:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class User { private int id; private String username; private String password; private String email; private String phone; //省略getter setter }
IUserDao接口:
public interface IUserDao { public User getUserById(int id); public User getUserByUsername(String username); public List<User> getUserList(); public void insert(User user); public void update(User user); public void deleteUserById(int id); }
MyBatisUserDao接口,繼承自IUserDao,使用Mybatis的annotation實現增刪改查功能,但是有些復雜的查詢必須使用XML。
public interface MyBatisUserDao extends IUserDao { @Select("SELECT * FROM rd_user WHERE id = #{id}") public User getUserById(@Param("id") int id); @Select("SELECT * FROM rd_user WHERE username = #{username}") public User getUserByUsername(@Param("username") String username); @Select("SELECT * FROM rd_user order by id") public List<User> getUserList(); @Insert("INSERT INTO rd_user(username, password, phone, email) " + "VALUES(#{username}, #{password}, #{phone}, #{email})")
//Insert成功之后會將id更新到User的對象 @SelectKey(statement="SELECT @@IDENTITY", keyProperty="id", before=false, resultType=int.class) public void insert(User user); @Update("UPDATE rd_user " + "SET username=#{username}, password=#{password}, phone=#{phone}, email=#{email} " + "WHERE id=#{id}") public void update(User user); @Delete("DELETE FROM rd_user WHERE id = #{id}") public void deleteUserById(@Param("id") int id); }
2.4 用Jersey實現的REST API
在Web.xml中整合Jersey和Spring:
<servlet> <servlet-name>jersey-serlvet</servlet-name> <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>com.demo.register.rest</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>jersey-serlvet</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping>
關於Jersey的使用參看官網的文檔:http://jersey.java.net/nonav/documentation/latest/user-guide.html
UserAPI的實現類:
package com.demo.register.rest; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import com.demo.register.bean.User; import com.demo.register.dao.IUserDao; @Path("/user") public class UserAPI { private IUserDao userDao; public IUserDao getUserDao() { return userDao; } public void setUserDao(IUserDao userDao) { this.userDao = userDao; } @GET @Produces(MediaType.APPLICATION_JSON) public List<User> getUserList() { return userDao.getUserList(); } @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public User addUser(User user) { userDao.insert(user); return user; } @GET @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public User getUser(@PathParam("id") int id) { return userDao.getUserById(id); } @PUT @Path("/{id}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public String updateUser(User user) { userDao.update(user); return "{\"success\":\"true\"}"; //這里如果不加雙引號,前端的JS不會將它識別為JSON,而且會產生error事件 } @DELETE @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public String deleteUser(@PathParam("id") int id) { userDao.deleteUserById(id); return "{\"success\":\"true\"}"; } @GET @Path("/validate/{username}") @Produces(MediaType.TEXT_PLAIN) public String validate(@PathParam("username") String username) { return userDao.getUserByUsername(username) == null ? "true" : "false"; } }
當然還需要在Spring中給UserAPI加上對UserDao的依賴,我試了一下這個userDao不能自動注入,我覺得是因為注冊bean的class是MyBatis里面的類而不是IUserDao的子類,或許還有我不知道的方法:
<bean id="userAPI" class="com.demo.register.rest.UserAPI"> <property name="userDao" ref="userDao"/> </bean>
2.5 用Spring AOP實現的日志功能
使用Spring AOP對所有的REST API相關方法輸出日志。首先需要一個日志類,然后再將這個日志類編織到REST API的相關方法。使用這樣的方法就不需要在每個REST API的方法中各自輸出日志了。
日志的實現類:
package com.demo.register.log; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; public class RestLogger { //為每個類創建一個Log private static final Map<Class<?>, Log> LOG_MAP = new HashMap<Class<?>, Log>(); private Log getLogger(Class<?> clazz) { Log log = (Log) LOG_MAP.get(clazz); if (log == null) { log = LogFactory.getLog(clazz); LOG_MAP.put(clazz, log); } return log; } public Object profileMethod(ProceedingJoinPoint call) throws Throwable { Log log = getLogger(call.getTarget().getClass()); if (log.isDebugEnabled()) { log.debug("method call: " + call.getSignature().toString()); //輸出調用的方法 log.debug("method parameter: " //輸出參數 + ReflectionToStringBuilder.toString(call.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE, true)); try { Object rt = call.proceed(); //調用原方法 log.debug("method return: " //輸出方法的返回值 + ReflectionToStringBuilder.toString(rt, ToStringStyle.SHORT_PREFIX_STYLE, true)); return rt; } catch (Throwable e) { log.error("method exception: " + e.getMessage(), e); throw e; } } else { return call.proceed(); } } }
在Spring中配置AOP
<bean id="restLogger" class="com.demo.register.log.RestLogger" ></bean> <aop:config> <aop:aspect ref="restLogger"> <aop:pointcut id="restMethod" expression="execution(* com.demo.register.rest.*.*(..))" /> <aop:around pointcut-ref="restMethod" method="profileMethod" /> </aop:aspect> </aop:config>
