Spring 프레임워크의 핵심 개념은 두 가지 특성에 집중해야 합니다: 의존성 주입(DI)과 관점 지향 프로그래밍(AOP)
의존성 주입(DI)
Spring 이전에는 객체 생성을 직접 new 키워드로 수행했습니다. 예를 들면 다음과 같습니다:
User user = new User();
이러한 방식은 과거의 일반적인 객체 생성 방법이었습니다. 그러나 Spring은 이러한 과정을 추상화하여 객체 생성을 관리하고, 필요할 때 Spring 컨테이너에서 가져오도록 합니다.
UserDao userDao = (UserDao)BeanFactory.getBean(beanName:"userDao");
보면 복잡해 보이지만, 이는 코드 간 결합도를 낮추는 효과가 있습니다.
이것이 바로 의존성 주입(DI)입니다.
의존성 주입 구현 원리
Spring은 이러한 기능을 리플렉션(Reflection)을 통해 구현합니다.
리플렉션을 통한 객체 생성
public class BeanFactory {
private static Properties config;
private static Map<String,Object> container;
static {
try {
config = new Properties();
InputStream input = BeanFactory.class.getClassLoader()
.getResourceAsStream("beans.properties");
config.load(input);
container = new HashMap<>();
Enumeration keys = config.keys();
while(keys.hasMoreElements()) {
String key = keys.nextElement().toString();
String className = config.getProperty(key);
Object instance = Class.forName(className).newInstance();
container.put(key, instance);
}
} catch(Exception e) {
throw new ExceptionInInitializerError("설정 파일 로드 실패");
}
}
public static Object getBean(String beanName) {
return container.get(beanName);
}
}
설정 파일을 읽어 클래스의 전체 경로를 얻고, 리플렉션을 사용해 인스턴스를 생성합니다.
beans.properties 파일 내용:
accountService = com.example.service.impl.AccountServiceImpl
accountDao = com.example.dao.impl.AccountDaoImpl
관점 지향 프로그래밍(AOP)
프록시 패턴을 기반으로 원본 객체에 대한 대리 객체를 생성하여 기존 코드 변경 없이 메서드를 확장합니다.
주요 활용 사례:
- 로그 기록
- 성능 모니터링
- 권한 검증
- 캐싱 최적화
- 트랜잭션 관리
AOP 구현 원리
Spring의 AOP 구현에는 두 가지 방식이 있습니다:
- JDK 동적 프록시
- CGLIB 동적 프록시
JDK 동적 프록시
프록시 클래스:
public class Client {
public static void main(String[] args) {
final IProducer producer = new Producer();
IProducer proxy = (IProducer) Proxy.newProxyInstance(
producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Float amount = (Float)args[0];
System.out.println("사전 처리");
if("saleProduct".equals(method.getName())) {
return method.invoke(producer, amount * 0.8f);
}
return null;
}
});
proxy.saleProduct(10000f);
}
}
서비스 클래스:
public class Producer implements IProducer {
public void saleProduct(Float amount) {
System.out.println("금액 수령: " + amount + ", 제품 판매");
}
public void afterService(Float amount) {
System.out.println("후속 서비스 제공, 금액 수령: " + amount);
}
}
CGLIB 동적 프록시
프록시 클래스:
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProxy = (Producer) Enhancer.create(
producer.getClass(),
new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Float amount = (Float)args[0];
System.out.println("사전 처리");
if("saleProduct".equals(method.getName())) {
return method.invoke(producer, amount * 0.8f);
}
return null;
}
});
cglibProxy.saleProduct(10000f);
}
}
Producer 클래스:
public class Producer {
public void saleProduct(Float amount) {
System.out.println("금액 수령: " + amount + ", 제품 판매");
}
public void afterService(Float amount) {
System.out.println("후속 서비스 제공, 금액 수령: " + amount);
}
}
Spring의 DI와 AOP에 대해 설명했습니다.
확장 내용
AOP에서 프록시 생성 방식에 대해 알아봅니다. Spring이 어떻게 대상 객체에 대해 프록시를 생성하는지 살펴보겠습니다.
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer proxy = (Producer) new CglibProxyFactory(producer).getProxyInstance();
proxy.saleProduct(10000f);
}
}
CglibProxyFactory 클래스의 getProxyInstance() 메서드를 통해 프록시 객체를 얻습니다.
CglibProxyFactory 클래스:
public class CglibProxyFactory {
private Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Enhancer.create(target.getClass(), new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("트랜잭션 시작");
Float amount = (Float)args[0];
Object result = method.invoke(target, amount * 0.8f);
System.out.println("트랜잭션 완료");
return result;
}
});
}
}