上篇文章 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 {@Beanpublic 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", "npi-sw@mail.foxconn.com")).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 {@Autowiredprivate ApplicationContext applicationContext;@PostConstructpublic 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"));// 重新加载beanDefaultListableBeanFactory 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 list2 = from.getConsumes();if (list2 != null) {swagger.setConsumes(new ArrayList(list2));} else {swagger.setConsumes((List)null);}List 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作为类变量使用,改写的方法如下所示:
@Overridepublic 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 list2 = from.getConsumes();if (list2 != null) {swagger.setConsumes(new ArrayList(list2));} else {swagger.setConsumes((List) null);}List 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 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"));// 重新加载beanDefaultListableBeanFactory 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 responseMap = new HashMap<>(2);Response r200 = new Response();r200.setDescription("OK");r200.setExamples(new JSONObject());r200.setHeaders(new HashMap<>());Property schema = new Property() {@Overridepublic Property title(String title) {return null;}@Overridepublic Property description(String description) {return null;}@Overridepublic String getType() {return "String";}@Overridepublic String getFormat() {return null;}@Overridepublic String getTitle() {return null;}@Overridepublic void setTitle(String title) {}@Overridepublic String getDescription() {return null;}@Overridepublic void setDescription(String title) {}@Overridepublic Boolean getAllowEmptyValue() {return null;}@Overridepublic void setAllowEmptyValue(Boolean value) {}@Overridepublic String getName() {return null;}@Overridepublic void setName(String name) {}@Overridepublic boolean getRequired() {return false;}@Overridepublic void setRequired(boolean required) {}@Overridepublic Object getExample() {return null;}@Overridepublic void setExample(Object example) {}@Overridepublic void setExample(String example) {}@Overridepublic Boolean getReadOnly() {return null;}@Overridepublic void setReadOnly(Boolean readOnly) {}@Overridepublic Integer getPosition() {return null;}@Overridepublic void setPosition(Integer position) {}@Overridepublic Xml getXml() {return null;}@Overridepublic void setXml(Xml xml) {}@Overridepublic void setDefault(String _default) {}@Overridepublic String getAccess() {return null;}@Overridepublic void setAccess(String access) {}@Overridepublic Map getVendorExtensions() {Map map = new HashMap<>();map.put("$ref", "$.get.responses.200.responseSchema.vendorExtensions");return map;}@Overridepublic 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方法
@PostConstructpublic void test() {System.err.println("enter swaggerConfiguration test()");DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();defaultListableBeanFactory.destroySingleton("serviceModelToSwagger2MapperImpl");defaultListableBeanFactory.registerSingleton("serviceModelToSwagger2MapperImpl", serviceModelToSwagger2MapperImpl2);}
此时新增接口后就可以直接通过/swagger-ui.html访问得到。
总之,这种方式确实解决了我的问题,但实在很不友好,自己修改swagger类实在需要补充太多内容,如果大佬们有更多好的方式,请务必指教我这个菜鸡。