1. 前言
上一篇教程中我们测试了本地开发环境,在本节教程中,我们来配置应用程序接口的开发环境。在开始配置环境之前先来了解一下什么是应用程序接口。
我们知道类似于C/S架构的程序本地是没有数据的,如果想要展示数据必须从远程服务器获取。那么客户端和服务器如何通信呢?最典型的一个例子就是在你在使用浏览器的时候,例如在你想要访问简书首页的时候只需要在浏览器中地址栏中输入简书的首页地址即可。那么在这个过程中究竟发生了什么事情呢?简单的说,就是浏览器向服务器发送一条请求,服务器在收到浏览器的请求之后判断浏览器需要什么,然后就给浏览器返回什么,浏览器接收到结果之后就会对结果进行解析,最后就是你看到的简书首页了。这里面有啥东西呢?首先是请求,Request,就是你输入的那条url;还有一个结果,Response,就是服务器返回给浏览器的内容;最后浏览器解析Response,以一种恰当的形式显示给用户。那么我们可以简单的把应用程序接口理解成是分析处理url的一段程序。
2. RESTful API
关于RESTful API我不想讲太多,关于她的探讨和说明的文章有很多,这里推荐这篇文章理解RESTful架构。
3. SpringMVC+Mybatis环境配置
SpringMVC+Mybatis的环境配置请参考之前写的那篇文章。
SpringMVC+Mybatis本地开发环境搭建示例(三)
4. 配置Swagger2
在配置之前先说明一下,Swagger2是啥?这是一个帮你自动生成应用程序接口说明文档的第三方库。有了这个高级的第三方框架我们就不需要自己写说明文档啦,光是想想就好开心的呢。
4.1 引入第三方库
在pom.xml文件中引入以下依赖:
<!-- 8.Swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.5.0</version>
</dependency>
4.2 编写Swagger2配置类SwaggerConfig.java
SwaggerConfig.java 文件在包cn.semiwarm.api.swagger
中
package cn.semiwarm.api.swagger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Swagger配置文件
* Created by alibct on 2017/2/28.
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createUserInfo() {
Docket docket = new Docket(DocumentationType.SWAGGER_2);
Contact contact = new Contact("半暖商城", "www.semiwarm.cn", "semiwarm@163.com");
ApiInfo apiInfo = new ApiInfo("API文档说明", "SemiWarm API文档", "V1.0", "", contact, "", "");
docket.apiInfo(apiInfo);
return docket;
}
}
4.3 在spring-mvc.xml
文件中添加以下配置
<!-- 3.配置swagger2 -->
<bean name="swaggerConfig" class="cn.semiwarm.api.swagger.SwaggerConfig"/>
4.4 编写测试接口
经过上面的配置,此时运行项目,在浏览器中输入http://localhost:8080/swagger-ui.html
就能够访问Swagger2的首页了。也就API说明文档首页。
下面开始编写测试接口。
4.4.1 编写User.java
包cn.semiwarm.api.entity
package cn.semiwarm.api.entity;
import java.io.Serializable;
import java.util.Date;
/**
* 描述:用户实体类
* <p>
* Created by alibct on 2017/2/23.
*/
public class User implements Serializable {
private Long userId; // 用户ID
private String userName; // 用户名称
private String userPhone; // 用户手机号
private String password; // 密码
private Boolean status; // 账号是否可用->默认可用
private Date createAt; // 用户创建时间
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPhone() {
return userPhone;
}
public void setUserPhone(String userPhone) {
this.userPhone = userPhone;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getStatus() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
public Date getCreateAt() {
return createAt;
}
public void setCreateAt(Date createAt) {
this.createAt = createAt;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userPhone='" + userPhone + '\'' +
", password='" + password + '\'' +
", status=" + status +
", createAt=" + createAt +
'}';
}
}
4.4.2 编写UserMapper.java
包cn.semiwarm.api.mapper
package cn.semiwarm.api.mapper;
import cn.semiwarm.api.entity.User;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
/**
* 描述:User表的基本查询接口
*
* Created by alibct on 2017/2/23.
*/
public interface UserMapper extends BaseMapper<User> {
int add(User user);
int delete(User user);
int update(User user);
User findById(Serializable id);
List<User> findAll();
// 根据用户手机号查询用户
User findUserByPhone(Serializable userPhone);
// 根据用户名称查询用户
User findUserByName(Serializable userName);
// 根据用户名验证
User verifyUserByName(HashMap hashMap);
// 根据手机号验证
User verifyUserByPhone(HashMap hashMap);
}
4.4.3 编写UserMapper.xml
位置/src/resources/mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace是指明Mybatis扫描的目录,mapper是我们的Dao层的映射目录 -->
<mapper namespace="cn.semiwarm.api.mapper.UserMapper">
<!--增加用户-->
<insert id="add" parameterType="cn.semiwarm.api.entity.User">
INSERT INTO USER (user_id, user_name, user_phone, password, status, create_at)
VALUES (#{userId}, #{userName}, #{userPhone}, password(#{password}), #{status}, #{createAt})
</insert>
<!--删除用户-->
<delete id="delete" parameterType="cn.semiwarm.api.entity.User">
DELETE FROM USER
WHERE user_id = #{userId}
</delete>
<!--更新用户-->
<update id="update" parameterType="cn.semiwarm.api.entity.User">
UPDATE USER
SET
user_name = #{userName},
user_phone = #{userPhone},
password = password(#{password}),
status = #{status}
WHERE user_id = #{userId}
</update>
<!--根据id查找用户-->
<select id="findById" parameterType="java.lang.Long" resultType="cn.semiwarm.api.entity.User">
SELECT *
FROM USER
WHERE user_id = #{userId}
</select>
<!--查找所有用户-->
<select id="findAll" resultType="cn.semiwarm.api.entity.User">
SELECT *
FROM USER
</select>
<!--根据手机号查询用户-->
<select id="findUserByPhone" parameterType="String" resultType="cn.semiwarm.api.entity.User">
SELECT *
FROM USER
WHERE user_phone = #{userPhone}
</select>
<!--根据用户名称查询用户-->
<select id="findUserByName" parameterType="String" resultType="cn.semiwarm.api.entity.User">
SELECT *
FROM USER
WHERE user_name = #{userName}
</select>
<!--根据用户名验证-->
<select id="verifyUserByName" parameterType="hashmap" resultType="cn.semiwarm.api.entity.User">
SELECT *
FROM USER
WHERE user_name = #{userName} AND password = password(#{password})
</select>
<!--根据手机号验证-->
<select id="verifyUserByPhone" parameterType="hashmap" resultType="cn.semiwarm.api.entity.User">
SELECT *
FROM USER
WHERE user_phone = #{userPhone} AND password = password(#{password})
</select>
</mapper>
4.4.4 编写测试类
位置/usr/test/java
import cn.semiwarm.api.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* UserMapper测试类
* 带有swagger2的单元测试需要添加@WebAppConfiguration注解
* Created by alibct on 2017/3/6.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring/spring-*.xml")
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testFindAll(){
System.out.println(userMapper.findAll());
}
}
这里注意,如果是集成了Swagger2之后做单元测试的,一定要添加@WebAppConfiguration
注解否则会报以下错误:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
因为篇幅原因我只测试了一个方法,其它的方法请自行测试
4.4.5 编写UserService.java接口
包cn.semiwarm.api.service
接口中主要声明了一些复杂的业务
package cn.semiwarm.api.service;
import cn.semiwarm.api.entity.User;
import java.util.List;
/**
* 用户服务接口
* Created by alibct on 2017/2/24.
*/
public interface UserService extends BaseService<User> {
// 获取所有用户
List<User> getAllUsers();
// 根据手机号获取用户信息
User getUserByPhone(String userPhone);
// 根据用户名获取用户信息
User getUserByName(String userName);
// 注册用户
String signUp(User user);
}
4.4.6 编写UserServiceImpl.java文件
包cn.semiwarm.api.service.impl
package cn.semiwarm.api.service.impl;
import cn.semiwarm.api.entity.User;
import cn.semiwarm.api.entity.UserInfo;
import cn.semiwarm.api.mapper.UserInfoMapper;
import cn.semiwarm.api.mapper.UserMapper;
import cn.semiwarm.api.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* 用户服务具体实现类
* Created by alibct on 2017/2/24.
*/
@Service("userService")
public class UserServiceImpl implements UserService {
// 使用构造函数注入UserMapper接口
private final UserMapper userMapper;
private final UserInfoMapper userInfoMapper;
@Autowired
public UserServiceImpl(UserMapper userMapper, UserInfoMapper userInfoMapper) {
this.userMapper = userMapper;
this.userInfoMapper = userInfoMapper;
}
public List<User> getAllUsers() {
return userMapper.findAll();
}
public User getUserByPhone(String userPhone) {
return userMapper.findUserByPhone(userPhone);
}
public User getUserByName(String userName) {
return userMapper.findUserByName(userName);
}
/**
* 注册用户
* 向User表插入数据后必须还要向UserInfo表插入数据
*
* @param user 用户信息
* @return 结果信息
*/
public String signUp(User user) {
Long userId = new Date().getTime();
user.setUserId(userId);
user.setStatus(true);
UserInfo userInfo = new UserInfo();
userInfo.setUserId(userId);
if (userMapper.add(user) > 0 && userInfoMapper.add(userInfo) > 0){
return "注册成功!";
}
return "注册失败!";
}
}
4.4.7 编写UserController.java文件
包cn.semiwarm.api.controller
package cn.semiwarm.api.controller;
import cn.semiwarm.api.entity.User;
import cn.semiwarm.api.service.impl.UserServiceImpl;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户前端控制器
* Created by alibct on 2017/2/28.
*/
@Controller
@RequestMapping(value = "/v1.0")
public class UserController {
private final UserServiceImpl userService;
@Autowired
public UserController(UserServiceImpl userService) {
this.userService = userService;
}
/**
* 获取用户列表
* url:www.semiwarm.cn/api/v1.0/users
*
* @return 用户列表信息
*/
@RequestMapping(value = "/users", method = RequestMethod.GET, produces = {"application/json;charset=utf-8"})
@ApiOperation(value = "获取用户列表")
public
@ResponseBody
List<User> getAllUsers() {
return userService.getAllUsers();
}
/**
* 根据手机号获取用户信息
* url:www.semiwarm.cn/api/v1.0/users/phone/{phone}
*
* @param phone 手机号
* @return 用户信息
*/
@RequestMapping(value = "/users/phone/{phone}", method = RequestMethod.GET, produces = {"application/json;charset=utf-8"})
@ApiOperation(value = "根据手机号获取用户信息")
@ResponseBody
User getUserByPhone(@PathVariable("phone") String phone) {
return userService.getUserByPhone(phone);
}
/**
* 根据用户名获取用户信息
* url:www.semiwarm.cn/api/v1.0/users/name/{name}
*
* @param name 用户名
* @return 用户信息
*/
@RequestMapping(value = "/users/name/{name}", method = RequestMethod.GET, produces = {"application/json;charset=utf-8"})
@ApiOperation(value = "根据用户名获取用户信息")
@ResponseBody
User getUserByName(@PathVariable("name") String name) {
return userService.getUserByName(name);
}
/**
* 注册用户
* url:www.semiwarm.cn/api/v1.0/users
*
* @param user 用户信息
* @return 受影响的行数
*/
@RequestMapping(value = "/users", method = RequestMethod.POST, produces = {"application/json;charset=utf-8"})
@ApiOperation(value = "注册用户")
@ResponseBody
String signUp(@RequestBody User user) {
return userService.signUp(user);
}
}
至此结构就写完了。
调用关系:
用户发送请求:http://localhost:8080/v1.0/users
第一个收到请求的是前端控制器,前端控制器调用处理器映射器,处理器映射调用处理器适配器,处理器适配器调用处理器,也就是我们这里的UserController.java
处理器根据具体的路径查找能处理该请求的方法,即getAllUsers()
方法
getAllUsers()
方法调用UserServiceImpl.java
中的getAllUsers()
方法
然后执行具体的业务逻辑,这里UserServiceImpl.java
中有调用了UserMapper
所以说之前准备的一切都是为了解耦,通过层层调用的关系将模块独立起来。
最后的运行结果如下图: