- 소심한 개발자가 스프링 부트 전환할 때는 이렇게 하면 된다.
지난주에 작업 했던 스프링 부트로 전환 과정을 간단하게 글로 남긴다. 참고로, 회사 소스는 보안상 외부에 글로 남길수가 없다. 필자가 작성한 코드는 회사 소스랑 별개로 필자가 따로 작성한 코드이며, 기타 회사 업무 관련된 내용은 필자의 글에서는 모두 제외하였다.
필자가 맡고 있는 플랫폼 중 레거시 프로젝트가 몇개 있다. 필자의 개발철학(?)으로는 계속 방치하는 건 문제가 있다고 생각하며 기술부채는 어떻게든 해결해야 하지만, 스프링 부트로 전환하는 과정에서 발생하는 예상치 못한 이슈에 대해서는 섬세하게 생각해야 한다. 개인 프로젝트라면 마음대로 할 수 있지만, 회사 플랫폼을 전환하는 과정에서 문제가 발생하면 누구의 책임인가? 플랫폼을 몇년동안 방치하고 퇴사한 전 개발자들의 책임인가? 아니면 전환 작업을 하다가 오류를 발생한 나의 책임인가? 결국 내 책임이겠지 하는 생각에 소심한 개발자인 필자의 마음이 흔들리기 시작한다. 진행 하기도 찜찜하고, 안하기도 찜찜하고.. 진짜 어려운 상황인데, 스프링 부트로의 전환을 함으로 얻는 장점이 훨씬 많고, 팀내에 스프링부트 경험자가 있으니 도움을 얻어가면서 결국 스프링부트로 전환하기로 결정하게 되었다. 물론, 어떤 이슈가 발생할지 모른다는 막연한 불안감이 가득하다.
필자는 박재성 강사님의 글을 참고해서 최소한의 작업만 해서 전환하기로 결정하였다.
https://www.slipp.net/wiki/pages/viewpage.action?pageId=22282245
작업을 진행하기 전에 기본적인 스프링 프레임워크 개념을 이해 해야 한다. 기본적인 Bean 설정 방법, Bean의 라이프사이클 등 기본 개념을 반드시 알고 있어야 한다. 모른다면 진행하지 말자.
XML Bean 설정 방법
Java @Bean 어노테이션 설정 방법
Java - @Component, @ComponentScan 개념 이해
스프링 DI 기본 개념
@SpringBootApplication 어노테이션이란?
Autoconfiguration 개념 이해
등 기본적인 이해가 되어야 한다.
한번 더 고민해보자. 스프링 부트 전환으로서 얻는 장점에 대해서 고민해야 한다. 자주 사용하지 않는 프로젝트라면 개발 리소스를 투자해서, 굳이 어렵게 바꿀 필요는 없다. 필자가 관리하는 프로젝트는 중요도가 높고, 장애가 지속적으로 발생하고 있기 때문에 개선이 반드시 필요한 프로젝트이다.
이 글을 쓰고 있는 필자는 소심하고, 장애가 너무 싫다. 그래서, 최소한의 작업으로 일단 부트로 실행하는 것을 목표로 하자. 물론, 최소한의 작업을 해도 장애가 발생할 수 있으니 꼼꼼히 봐야한다.
아마도 레거시 프로젝트는 대부분 Maven 환경일 것이다. 부트 전환하면서 Gradle 작업도 같이하고 싶지만 나중에 작업하기로 하자. pom.xml 을 유지하면서 스프링 부트 스타터 디펜던시를 추가하면 된다. 솔직히, Gradle 이나 Maven이나 운영하는데는 큰 차이는 없다고 생각한다.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
생략..
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
생략..
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
스프링부트 2.0.X 로 전환하는 것은 너무 큰 리스크라고 판단이 되어서 적당히 스프링 부트 1.5.X 로 전환하기로 결정하였다. 참고로 레거시 프로젝트의 스프링 버전은 3.1.X 이다. 스프링부트 1.5.2 로 전환하면서 스프링 버전이 3.X 에서 4.X 으로 디펜던시가 변경 된다. 스프링 부트 스타터 를 추가했으니, 스프링 관련 기존 설정을 모두 제거시키자. 필자는 아래와 같은 설정을 모두 제외시켰다.
org.springframework:spring-context
org.springframework:spring-webmvc
예를 들어서 아래와 같은 webmvc 설정이 있다고 가정하면... 지워버리면 된다.
<!--지우자-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
사실, 제거를 안해도 오류가 발생하지는 않지만 지워주는 것이 운영 상 좋을 듯 하다.
스프링 부트는 디펜던시를 자동으로 추가하고, 기존의 설정은 깔끔하게 지워버리자.
스프링 부트 스타터 parent 추가로 인해서, 기존 속성 중 중복 되는 설정은 지워주자. 굳이 안지워도 오류가 발생하지는 않는다.
<!--지우자-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
이 설정은 이미 스프링부트스타터 parent 에 선언이 되어있다. 굳이 남겨놓을 필요가 없다. 아래 링크를 참고하자 .
물론, 명시적으로 프로젝트에 써주는 것이 필요하다면, 남겨놓아도 크게 이슈가 되지는 않는다.
빌드를 하면서 오류가 나는 부분을 찾아서 수정하면 된다. 예를 들어서, 스프링 3.1.X 버전에서 MappingJacksonHttpMessageConverter 를 사용하고 있었다면, 스프링 4.X 버전에서는 신규 클래스인 MappingJackson2HttpMessageConverter 를 사용해야 한다. 필자는 아래와 같이 변경하였다.
기존
<bean id="" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
변경
<bean id="" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
아마도 대부분의 프로젝트에서 jackson 라이브러리를 사용하고 있었을 것이다. 사용하지 않는다면 작업할 필요는 없다. pom.xml 에서 jackson 라이브러리 깔끔하게 디펜던시를 지우자.
<!-- 지우자 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>버전정보</version>
</dependency>
<!-- 참고로 org.codehaus.jackson 은 매우 오래 전 패키지이다. com.fasterxml.jackson 으로 업그레이드 되었다. -->
그렇다면 jack 라이브러리는 어떻게 사용할 수 있는가? 스프링부트에서 자동으로 디펜던시를 넣어준다. 아래 링크를 참고하자.
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web/1.5.2.RELEASE
디펜던시만 추가 되었다고 끝이 아니다. 만약 레거시 프로젝트가 org.codehaus 패키지를 사용한다면, com.fasterxml.jackson 으로 모두 변경해야 한다. 참고로, 각 클래스의 패키지 경로가 많이 바뀌었다. 필자는 아래와 같은 패키지를 모두 변경하였다.
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
-->
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
-->
import com.fasterxml.jackson.annotation.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
-->
import com.fasterxml.jackson.databind.ObjectMapper;
import org.codehaus.jackson.map.util.JSONPObject;
-->
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.codehaus.jackson.annotate.JsonIgnore;
-->
import com.fasterxml.jackson.annotation.JsonIgnore;
등등, 변경한 패키지가 전부 기억나지는 않지만, 신규 패키지로 변경한 이후에 오류가 발생하지는 않았다. 아직까지는...
이 글에서 가장 중요한 부분이다. 각종 XML 파일을 @ImportResource 어노테이션으로 추가 해줘야 한다. 필자는, XML설정을 Java 컨피그 설정으로 변경하는 작업은 당장 진행하지 않을 것이다. 프로젝트의 XML 컨피그를 한번에 전환하는 작업은 리스크가 크다. 아래와 같이 XML 파일을 @ImportResource 어노테이션으로 추가한다.
@Configuration
@EnableAutoConfiguration
@ImportResource(value={ "classpath:servlet-context.xml",
"classpath:/경로/하이버네이트.xml",
"classpath:/경로/기타설정파일1.xml",
"classpath:/경로/기타설정파일2.xml"})
@ComponentScan(basePackages = "패키지경로")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication 를 사용하지 않아도 부트 실행은 잘 될것이다. 아무튼, 이렇게 수정하고 스프링부트를 실행해보자.
하지만...아마 잘 안될 것이다. 오류가 날것이다.
특별한 이유는 없다. 각자 프로젝트 환경이 다 다르기 때문이다. 에러 로그를 보면서 스프링 부트 실행 할 수 있도록 수정해야한다. 상세한 설명을 할 수 없어서 미안하다. 참고로, 회사에서는 한두건 정도 추가 수정사항을 진행했다. (회사 라이브러리 관련 내용이라서 상세한 설명은 보안상 패스하겠다.)
@SpringBootApplication 어노테이션으로 변경하자. @ImportResource 는 유지한다.
@SpringBootApplication
@ImportResource(value={ "classpath:servlet-context.xml",
"classpath:/경로/하이버네이트.xml",
"classpath:/경로/기타설정파일1.xml",
"classpath:/경로/기타설정파일2.xml"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
필자의 프로젝트 환경에서는 web.xml 파일을 지워도 되는 상황이라서 그냥 깔끔하게 지워버렸다. web.xml 에 부가적인 설정이 있다면 해당 부분을 오류가 나지 않도록 꼼꼼히 보면서 수정해야한다. 이부분은 확실하지 않아서 자세한 설명은 패스하겠다. 각자 프로젝트 환경에 맞게 수정을 해야한다.
배포는 신중하게 잘 진행하자. 이건 뭐 노하우가 없다. 배포 전에 꼼꼼히 테스트 하고, 별일 없기를 기도하는 수 밖에 없다. 기타 신경써야 하는 이슈에 대해서 간단하게 정리해보면 아래와 같다.
부트 환경에서는 tomcat 설정이 기본으로 설정이 될 것이다. 커스터마이징하게 셋팅되어있던 설정을 부트 전환할 때도 같이 맞춰야 한다. application.properties 에 임베디드 톰캣을 잘 설정해보자.
부트 이전에 설정이 되어있다면, 부트로 전환하면서 같이 맞춰야 한다. 기존 설정과 다르면 에러가 발생할 수도 있다. 예를 들어서 힙메모리 할당, GC 설정 등 꼼꼼하게 봐야 한다. 안그러면 장애난다.
jar 실행 옵션에서 인코딩 설정을 놓쳐서는 안된다. 필자는 utf8 옵션을 깜빡해서 장애가 발생했다. ㅠㅠ
프로퍼티 설정이 잘 되었는지 한번 더 확인해보자. 부트로 전환하는 과정에서 속성 값을 빠뜨려서 장애가 발생할 수도 있다.
아마도 배포를 잘 했고, 안정적으로 스프링 부트 전환이 되었을 것이다. 이제, 서비스는 안정화 단계가 되었다고 판단되면, 2단계를 슬슬 시작해보자. 스프링부트 전환 2단계는, XML 설정을 하나씩 Java 설정으로 바꾸는 과정이다. 이 작업은 단계적으로 진행하면 된다. 예를 들어서 RestTemplate.xml 설정이 있다고 가정하자. 아래와 같이 변경하면 된다.
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
자바 컨피그로 변경하면
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
private ClientHttpRequestFactory clientHttpRequestFactory() {
생략...
필자는 이 외에, DataSource 관련, 하이버네이트 관련 코드 수정을 XML 을 그대로 import 해서 유지하면서 추후에 단계적으로 진행하기로 결정하였다. 관련해서는 박재성 강사님의 글을 참고하면 된다.
https://www.slipp.net/wiki/pages/viewpage.action?pageId=22282248
자바 컨피그로 변경하는 작업을 진행하지 않고, 최소화 단계로 작업을 하니 개발 시간은 일주일도 걸리지 않았다. 다행히 테스트 코드 빌드에서도 문제가 발생하지 않았지만, 테스트 코드를 믿기에는 너무 레거시라서 걱정이 크다. 테스트 코드외에, 별도로 꼼꼼히 하나하나 테스트 하고 별일 없기를 기도하는 수밖에 없다. 혹시라도 스프링부트 전환에 대해서 좋은 의견이 있다면 피드백을 해주길 바란다. 참고로, 네이버에서도 좋은 사례가 있는데, 정리가 깔끔히 되어있으니 참고하길 바란다.