Development/Daily Error

일간에러 More than one fragment with the name [spring_web] was found.

📝 작성 : 2022.03.17  ⏱ 수정 : 
반응형

Intro

Caused by: java.lang.IllegalArgumentException: More than one fragment with the name [spring_web] was found. This is not legal with relative ordering. See section 8.2.2 2c of the Servlet specification for details. Consider using absolute ordering.
adm_1         |         at org.apache.tomcat.util.descriptor.web.WebXml.orderWebFragments(WebXml.java:2205)
adm_1         |         at org.apache.tomcat.util.descriptor.web.WebXml.orderWebFragments(WebXml.java:2164)
adm_1         |         at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1083)
adm_1         |         at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:779)
adm_1         |         at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:299)
adm_1         |         at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
adm_1         |         at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5130)
adm_1         |         at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
adm_1         |         ... 6 more
adm_1         | 17-Mar-2022 11:42:21.738 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
adm_1         | 17-Mar-2022 11:42:21.738 INFO [main] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
adm_1         | 17-Mar-2022 11:42:21.740 INFO [main] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]

최근 SpringBoot 버전을 1.5에서 2.5 로 마이그레이션을 진행 했다. 생각보다 작업량이 꽤나 많았고, 어느 정도 진행이 완료되어 프로젝트가 개발환경에서 정상적으로 구동하는 것을 확인 하여, 개발 서버에서 배포를 시도해 보았다.

docker-compose 에서 tomcat8.5 컨테이너를 띄우고, webapps 폴더를 볼륨으로 연결해 배포한 war 파일을 띄우는 방식.

각각의 어플리케이션 war파일들을 export하여 개발 서버에서 docker-compose up을 하자 굉장한 에러가 발생하였다. 한번에 될리가 없다.

원인 More than one fragment

Spring_web 이라는 같은 이름으로 된 fragment 가 하나 이상 발견되었다는걸로 보아, 스프링 부트 버전을 올린게 원인이었음이 확실하다.

stack overflow 답변에 따르면, tomcat webapps의 project history file을 비워야 한다고 한다.

그래서 tomcat 컨테이너에 문제가 있는지 먼저 확인을 위해 docker-compose의 모든 컨테이너를 삭제 해 보았다.

모든 컨테이너의 모든 저장되어야 하는 파일들은 Volume으로 빼놓았을 때에만 컨테이너를 과감하게 삭제 할 수 있다.

볼륨을 지정 해 두지 않았다면 모든 데이터를 날리게 된다.

docker rm -f $(docker ps -a -q)

이후 모든 컨테이너를 새로 띄워 보았지만, 여전히 어플리케이션들이 구동 되지 않는다. 생성한 war파일에 이미 문제가 있음이 확실하다.

image-20220317115755838

그래서 생성된 war 파일의 /WEB-INF/lib/ 폴더를 확인 해 보니 모든 라이브러리들이 과거 버전과 최근 변경된 버전 두개씩 중복으로 들어 가 있다. 어쩐지 war파일의 용량이 기존 파일의 2배가까이 되어서 이상하다 했다. 위의 스크린샷에서도 보면 라이브러리에 spring-boot-starter-web이 1.5버전과 2.5버전이 동시에 들어있으미 확인된다.

해결

https://stackoverflow.com/questions/59732812/war-file-creates-a-duplicate-dependency

war 파일에 의존성 중복이 되는 경우에 대한 stackoverflow 검색 결과 mvn clean package가 해결책으로 제시되었다.

나는 IntelliJ IDEA의 Build Artifact를 활용해 war파일을 만들고 있었기 때문에

image-20220317132009235

IntelliJ IDEA의 Maven 플러그인을 활용해 Clean을 수행 했다.

image-20220317132218162

1초 내에 모든 프로젝트의 패키지를 Clean 해 주었다.

이후 다시 Build Artifact를 통해 빌드를 하자 더이상 필요 없는 패키지를 local Maven Repository에서 꺼내오지 않았다.

/WEB-INF/lib/ 폴더내에는 딱 필요한 라이브러리들만 들어 있는것이 확인 되었다. 그대로 다시 배포해 서버에서 실행. 보통의 경우는 아마 여기서 대부분 해결 될 거라고 본다.

원인2 RequestLog$Writer

하지만 난 대부분의 어플리케이션이 정상적으로 실행 되었지만 딱 하나의 어플리케이션(adm)이 에러를 뿜는다.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tomcatServletWebServerFactory' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration$EmbeddedTomcat.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jettyWebServerFactoryCustomizer' defined in class path resource [org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration$JettyWebServerFactoryCustomizerConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer]: Factory method 'jettyWebServerFactoryCustomizer' threw exception; nested exception is java.lang.NoClassDefFoundError: org/eclipse/jetty/server/RequestLog$Writer

이 외에도 에러 메시지가 길었는데 핵심은 java.lang.NoClassDefFoundError: org/eclipse/jetty/server/RequestLog$Writer 라고 봤다.

JettyWebServer를 왜 찾지? 나는 외장 Tomcat을 사용하고 있는데.. 최근 Swagger를 추가했는데 거기에서 Webflux 클래스를 필요로 하던데 Webflux를 사용하면 기본 내장 WAS가 Netty가 되는 것 때문인가 의심.

구글에 검색해도 해당 에러에 대한 내용은 https://stackoverflow.com/questions/63394287/spring-boot-application-is-not-running-after-getting-error-caused-by-java-lang/71509270#71509270 딸랑 하나 나오는데 답변도 없었다. 결국 내가 해결을 한 이후에 스스로 답변을 남겨 두었다.

여러 어플리케이션중 adm에만 Swagger를 설치했는데 마침 실행이 정상적으로 되지 않는 어플리케이션도 adm 하나 뿐인 상황.

실제 에러가 나는 부분을 찾아 가 보니, EmbeddedWebServerFactoryCustomizerAutoConfiguration에서 자동 으로 내장 WAS에 대한 설정을 하는데

EmbeddedWebServerFactoryCustomizerAutoConfiguration.java

image-20220317163145888

자동 설정 중 dependency에 등록 하지도 않은 Jetty를 설정 하려고 들어가고 있었고

JettyWebServerFactoryCustomizer의 코드를 확인 해 보면

image-20220317163715628

jetty 의존성을 추가하지 않았기 때문에 가지고 있지 않은 RequestLogWriter 등의 클래스를 사용하려고 하면 당연히 NoClassDefFoundError 익셉션이 뜰 수 밖에. RequestLogWriter만 못찾는건 아닌데 제일 먼저 찾다 실패한 클래스가 저거다 보니 에러메시지도 저렇게 뜬다. 암만 검색해도 참고할 만한 자료가 극히 적었다.

혹시나 하는 맘에 dependency에 jetty를 추가 해 봤지만, jetty로 설정되어 있는데 (외장)톰캣으로 서버를 띄웠다며 심술을 부린다. 호락호락하게 해결해주지 않는다.

해결2

누군가는 TomcatServletWebServerFactory를 Bean으로 등록 하면 내장 WAS 설정이 강제로 된다고 했는데.

https://stackoverflow.com/questions/35844784/how-to-force-spring-boot-to-use-tomcat-server-in-integration-tests/71509171#71509171

@Configuration
@AutoConfigureBefore(ServletWebServerFactoryAutoConfiguration.class)
public class ForceTomcatAutoConfiguration {

    @Bean
    TomcatServletWebServerFactory tomcat() {
         return new TomcatServletWebServerFactory();
    }
}

SpringBoot 2.5.10 버전 기준으로 나는 위의 방법으로는 해결이 안되었고 오히려 다른 에러가 발생했다.

그래서

@SpringBootApplication(exclude = org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration.class)

혹시나 하는 맘에 Embedded 서버를 자동 설정하는 Bean을 exclude 시켜 버리고 다시 실행 해 보았는데..

아무 문제 없이 tomcat을 내장 서버로 잘 사용하기 시작했다. 이때쯤부터는 scp로 war파일 전송하는것도 지쳐서 로컬에서 외장 톰캣을 하나 띄워놓고 war파일 넣어가며 테스트 하고 있었는데 처음으로 에러가 없었다.

바로 서버에도 배포 해 보았는데 드디어 정상적으로 Spring Boot 2.5.10 으로 마이그레이션 한 어플리케이션이 동작한다. jetty로 자동 설정이 되었던 이유는 swagger(springfox3) 에 관련이 있다고 추측이 된다. 어쨌든 나름 복잡한 문제였는데 생각보다 깔끔하게 해결 되어 다행이다.

막상 버그 해결을 끝내 commit을 하려 보니 추가된 코드는 단 한줄이다. 속이 참 쓰리다.

반응형