上篇文章 https://blog.csdn.net/lmchhh/article/details/128634606?spm=1001.2014.3001.5502 我讲了SpringBoot动态生成接口,接下来要处理的就是新生成的接口如何被Swagger发现,并且可以通过/swagger-ui.html和/v2/api-docs查到
一,启动时加载
第一种最简单的方式就是在SpringBoot项目启动时加载,就像在上篇文章中,我动态创建接口的接口基本上都是在main函数中添加
@SpringBootApplication
public class ServiceApiApplication {
public static void main(String[] args) throws NoSuchMethodException {
ApplicationContext application = SpringApplication.run(ServiceApiApplication.class, args);
RequestMappingHandlerMapping bean = application.getBean(RequestMappingHandlerMapping.class);
// 无参get方法
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths("/lmcTest").methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo, "adapterController", AdapterController.class.getDeclaredMethod("myTest"));
// 带一参数的get方法
RequestMappingInfo requestMappingInfo1 = RequestMappingInfo.paths("/lmcTest2").params(new String[]{"fileName"}).methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo1, "adapterController", AdapterController.class.getDeclaredMethod("myTest2", String.class));
// 带多个参数的get方法
RequestMappingInfo requestMappingInfo2 = RequestMappingInfo.paths("/lmcTest3")
.params(new String[]{"fileName", "type", "isSort"})
.methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo2, "adapterController", AdapterController.class.getDeclaredMethod("myTest3", String.class, String.class, Boolean.class));
// 无参post方法
RequestMappingInfo requestMappingInfo3 = RequestMappingInfo.paths("/lmcTest4").methods(RequestMethod.POST).build();
bean.registerMapping(requestMappingInfo3, "adapterController", AdapterController.class.getDeclaredMethod("myTest"));
// 带参post方法
RequestMappingInfo requestMappingInfo4 = RequestMappingInfo.paths("/lmcTest5")
.params(new String[]{"fileName", "type", "isSort"})
.methods(RequestMethod.POST).build();
bean.registerMapping(requestMappingInfo4, "adapterController", AdapterController.class.getDeclaredMethod("myTest3", String.class, String.class, Boolean.class));
// body带参的post方法
RequestMappingInfo requestMappingInfo5 = RequestMappingInfo.paths("/lmcTest6")
.produces(new String[]{"text/plain;charset=UTF-8"})
.methods(RequestMethod.POST).build();
bean.registerMapping(requestMappingInfo5, "adapterController", AdapterController.class.getDeclaredMethod("myTest4", HttpServletRequest.class));
System.err.println("已经加载/lmcTest");
}
}
然后配置Swagger
@Configuration
@EnableSwagger2
@Slf4j
public class Swagger2Config {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.foxconn.serviceapi.controller")).build();
}
private ApiInfo apiInfo() {
System.err.println("开始配置Swagger");
log.info("开始配置Swagger");
return new ApiInfoBuilder()
.title("superFA说明文档")
.description("API平台接口说明文档")
.termsOfServiceUrl("")
.contact(new Contact("zg", "npi-sw", "[email protected]"))
.version("0.0.1-SNAPSHOT") // 該數據從配置文件中獲取
.build();
}
}
运行后发现,程序日志打印中,
开始配置Swagger
已经加载/lmcTest
先加载Swagger配置,再加载自定义接口,启动成功后,即使通过 /actuator/mappings可以查到自定义接口,但是在/v2/api-docs中查不到,此时需要让Swagger配置的优先级在新增接口之后,新创建接口配置类:
MappingConfig.java
/**
* @ClassName: MappingConfig
* @author: Leemon
* @Description: TODO
* @date: 2023/1/6 14:04
* @version: 1.0
*/
@Configuration
public class MappingConfig {
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void construct() throws NoSuchMethodException {
RequestMappingHandlerMapping bean = applicationContext.getBean(RequestMappingHandlerMapping.class);
// 无参get方法
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths("/lmcTest").methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo, "adapterController", AdapterController.class.getDeclaredMethod("myTest"));
// 带一参数的get方法
RequestMappingInfo requestMappingInfo1 = RequestMappingInfo.paths("/lmcTest2").params(new String[]{"fileName"}).methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo1, "adapterController", AdapterController.class.getDeclaredMethod("myTest2", String.class));
// 带多个参数的get方法
RequestMappingInfo requestMappingInfo2 = RequestMappingInfo.paths("/lmcTest3")
.params(new String[]{"fileName", "type", "isSort"})
.methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo2, "adapterController", AdapterController.class.getDeclaredMethod("myTest3", String.class, String.class, Boolean.class));
// 无参post方法
RequestMappingInfo requestMappingInfo3 = RequestMappingInfo.paths("/lmcTest4").methods(RequestMethod.POST).build();
bean.registerMapping(requestMappingInfo3, "adapterController", AdapterController.class.getDeclaredMethod("myTest"));
// 带参post方法
RequestMappingInfo requestMappingInfo4 = RequestMappingInfo.paths("/lmcTest5")
.params(new String[]{"fileName", "type", "isSort"})
.methods(RequestMethod.POST).build();
bean.registerMapping(requestMappingInfo4, "adapterController", AdapterController.class.getDeclaredMethod("myTest3", String.class, String.class, Boolean.class));
// body带参的post方法
RequestMappingInfo requestMappingInfo5 = RequestMappingInfo.paths("/lmcTest6")
.produces(new String[]{"text/plain;charset=UTF-8"})
.methods(RequestMethod.POST).build();
bean.registerMapping(requestMappingInfo5, "adapterController", AdapterController.class.getDeclaredMethod("myTest4", HttpServletRequest.class));
System.err.println("已经加载/lmcTest");
}
}
然后把main函数中多余代码删除
再重新配置Swagger2Config,对其类新增注解@AutoConfigureAfter
@Configuration
@EnableSwagger2
@AutoConfigureAfter(MappingConfig.class)
@Slf4j
public class Swagger2Config{
}
此时重新启动后加载顺序就会改变
二,运行时加载
启动时加载还是算比较简单的,但是运行时加载就比较麻烦。我一开始的思路是在运行时重新加载Swagger2Config类,但是无效(可能是我操作问题),实在找不到实现的思路和方法
我重新加载的方式如下所示:
RequestMappingHandlerMapping bean = applicationContext.getBean(RequestMappingHandlerMapping.class);
// 无参get方法
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths("/leenai").methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo, "adapterController", AdapterController.class.getDeclaredMethod("myTest"));
// 重新加载bean
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
defaultListableBeanFactory.destroySingleton("swagger2Config");
defaultListableBeanFactory.registerSingleton("swagger2Config", swagger2Config);
在尝试了很多种方式后,我找到了加载/swagger-ui.html页面时会调用的类ServiceModelToSwagger2MapperImpl和其方法public Swagger mapDocumentation(Documentation from)
public Swagger mapDocumentation(Documentation from) {
if (from == null) {
return null;
} else {
Swagger swagger = new Swagger();
swagger.setVendorExtensions(this.vendorExtensionsMapper.mapExtensions(from.getVendorExtensions()));
swagger.setSchemes(this.mapSchemes(from.getSchemes()));
swagger.setPaths(this.mapApiListings(from.getApiListings()));
swagger.setHost(from.getHost());
swagger.setDefinitions(this.modelMapper.modelsFromApiListings(from.getApiListings()));
swagger.setSecurityDefinitions(this.securityMapper.toSecuritySchemeDefinitions(from.getResourceListing()));
ApiInfo info = this.fromResourceListingInfo(from);
if (info != null) {
swagger.setInfo(this.mapApiInfo(info));
}
swagger.setBasePath(from.getBasePath());
swagger.setTags(this.tagSetToTagList(from.getTags()));
List<String> list2 = from.getConsumes();
if (list2 != null) {
swagger.setConsumes(new ArrayList(list2));
} else {
swagger.setConsumes((List)null);
}
List<String> list3 = from.getProduces();
if (list3 != null) {
swagger.setProduces(new ArrayList(list3));
} else {
swagger.setProduces((List)null);
}
return swagger;
}
}
可以看到,返回类型swagger作为方法内部变量使用,然后我重写ServiceModelToSwagger2MapperImpl类
@Primary
@Component("serviceModelToSwagger2MapperImpl2")
@Slf4j
public class ServiceModelToSwagger2MapperImpl2 extends ServiceModelToSwagger2Mapper{
public Swagger swagger = new Swagger();
public static Boolean isFirstProcess = true;// 是否第一次访问
}
将这个方法中的swagger作为类变量使用,改写的方法如下所示:
@Override
public Swagger mapDocumentation(Documentation from) {
if (from == null) {
return null;
} else {
swagger.setVendorExtensions(this.vendorExtensionsMapper.mapExtensions(from.getVendorExtensions()));
swagger.setSchemes(this.mapSchemes(from.getSchemes()));
if (isFirstProcess) {
swagger.setPaths(this.mapApiListings(from.getApiListings()));
isFirstProcess = false;
}
swagger.setHost(from.getHost());
// swagger.setDefinitions(this.modelMapper.modelsFromApiListings(from.getApiListings()));
swagger.setSecurityDefinitions(this.securityMapper.toSecuritySchemeDefinitions(from.getResourceListing()));
ApiInfo info = this.fromResourceListingInfo(from);
if (info != null) {
swagger.setInfo(this.mapApiInfo(info));
}
swagger.setBasePath(from.getBasePath());
swagger.setTags(this.tagSetToTagList(from.getTags()));
List<String> list2 = from.getConsumes();
if (list2 != null) {
swagger.setConsumes(new ArrayList(list2));
} else {
swagger.setConsumes((List) null);
}
List<String> list3 = from.getProduces();
if (list3 != null) {
swagger.setProduces(new ArrayList(list3));
} else {
swagger.setProduces((List) null);
}
log.info("enter serviceModelToSwagger2MapperImpl class method mapDocumentation()");
for (Map.Entry<String, Path> o : swagger.getPaths().entrySet()) {
System.err.println(JSON.toJSONString(o.getValue()));
}
return swagger;
}
}
然后在每次创建新自定义接口的时候,手动对其添加
@GetMapping("create")
public String create() throws NoSuchMethodException {
System.err.println(serviceModelToSwagger2MapperImpl2.swagger.getPaths().keySet().toString());
RequestMappingHandlerMapping bean = applicationContext.getBean(RequestMappingHandlerMapping.class);
// 无参get方法
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths("/leenai").methods(RequestMethod.GET).build();
bean.registerMapping(requestMappingInfo, "adapterController", AdapterController.class.getDeclaredMethod("myTest"));
// 重新加载bean
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
Path path = new Path();
Operation get = new Operation();
get.consumes(Arrays.asList());
get.setOperationId("leenaiUsingGET");
get.setProduces(Arrays.asList("*/*"));
get.setDeprecated(false);
get.setTags(Arrays.asList("adapter-controller"));
Map<String, Response> responseMap = new HashMap<>(2);
Response r200 = new Response();
r200.setDescription("OK");
r200.setExamples(new JSONObject());
r200.setHeaders(new HashMap<>());
Property schema = new Property() {
@Override
public Property title(String title) {
return null;
}
@Override
public Property description(String description) {
return null;
}
@Override
public String getType() {
return "String";
}
@Override
public String getFormat() {
return null;
}
@Override
public String getTitle() {
return null;
}
@Override
public void setTitle(String title) {
}
@Override
public String getDescription() {
return null;
}
@Override
public void setDescription(String title) {
}
@Override
public Boolean getAllowEmptyValue() {
return null;
}
@Override
public void setAllowEmptyValue(Boolean value) {
}
@Override
public String getName() {
return null;
}
@Override
public void setName(String name) {
}
@Override
public boolean getRequired() {
return false;
}
@Override
public void setRequired(boolean required) {
}
@Override
public Object getExample() {
return null;
}
@Override
public void setExample(Object example) {
}
@Override
public void setExample(String example) {
}
@Override
public Boolean getReadOnly() {
return null;
}
@Override
public void setReadOnly(Boolean readOnly) {
}
@Override
public Integer getPosition() {
return null;
}
@Override
public void setPosition(Integer position) {
}
@Override
public Xml getXml() {
return null;
}
@Override
public void setXml(Xml xml) {
}
@Override
public void setDefault(String _default) {
}
@Override
public String getAccess() {
return null;
}
@Override
public void setAccess(String access) {
}
@Override
public Map<String, Object> getVendorExtensions() {
Map<String, Object> map = new HashMap<>();
map.put("$ref", "$.get.responses.200.responseSchema.vendorExtensions");
return map;
}
@Override
public Property rename(String newName) {
return null;
}
};
ModelImpl model = new ModelImpl();
model.setType("String");
model.setSimple(false);
model.setVendorExtensions(new HashMap<>());
r200.setSchema(schema);
r200.setResponseSchema(model);
responseMap.put("200", r200);
Response r401 = new Response();
responseMap.put("401", r401);
r401.setDescription("Unauthorized");
get.setResponses(responseMap);
get.setSchemes(Arrays.asList());
get.setSecurity(Arrays.asList());
get.setSummary("leenai");
path.setGet(get);
serviceModelToSwagger2MapperImpl2.swagger.getPaths().put("/leenai", path);
System.err.println(serviceModelToSwagger2MapperImpl2.swagger.getPaths().keySet().toString());
return "success to create and reload createRestApi()";
}
同时,还要到Swagger2Config类中添加@PostConstruct方法
@PostConstruct
public void test() {
System.err.println("enter swaggerConfiguration test()");
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
defaultListableBeanFactory.destroySingleton("serviceModelToSwagger2MapperImpl");
defaultListableBeanFactory.registerSingleton("serviceModelToSwagger2MapperImpl", serviceModelToSwagger2MapperImpl2);
}
此时新增接口后就可以直接通过/swagger-ui.html访问得到。
总之,这种方式确实解决了我的问题,但实在很不友好,自己修改swagger类实在需要补充太多内容,如果大佬们有更多好的方式,请务必指教我这个菜鸡。