시스템 개발 시 생성 비용이 큰 '중량급 객체'를 빈번하게 만들고 파괴하는 과정은 성능 저하의 주요 원인이 됩니다. 객체 풀링(Object Pooling) 기법은 초기화 단계에서 일정 수의 객체를 미리 생성해 두고, 필요할 때마다 풀에서 객체를 빌려(Borrow) 사용한 뒤 다시 반납(Return)하는 방식을 취합니다. 이를 통해 객체 생성 오버헤드를 줄이고 시스템 리소스를 효율적으로 관리할 수 있습니다.
1. 의존성 추가
Apache Commons Pool2 라이브러리를 사용하기 위해 pom.xml에 다음과 같은 의존성을 추가합니다.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
2. 풀링 대상 객체 정의
풀에서 관리할 대상인 중량급 리소스 클래스를 정의합니다. 여기서는 ManagedResource라는 이름으로 정의하겠습니다.
public class ManagedResource {
private String resourceId;
private boolean available;
public ManagedResource(String resourceId) {
this.resourceId = resourceId;
this.available = true;
}
public String getResourceId() {
return resourceId;
}
public boolean isAvailable() {
return available;
}
public void setAvailable(boolean available) {
this.available = available;
}
public void clear() {
// 리소스 상태 초기화 로직
}
}
3. 객체 팩토리 구현
풀에서 객체의 생성, 검증, 파괴 등을 담당할 팩토리 클래스가 필요합니다. BasePooledObjectFactory를 상속받아 구현하는 것이 일반적입니다.
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
public class ResourceFactory extends BasePooledObjectFactory<ManagedResource> {
@Override
public ManagedResource create() throws Exception {
// 실제 객체 생성 로직
return new ManagedResource("Res-" + System.nanoTime());
}
@Override
public PooledObject<ManagedResource> wrap(ManagedResource obj) {
// 생성된 객체를 PooledObject로 감싸기
return new DefaultPooledObject<>(obj);
}
@Override
public void destroyObject(PooledObject<ManagedResource> p) throws Exception {
p.getObject().clear();
}
@Override
public boolean validateObject(PooledObject<ManagedResource> p) {
return p.getObject().isAvailable();
}
}
4. 커스텀 객체 풀 정의
GenericObjectPool을 확장하여 특정 타입에 맞는 풀을 구성합니다.
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
public class ManagedResourcePool extends GenericObjectPool<ManagedResource> {
public ManagedResourcePool(ResourceFactory factory, GenericObjectPoolConfig<ManagedResource> config) {
super(factory, config);
}
}
5. Spring Boot 연동 및 설정
외부 설정 파일(application.yml 등)로부터 설정을 주입받기 위한 프로퍼티 클래스와 자동 설정 빈을 구성합니다.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "app.resource-pool")
public class PoolSettings {
private int maxTotal = 10;
private int maxIdle = 5;
private int minIdle = 1;
private int initialSize = 2;
// Getter, Setter 생략
}
다음은 빈 설정을 담당하는 구성 클래스입니다. Spring Boot 환경에서 JMX 충돌을 방지하기 위해 setJmxEnabled(false) 설정이 권장됩니다.
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
@Configuration
public class ResourcePoolConfig {
private ManagedResourcePool resourcePool;
@Bean
public ManagedResourcePool managedResourcePool(PoolSettings settings) {
ResourceFactory factory = new ResourceFactory();
GenericObjectPoolConfig<ManagedResource> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(settings.getMaxTotal());
config.setMaxIdle(settings.getMaxIdle());
config.setMinIdle(settings.getMinIdle());
config.setTestOnBorrow(true);
config.setJmxEnabled(false); // 중요: Spring Boot JMX 중복 등록 방지
this.resourcePool = new ManagedResourcePool(factory, config);
// 초기 객체 로딩
try {
for (int i = 0; i < settings.getInitialSize(); i++) {
resourcePool.addObject();
}
} catch (Exception ignored) {}
return resourcePool;
}
@PreDestroy
public void closePool() {
if (resourcePool != null) {
resourcePool.close();
}
}
}
6. 풀 활용 예제
서비스 레이어에서 풀을 주입받아 객체를 대여하고 반납하는 기본적인 사용법입니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
@Autowired
private ManagedResourcePool resourcePool;
public void processTask() {
ManagedResource resource = null;
try {
// 풀에서 객체 대여
resource = resourcePool.borrowObject();
// 비즈니스 로직 수행
System.out.println("Using resource: " + resource.getResourceId());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (resource != null) {
// 사용 완료 후 반드시 풀에 반납
resourcePool.returnObject(resource);
}
}
}
}
Apache Commons Pool2의 주요 파라미터는 다음과 같습니다.
- maxTotal: 풀에서 관리하는 최대 객체 수
- maxIdle: 풀에 보관할 수 있는 최대 유휴 상태 객체 수
- minIdle: 풀에서 유지해야 하는 최소 유휴 상태 객체 수
- testOnBorrow: 객체를 빌릴 때 유효성 검사 여부
- blockWhenExhausted: 풀이 가득 찼을 때 대기 여부