⟡ 개요

회사에서 담당하고 있는 서비스의 마이그레이션 작업을 높은 완성도로 마무리하기 위한 목적과,
퇴근 후 별도로 진행하는 학습 내용들을 스스로 정리하고 축적하기 위해 작성하는 기록입니다.
초보 개발자 관점에서 정리하는 흐름이지만, 기술적 오류나 더 나은 방향이 있다면
언제든지 피드백을 제안해 주시면 적극 반영하겠습니다.🫶
⟡ 개발환경
| AS-IS | TO-BE |
| JDK 1.7 | JDK 17 |
| Maven 2.9 | Gradle 8.8 |
| Spring Framework 4.2.5 (XML 기반) | Spring Boot 3.3.2 |
| MyBatis 3.3.0 | MyBatis 3.5.13 |
| Tomcat 7 | Embedded Tomcat |
| MariaDB | |
| JSP, EL, JSTL 기반 SSR(Server Side Rendering) | |
| HTML, CSS, JavaScript, jQuery | |
| Eclipse | IntelliJ or VSCode |
| Linux(사내 Cloud) | |
⟡ 레거시 코드 고도화의 5단계 프로세스
1단계 : 목표 및 문제 이해
레거시 코드를 처음 마주했을 때 가장 먼저 해야 할 일은 코드가 무엇을 하는지 파악하는 것이다.
"이 코드는 어떤 역할을 하는가?"단순히 메서드 하나를 보는 것이 아니라
그 코드가 전체 시스템에서 어떤 위치에 있고 어떤 책임을 지고 있는지 파악해야 한다.
설정 파일을 읽어오는 유틸리티 클래스라면 애플리케이션 전역에서 사용되는 핵심 기능일 가능성이 높다.
"무엇이 문제인가?" IDE에 표시되는 컴파일 오류 메시지를 주의 깊게 살펴본다.
"클래스를 찾을 수 없다", "메서드가 존재하지 않는다"와 같은 메시지는 라이브러리 버전 불일치를 의미하는 경우가 많다.
2단계 : 의존성 분석 및 마이그레이션 가이드 확인
라이브러리 식별 : build.gradle 파일에서 문제가 되는 라이브러리를 확인한다.
빌드 도구는 프로젝트의 모든 의존성을 한 곳에 모아두기 때문에 어떤 라이브러리를 어떤 버전으로 사용 중인지 한눈에 파악할 수 있다.
최신 버전 찾기 : Maven Central Repository나 사내 Nexus에서 해당 라이브러리의 최신 안정 버전을 찾는다.
무조건 최신 버전을 선택하기보다는 Java 17과 Spring Boot 3을 공식적으로 지원하는 버전을 선택해야 한다.
공식 마이그레이션 가이드 확인 (가장 중요) : 반드시 공식 문서를 확인해야 한다.
"라이브러리명 + migration guide" 키워드로 검색하면 공식 마이그레이션 가이드를 찾을 수 있다.
이 문서에는 클래스명 변경, 사용 패턴 변화, API 변경사항이 모두 정리되어 있다.
구글링으로 단편적인 답을 찾는 것보다 공식 문서를 처음부터 끝까지 읽는 것이 시간을 절약하는 길이다.
3단계 : 코드 수정 계획 수립
본격적인 코드 수정 전에 구체적인 계획을 세운다.
Import 구문 변경
패키지 구조가 바뀐 경우 모든 import 구문을 일괄 변경해야 한다.
IntelliJ IDEA의 "Replace in Path" 기능을 사용하면 프로젝트 전체에서 한 번에 교체할 수 있다.
객체 생성 방식 변경
라이브러리 버전업 시 디자인 패턴이 바뀌는 경우가 많다.
최근 라이브러리들은 빌더 패턴을 많이 사용하는 추세다.
API 변경점 적용
메서드 시그니처가 바뀐 부분을 확인한다.
제네릭 지원이 추가되어 타입을 명시할 수 있게 되었다면 이를 활용해 타입 안정성을 높일 수 있다.
핵심 기능 유지
기존에 동작하던 핵심 기능을 놓치지 않아야 한다.
설정 파일 자동 리로딩 같은 기능이 있었다면 새로운 라이브러리에서 이를 어떻게 구현하는지 반드시 확인한다.
4단계 : 코드 리팩토링 실행 (Refactor)
세운 계획대로 코드를 수정한다.
먼저 import 구문을 모두 새 버전에 맞게 변경하고 IDE가 표시하는 컴파일 오류를 하나씩 해결해 나간다.
더 이상 사용되지 않는 import 구문이나 변수는 과감히 삭제하여 코드를 깔끔하게 정리한다.
5단계 : 테스트 및 검증 (Test & Verify)
컴파일 및 실행 : 코드가 오류 없이 컴파일되고 애플리케이션이 정상적으로 실행되는지 확인한다.
기능 검증 : 실제로 의도한 대로 동작하는지 확인하는 것이 가장 중요하다.
설정 파일의 값을 제대로 읽어오는지, 자동 리로딩 기능이 의도대로 동작하는지 직접 테스트하여 검증한다.
⟡ Java 1.7에서 Java 17로 : 알아야 할 문법 변경사항
Java는 1.7부터 17까지 약 10년에 걸쳐 큰 발전을 이루었다.
새로운 문법과 API를 이해하고 활용할 수 있어야 진정한 업그레이드다.
Java 8 : 람다와 스트림 API
Java 8은 함수형 프로그래밍 개념을 도입한 획기적인 버전이다.
람다 표현식 (Lambda Expressions)
// Java 7: 익명 클래스 사용
List<String> names = Arrays.asList("홍길동", "김철수", "이영희");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
// Java 8 이후: 람다 표현식
List<String> names = Arrays.asList("홍길동", "김철수", "이영희");
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
// 메서드 레퍼런스
Collections.sort(names, String::compareTo);
스트림 API (Stream API)
컬렉션 데이터 처리 시 for 루프 반복 작성이 스트림 API로 선언적으로 바뀌었다.
// Java 7: 명령형 프로그래밍
List<User> activeUsers = new ArrayList<>();
for (User user : allUsers) {
if (user.isActive()) {
activeUsers.add(user);
}
}
List<String> userNames = new ArrayList<>();
for (User user : activeUsers) {
userNames.add(user.getName());
}
// Java 8 이후: 스트림 API
List<String> userNames = allUsers.stream()
.filter(User::isActive) // 활성 사용자만 필터링
.map(User::getName) // 사용자 이름만 추출
.collect(Collectors.toList()); // 결과를 리스트로 수집
Optional 클래스
NullPointerException을 방지하기 위한 Optional 클래스가 도입되었다.
// Java 7: null 체크
public String getUserEmail(Long userId) {
User user = userRepository.findById(userId);
if (user != null) {
Email email = user.getEmail();
if (email != null) {
return email.getAddress();
}
}
return "이메일 없음";
}
// Java 8 이후: Optional 사용
public String getUserEmail(Long userId) {
return userRepository.findById(userId) // Optional<User> 반환
.map(User::getEmail) // Optional<Email>로 변환
.map(Email::getAddress) // Optional<String>으로 변환
.orElse("이메일 없음"); // 값이 없으면 기본값 반환
}
Java 9 : 컬렉션 팩토리 메서드
불변 컬렉션 생성이 훨씬 간단해졌다.
// Java 8 이전: 불변 리스트 생성
List<String> oldList = new ArrayList<>();
oldList.add("사과");
oldList.add("바나나");
oldList.add("포도");
List<String> immutableList = Collections.unmodifiableList(oldList);
// Java 9 이후: 팩토리 메서드
List<String> fruits = List.of("사과", "바나나", "포도");
Set<Integer> numbers = Set.of(1, 2, 3, 4, 5);
Map<String, Integer> scores = Map.of(
"홍길동", 95,
"김철수", 88,
"이영희", 92
);
Java 10 : var 키워드
타입 추론을 통해 지역 변수 선언이 간결해졌다.
// Java 9 이전: 타입 명시
HashMap<String, List<UserDTO>> usersByDepartment = new HashMap<>();
ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder =
new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class);
// Java 10 이후: var 키워드
var usersByDepartment = new HashMap<String, List<UserDTO>>();
var builder = new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class);
var는 타입이 명확한 경우에만 사용해야 한다.
타입을 명시하는 것이 가독성에 도움이 되는 경우라면 굳이 var를 쓸 필요가 없다.
Java 14-17 : Record, Sealed Class, Pattern Matching
Record (Java 16 정식)
DTO나 VO 클래스 작성 시 보일러플레이트 코드가 대폭 줄어든다.
// Java 13 이전: 전통적인 DTO
public class UserDTO {
private final String name;
private final String email;
private final int age;
public UserDTO(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
public String getName() { return name; }
public String getEmail() { return email; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
// equals 구현...
}
@Override
public int hashCode() {
// hashCode 구현...
}
@Override
public String toString() {
// toString 구현...
}
}
// Java 16 이후: Record
// 생성자, getter, equals, hashCode, toString 자동 생성
public record UserDTO(String name, String email, int age) {}
Sealed Class (Java 17 정식)
특정 클래스만 상속하도록 제한할 수 있다.
// permits 키워드로 허용된 서브클래스만 상속 가능
public sealed class PaymentMethod
permits CreditCard, DebitCard, BankTransfer {
}
public final class CreditCard extends PaymentMethod {
private final String cardNumber;
// 구현...
}
public final class DebitCard extends PaymentMethod {
private final String accountNumber;
// 구현...
}
public final class BankTransfer extends PaymentMethod {
private final String bankCode;
// 구현...
}
Pattern Matching for instanceof (Java 16 정식)
타입 체크와 캐스팅을 동시에 할 수 있다.
// Java 15 이전: instanceof 체크 후 캐스팅
if (payment instanceof CreditCard) {
CreditCard card = (CreditCard) payment;
processCard(card.getCardNumber());
}
// Java 16 이후: 패턴 매칭
if (payment instanceof CreditCard card) {
processCard(card.getCardNumber());
}
Java 15 : Text Blocks
여러 줄 문자열 처리 시 가독성이 크게 향상된다.
// Java 13 이전: 문자열 연결
String sql = "SELECT u.id, u.name, u.email\n" +
"FROM users u\n" +
"INNER JOIN departments d ON u.dept_id = d.id\n" +
"WHERE u.active = true\n" +
"ORDER BY u.name";
// Java 15 이후: Text Blocks
String sql = """
SELECT u.id, u.name, u.email
FROM users u
INNER JOIN departments d ON u.dept_id = d.id
WHERE u.active = true
ORDER BY u.name
""";
⟡ Spring Framework 4에서 Spring Boot 3로 : 필수 변경사항
Java 17 필수 요구사항
Spring Boot 3는 Java 17을 최소 요구사항으로 한다.
Java 11 이하 버전으로는 Spring Boot 3를 사용할 수 없다.
Spring Boot 3가 Java 17의 새로운 기능들을 내부적으로 활용하기 때문이다.
Jakarta EE로의 전환
가장 큰 변화 중 하나는 패키지명 변경이다.
Oracle의 Java EE가 Eclipse Foundation으로 이관되면서 Jakarta EE로 이름이 바뀌었다.
// Spring Framework 4 (Java EE)
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
// Spring Boot 3 (Jakarta EE)
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
모든 javax.* 패키지가 jakarta.*로 바뀌었다.
IntelliJ IDEA의 "Replace in Path" 기능을 사용하면 일괄 변경할 수 있다.
XML 설정에서 Java Config로
Spring Framework 4는 XML 기반 설정을 주로 사용했다.
Spring Boot 3는 Java Config와 어노테이션 중심으로 완전히 전환되었다.
데이터소스 설정 변화
| AS-IS(Spring Framework 4) | TO-BE(Spring Boot3) |
| root-context.xml에서 XML로 빈 정의 | application.properties + 자동 구성 |
| DataSource, SqlSessionFactory 수동 설정 | spring-boot-starter-mybatis가 자동 생성 |
| TransactionManager 명시적 선언 | @EnableTransactionManagement로 자동화 |
<!-- AS-IS: root-context.xml -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
<property name="maxTotal" value="20" />
<property name="maxIdle" value="10" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="mapperLocations" value="classpath:mappers/**/*.xml" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
# TO-BE: application.properties
# DataSource 설정
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
# HikariCP 설정 (Spring Boot 3 기본 커넥션 풀)
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=10
# MyBatis 설정
mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mappers/**/*.xml
mybatis.type-aliases-package=com.company.model
세밀한 제어가 필요한 경우 Java Config로 작성할 수 있다.
@Configuration
@MapperScan("com.company.mapper") // MyBatis Mapper 인터페이스 스캔
public class DatabaseConfig {
// properties의 spring.datasource.* 설정 자동 바인딩
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setConfigLocation(
new PathMatchingResourcePatternResolver()
.getResource("classpath:mybatis-config.xml")
);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mappers/**/*.xml")
);
return factoryBean.getObject();
}
}
컴포넌트 스캔의 간소화
| AS-IS(Spring Framework 4) | TO-BE(Spring Boot3) |
| servlet-context.xml에서 패키지별로 명시 | @SpringBootApplication이 자동 스캔 |
| context:component-scan 태그 사용 | 메인 클래스 패키지 하위 자동 인식 |
<!-- AS-IS: servlet-context.xml -->
<context:component-scan base-package="com.company.service" />
<context:component-scan base-package="com.company.controller" />
<context:component-scan base-package="com.company.repository" />
// TO-BE: Application.java
// @SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Embedded Server : Tomcat 설정
Spring Framework 4에서는 Tomcat을 별도 설치하고 WAR 파일을 배포했다.
Spring Boot 3는 Tomcat을 내장하고 있어 JAR 파일만 실행하면 된다.
# TO-BE: application.properties
server.port=8080
server.servlet.context-path=/api
server.tomcat.threads.max=200
server.tomcat.threads.min-spare=10
server.tomcat.accept-count=100
server.tomcat.connection-timeout=20000
보안 설정의 변화
Spring Security도 큰 변화가 있었다.
// AS-IS: Spring Framework 4 + Spring Security 4
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll();
}
}
// TO-BE: Spring Boot 3 + Spring Security 6
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// SecurityFilterChain을 빈으로 등록
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 람다 DSL 스타일 사용
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN") // antMatchers → requestMatchers
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
.build();
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("user123"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}
주요 변경사항은 다음과 같다.
1. WebSecurityConfigurerAdapter deprecated → SecurityFilterChain 빈 등록 방식
2. antMatchers → requestMatchers로 변경
3. 람다 DSL 스타일 권장
⟡ 실전 사례 : Apache Commons Configuration 마이그레이션
실제 오픈소스 라이브러리 마이그레이션 사례를 통해 5단계 프로세스 적용 과정을 살펴본다.
1단계 : 문제 파악
기존 코드는 commons-configuration 1.10을 사용하여 properties 파일을 읽고 있었다.
// AS-IS: commons-configuration 1.x
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
public class ConfigReader {
private PropertiesConfiguration config;
public ConfigReader(String configFile) throws ConfigurationException {
config = new PropertiesConfiguration(configFile);
// 60초마다 파일 변경 체크 및 자동 리로딩
FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
strategy.setRefreshDelay(60000);
config.setReloadingStrategy(strategy);
}
public String getString(String key) {
return config.getString(key);
}
public List getList(String key) { // 제네릭 없음
return config.getList(key);
}
}
Spring Boot 3 프로젝트에서 컴파일 오류 발생
Cannot resolve symbol 'PropertiesConfiguration'
Cannot resolve symbol 'FileChangedReloadingStrategy'
Package 'org.apache.commons.configuration' does not exist
2단계 : 마이그레이션 가이드 확인
build.gradle에서 commons-configuration:commons-configuration:1.10 사용 확인.
Apache 공식 사이트에서 최신 버전 commons-configuration2:2.10.1 확인 및 마이그레이션 가이드 조사.
주요 변경사항
1. 패키지명 변경: org.apache.commons.configuration → org.apache.commons.configuration2
2. 빌더 패턴 도입: 직접 생성자 호출 → 빌더 패턴
3. 리로딩 전략 변경: FileChangedReloadingStrategy → ReloadingController + PeriodicReloadingTrigger
4. 제네릭 지원: getList() → getList(Class<T>, String)
3단계 : 수정 계획
1. 의존성을 commons-configuration2:2.10.1로 변경
2. import 구문을 configuration2 패키지로 변경
3. 빌더 패턴으로 Configuration 객체 생성
4. 리로딩 로직을 새 API로 재작성
5. 제네릭을 활용한 타입 안정성 개선
6. 리소스 정리를 위한 close 메서드 추가
4단계 : 코드 리팩토링
// build.gradle
dependencies {
// AS-IS
// implementation 'commons-configuration:commons-configuration:1.10'
// TO-BE
implementation 'org.apache.commons:commons-configuration2:2.10.1'
}
// TO-BE: commons-configuration 2.x + Java 17
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.reloading.PeriodicReloadingTrigger;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class ConfigReader {
private final ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder;
private final PeriodicReloadingTrigger trigger;
public ConfigReader(String configFile) throws ConfigurationException {
// Java 10 var 키워드 사용
var params = new Parameters();
// 빌더 패턴으로 Configuration 생성
this.builder = new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(params.fileBased()
.setFileName(configFile));
// 주기적 리로딩 트리거 설정 (60초마다)
this.trigger = new PeriodicReloadingTrigger(
builder.getReloadingController(),
null,
60,
TimeUnit.SECONDS
);
trigger.start();
}
public String getString(String key) throws ConfigurationException {
Configuration config = builder.getConfiguration();
return config.getString(key);
}
// 제네릭 지원으로 타입 안정성 개선
public List<String> getList(String key) throws ConfigurationException {
Configuration config = builder.getConfiguration();
return config.getList(String.class, key);
}
// 리소스 정리
public void close() {
if (trigger != null) {
trigger.stop();
}
}
}
주요 개선사항
1. Java 10 var 키워드로 타입 선언 간소화
2. 빌더 패턴으로 유연한 설정 가능
3. 제네릭으로 List<String> 타입 안정성 향상
4. 리로딩 로직 명확히 분리
5. close 메서드로 리소스 누수 방지
5단계 : 테스트
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class ConfigReaderTest {
private ConfigReader reader;
private static final String TEST_CONFIG = "src/test/resources/test-config.properties";
@BeforeEach
void setUp() throws Exception {
// 테스트용 설정 파일 생성
Files.writeString(Paths.get(TEST_CONFIG), """
app.name=MyApplication
servers=server1.example.com,server2.example.com,server3.example.com
test.value=original
""");
reader = new ConfigReader(TEST_CONFIG);
}
@AfterEach
void tearDown() {
if (reader != null) {
reader.close();
}
}
@Test
void testConfigurationReading() throws Exception {
// 기본 문자열 값 읽기
String appName = reader.getString("app.name");
assertEquals("MyApplication", appName);
// 리스트 값 읽기 (제네릭 확인)
List<String> servers = reader.getList("servers");
assertEquals(3, servers.size());
assertTrue(servers.contains("server1.example.com"));
}
@Test
void testConfigurationReloading() throws Exception {
// 초기 값 확인
String originalValue = reader.getString("test.value");
assertEquals("original", originalValue);
// 설정 파일 수정
Files.writeString(Paths.get(TEST_CONFIG), """
app.name=MyApplication
servers=server1.example.com,server2.example.com,server3.example.com
test.value=modified
""");
// 리로딩 대기 (60초 + 여유시간)
Thread.sleep(65000);
// 변경된 값 확인
String modifiedValue = reader.getString("test.value");
assertEquals("modified", modifiedValue);
}
}
⟡ 마치며
지난 몇 개월간의 마이그레이션 작업을 돌아보며 가장 중요한 교훈은 "공식 문서를 읽자"는 것이다.
구글링으로 단편적인 답을 찾는 것보다 공식 마이그레이션 가이드를 처음부터 끝까지 읽는 것이 시간을 절약하는 길이었다.
코드를 한 번에 다 바꾸려 하지 말고 작은 단위로 나누어 하나씩 검증하면서 진행하는 것이 안전하다.
하나의 클래스를 완벽하게 마이그레이션하고 테스트를 통과시킨 후 다음 클래스로 넘어가는 방식이 오히려 빠르다.
레거시 코드 고도화는 단순히 오래된 코드를 새 코드로 바꾸는 것이 아니다.
과거 개발자들이 남긴 의도를 이해하고 현재의 기술로 더 나은 방식을 제시하는 과정이다.
이 과정을 통해 단순히 코드를 작성하는 개발자에서 시스템을 이해하고 개선할 수 있는 개발자로 한 걸음 나아갈 수 있었다.
⟡ 참고 자료
- Apache Commons Configuration 2.x Documentation
https://commons.apache.org/proper/commons-configuration/userguide/user_guide.html
https://commons.apache.org/proper/commons-configuration/apidocs/index.html - Spring Boot 3.x Migration Guide
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide
https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0 - Spring Security 6.x Documentation
https://docs.spring.io/spring-security/reference/index.html
https://docs.spring.io/spring-security/reference/migration/index.html - Java SE 17 Documentation & Release Notes
https://docs.oracle.com/en/java/javase/17/docs/api/index.html
https://www.oracle.com/java/technologies/javase/17-relnote-issues.html
https://openjdk.org/projects/jdk/17/
JDK 17
JDK 17 JDK 17 is the open-source reference implementation of version 17 of the Java SE Platform, as specified by by JSR 390 in the Java Community Process. JDK 17 reached General Availability on 14 September 2021. Production-ready binaries under the GPL
openjdk.org
JDK 17 Release Notes, Important Changes, and Information
These notes describe important changes, enhancements, removed APIs and features, deprecated APIs and features, and other information about JDK 17 and Java SE 17. In some cases, the descriptions provide links to additional detailed information about an issu
www.oracle.com
Overview (Java SE 17 & JDK 17)
This document is divided into two sections: Java SE The Java Platform, Standard Edition (Java SE) APIs define the core Java platform for general-purpose computing. These APIs are in modules whose names start with java. JDK The Java Development Kit (JDK) AP
docs.oracle.com
Migrating to 7.0 :: Spring Security
The first step is to ensure you are the latest patch release of Spring Boot 4.0. Next, you should ensure you are on the latest patch release of Spring Security 7. For directions, on how to update to Spring Security 7 visit the Getting Spring Security secti
docs.spring.io
Spring Security :: Spring Security
If you are ready to start securing an application see the Getting Started sections for servlet and reactive. These sections will walk you through creating your first Spring Security applications. If you want to understand how Spring Security works, you can
docs.spring.io
Preparing for Spring Boot 3.0
Spring Boot 2.0 was the first release in the 2.x line and was published on Feburary 28th 2018. We’ve just released Spring Boot 2.7 which means that, so far, we’ve been maintaining the 2.x line for just over 4 years. In total we’ve published 95 distin
spring.io
Spring Boot 3.0 Migration Guide
Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss. - spring-projects/spring-boot
github.com
Overview (Apache Commons Configuration 2.13.0 API)
Copyright © 2001-2025 The Apache Software Foundation. All rights reserved.Apache Commons Configuration | Issue management | Source repository
commons.apache.org
Commons Configuration User's Guide – Apache Commons Configuration
commons.apache.org