Spring boot 2.7.3에서 Swagger 3.0.0 버전과 함께 Actuator dependency를 추가하는 과정에서 아래와 같은 오류가 발생했다.
Swagger 3.0.0 dependency를 추가했던 과정에서 발생한 오류와 동일했다.
1. 문제 발생 🤬
Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
porm.xml - Dependency
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> | cs |
2. 문제 해결 😁
Swagger는 모든 endpoint에 대해 documentation을 해주는 기능이다. Actuator 또한 endpoint(refresh, beans, health) 등을 직접 생성하는 역할이다 보니 둘의 의존성이 충돌하게 된다.
Sprinfox는 Sprinf MVC가 Ant-based path matcher를 기본값으로 하여 경로를 찾는 것으로 가정하는데 Spring MVC 2.6.X부터 기본값을 Ant-based path matcher에서 PathPatter-based matcher로 변경하여 발생하는 버그이다.
해결 방법으로는 3가지가 있는데 나는 2번으로 해결하였다.
- SpringBoot 버전을 2.5.x이하로 변경한다.
- Ant-based path matcher으로 경로 변경 & springfoxHandlerProvider 추가
- Springdoc 사용..
application.yml
1 2 3 4 | spring: mvc: pathmatch: matching-strategy: ant_path_matcher | cs |
SwaggerConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | @Configuration @EnableSwagger2 public class SwaggerConfig { @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); } } }; } // CONTACT private static final Contact DEFAULT_CONTACT = new Contact("Kenneth Lee", "http://www.joneconsulting.co.kr","edowon@joneconsulting.co.kr" ); // API INFO private static final ApiInfo DEFAULT_API_INFO = new ApiInfo("Awesome API Title", "My User management REST API Service", "1.0", "urn:tos", DEFAULT_CONTACT, "Apache License Version 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList<>()); // PRODUCES AND CONSUMES --> application.yml private static final Set<String> DEFAULT_PRODUCES_AND_CONSUMES = new HashSet<>( Arrays.asList("application/json", "application/xml")); // localhost:8088/v2/api-docs @Bean public Docket DocumentApi(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(DEFAULT_API_INFO) .produces(DEFAULT_PRODUCES_AND_CONSUMES) .consumes(DEFAULT_PRODUCES_AND_CONSUMES); } } | cs |
이처럼 config에 springfoxHandlerProvider를 추가하니 문제를 해결했다!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | @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); } } }; } | cs |
요약 👉 Springboot 2.6.x 이상에서 swagger와 Acutator의 버전 호환성 문제가 발생하면
1) application.yml에 spring: mvc: pathmatch: matching-strategy: ant_path_matcher을 추가하고
2) springfoxHandlerProvider를 추가하자!
Reference
https://middleearth.tistory.com/69
https://dkswnkk.tistory.com/672
반응형
'Trouble Shooting' 카테고리의 다른 글
[Trouble Shooting] HTTP 리다이렉션 시 POST, PUT, DELETE (CUD)가 GET으로 바뀌는 문제점 (0) | 2023.10.23 |
---|---|
[Spring Boot] Springfox - documentationPluginsBootstrapper 오류 (0) | 2023.04.14 |
[Spring Boot] API Docs : Springdoc & Springfox-Swagger (0) | 2023.04.05 |