任何时候我们都不能避免偏见,即便是讨论技术也是如此,并没有一种技术真的能绝对。
英国作家斯威夫特在《格列佛游记》里以戏谑的语调所描述的大头派和小头派(其争论焦点是吃鸡蛋剥皮时究竟应该从大头敲破还是从小头敲破)原本是为了讥讽英国议会里那些因小题大做而分党立派的荒唐议员,然而事实上我们每个人都是大头派或小头派的一员,只是在某些问题上属于大头派,在另一些问题上属于小头派而已。我们必须承认的是,斯威夫特的寓言所揭示的意义远比作者的意图本身更为深刻。
定义Web API规范
Web开发中,API开发是程序员们非常熟悉的,而使用SpringBoot构建Web API有几种选择,要么使用spring-boot-starter-jersey构建RESTful风格的Web API,要么选择spring-boot-starter-hateoas构建更加有关联性和相对“智能”的Web API,但是,引入任何一种框架都会带来学习成本,如果是一个人开发问题不大,而如果是团队协作开发,沟通成本就会很高。
所以,我们可以选择相对宽松的RPC over HTTP的方式来构建Web API。我们先定义一个简单的json响应格式:
{
"code" : 0,
"msg" : "XXXXX",
"data" : { ... }
}
其中,code表示调用结果的状态,0表示成功,非0表示失败,并且可以根据状态传入需要提示的字段,data字段用于规范定义特定于Web API的响应内容。
实战
新建Spring Boot项目
我们就可以根据规范来构建我们的API了,参考boot-start,或者使用http://start.spring.io构建新项目,并依赖spring-boot-starter-web模块。
示例服务
这里我们创建一个简单的查询服务QueryUserService,如下代码:
@Service
public class QueryUserService {
public List<Man> query(){
Man man1 = new Man("mickjoust","1","111111");
Man man2 = new Man("mia","1","6666666");
Man man3 = new Man("max","1","33333333");
List<Man> manList = new ArrayList<>();
manList.add(man1);
manList.add(man2);
manList.add(man3);
return manList;
}
}
查询的POJO对象:
public class Man {
private String name;
private String age;
private String phone;
public Man(String name, String age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
//同样省略get、set
提供API
使用Spring技术栈,我们能够快速的构建Web API,这里创建一个API Controller:
@RestController
@SpringBootApplication
public class QueryUserController {
public static void main(String[] args) {
SpringApplication.run(QueryUserController.class,args);
}
@Autowired
private QueryUserService queryUserService;
@RequestMapping("/query1")
public List<Man> getUsers(){
return queryUserService.query();
}
}
其中,@RestController
表示这个是一个REST风格的API。
加强约束
到上小节,我们已经能够向外提供一个具有查询用户服务能力的Web API了,但等一下,这时返回的响应数据里似乎并没有我们前面定义的规范,于是,我们这样扩展代码:
@RequestMapping("/query2")
public Map<String,Object> getUsersExt(){
Map<String,Object> resultMap = new HashMap<>();
if (null != queryUserService.query()
|| !queryUserService.query().isEmpty()){
resultMap.put("code",0);
resultMap.put("msg","sucess");
resultMap.put("data",queryUserService.query());
} else {
resultMap.put("code",1);
resultMap.put("msg","error");
resultMap.put("data","");
}
return resultMap;
}
不过,这是最简单但最错误的做法,因为下一个开发人员来看代码时,就会大骂这样的一次性代码难于维护。
要开发符合我们的Web API规范的Web API,有两种方案可以选择——
- 显式的强类型封装方式(explicit type wrapper)
- 隐式的自动转换方式(implicit conversion)
针对第一点,spring-boot-starter-web已经提供了MappingJackson2HttpMessageConverter用于对象类型到JSON的类型转换,不过鉴于很多人喜欢用fastjson,参考文章《Spring Boot 实践折腾记(五):自定义配置,扩展Spring MVC配置并使用fastjson》来进行Converter配置,那么,我们只要提供一个响应类即可,比如:
public class ApiResponse<T> {
public static final int SUCCESS_CODE = 0;
public static final int ERROR_CODE = 1;
private int code; //0-成功,1错误
private String msg;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(SUCCESS_CODE);
response.setData(data);
response.setMsg("sucess");
return response;
}
public static <T> ApiResponse<T> error(String error,T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(ERROR_CODE);
response.setData(data);//注意这里的该怎么处理?对于移动端来说,如果抛null会直接导致app退出
response.setMsg((null==error||"".equals(error)?"error":error));
return response;
}
//我就是不写get、set
然后,所有的Web API的处理方法统一定义为返回ApiResponse作为结果类型:
@RequestMapping("/query3")
public ApiResponse<List<Man>> getUsers3(){
return ApiResponse.success(queryUserService.query());
}
不过,这种模式过于强调规范的管控,对开发者来说不是太友好(不过架构师都很喜欢,因为好管控),但从API的使用者角度来看,这种设计并非最优,最好的方式其实应该是隐式的自动转换
方式。
这里要注意一点,不要直接继承AbstractHttpMessage-Converter<Object>
,在定制的同时打破了Boot对MVC的良好支持,而有时想要支持视图就会出现问题,毕竟每个人的选择都是有所偏见的。
在隐式的自动转换方式下,用户的Web API处理方法定义保持不变,直接返回最原始的值类型(比如:List),具体实现可以参考boot-properties里的代码实现,但原来的代码是基于1.0写的,在boot2.0里WebMvcConfigurerAdapter
已被废弃,原因是因为适配的接口WebMvcConfigurer
在java8里,接口可以提供默认实现方法,不需要单独包装一层。
API的一个难题
写原生API虽然方便,特别是现在有Spring Boot后更是方便,但同样有最核心的问题没有解决,就是API文档,API的一个作用除了提供对应功能,还需要使用告诉使用者该怎么使用(对于很多开发同学来说,面对大量繁琐的说明文档的,曾经受伤过的人,举手!)
而一个难题是,不同团队间都在使用API时,就设计到接口修改和文档修改后的同步,这是很让人头疼的一件事。
为了缓解这个难题,现在出现了很多API Doc和API Mock类库,可以方便我们使用,比如,swagger,有兴趣的可以查看这篇文章来构建swagger《Spring Boot 实践折腾记(七):使用Swagger 2构建RESTful API文档》。
小结
本文主要介绍了根据一定规范来构建Web API的方法,这种方法不一定是最优的,但提供了一种构建API的“笨办法”,同时实战了一个简单的boot应用,以及API的短板和不足之处和缓解办法。如何在约束性和便捷性间平衡,是一个值得思考的问题。
最后 关于boot中的MVC配置,,spring-boot-starter-web默认将为我们一些Spring MVC必要组件进行了自动配置,如下:
-1、必要的ViewResolver,比如ContentNegotiatingViewResolver
和Bean-NameViewResolver
。
-2、将必要的Converter
、GenericConverter
和Formatter
等bean注册到IoC容器。
-3、添加一系列的HttpMessageConverter
以便支持对Web请求和相应的类型转换。
-4、自动配置和注册MessageCodesResolver
。
请记住,任何时候构建项目,我们都是可以选择的,比如对默认提供的SpringMVC组件设定不满意,都可以在IoC容器中注册新的同类型的bean定义来替换,或者直接提供一个基于WebMvcConfigurerAdapter
类型的bean定义来定制,也可以提供一个标注了@EnableWebMvc
的@Configuration
配置类来完全接管所有SpringMVC的相关配置,自己完全重新配置。
参考资源
1、Spring Boot官网
2、《Spring Boot 揭秘》
持续践行,我们一路同行。