前后端分离概念
我们要了解swagger,我们就要先从前后端分离去入手。按照现在的发展趋势,可以说前后端分离已经是业内对开发和部署方式所达成的一种共识。但是还有很多公司还是采用传统的开发风格,也就是以后端的MVC为主,前端人员只要提供一些静态的HTML页面、JS、CSS、Image等,然后大部分的后端团队担当了大量的开发工作,如后端团队会将前端人员提供的静态素材再配以模板技术如jsp、framemark等,实现项目的开发。在这种一种开发模式下,可以说前端的开发和调试都是需要依赖于后端的web容器的支持,实际上这种开发模式根本无法做到前后端真正的分离。那到底什么是真正的前后端分离呢?
真正的前后端分离的方式如上图所示,首先后端开发团队,他们只要专注后端的控制层(Controller层)、服务层(service层)、数据访问层(dao层),整个后台会通过Restful风格的API向前端去提供数据或者讲前端会通过Restful风格的API到后端获取数据。
然后后端开发团队,他们只要专注于前端的控制层、视图层。即除了负责前端的静态页面,还需要负责页面上所有的交互代码以及与后端API的交互工作,它要对API进行相应的调用,并获取到数据,拿到数据后对数据进行相应地处理,将数据在视图层进行相应的展示。最终在前后端分离的模式下,前端可以实现在没有后端API的情况下,还能完美地运行和完美地奔跑。
所以以这样的一种方式形成的前后端分离,无论是在开发上还是在项目部署上都可以说是各自独立且松耦合,这才是真正意义上的前后端分离。
API(Application Programming Interface,应用程序编程接口)是一些预先定义的方法,开发人员只要调用API中所提供的方法就能实现相应的功能,而且开发人员是不用知道方法内部的实现细节。
前后端分离带来的问题
作为一个web项目来说,在前后端分离模式下可以实现项目的独立开发、部署。但是最终还是需要进行前后端集成的,在集成过程中肯定要进行集成测试以及接口的联调。但是这个集成往往都是令人头痛的问题。
问题:比如在最后集成的时候才发现,最开始设计商量好的数据结构发生了变化,而且这种变化是在所难免的,这样的话,前后端人员在集成的时候肯定要花费大量的时间去集成。
产生这样的问题的原因是前后端人员无法做到”及时协商、尽早解决” 。那么怎么才能保证我们前后端人员在开发的过程中不至于分道扬镳、越走越远呢?
解决方案:首先定义schema(方案/计划),并实时跟踪最新的API,降低集成风险。
即后端主要是开发API,再根据已经定义好的schema来进行数据测试。那么前端团队在开发前端工程当中,也会按照schema来做这个mock数据(模拟的假数据)用进行相应的测试。这样才能达到这个工作的正常进行。
但是这个schema并不是在开始设计好后就不能被修改,是有可能会修改的,比如当需求有变化或者是一开始的设计并不是十分合理,可能考虑得并不是十分周到。我们肯定在后期会对schema做一定的修改,那么在修改的过程中我们要做的是什么?是前后端双方都必须实时跟踪最新的API,如果是实时跟踪那么可能出现这样的问题,比如后端团队修改schema生成最新的API,然后因为前端人员没能及时跟踪最新的API,导致前端人员还是拿着旧的API来进行相应的mock数据的测试以及相应的API接口的调用,那么最后集成的时候肯定会出现问题。
那么大家会想,如何在定义这个schema(方案),如何写API的描述文档呢?有些人会说手写API描述文档,那么这种手写的方式肯定不合理,它会产生巨大的工作量。那么这个时候我们就想有一个自动化的工具来帮助我们自动去生成最新的API说明文档。
swagger是一个自动生成API说明文档的工具,帮助我们更好地实现前后端分离。swagger号称是全球最流行的API框架,官方的说法是swagger是一个规范的完整的框架,主要是用于生成、描述和调用以及可视化的Restful风格的WEB服务,它是一个既简单又强大的Restful API文档在线自动生成器。另外还有如下特点。
- 它支持API文档与API定义同步更新的特点,即当API有了变化那么API的说明文档也会随之发生变化。
- 它可以直接运行,并且能够在线测试API接口。这是因为它提供了Swagger UI这样的界面,供我们进行API接口的测试。
- 它是支持多种语言(比如:Java、PHP等)。
在项目中集成swagger自动生成API文档
Spring集成Swagger之配置步骤
<!-- 引入swagger -->
<!--springfox的核心jar包 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<!--springfox-ui的jar包(里面包含了swagger的界面静态文件) -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
<!--springfox依赖的jar包;如果你的项目中已经集成了无需重复 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.5</version>
</dependency>
写一个自定义配置类:
@ComponentScan:设置Swagger扫描包
@EnableSwagger2:使用Swagger2生效
@Configuration:自动在本类上下文加载一些环境变量信息
@EnableSwagger2
@Configuration
public class SwaggerConfig extends WebMvcConfigurationSupport {
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.appsys.controller"))
.paths(PathSelectors.any())
.build();
}
/**
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
* 访问地址:http://ip:port/swagger-ui.html
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("APP信息管理平台-主业务模块API") //标题。
.termsOfServiceUrl("http://192.168.98.129:8080/swagger-ui.html")
.contact("杰哥项目团队") //开发团队名称
.version("1.0") //版本
.build();
}
}
SpringMVC配置文件
<mvc:default-servlet-handler/>
添加指定扫描:<context:component-scan/>
1) 在SSM项目中的springmvc-servlet.xml配置文件中加入<mvc:default-servlet-handler/>,它主要的作用是防止js、css、image等静态资源读取不到进而报404错误,因为是SpringMVC是通过DispatcherServlet去拦截所有的请求,同时js、css、image这样的静态资源也会被拦截。而SwaggerUI的项目都是静态资源,所以Restful形式的拦截方式会将静态资源进行相应的拦截处理。所以在springmvc或spring的配置文件中需要配置对静态文件的处理方式。就需要加入<mvc:default-servlet-handler/>的配置。
2) <context-component-san backpage=”cn.appsys.controller”>它会对cn.appsys.controller下的所有包进行扫描所有的类,让这些类上标注的关于swagger注解生效。而且API扫描文档也会进行及时更新。
<!--START ==== 添加Spring集成Swagger所需要的配置 ==== START-->
<mvc:default-servlet-handler/>
<context:component-scan base-package="cn.appsys.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
<context:include-filter type="annotation" expression="org.springframework.scheduling.annotation.Scheduled"/>
</context:component-scan>
<!--END ==== 添加Spring集成Swagger所需要的配置 ==== END-->
在API中加入Swagger
在各类的配置完成之后,怎么在页面上显示API接口的描述性注释呢?那么就要在API中加入Swagger。如何在API中加入Swagger呢?可以通过在API上添加注解实现API文档的同步效果,具体的注解如下。
1) @Api
表明可供Swagger展示的接口类(多用在Controller类上面)
tags=”说明该类的作用,非空时将覆盖value的值”。
value=”描述类的作用”
basePath=”基本路径”,基本路径可不配置,在Swagger1.5 版本后不再支持。
2) @ApiOperation
描述API方法(用在方法上面)
value=”说明方法的用途、作用”
httpMethod 指定HTTP请求的方式,GET/POST等。
produces 设置MIME类型列表,如application/json,application/xml等。
notes 接口发布说明
protocols 设置特定协议,如http、https等。
response 响应类型(即返回对象)
3) @ApiParam
单个参数描述(方法中的参数只有加了@ApiParam注解才能生成中文的描述)
注:上面三个注解在下面示例一中得到体现。
4) @ApiModel
在请求的方法中如果是以对象作为入参时,可使用注释@RequestBody 对象类型参数名。如 @RequestBody Users users,然后如果想在SwaggerUI中看出这个对象属性的 详细描述就可以使用两 个注解,分别是@ApiModel、@ApiModelProperty。
其中@ApiModel注解是写到实体类上面。
5) @ApiModelProperty
该注解写到实体类中的属性上面,用来定义属性的详细信息(注释)。
注:第4、5个注解在下面示例二、三中得到体现。
示例一如下所示。
// 在修改appinfo过程中,当点击[删除]链接跳转到当前方法中。 在修改appinfo时,删除LOGO图片
@ApiOperation(
value="根据appinfo主键的id查询appinfo详情对象信息,将此对象中的LOGO图片删除",
httpMethod = "GET", protocols = "HTTP",produces = "application/json",
response = Object.class,
notes="<p>返回的值是一个Map集合,key都为result,值的取值如下/p>" +
"<p>value='failed' 表示删除失败!</p>" +
"<p>value='success' 表示删除成功!</p>" +
"<p>value='noexist' 表示要删除的LOGO图片不存在!</p>" )
@RequestMapping(value = "/delfile", method = RequestMethod.GET)
@ResponseBody
public Object delfile(
@ApiParam(required = true, name="id" ,value="appinfo主键的id")
@RequestParam("id") Integer id) {// id为appinfo的主键id
Map<String, String> map = new HashMap<String, String>();
if (id == null) {
map.put("result", "failed"); // 删除失败
} else {
}
return map;
}
示例二的代码:
// 负责完成最终的新增app基础信息操作
@ApiOperation(
value="负责完成最终的新增app基础信息操作",protocols = "HTTP",
response = String.class, httpMethod = "GET",
notes = "调用业务层的insertAppInfo方法返回int类型值,取值分别是:" +
"<p>result > 0时,新增成功跳转到app列表信息页面</p>" +
"<p>否则,新增失败又跳转回新增页面</p>" )
@RequestMapping("/appinfoaddsave")
public String appAddSave(HttpSession session, HttpServletRequest request,
@RequestBody AppInfo appInfo, Model model,
@RequestParam("a_logoPicPath") MultipartFile attach) {
int result = deveAppInfoService.insertAppInfo(appInfo);
if (result > 0) {
return "redirect:/dev/appinfo/list"; // 新增成功跳转到app列表信息页面
} else {
return "developer/appinfoadd"; // 新增失败又跳转回新增页面
}
}
在示例二代码中如果ajax请求方法中是以对象作为入参时,可使用注释@RequestBody,然后再到这个注解所对应的对象类型的实体类中加入相关注解,如示例三所示。如此就可以在SwaggerUI界面右下角的Model选项卡中就可以看出对象中属性的详细信息。
示例三的代码:
@ApiModel(value="AppInfo", description = "App信息实体类")
public class AppInfo {
@ApiModelProperty("【必填】App的id")
private Integer id;//主键id
@ApiModelProperty("【必填】软件名称")
private String softwareName;//软件名称
@ApiModelProperty("【必填】APK名称")
private String APKName;//APK名称
@ApiModelProperty("【必填】支持ROM")
private String supportROM;//支持ROM
@ApiModelProperty("【必填】界面语言")
private String interfaceLanguage;//界面语言
@ApiModelProperty("【必填】更新日期")
private Date updateDate;//更新日期
@ApiModelProperty("【必填】软件大小(单位:M)")
private BigDecimal softwareSize;//软件大小(单位:M)
@ApiModelProperty("【必填】开发者id")
private Integer devId;//开发者id
@ApiModelProperty("【必填】应用简介")
private String appInfo;//应用简介
@ApiModelProperty("【必填】app状态id")
private Integer status;//app状态id
@ApiModelProperty("【必填】上架时间")
private Date onSaleDate;//上架时间
访问Swagger界面
访问Swagger界面URL:http://IP:port/项目名/swagger-ui.html。
准备工作:将App信息管理平台项目的相关代码编写完后,打成war包,然后发布到linux系统上的tomcat中,启动tomcat后,http://IP:port/项目名/swagger-ui.html进行访问。
如果出现上面这种情况,swagger2与fastjson兼容的问题具体因为springmvc-servlet.xml文件中使用fastJsonHttpMessageConverter导致swagger2 解析json失败,错误信息如下。
java.lang.ClassCastException: org.springframework.web.accept.ContentNegotiationManagerFactoryBean$$EnhancerByCGLIB$$f23d82f8 cannot be cast to org.springframework.web.accept.ContentNegotiationManager
方式一:升级fastjson的jar包版本(推荐)。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
方式二:自定义fastjson转换器。步骤如下(了解):
1) 自定义的fastJosn转换器——FastJsonHttpMessageContextEx,具体代码如下。
public class FastJsonHttpMessageConverterEx extends FastJsonHttpMessageConverter {
public FastJsonHttpMessageConverterEx() {
super();
this.getFastJsonConfig().getSerializeConfig().put(Json.class, SwaggerJsonSerializer.instance);
}
}
2) SwaggerJsonSerializer中具体代码如下。
public class SwaggerJsonSerializer implements ObjectSerializer, ObjectDeserializer {
public final static SwaggerJsonSerializer instance = new SwaggerJsonSerializer();
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
SerializeWriter out = serializer.getWriter();
Json json = (Json) object;
out.write(json.value());
}
@Override
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
return null;
}
@Override
public int getFastMatchToken() {
return 0;
}
}
3) 修改springmvc-servlet.xml中的代码,把原来的FastJsonXXX替换成新下定义的。
<!-- 配置FastJson的消息转换器来解决 JSON数据传递过程中的日期格式问题 -->
<bean
class="cn.appsys.tools.FastJsonHttpMessageConverterEx">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
<!-- 设置当前 消息转换器的编码格式为UTF-8,原来是ISO-8859-1 text/html;charset=UTF-8 -->
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="features">
<list>
<!-- 让当前json数据中的日期格式采用FastJson默认的日期格式(yyyy-MM-dd HH:mm:ss) -->
<value>WriteDateUseDateFormat</value>
</list>
</property>
</bean>