Error Solution

swagger + springdoc 연동 및 파라미터 이름 미스매핑 arg0 해결

lsh2613 2023. 11. 22. 17:37

기존에는 spring boot 2.* springfox를 사용하여 swagger를 이용했지만 boot 3.* 부터는 springdoc을 더 권장하는 분위기이고 springfox는 2년 전 개발이 중단되었지만 springdoc은 계속 개발되고 있어 이번에는 boot3과 springdoc을 사용해보았다.

 

설정 부분에서는 크게 달라진 점 없었고 아래 종속성만 추가해주면 된다.

//swagger 연동
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'

 

boot3으로 넘어오면서 바뀐건지 해당 종속성 문제인지는 모르겠지만 앱 실행하면 다음과 같은 에러가 발생한다.

jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
	at jakarta.validation.Validation$GenericBootstrapImpl.configure(Validation.java:291) ~[jakarta.validation-api-3.0.2.jar:na]
	at jakarta.validation.Validation.buildDefaultValidatorFactory(Validation.java:103) ~[jakarta.validation-api-3.0.2.jar:na]
	at org.hibernate.cfg.beanvalidation.TypeSafeActivator.getValidatorFactory(TypeSafeActivator.java:479) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.cfg.beanvalidation.TypeSafeActivator.activate(TypeSafeActivator.java:82) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[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:568) ~[na:na]
	at org.hibernate.cfg.beanvalidation.BeanValidationIntegrator.integrate(BeanValidationIntegrator.java:137) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:287) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:415) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1423) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
	at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.0.13.jar:6.0.13]
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:376) ~[spring-orm-6.0.13.jar:6.0.13]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-6.0.13.jar:6.0.13]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-6.0.13.jar:6.0.13]
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:352) ~[spring-orm-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1817) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1766) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.0.13.jar:6.0.13]
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1166) ~[spring-context-6.0.13.jar:6.0.13]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:940) ~[spring-context-6.0.13.jar:6.0.13]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616) ~[spring-context-6.0.13.jar:6.0.13]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.0.12.jar:3.0.12]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:733) ~[spring-boot-3.0.12.jar:3.0.12]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:435) ~[spring-boot-3.0.12.jar:3.0.12]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) ~[spring-boot-3.0.12.jar:3.0.12]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-3.0.12.jar:3.0.12]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290) ~[spring-boot-3.0.12.jar:3.0.12]
	at com.feathercode.FeatherCodeApplication.main(FeatherCodeApplication.java:12) ~[classes/:na]

 

발생 이유를 조사해본 바로는 Hibernate Validator는 validation 라이브러리를 사용하는 bean 클래스로 swagger 내부에서 사용하는데 현 프로젝트에는 포함이 되어 있지 않았다. 따라서 해당 클래스의 종속성을 추가해주기만 하면 된다.

##maven
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.4.Final</version>
</dependency>

##gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'

 

잘 해결되나 싶었지만 구현과정에서 불편한 점이 발생했다. controller에서 구현한 메소드의 파라미터 이름과 swagger에서 제공하는 api ui의 파라미터 이름이 매핑이 되지 않아 파라미터가 제대로 넘어가지 않는다.

 

@Operation(summary = "문자열 이어붙이기") // 메소드 설명
@GetMapping("/swagger/concat")
public String concat(String str1,
                   @RequestParam(name = "str2") String str2) {
    return str1 + " " + str2;
}

 

str2처럼 @RequestParam의 name속성을 지정해주면 swagger ui에서도 잘 매핑되지만, str1처럼 그대로 구현하게 되면 arg0로 넘어가기 때문에 에러가 발생한다.

 

물론 매번 @RequestParam을 통해 name속성을 지정해주면 해결할 수 있지만 매우 번거로운 일이고 더 큰 문제는 @RequestBody에서 나타난다. @RequestBody에는 name속성이 없어 파라미터로 값을 받아올 수 없다.. (해결법이 있을 순 있지만 저는 해결못함)

 

여러 자료를 찾아보다가 이미 이슈된 문제지만 딱히 해결방법은 없고 springdoc 이전 버전에서는 문제가 없었다고 해서 2.0.2 -> 2.0.0으로 다운그레이드해보았다.

//swagger 연동
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.0'

 

이제 이름을 지정해주지 않아도 파라미터 이름이 제대로 매핑되는 것을 확인할 수 있다.

@Operation(summary = "문자열 이어붙이기") // 메소드 설명
@GetMapping("/swagger/concat")
public String concat(String str1, String str2) {
    return str1 + " " + str2;
}

 

당연히 (@RequestBody Dto dto)에 대한 파라미터에 대해서도 dto의 필드를 정확히 읽고 파라미터 이름이 매핑된다.

결론

springdoc 2.0.0 버전을 사용하자