解决什么问题:
1)在前后台分离的开发模式中,减小接口定义沟通成本,方便开发过程中测试,自动生成接口文档。提高Java服务端和Web前端以及移动端的对接效率。
2)Swagger是当前最好用的Restful API文档生成的开源项目,通过swagger-spring项目实现了与SpingMVC框架的无缝集成功能,方便生成spring restful风格的接口文档,同时swagger-ui还可以测试spring restful风格的接口功能。
1、Swagger 包含的主要套件:
- Swagger Editor - 基于浏览器的编辑器,用来编写 OpenAPI 规范。
- Swagger UI - 基于 OpenAPI 规范动态生成 API 规范文档。
- Swagger Codegen - 个模板驱动引擎,用来生成客户端代码。
2、OpenAPI是什么?
OpenAPI 规范,以前叫 Swagger 规范。它是一个为 REST API的接口定义的规范。
OpenAPI 可以定义的 API 实体内容包括以下几个部分。
- 请求地址(如:/user)
- 请求类型(如:GET、POST 等)
- 请求参数
- 响应参数
- 验证方式
- 文档信息:如联系人、许可证、服务条件等
1、对于一个SpringMVC项目,使用swagger的配置如下:pom.xml
io.swagger
swagger-core
1.5.8
io.springfox
springfox-swagger2
2.4.0
io.springfox
springfox-swagger-ui
2.4.0
2、SwaggerConfiguration.java
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
3、SwaggerWebMvcConfigurerAdapter.java
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.xx.travel.csc.stat.controller")
public class SwaggerWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
4、Controller实例
然后,只要在我们的Controller里面增加注解 ApiOperation和ApiParam 即可。
@Controller
@RequestMapping(value = "/stat")
public class SwaggerController {
@ResponseBody
@RequestMapping(value = "/helloworld", method = RequestMethod.GET)
@ApiOperation(nickname = "swagger-helloworld", value = "Swagger的世界", notes = "测试HelloWorld")
public String helloWorld(@ApiParam(value = "昵称") @RequestParam String nickname) {
return "Hello world, " + nickname;
}
@ResponseBody
@RequestMapping(value = "/objectio", method = RequestMethod.POST)
@ApiOperation(nickname = "swagger-objectio", value = "Swagger的ObjectIO", notes = "测试对象输入输出")
public SwaggerOutput objectIo(@ApiParam(value = "输入") @RequestBody SwaggerInput input) {
SwaggerOutput output = new SwaggerOutput();
output.setId(input.getId());
output.setName("Swagger");
return output;
}
}
5、Web界面
启动项目,输入Http://Path:Post/swagger-ui.html,就可以给前端展示相关的API文档,并像使用Postman以及Curl命令一样,通过Web界面进行接口测试。
1、@Api:用于controller上,表示标识这个类是swagger的资源 。
@Api(tags = {"user"}, description = "Operations about user")
属性名称 | 备注 |
---|---|
value | url的路径值 |
tags | 如果设置这个值、value的值会被覆盖 |
description | 对api资源的描述 |
2、@ApiOperation:用于方法上,说明方法的作用。
@ApiOperation(value="获取项目列表", notes="获取项目列表", httpMethod="POST")
属性名称 | 备注 |
---|---|
value | url的路径值 |
notes | 对api资源的描述 |
httpMethod | 请求类型。GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" , "PATCH" |
3、@ApiResponse:用于方法上,表示响应配置。
@ApiResponses({
@ApiResponse(code = 200, message = "返回成功", response = Entity.class)
})
属性名称 | 备注 |
---|---|
code | http的状态码 |
message | 描述 |
response | 默认响应类 Void |
4、@ApiResponses:用于方法上,表示响应集配置。
@ApiResponses({ @ApiResponse(code = 400, message = "Invalid Order"),
@ApiResponse(code = 200, message = "Success")
})
5、@ApiImplicitParam:用于方法上,指定一个请求参数的信息。
@ApiImplicitParam(name = "id", value = "主键id", required = true, paramType = "query", dataType = "Int")
属性名称 | 备注 |
---|---|
name | 参数名 |
value | 参数说明 |
dataType | 参数类型(int、String) |
paramType | 参数放在哪个地方 1.header-->请求参数的获取:@RequestHeader(代码中接收注解)。 2.query-->请求参数的获取:@RequestParam(代码中接收注解)。 3.path(用于restful接口)-->请求参数的获取:@PathVariable(代码中接收注解)。 4.body-->请求参数的获取:@RequestBody(代码中接收注解)。 5.form(不常用)。 |
required | 是否必填 |
defaultValue | 参数的默认值 |
allowMultiple | true -- > 表示是数组格式的参数 |
6、@ApiImplicitParams:用于方法上,包含一组参数说明。
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "主键id", required = true, paramType = "query", dataType = "Int"),
@ApiImplicitParam(name = "name", value = "名字", required = true, paramType = "query", dataType = "String")
})
7、@ApiParam:用在方法参数前,表示api需要传递的参数。
@ApiParam(value = "接口说明", required = true)
属性名称 | 备注 |
---|---|
name | 属性名称 |
value | 属性值 |
defaultValue | 默认属性值 |
required | 是否必填 |
example | 示例 |
8、@ApiModel:用于类上,表示标识这个类是swagger的资源。
@ApiModel(value = "参数对象", description = "参数对象")
9、@ApiModelProperty:用于属性上,表示对model属性的说明或者数据操作更改。
@ApiModelProperty(value = "类型1", name = "type", example = "1", required = true)
属性名称 | 备注 |
---|---|
name | 属性名称 |
value | 属性说明 |
dataType | 参数类型 |
required | 是否必填 |
example | 示例 |
10、封装Map请求API
(1)创建ApiJsonObject.java
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
ApiJsonProperty[] value(); //对象属性值
String name(); //对象名称
}
(2)创建ApiJsonProperty.java
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty {
String key(); //key
String example() default "";
String type() default "string"; //支持string 和 int
String description() default "";
}
(3)创建ApiJsonProperty.java
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.pactera.cloud.item.model.ApiJsonObject;
import com.pactera.cloud.item.model.ApiJsonProperty;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import java.util.Map;
@Component
@Order
public class MapApiReader implements ParameterBuilderPlugin {
@Autowired
private TypeResolver typeResolver;
@Override
public void apply(ParameterContext parameterContext) {
ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
if (methodParameter.getParameterType().canCreateSubtype(Map.class) || methodParameter.getParameterType().canCreateSubtype(String.class)) { //判断是否需要修改对象ModelRef,这里我判断的是Map类型和String类型需要重新修改ModelRef对象
Optional optional = methodParameter.findAnnotation(ApiJsonObject.class); //根据参数上的ApiJsonObject注解中的参数动态生成Class
if (optional.isPresent()) {
String name = optional.get().name(); //model 名称
ApiJsonProperty[] properties = optional.get().value();
parameterContext.getDocumentationContext().getAdditionalModels().add(typeResolver.resolve(createRefModel(properties, name))); //像documentContext的Models中添加我们新生成的Class
parameterContext.parameterBuilder() //修改Map参数的ModelRef为我们动态生成的class
.parameterType("body")
.modelRef(new ModelRef(name))
.name(name);
}
}
}
private final static String basePackage = "com.xx.xxx.in.swagger.model."; //动态生成的Class名
/**
* 根据propertys中的值动态生成含有Swagger注解的javaBeen
*/
private Class createRefModel(ApiJsonProperty[] propertys, String name) {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(basePackage + name);
try {
for (ApiJsonProperty property : propertys) {
ctClass.addField(createField(property, ctClass));
}
return ctClass.toClass();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据property的值生成含有swagger apiModelProperty注解的属性
*/
private CtField createField(ApiJsonProperty property, CtClass ctClass) throws NotFoundException, CannotCompileException {
CtField ctField = new CtField(getFieldType(property.type()), property.key(), ctClass);
ctField.setModifiers(Modifier.PUBLIC);
ConstPool constPool = ctClass.getClassFile().getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation ann = new Annotation("io.swagger.annotations.ApiModelProperty", constPool);
ann.addMemberValue("value", new StringMemberValue(property.description(), constPool));
if (ctField.getType().subclassOf(ClassPool.getDefault().get(String.class.getName())))
ann.addMemberValue("example", new StringMemberValue(property.example(), constPool));
if (ctField.getType().subclassOf(ClassPool.getDefault().get(Integer.class.getName())))
ann.addMemberValue("example", new IntegerMemberValue(Integer.parseInt(property.example()), constPool));
attr.addAnnotation(ann);
ctField.getFieldInfo().addAttribute(attr);
return ctField;
}
private CtClass getFieldType(String type) throws NotFoundException {
CtClass fileType = null;
switch (type) {
case "string":
fileType = ClassPool.getDefault().get(String.class.getName());
break;
case "int":
fileType = ClassPool.getDefault().get(Integer.class.getName());
break;
}
return fileType;
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
(4)示例
/*
* 获取用户信息
*/
@PostMapping("/getUserInfo")
@ApiOperation(value="获取用户信息", notes="获取用户信息", httpMethod="POST")
@ApiResponses({
@ApiResponse(code = 200, message = "返回成功", response = TbProject.class)
})
public ResponseView getUserInfo(@ApiJsonObject(name = "user_info", value = {
@ApiJsonProperty(key = "userName", example = "大王", description = "用户名称")
}) @RequestBody Map param) {
String userName= String.valueOf(param.get("userName"));
return userService.getUserInfo(userName);
}
四.问题汇总
1、Swagger-UI 提示Unable to infer base url。