728x90
반응형

개요

OpenRewrite는 소스 코드를 대규모로 리팩토링하기 위한 오픈소스 도구입니다. 특히 Java, SpringBoot 버전 업그레이드와 같은 복잡한 마이그레이션 작업을 자동화하는 데 매우 유용합니다.

1. OpenRewrite 소개

주요 특징

  • 소스 코드의 LST(Lossless Semantic Trees) 기반 분석 및 변환
  • 다양한 마이그레이션 레시피 제공
  • 대규모 코드베이스에서 일관된 스타일로 코드 리팩토링 변경 지원
  • 변경 사항에 대한 미리보기(dryRun) 및 검증 기능

사용 시나리오

  • Java 버전 업그레이드 (e.g. 8 → 11 → 17 → 21)
  • SpringBoot 버전 업그레이드 (e.g. 2.x → 3.x)
  • 대대적인 보안 취약점 업데이트 (e.g. 2021년 log4j 보안취약점)
  • 종속성 버전 업데이트
  • 코드 스타일 및 패턴 업데이트

2. OpenRewrite의 예시

마이그레이션의 고통 포인트

SpringBoot 2.x에서 3.x로의 마이그레이션은 다음과 같은 귀찮은 작업들이 필요합니다

  • javax.*jakarta.* 패키지 변경 (수백 개의 import 문 수정)
  • deprecated API들의 대체 작업
  • Spring Security 설정 방식 변경
  • 수많은 라이브러리 버전 호환성 체크
  • Java 17 마이그레이션
 
// 이런 코드가 프로젝트에 수백 개...
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;

// 모두 아래와 같이 변경 필요
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotNull;

3. OpenRewrite의 기술적 해결책

1. LST(Lossless Semantic Trees) 기반 분석

LST의 타입 속성(Type Attribution)

// 원본 코드
List<String> names = getNames();
names.stream()
     .filter(name -> name.length() > 5)
     .collect(Collectors.toList());

// LST는 다음과 같은 타입 정보를 보존
List<String> names = getNames();          // names: java.util.List<java.lang.String>
names.stream()                            // stream(): java.util.stream.Stream<java.lang.String>
     .filter(name -> name.length() > 5)   // filter(): Stream<T> 
     .collect(Collectors.toList());       // collect(): java.util.List<java.lang.String>
 

2. 포맷 보존(Format Preservation)

주석과 공백 보존

 
/* 원본 코드의 복잡한 포맷팅 */
public class User {
    
    private final String name;    // 사용자 이름
    
    // 생성자 주석
    public User(String name) {
        this.name = name;         // 필드 초기화
    }                             // 후행 주석
}

// LST는 위의 모든 주석, 공백, 들여쓰기를 그대로 보존

 

// 기존 코드의 스타일
public class Example {
    private void method1() {
        if (condition) {
            doSomething();
        }
    }
    
    // OpenRewrite가 새로 추가하는 코드는 주변 스타일을 따름
    private void newMethod() {    // 동일한 들여쓰기 적용
        if (condition) {          // 중괄호 스타일 매칭
            doSomethingNew();
        }
    }
}

2. 정교한 의존성 분석

  • 프로젝트의 모든 pom.xml/build.gradle 파일 스캔
  • 호환되지 않는 라이브러리 버전 자동 감지
  • 전이 의존성 충돌 해결

4. 실제 사용 예시

1. Gradle 프로젝트 설정

plugins {
    id("org.openrewrite.rewrite") version("5.x.x")
}

rewrite {
    activeRecipe("org.openrewrite.java.spring.boot3.SpringBoot3Migration")
}
 

2. 마이그레이션 실행

# Gradle의 경우
./gradlew rewriteDryRun # 변경 예정 사항 미리보기
./gradlew rewriteRun    # 실제 변경 적용

Junit Assert 리팩토링

// 변경 전 
import org.junit.Assert;

Assert.assertTrue(condition);

// 변경 후
import static org.junit.Assert.assertTrue;

assertTrue(condition);

Spring Security 설정 마이그레이션

// 변경 전 WebSecurityConfigurerAdapter 사용
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**").authenticated();
    }
}

// 변경 후 컴포넌트 기반 설정
@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> 
            auth.requestMatchers("/api/**").authenticated()
        );
        return http.build();
    }
}

JPA 엔티티 마이그레이션

// 변경 전
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
}

// 변경 후
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
}

5. 마이그레이션 전략

  1. 코어 모듈부터 시작
  2. 테스트가 많은 모듈 우선 적용
  3. 의존성이 적은 모듈부터 진행
# 변경 사항 미리보기
./gradlew rewriteDryRun

# 실제 변경 적용
./gradlew rewriteRun

# 특정 모듈만 적용
./gradlew :core:rewriteRun

 

6. 레퍼런스

728x90
반응형

+ Recent posts