在学习了如何快速创建一个Spring Boot项目和简单使用Spring Boot搭建web项目,接下来要学习的是如何将在mvc模式中使用,本文完成了一个RESTful API
+ curd
的简单例子,并使用TestRestTemplate
来进行junit测试。
使用环境参考Spring Boot基础教程汇总中的讲解:详情请点击
上图是Spring Boot推荐的典型结构,通常建议将应用的main类(Application
)放到其他类所在的包的顶层(root package),并将@EnableAutoConfiguration
注解到你的main类上,其作用是开启自动配置。
The second class-level annotation is @EnableAutoConfiguration. This annotation tells Spring Boot to “guess” how you will want to configure Spring, based on the jar dependencies that you have added. Since spring-boot-starter-web added Tomcat and Spring MVC,the auto-configuration will assume that you are developing a web application and setup Spring accordingly.Starters and Auto-Configuration.
简要概括,@EnableAutoConfiguration
会根据添加的jar包对Spring进行相应的设置,比如你加载了spring-boot-starter-web
,则会开启Spring Mvc的自动配置,在日志中,可以看到加载了tomcat和spring mvc。想了解更多关于@EnableAutoConfiguration
注解,请看一下这篇文章:springboot EnableAutoConfiguration。
采用root package的结构,就可以使用@ComponentScan
而不需要指定basePackage属性,因为默认从Appliacation
由上向下,逐层扫描加了注解的类。
无特别引入其他依赖,该介绍可参考文章创建第一个Spring Boot应用之Hello World中的相关说明。
User实体定义:
public class User {
private long id;
private String name;
private String country;
public User(long id, String name, String country) {
this.id = id;
this.name = name;
this.country = country;
}
public User() {
}
// 省略setter和getter
}
UserService定义:实现简单的curd
@Service
public class UserServiceImpl implements UserService{
private static List users;
static {
users = initUser();
}
private static List initUser() {
List users = new ArrayList<>(10);
users.add(new User(1L,"Mac","USA"));
users.add(new User(2L,"HuaWei","CHINA"));
users.add(new User(3L,"3Idiots","INDIA"));
users.add(new User(4L,"Cartoon","JAPAN"));
users.add(new User(5L,"TUHao","DUBAI"));
return users;
}
@Override
public List getUser() {
return users;
}
@Override
public User findUserById(Long id) {
for(User user:users){
if(user.getId() == id){
return user;
}
}
return null;
}
@Override
public void createUser(User user) {
users.add(user);
}
@Override
public void update(User user) {
int index = users.indexOf(user);
users.add(index,user);
}
@Override
public void deleteUserById(Long id) {
Iterator iterator = users.iterator();
while (iterator.hasNext()){
User user = iterator.next();
if(user.getId() == id){
iterator.remove();
}
}
}
}
实现对User对象的rest
@Controller
类级别修饰符,修饰class,用来创建处理http请求@ResponseBody
用来返回json对象@RestController
相当于 @Controller + @ResponseBody的作用@RequestMapping
配置url映射@GetMapping
相当于@RequestMapping(method = HttpMethod.GET)produces
返回的数据类型为 MediaType.APPLICATION_JSON_VALUE
headers
请求头为 Accept=application/json
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public List getUsers() {
List users = userService.getUser();
return users;
}
@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity findUserById(@PathVariable("id") Long id) {
User user = userService.findUserById(id);
if (null == user) {
return new ResponseEntity<>(user, HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(user, HttpStatus.OK);
}
@PostMapping(value = "/create",headers ="Accept=application/json")
public ResponseEntity createUser(@RequestBody User user) {
userService.createUser(user);
User reUser = userService.findUserById(user.getId());
return new ResponseEntity<>(reUser, HttpStatus.CREATED);
}
@PutMapping(value = "/update",headers = "Accept=application/json")
public ResponseEntity update(@RequestBody User currentUser){
User user = userService.findUserById(currentUser.getId());
if(null == user){
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
user.setName(currentUser.getName());
user.setCountry(currentUser.getCountry());
userService.update(user);
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping(value = "/{id}")
public ResponseEntity deleteUserById(@PathVariable Long id){
User user = userService.findUserById(id);
if(null == user){
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
userService.deleteUserById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
应用程序的入口main方法位于Application
中。main方法通过调用run
,将业务委托给了Spring Boot的SpringApplication类。我们需要将Example.class
作为参数传递给run
方法,以此决定谁是主要的Spring组件。
一般我们在启动类上添加这三个注解,
@Configuration
表明这是一个注解bean@EnableAutoConfiguration
本文已经提及,故不解释@ComponentScan
扫描注解 可以使用@SpringBootApplication
来替换以上三个注解
//@Configuration
//@EnableAutoConfiguration
//@ComponentScan
@SpringBootApplication
public class SpringBootRestCurdApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRestCurdApplication.class, args);
}
}
在程序的根目录下(与pom.xml
同级),执行
mvn spring-boot:run
以下情况表明启动成功
浏览器中使用url进行访问(不修改配置默认为8080端口,启动不成功检查下端口是否被占用)
http://localhost:8080/users/
在集成测试中,可以使用TestRestTemplate
来获取一个普通的或发送基本HTTP认证(使用用户名和密码)的模板:不允许重定向(这样才可以对相应地址进行断言),忽略cookies(模板本身就是无状态的),对于服务出错不会抛出异常。
如果使用了@SpringBootTest
,且配置了WebEnvironment.RANDOM_PORT
或WebEnvironment.DEFINED_PORT
属性,使用@LocalServerPort
获取端口,使用@Autowired
来加载TestRestTemplate
发起REST调用,如下:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringBootRestCurdApplicationTests {
/** 获取随机端口 */
@LocalServerPort
private int port;
private URL base;
@Autowired
private TestRestTemplate restTemplate;
@Before
public void setUp() throws Exception {
this.base = new URL("http://localhost:" + port + "/users/");
}
@Test
public void testGetUsers() {
// 获取json字符
ResponseEntity response = restTemplate.getForEntity(base.toString(), String.class);
assertEquals(HttpStatus.OK,response.getStatusCode());
ArrayList users =
JSONObject.parseObject(response.getBody(), new TypeReference>() {});
assertEquals(5,users.size());
for (User user:users){
System.out.println(user);
}
}
@Test
public void testFindUserById(){
ResponseEntity response = restTemplate.getForEntity(base + "1", String.class);
assertEquals(HttpStatus.OK,response.getStatusCode());
User user = JSON.parseObject(response.getBody(), User.class);
assertEquals("1",user.getId());
System.out.println(user);
}
@Test
public void testCreateUser(){
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT,"application/json");
User user = new User(1000L,"mcrwayfun","China");
HttpEntity httpEntity = new HttpEntity(user,headers);
ResponseEntity response =
restTemplate.exchange(base + "create", HttpMethod.POST, httpEntity, String.class);
assertEquals(HttpStatus.CREATED,response.getStatusCode());
User reUser = JSON.parseObject(response.getBody(), User.class);
assertEquals(reUser.getName(),"mcrwayfun");
}
@Test
public void testUpdateUser(){
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT,"application/json");
User user = new User(1L,"mcrwayfun","China");
HttpEntity httpEntity = new HttpEntity(user,headers);
// 执行修改操作
ResponseEntity response =
restTemplate.exchange(base + "update", HttpMethod.PUT, httpEntity, String.class);
assertEquals(HttpStatus.OK,response.getStatusCode());
// 查询刚才修改的user
// 结果为:user已修改
response = restTemplate.getForEntity(base + "1", String.class);
assertEquals(HttpStatus.OK,response.getStatusCode());
user = JSON.parseObject(response.getBody(), User.class);
assertEquals("mcrwayfun",user.getName());
}
@Test
public void testDeleteUserById(){
// 删除操作
restTemplate.delete(base + "1",String.class);
// 查询刚才修改的user
// 结果为:不存在
ResponseEntity response = restTemplate.getForEntity(base + "1", String.class);
assertEquals(HttpStatus.NOT_FOUND,response.getStatusCode());
}
}
至此,我们通过引入了web模块,利用Spring Mvc的功能,实现了对一组User对象操作RESTful API,在此过程中,结合代码讲解了部分注解的作用,说明Spring Boot工程的结构,如何映射HTTP请求,传参以及利用TestRestTemplate编写单元测试。
CreateUser
发送POST请求时,发生了如下错误
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of xxx(no Creators, like default construct, exist):
cannot deserialize from Object value (no delegate- or property-based Creator)
项目源码,欢迎star,本小节为spring-boot-rest-curd:https://github.com/mcrwayfun/spring-boot-learning
springboot EnableAutoConfiguration:https://www.cnblogs.com/diegodu/p/7889379.html