Introduction
后端开发人员需要执行的最常见任务之一是生成面向微服务的API。 当我们谈论微服务时,我们指的是小型端点集,每个端点都解决了一个不同的问题,可以快速而简单地将它们单独部署,因此在我们需要照顾高水平的情况下,它们很容易升级。 特定区域中的请求数量。 与经典的单片式架构相比,这是一个巨大的优势,后者需要立即部署所有服务,这需要花费更多的时间和资源。
REST服务比SOAP服务更高效,并且更易于安全化,因此毫不奇怪,它们变得如此流行,但是部署和测试服务可能会很麻烦。 幸运的是,有一些框架为我们提供了一些易于使用的工具。
让我们将此问题分为三个步骤:
- 使用JSON生成REST服务。使用Spring Boot启动服务。记录下来,并获得一个用于使用Swagger进行测试的用户友好界面。
在示例代码中,我们将模拟一个小型系统,以通过REST处理视频游戏数据库。 代码包将定义如下:
- API包将包含所有REST API,将其按层(服务,数据访问和数据传输对象)划分,并为控制器提供端点。要设置swagger系统,我们将需要一个配置文件,此外API包类应获取新的注释以记录其内容。最后,我们将添加通用的Spring Boot运行器,该启动器可用于任何软件包,并且只需很少的调整。
Process
1. Designing a dummy RESTful web service
第一步是为REST服务创建基本结构。 我们将其分为不同的层:
- 控制器将是VideogameController,并将包含所有端点,我们稍后将其保留,因为我们将谈论用大摇大摆进行设置。IVideogameService接口将定义服务层,该服务层将具有“正常”端点。IVideogameDAO提供了可通过数据访问层使用的方法的概念。 为了消除建立数据库连接或真实存储库的麻烦,我们将对其进行模拟。
一个好的实践是通过使用接口来拆分设计和实现。 确实不是必需的,但是它将使代码更可重用。
因此,该服务将模拟通常称为CRUD的操作:获取(查找),设置(更新),添加(保存)和删除(删除)。
import org.thalion.snippet.swagger.api.dto.Videogame;
public interface IVideogameService {
Collection<Videogame> findAll();
Videogame findOne(String name);
String save(Videogame videogame);
void update(Videogame setName);
void delete(String name);
}
该服务今天将无事可做,它将仅连接到数据访问层并让其处理所有数据库工作。
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thalion.snippet.swagger.api.dao.IVideogameDAO;
import org.thalion.snippet.swagger.api.dto.Videogame;
@Service
public class VideogameService implements IVideogameService {
@Autowired
private IVideogameDAO dao;
@Override
public Collection<Videogame> findAll() {
return dao.findAll();
}
@Override
public Videogame findOne(String name) {
return dao.findOne(name);
}
@Override
public String save(Videogame videogame) {
return dao.save(videogame);
}
@Override
public void update(Videogame videogame) {
update(videogame);
}
@Override
public void delete(String name) {
dao.delete(name);
}
}
数据访问对象或DAO实际上会完成艰巨的工作,它是将连接到数据库的对象。
import java.util.Collection;
import org.thalion.snippet.swagger.api.dto.Videogame;
public interface IVideogameDAO {
Collection<Videogame> findAll();
Videogame findOne(String name);
String save(Videogame videogame);
void update(Videogame videogame);
void delete(String name);
}
但是在这种情况下,由于我们专注于服务层,因此我们希望使其保持简单。因此,与其配置数据库连接和编写一些查询,我们将使用简单的Map结构来模拟它。
import java.util.Collection;
import java.util.Map;
import org.springframework.stereotype.Repository;
import org.thalion.snippet.swagger.api.dto.Videogame;
@Repository
public class VideogameDAOMocker implements IVideogameDAO {
private Map<String, Videogame> storage;
@Override
public Collection<Videogame> findAll() {
return storage.values();
}
@Override
public Videogame findOne(String name) {
return storage.get(name);
}
@Override
public String save(Videogame videogame) {
storage.put(videogame.getName(), videogame);
return videogame.getName();
}
@Override
public void update(Videogame videogame) {
storage.put(videogame.getName(), videogame);
}
@Override
public void delete(String name) {
storage.remove(name);
}
}
现在,数据传输对象或DTO并不十分有趣,因为它就像带有几个用于定义其属性的字符串的类一样简单。 因此,我们暂时跳过其内容。
最后,我们将端点称为Controller,并且由于我们将使用带有JSON的REST服务,因此我们需要添加其maven依赖项。
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
</dependency>
Spring MVC控制器将是一个额外的层,在此层中,我们今天正在研究的大多数有趣的事情都将发生。 首先,我们将其与服务连接,并通过春季JAX-RS注释设置REST:
- 请求映射配置路径。请求映射表示该方法需要HTTP请求。RequestBody从HTTP正文获取信息。路径变量从URL获取值。响应状态将存储状态码以发送回去。ResponseBody gets the information from the HTTP body
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thalion.snippet.swagger.api.dto.Videogame;
import org.thalion.snippet.swagger.api.service.IVideogameService;
@RestController
@RequestMapping("/api/videogames")
public class VideogameControllerBeta {
@Autowired
private IVideogameService service;
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Collection<Videogame> getAllVideogames() {
return service.findAll();
}
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET, value = "{name}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Videogame getVideogameByName(@PathVariable String name) {
return service.findOne(name);
}
@RequestMapping(method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createVideogame(@RequestBody Videogame videogame) {
String videogameCreatedId = service.save(videogame);
return new ResponseEntity<String>(videogameCreatedId, HttpStatus.CREATED);
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.PUT, value = "{name}")
public void updateVideogame(@PathVariable String name,
@RequestBody Videogame videogame) {
videogame.setName(name);
service.update(videogame);
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.DELETE, value = "{name}")
public void deleteInfo( @PathVariable String name) {
service.delete(name);
}
}
有了这些,我们就完成了设置,因此让我们找到一个地方来运行此代码。
2. Let’s boot it!
Spring Boot是生成“正好运行”应用程序的工具,该应用程序可以仅使用几行代码和注释进行设置,而无需任何xml配置。 为了真正快速地运行和测试Java微服务,这变得非常有趣。
首先,添加其maven依赖项并设置其刚刚运行的配置:
<dependencies>
<!-- spring boot setup ability -->
<dependency>
<groupId>org.springframework.boot>/groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- Just run configuration -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
然后,我们需要在Java代码本身中进行设置。 最长的配置方法是使用以下注释:
- RestController,它是一个Spring-MVC,它将此类设置为REST服务的控制器启用自动配置,这将根据我们包含的依赖项配置引导服务。 由于我们使用了TesController,因此系统将考虑添加Spring MVC和Tomcat。组件扫描,扫描软件包的组件及其子软件包。
但是所有这3个注释都可以简化为一个:SpringBootApplication(scanBasePackages = {“ replace_with_main_package”}),这是示例中使用的那个。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Generic SpringBoot, only configured by setting the scanBasePackages
* restricts the scanned packages
*/
@SpringBootApplication(scanBasePackages = { "org.thalion.snippet.swagger" })
public class SpringbootRunner {
public static void main(String[] args) {
SpringApplication.run(SpringbootRunner.class, args);
}
}
因此,仅通过运行此类,就可以部署本地服务器,并且我们已经准备好测试服务了……但这并不直观。 让我们做得更好。
3. Making it pretty and easy to test, plus avoiding setting up the client side
记录API以使其易于被其他开发人员理解并使其对测试人员友好并不是一件容易的事。 Swagger是一个框架,它使用一些额外的注释,可以使用可用的元数据生成一个简单的Web用户界面,其中包含对记录的API的REST调用。 我们将使用springfox版本,因为它已经捆绑了正确的注释。
was这是最初撰写帖子时的稳定版本。
<dependencies>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
最常用的注释是:
- 阿皮:对于控制器类,它设置API端点文档。阿皮model:用于数据传输对象的类名。阿皮ModelProperty:用于数据传输对象属性。阿皮Operation:它们超越了端点上可用的方法或服务。阿皮Param:用于方法的输入参数。阿皮Response: for output parameters. You can have more than one with a set of 阿皮Responses (e.g. also add the 404 error, etc).
然后,为了完全进行设置,有2个重要点需要重新检查:DTO和控制器。
让我们从DTO开始,因为它是最直接的部分:我们只需要记录有关类定义本身和属性的信息。
package org.thalion.snippet.swagger.api.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value = "Videogame entity", description = "Complete data of an entity
videogame")
public class Videogame {
@ApiModelProperty(value = "The name of the videogame", required = true)
private String name;
@ApiModelProperty(value = "The developer of the videogame", required = false)
private String developer;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDeveloper() {
return developer;
}
public void setDeveloper(String developer) {
this.developer = developer;
}
}
控制器要更严格一些,因为我们需要记录每个端点。
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thalion.snippet.swagger.api.dto.Videogame;
import org.thalion.snippet.swagger.api.service.IVideogameService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
@RestController
@RequestMapping("/api/videogames")
public class VideogameController {
@Autowired
private IVideogameService service;
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET, produces =
MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get Videogames", notes = "Returns all the videogame data")
@ApiResponses({ @ApiResponse(code = 200, message = "Returns this information") })
public Collection<Videogame> getAllVideogames() {
return service.findAll();
}
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET, value = "{name}", produces =
MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get the info for one videogame", notes = "Returns the info
from one videogame")
@ApiResponses({ @ApiResponse(code = 200, message = "Exists this information") })
public Videogame getVideogameByName(
@ApiParam(defaultValue = "default", value = "The name of the videogame to return")
@PathVariable String name) {
return service.findOne(name);
}
@RequestMapping(method = RequestMethod.POST, produces =
MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Create videogame information", notes = "Create a videogame
entry")
@ApiResponses({ @ApiResponse(code = 201, message = "The videgame entry was created
successfully") })
public ResponseEntity<String> createVideogame(@RequestBody Videogame videogame) {
String videogameCreatedId = service.save(videogame);
return new ResponseEntity<String>(videogameCreatedId, HttpStatus.CREATED);
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.PUT, value = "{name}")
@ApiOperation(value = "Update videogame information", notes = "Update a videogame
information entry")
@ApiResponses({ @ApiResponse(code = 204, message = "The videgame entry was updated
successfully") })
public void updateVideogame(
@ApiParam(defaultValue = "Default", value = "The name of the videogame to update")
@PathVariable String name,
@RequestBody Videogame videogame) {
videogame.setName(name);
service.update(videogame);
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.DELETE, value = "{name}")
@ApiOperation(value = "Delete videogame", notes = "Deletes a videogame entry")
@ApiResponses({ @ApiResponse(code = 204, message = "The videgame entry was deleted
successfully") })
public void deleteInfo(
@ApiParam(defaultValue = "Default", value = "The name of the videogame to delete")
@PathVariable String name) {
service.delete(name);
}
}
写下所有文档注释后,我们只需要在Docket构建器的帮助下配置API页面即可,该构建器为我们的swagger实现提供了主要的图形界面。
由于我们使用的是API,因此我们只需要担心该网址下的网址/ api /模式,我们可以借助正则表达式进行选择。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket newsApi() {
return new Docket(DocumentationType.SWAGGER_2).groupName("api-videogames")
.apiInfo(apiVideogames()).select().paths(PathSelectors.regex("/api.*")).build();
}
private ApiInfo apiVideogames() {
return new ApiInfoBuilder().title("Videogames REST api POC")
.description("PoC of a REST api, to test both Springboot and Swagger")
.termsOfServiceUrl("https://creativecommons.org/licenses/by/4.0/")
.contact("abcd@mail.com")
.license("GNU General Public License v3.0").licenseUrl(
"https://www.gnu.org/licenses/gpl-3.0.en.html").version("3.0").build();
}
}
最后,我们得到了包含所有端点的漂亮网页,我们可以轻松地与之交互本地主机:8080 / swagger-ui.html。 我们可以与不同的方法进行交互并设置参数以进行快速测试,而无需编写客户端或使用诸如SoapUI之类的REST请求生成器。
恭喜你! 您的演示已完全设置并可以使用。