SpringBoot项目运行时动态生成接口被Swagger发现

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

上篇文章 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", "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 {

    @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类实在需要补充太多内容如果大佬们有更多好的方式请务必指教我这个菜鸡。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring

“SpringBoot项目运行时动态生成接口被Swagger发现” 的相关文章