{ "code" : 1, "error" : "XXXXX", "data" : { ... }}
其中,code 表示调用结果的状态,0 表示成功,非 0 表示失败,并且失败情况下 error 字段将提供对应的错误信息描述,data 字段用于规范定义特定于 Web API 的响应内容。因为我们已经实现了 CurrencyRateService,所以,可以直接将其作为项目依赖的一部分(当然,这样也让我们的 Web API 看起来更像一个适配网关了)。
- <?xml version="1.0" encoding="UTF-8"?>
- <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.keevol.springboot.chapter4</groupId>
- <artifactId>currency-webapi</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>currency-webapi</name>
- <description>Demo project for Spring Boot</description>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.3.1.RELEASE</version>
- <relativePath /> <!-- lookup parent from repository -->
- </parent>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>com.keevol.springboot</groupId>
- <artifactId>currency-rates-service</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
spring-boot-starter-web 默认会提供一系列的 HttpMessageConverter 用于对请求参数和响应结果做类型转换。所以,ExchangeRate 类型将通过默认 HttpMessageConverter 序列中的 MappingJackson2HttpMessageConverter 转换成对应的 JSON 响应结果,类似于:
- @Controller
- public class CurrencyRateQueryController {
- @Autowired
- private CurrencyRateService currencyRateService;
- @RequestMapping(value = "/", method = RequestMethod.GET)
- @ResponseBody
- public ExchangeRate quote(String symbol) throws IOException {
- return currencyRateService.quote(CurrencyPair.from(symbol));
- }
- }
{ currencyPair: { symbol: “USD/CNY” }, bidPrice: 6.67, askPrice: 6.56}
整个 Web API 的功能流程算是跑通了,但跟我们之前定义的 Web API 规范却没有关系,所以,下一步我们要做的事情就是在此基础上规范 HTTP 响应格式,使其遵循我们之前定义的 Web API 规范,从而任何访问我们提供的 Web API 访问者都可以相同的认知使用这些 Web API,进而也可以打造和沉淀相应的工具或者类库。然后,所有的 Web API 的处理方法统一定义为返回 WebApiResponse 作为结果类型:
- public class WebApiResponse<T> {
- public static final int SUCCESS_CODE = 0;
- public static final int ERROR_CODE = 1;
- private int code;
- private String error;
- private T data;
- // getters, setters, toString(), etc.
- }
不过,这种模式过于强调规范的管控,对开发者来说不是太友好,即使我们通过 Builder 模式来简化 WebApiResponse 的构造过程,比如:
- @RequestMapping(value = "/", method = RequestMethod.GET)
- @ResponseBody
- public WebApiResponse<ExchangeRate> quote(String symbol) throws IOException {
- WebApiResponse<ExchangeRate> response = new WebApiResponse<>();
- response.setCode(WebApiResponse.SUCCESS_CODE);
- response.setData(currencyRateService.quote(CurrencyPair.from(symbol)));
- return response;
- }
但从 API 的使用者角度来看,这种设计并非最优,最好的方式其实应该是隐式的自动转换方式。在隐式的自动转换方式下,用户的 Web API 处理方法定义保持不变,直接返回最原始的值类型(比如 ExchangeRate):
- public class WebApiResponse<T> {
- public static final int SUCCESS_CODE = 0;
- public static final int ERROR_CODE = 1;
- private int code;
- private String error;
- private T data;
- public static <T> WebApiResponse<T> success(T data) {
- WebApiResponse<T> response = new WebApiResponse<>();
- response.setCode(SUCCESS_CODE);
- response.setData(data);
- return response;
- }
- public static <T> WebApiResponse<T> error(String errorMessage) {
- return WebApiResponse.<T>error(errorMessage, ERROR_CODE);
- }
- // ...
- @RequestMapping(value = "/", method = RequestMethod.GET)
- @ResponseBody
- public WebApiResponse<ExchangeRate> quote(String symbol) throws IOException {
- return WebApiResponse.success(currencyRateService.quote(CurrencyPair.from(symbol)));
- }
- }
通过在框架层面对原始的值类型进行符合规范行为的封装,最终返回给用户的响应结果“自动”的或者说以“不打扰 API 开发者”的形式变成了符合我们 Web API 规范的响应结果形式。
- @RequestMapping(value = "/", method = RequestMethod.GET)
- @ResponseBody
- public ExchangeRate quote(String symbol) throws IOException {
- return currencyRateService.quote(CurrencyPair.from(symbol));
- }
但是,这会导致一些问题或者不便:
- public class JsonHttpMessageConverter extends AbstractHttpMessage-Converter<Object> {
- @Override
- protected boolean supports(Class<?> clazz) {
- return !clazz.isPrimitive();
- }
- @Override
- protected Object readInternal(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
- return null;
- }
- @Override
- protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
- httpOutputMessage.getHeaders().add("Content-Type", "application/json");
- // 其他header设置
- // toJson()方法中可以使用jackson或者fastjson等类库完成对象到json的转换
- httpOutputMessage.getBody().write(toJson(o));
- httpOutputMessage.getBody().flush();
- }
- }
extendMessageConverters 属于已经添加过默认 HttpMessageConverter 序列的参数(比如针对 String 的 HttpMessageConverter,或者针对 byte[] 的 Http-MessageConverter 等),所以,我们只要在其基础上添加或者插入我们的 HttpMessageConverter 实现类就可以了。
- @Configuration
- public class WebApiConfiguration extends WebMvcConfigurerAdapter {
- @Override
- public void extendMessageConverters(List<HttpMessageConvert-er<?>> converters) {
- // 添加或者插入我们自定义的HttpMessageConverter实现类
- // converters.add(converter)或者converters.add(0, converter)
- }
- }
在 HttpMessageConverter 的场景中就是,我们只能根据目标对象的类型以及 mediaType 来判断是否应该使用当前这个 HttpMessageConverter,如果需要在这两种判断条件都相同的情况下,还要根据其他条件来判定是否应该使用当前 HttpMessageConverter,此时这种设计显然就无法满足需求了。尴尬之处就在于此。
- for(HttpMessageConverter converter: converters){
- if(converter.canWrite(clazz, media)) {
- converter.write(..);
- }
- }
以及对应的 JavaConfig 配置类示例:
- <?xml version="1.0" encoding="UTF-8"?>
- <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>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starters</artifactId>
- <version>1.2.5.RELEASE</version>
- </parent>
- <groupId>com.keevol.springboot</groupId>
- <artifactId>spring-boot-starter-webapi</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>spring-boot-starter-webapi</name>
- <url></url>
- <properties>
- <java.version>1.8</java.version>
- <file.encoding>UTF-8</file.encoding>
- </properties>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.2</version>
- <configuration>
- <source>${java.version}</source>
- <target>${java.version}</target>
- <encoding>${file.encoding}</encoding>
- </configuration>
- </plugin>
- </plugins>
- </build>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>2.1.2</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>2.1.2</version>
- </dependency>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <version>${servlet-api.version}</version>
- </dependency>
- </dependencies>
- </project>
关于如何将 WebApiAutoConfiguration 配置到 META-INF/spring.factories 并发布项目则不再赘述。有了 spring-boot-starter-webapi 之后,Web API 形式的微服务开发者所要做的仅仅是把它加为项目依赖:
- @Configuration
- @EnableSwagger2
- @ComponentScan("com.wacai.springboot.webapi.errors")
- @AutoConfigureAfter(WebMvcAutoConfiguration.class)
- public class WebApiAutoConfiguration extends WebMvcConfigurerAdapter {
- protected Logger logger = LoggerFactory.getLogger(WebApiAuto - Configuration.class);
- @Value("${springfox.api.group:[your api group name]}")
- private String apiGroupName;
- @Value("${springfox.api.title:[set a api title via 'springfox.api.title']}")
- private String title;
- @Value("${springfox.api.description:[add your api description via 'springfox.api.description'}]")
- private String desc;
- @Value("${springfox.api.version:[set specific api version via 'springfox.api.version'}]")
- private String version;
- @Value("${springfox.api.termsOfServiceUrl:[set termsOf-ServiceUrl via 'springfox.api.termsOfServiceUrl']}")
- private String termsOfServiceUrl;
- @Value("${springfox.api.contact:[set contact via 'springfox.api.contact'}]")
- private String contact;
- @Value("${springfox.api.license:Your WebAPI License}")
- private String license;
- @Value("${springfox.api.licenseUrl:http://keevol.com}")
- private String licenseUrl;
- @Autowired
- private TypeResolver typeResolver;
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2).groupName(apiGroupName)
- .apiInfo(new ApiInfo(title, desc, version, termsOf - ServiceUrl, contact, license, licenseUrl)).select()
- .apis(RequestHandlerSelectors.any()).paths(excludedPathSelector()).build().pathMapping("/")
- .directModelSubstitute(Date.class, String.class).genericModelSubstitutes(ResponseEntity.class)
- .alternateTypeRules(newRule(
- typeResolver.resolve(DeferredResult.class,
- typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
- typeResolver.resolve(WildcardType.class)))
- .useDefaultResponseMessages(false)
- .globalResponseMessage(RequestMethod.GET, newArrayList(new ResponseMessageBuilder().code(500)
- .message("服务出错啦~").responseModel(new ModelRef("Error")).build()))
- .forCodeGeneration(true);
- }
- // ...
- }
然后像往常那样写 SpringMVC 的 @Controller 或者 @RestController 就可以了,现在,我们可以直接享受 API 文档的自动生成。
- <dependency>
- <groupId>com.keevol.springboot</groupId>
- <artifactId>spring-boot-starter-webapi</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- </dependency>
Copyright © 广州京杭网络科技有限公司 2005-2025 版权所有 粤ICP备16019765号
广州京杭网络科技有限公司 版权所有