springfox / springfox

Automated JSON API documentation for API's built with Spring
http://springfox.io
Apache License 2.0
5.93k stars 1.54k forks source link

Spring 5.3/Spring Boot 2.4 support #3462

Open MiniDigger opened 4 years ago

MiniDigger commented 4 years ago

If you enable the new PathPatternParser (https://spring.io/blog/2020/06/30/url-matching-with-pathpattern-in-spring-mvc), springfox fails with an NPE

Details ``` org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:185) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:53) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:360) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:158) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:122) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:913) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:570) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143) ~[spring-boot-2.4.0-M1.jar:2.4.0-M1] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.4.0-M1.jar:2.4.0-M1] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) ~[spring-boot-2.4.0-M1.jar:2.4.0-M1] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.4.0-M1.jar:2.4.0-M1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.4.0-M1.jar:2.4.0-M1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) ~[spring-boot-2.4.0-M1.jar:2.4.0-M1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.4.0-M1.jar:2.4.0-M1] at me.minidigger.hangar.HangarApplication.main(HangarApplication.java:10) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.4.0-M1.jar:2.4.0-M1] Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns(WebMvcPatternsRequestConditionWrapper.java:56) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0] at springfox.documentation.RequestHandler.sortedPaths(RequestHandler.java:113) ~[springfox-core-3.0.0.jar:3.0.0] at springfox.documentation.spi.service.contexts.Orderings.lambda$byPatternsCondition$3(Orderings.java:89) ~[springfox-spi-3.0.0.jar:3.0.0] at java.base/java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469) ~[na:na] at java.base/java.util.TimSort.binarySort(TimSort.java:296) ~[na:na] at java.base/java.util.TimSort.sort(TimSort.java:239) ~[na:na] at java.base/java.util.Arrays.sort(Arrays.java:1306) ~[na:na] at java.base/java.util.ArrayList.sort(ArrayList.java:1720) ~[na:na] at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:392) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:81) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0] at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1624) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.withDefaults(AbstractDocumentationPluginsBootstrapper.java:107) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.buildContext(AbstractDocumentationPluginsBootstrapper.java:91) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.bootstrapDocumentationPlugins(AbstractDocumentationPluginsBootstrapper.java:82) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:100) ~[springfox-spring-web-3.0.0.jar:3.0.0] at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:182) ~[spring-context-5.3.0-M1.jar:5.3.0-M1] ... 19 common frames omitted ```

I thought it was fixed by this line https://github.com/springfox/springfox/blob/master/springfox-spring-webmvc/src/main/java/springfox/documentation/spring/web/WebMvcRequestHandler.java#L83

-        requestMapping.getPatternsCondition());
+        requestMapping.getActivePatternsCondition());

but I couldn't get this to work in 5 minutes, this prolly needs larger refactors.

for reference: https://github.com/spring-projects/spring-framework/blob/b572f7618f01897b611a47376c538b88be4dff80/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java#L219

edit: if you face this issue, you should consider upgrading to springdoc since this lib seems dead: https://springdoc.org/migrating-from-springfox.html

jojijohn commented 3 years ago

I am also running into this same issue. Is there a ETA for Spring 5.3 support?

euler-king commented 3 years ago

I also have the same problem。 I find this:spring boot 2.6.0-M2 is support,but spring boot 2.6.0-M3 not support spring framwork 5.3.9 support, but spring framwork 5.3.10 not support now I am find what problem cause。

and I want to know if this project not continue maintenance。it has 12 month not update。

leinadpb commented 3 years ago

Are you planning on adding support for Spring Boot 2.6.0-M3?

pdavie commented 3 years ago

Confirming that I have encountered this issue in Spring Boot 2.6.0-M3 also.

wuyinq commented 3 years ago

I also have the same problem。 I find this:spring boot 2.6.0-M2 is support,but spring boot 2.6.0-M3 not support spring framwork 5.3.9 support, but spring framwork 5.3.10 not support now I am find what problem cause。

and I want to know if this project not continue maintenance。it has 12 month not update。

I also have the same proplem. And the reason is this. Thanks!

euler-king commented 3 years ago

Are you planning on adding support for Spring Boot 2.6.0-M3?

I wait springfox official to fix this problem。or hope spring boot 2.6.0 release can support old version。when I have time, I alse want to support affter Spring Boot 2.6.0-M3?

Sonix commented 3 years ago

The same problem still happens with Spring Boot 2.6.0-RC1.

Sonix commented 3 years ago

Hacky workaround (don't know what that breaks, I don't use Documentation):

Remove @Component from springfox-spring-web/src/main/java/springfox/documentation/spring/web/plugins/DocumentationPluginsBootstrapper.java

My app launches afterwards and works.

ubaid4j commented 3 years ago

Not working with Spring Boot 2.6.0

lWoHvYe commented 3 years ago

I find this in the Spring Boot Release-Notes, But it seems not works for me. https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes#pathpattern-based-path-matching-strategy-for-spring-mvc

ubaid4j commented 3 years ago

I find this in the Spring Boot Release-Notes, But it seems not works for me. https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes#pathpattern-based-path-matching-strategy-for-spring-mvc

yeah it didn't work

dujie-js commented 3 years ago

From the perspective of debugging, the condition content of the old version has value / {???} / {???} But the new version is empty

quaff commented 3 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
    public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
            List<RequestMappingInfoHandlerMapping> handlerMappings) {
        this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
                .collect(Collectors.toList());
artem-cgi commented 3 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
  public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
          List<RequestMappingInfoHandlerMapping> handlerMappings) {
      this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
              .collect(Collectors.toList());

how you hack it?

renanleandrof commented 3 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
  public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
          List<RequestMappingInfoHandlerMapping> handlerMappings) {
      this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
              .collect(Collectors.toList());

How exactly is this "hack"? Can you help us with some more complete info?

quaff commented 3 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
    public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
            List<RequestMappingInfoHandlerMapping> handlerMappings) {
        this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
                .collect(Collectors.toList());

How exactly is this "hack"? Can you help us with some more complete info?

Copy WebMvcRequestHandlerProvider.java to your project main source directory, then modify this line, that's why I call it hack not workaround.

alexandreJavaDeveloper commented 3 years ago

Thanks for the answer, but this option does not work for me. It comes with more issues such as:

Failed to process import candidates for configuration class [springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'webMvcRequestHandlerProvider' for bean class [springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider] conflicts with existing, non-compatible bean definition of same name and class [test.com.WebMvcRequestHandlerProvider]

quaff commented 3 years ago

Thanks for the answer, but this option does not work for me. It comes with more issues such as:

Failed to process import candidates for configuration class [springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'webMvcRequestHandlerProvider' for bean class [springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider] conflicts with existing, non-compatible bean definition of same name and class [test.com.WebMvcRequestHandlerProvider]

You shouldn't change the package name to your own test.com.

alexandreJavaDeveloper commented 3 years ago

Thanks @quaff but I put "test.com" just to not add here all package of my private project.

quaff commented 3 years ago

springfox.documentation.spring.web.plugins

I mean you should keep the original package name springfox.documentation.spring.web.plugins, not your own.

alexandreJavaDeveloper commented 3 years ago

It worked =) Thanks a lot.

Hope SpringFox can release a new version soon to remove the workaround solution.

mbazos commented 3 years ago

I don't have a lot of time today but I started this PR https://github.com/springfox/springfox/pull/3936 it's definitely not great because I think a lot of dependencies should be upgraded but it does build and all the tests pass.

I am not sure what @dilipkrish wants to do here, if you upgrade spring you will be forced to upgrade gradle/groovy and some other things.

deepak-auto commented 3 years ago

Hi, will this be patched to SpringFox 2.9.x or would we need to upgrade to SpringFox 3.0.x?

maxxedev commented 3 years ago

Here is another work-around:

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. add this bean to your app:

    @Bean
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            }
            return bean;
        }
    
        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
            List<T> copy = mappings.stream()
                    .filter(mapping -> mapping.getPatternParser() == null)
                    .collect(Collectors.toList());
            mappings.clear();
            mappings.addAll(copy);
        }
    
        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
            try {
                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    };
    }
quaff commented 3 years ago

👍 Better than mine.

seanmbowen commented 2 years ago

Here is another work-around:

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. add this bean to your app:
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            }
            return bean;
        }

        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
            List<T> copy = mappings.stream()
                    .filter(mapping -> mapping.getPatternParser() == null)
                    .collect(Collectors.toList());
            mappings.clear();
            mappings.addAll(copy);
        }

        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
            try {
                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    };
}

Can you explain how I need to add this bean to my code? Sorry I am new to all of this. I added the bean to my SwaggerConfig class but getting an error "Cannot resolve method 'findField(java.lang.Class<capture<? extends java.lang.Object>>, java.lang.String)'" at this line: Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");

teopapath commented 2 years ago

Is there any update on this issue? Should we wait for a new version soon?

quaff commented 2 years ago

Is there any update on this issue? Should we wait for a new version soon?

I suggest you migrate to springdoc.

teopapath commented 2 years ago

so this project is not maintained anymore?

alexandreJavaDeveloper commented 2 years ago

Honestly, I am thinking to migrate also, as I am facing several issue on my 2 projects (even though it worked for 1 project), and the project itself its a bit confusing, as basic example we never know which URL we must to use, and when has a migration, they change the ULR.

chqiuu commented 2 years ago

Here is another work-around:

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. add this bean to your app:
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            }
            return bean;
        }

        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
            List<T> copy = mappings.stream()
                    .filter(mapping -> mapping.getPatternParser() == null)
                    .collect(Collectors.toList());
            mappings.clear();
            mappings.addAll(copy);
        }

        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
            try {
                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    };
}

This way can really solve the problem I'm having right now, thank you!

ranma2913 commented 2 years ago

Here is another work-around:

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. add this bean to your app:
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            }
            return bean;
        }

        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
            List<T> copy = mappings.stream()
                    .filter(mapping -> mapping.getPatternParser() == null)
                    .collect(Collectors.toList());
            mappings.clear();
            mappings.addAll(copy);
        }

        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
            try {
                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    };
}

Can you explain how I need to add this bean to my code? Sorry I am new to all of this. I added the bean to my SwaggerConfig class but getting an error "Cannot resolve method 'findField(java.lang.Class<capture<? extends java.lang.Object>>, java.lang.String)'" at this line: Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");

Hi, you just have to put that bean in a class annotated with @Configuration like this:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.*;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.*;
import springfox.documentation.service.*;
import springfox.documentation.spi.*;
import springfox.documentation.spring.web.plugins.*;

import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
public class Swagger2Config {
  @Bean
  public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

      @Override
      public Object postProcessAfterInitialization(Object bean, String beanName)
          throws BeansException {
        if (bean instanceof WebMvcRequestHandlerProvider
            || bean instanceof WebFluxRequestHandlerProvider) {
          customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
        }
        return bean;
      }

      private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(
          List<T> mappings) {
        List<T> copy =
            mappings.stream()
                .filter(mapping -> mapping.getPatternParser() == null)
                .collect(Collectors.toList());
        mappings.clear();
        mappings.addAll(copy);
      }

      @SuppressWarnings("unchecked")
      private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
        try {
          Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
          field.setAccessible(true);
          return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
        } catch (IllegalArgumentException | IllegalAccessException e) {
          throw new IllegalStateException(e);
        }
      }
    };
  }
}
narendarmadduru commented 2 years ago

Here is another work-around:

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. add this bean to your app:
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            }
            return bean;
        }

        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
            List<T> copy = mappings.stream()
                    .filter(mapping -> mapping.getPatternParser() == null)
                    .collect(Collectors.toList());
            mappings.clear();
            mappings.addAll(copy);
        }

        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
            try {
                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    };
}

Can you explain how I need to add this bean to my code? Sorry I am new to all of this. I added the bean to my SwaggerConfig class but getting an error "Cannot resolve method 'findField(java.lang.Class<capture<? extends java.lang.Object>>, java.lang.String)'" at this line: Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");

Hi, you just have to put that bean in a class annotated with @configuration like this:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.*;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.*;
import springfox.documentation.service.*;
import springfox.documentation.spi.*;
import springfox.documentation.spring.web.plugins.*;

import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
public class Swagger2Config {
  @Bean
  public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

      @Override
      public Object postProcessAfterInitialization(Object bean, String beanName)
          throws BeansException {
        if (bean instanceof WebMvcRequestHandlerProvider
            || bean instanceof WebFluxRequestHandlerProvider) {
          customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
        }
        return bean;
      }

      private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(
          List<T> mappings) {
        List<T> copy =
            mappings.stream()
                .filter(mapping -> mapping.getPatternParser() == null)
                .collect(Collectors.toList());
        mappings.clear();
        mappings.addAll(copy);
      }

      @SuppressWarnings("unchecked")
      private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
        try {
          Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
          field.setAccessible(true);
          return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
        } catch (IllegalArgumentException | IllegalAccessException e) {
          throw new IllegalStateException(e);
        }
      }
    };
  }
}

The above solution worked for me, Thanks

SG0ne commented 2 years ago

Workaround worked for startup problems - but no swagger api documentation will be found anymore.

Anyway for anyone who needs it in Kotlin

@Configuration
class SwaggerBeanPostProcessor {

    @Bean
    fun springfoxHandlerProviderBeanPostProcessor(): BeanPostProcessor {
        return object : BeanPostProcessor {
            @Throws(BeansException::class)
            override fun postProcessAfterInitialization(bean: Any, beanName: String): Any {
                if (bean is WebMvcRequestHandlerProvider || bean is WebFluxRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean))
                }
                return bean
            }

            private fun <T : RequestMappingInfoHandlerMapping?> customizeSpringfoxHandlerMappings(mappings: MutableList<T>) {
                val copy = mappings.filter { mapping -> mapping?.patternParser == null }
                mappings.clear()
                mappings.addAll(copy)
            }

            @Suppress("UNCHECKED_CAST")
            private fun getHandlerMappings(bean: Any): MutableList<RequestMappingInfoHandlerMapping> {
                return try {
                    val field = ReflectionUtils.findField(bean.javaClass, "handlerMappings")
                    field?.setAccessible(true)
                    field?.get(bean) as MutableList<RequestMappingInfoHandlerMapping>
                } catch (e: Exception) {
                    throw IllegalStateException(e)
                }
            }
        }
    }

}
quaff commented 2 years ago
  1. spring.mvc.pathmatch.matching-strategy to ant-path-matcher

Have you set spring.mvc.pathmatch.matching-strategy to ant-path-matcher?

ScGPS commented 2 years ago

Please Refer: https://github.com/springfox/springfox#migrating-from-earlier-snapshot In application.properties add: spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

In pom.xml add:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

It's ok.

kdebski85 commented 2 years ago

The customizeSpringfoxHandlerMappings method in the workaround can be simplified with:

    private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
        mappings.removeIf(mapping -> mapping.getPatternParser() != null);
    }
mywill commented 2 years ago

Please Refer: https://github.com/springfox/springfox#migrating-from-earlier-snapshot In application.properties add: spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

In pom.xml add:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

It's ok.

To add to this if you are still having the issue it seems to not work if you have the actuator dependency in your project found in this stackoverflow

Just to add this will not work if spring boot actuator dependency is there. I had to remove the actuator dependency (losing the actuator endpoints of course). and after adding this property with swagger version(s) up to 3.0.0 this worked – 
M. Amer
Dec 19 at 18:00
danparisi commented 2 years ago

Here is another work-around:

1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher

2. add this bean to your app:
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            }
            return bean;
        }

        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
            List<T> copy = mappings.stream()
                    .filter(mapping -> mapping.getPatternParser() == null)
                    .collect(Collectors.toList());
            mappings.clear();
            mappings.addAll(copy);
        }

        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
            try {
                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    };
}

In my case (Spring Boot 2.6.2 / Springfox 3.0.0) adding this bean was enough, I didn't need to revert matching strategy. Thank you.

UsamaIslam commented 2 years ago

In my case shifting to SpringDoc worked like charm

mbazos commented 2 years ago

I did a half-baked PR to fix this but I also gave up on it. It was easier just to switch to SpringDoc

laohuihui178 commented 2 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
  public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
          List<RequestMappingInfoHandlerMapping> handlerMappings) {
      this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
              .collect(Collectors.toList());

我下载了springfox的源码,按你说的修改了WebMvcRequestHandlerProvider 这个类,重新打个定制版的springfox-spring-webmvc-3.0.0.jar替换官方的jar, 项目成功启动,哈哈

quaff commented 2 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
    public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
            List<RequestMappingInfoHandlerMapping> handlerMappings) {
        this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
                .collect(Collectors.toList());

我下载了springfox的源码,按你说的修改了WebMvcRequestHandlerProvider 这个类,重新打个定制版的springfox-spring-webmvc-3.0.0.jar替换官方的jar, 项目成功启动,哈哈

@laohuihui178 可以试试替换成springdoc

laohuihui178 commented 2 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
  public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
          List<RequestMappingInfoHandlerMapping> handlerMappings) {
      this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
              .collect(Collectors.toList());

我下载了springfox的源码,按你说的修改了WebMvcRequestHandlerProvider 这个类,重新打个定制版的springfox-spring-webmvc-3.0.0.jar替换官方的jar, 项目成功启动,哈哈

@laohuihui178 可以试试替换成springdoc

我是通过knife4j 使用的swagger, 换成springdoc怕是又要一堆奇奇怪怪的异常

kontownik commented 2 years ago

In my case shifting to SpringDoc worked like charm

I can confirm. Tried most of the workarounds listed here, as far as I understand you cannot make it work if you have spring-boot-starter-actuator in your stack. I can confirm moving to latest org.springframework.boot = 2.6.2 and springdoc-openapi-ui = 1.6.3 solved the issue. For me, it was a clean solution.

leefengzi commented 2 years ago

Workaround for Spring Boot 2.6.x

  1. revert matching strategy spring.mvc.pathmatch.matching-strategy to ant-path-matcher
  2. hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
    public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
            List<RequestMappingInfoHandlerMapping> handlerMappings) {
        this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
                .collect(Collectors.toList());

我下载了springfox的源码,按你说的修改了WebMvcRequestHandlerProvider 这个类,重新打个定制版的springfox-spring-webmvc-3.0.0.jar替换官方的jar, 项目成功启动,哈哈

@laohuihui178 可以试试替换成springdoc

我是通过knife4j 使用的swagger, 换成springdoc怕是又要一堆奇奇怪怪的异常

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
arey commented 2 years ago

Same error with Spring Boot 2.6.2 and Spring Fox 3.0.0. See hack in https://github.com/spring-petclinic/spring-petclinic-rest and its related commit https://github.com/spring-petclinic/spring-petclinic-rest/commit/517a399cabe4dd7a40c07a81facd599df3cbf9cd Same behavior in my company. Could you please fix this issue?

hhhhsw commented 2 years ago
  1. set spring.mvc.pathmatch.matching-strategy: ant_path_matcher in application.properties
  2. add this bean
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
        List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
        Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
        allEndpoints.addAll(webEndpoints);
        allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
        allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
        String basePath = webEndpointProperties.getBasePath();
        EndpointMapping endpointMapping = new EndpointMapping(basePath);
        boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
        return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
    }

private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
        return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
    }
WilliamZhai commented 2 years ago
  1. set spring.mvc.pathmatch.matching-strategy: ant_path_matcher in application.properties
  2. add this bean
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
        List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
        Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
        allEndpoints.addAll(webEndpoints);
        allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
        allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
        String basePath = webEndpointProperties.getBasePath();
        EndpointMapping endpointMapping = new EndpointMapping(basePath);
        boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
        return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
    }

private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
        return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
    }

I have actuator and this worked, Thanks a lot! I'm using Spring boot 2.6.2, springfox 3.0.0, and actuator 2.6.2.

teodoracornea commented 2 years ago
  1. set spring.mvc.pathmatch.matching-strategy: ant_path_matcher in application.properties
  2. add this bean
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
        List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
        Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
        allEndpoints.addAll(webEndpoints);
        allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
        allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
        String basePath = webEndpointProperties.getBasePath();
        EndpointMapping endpointMapping = new EndpointMapping(basePath);
        boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
        return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
    }

private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
        return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
    }

I have actuator and this worked, Thanks a lot! I'm using Spring boot 2.6.2, springfox 3.0.0, and actuator 2.6.2.

Where should I add the bean?