Spring 핵심 원리 분석

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 구현에는 두 가지 방식이 있습니다:

  1. JDK 동적 프록시
  2. 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;
            }
        });
    }
}

태그: Spring IOC AOP DI Proxy

8월 10일 13:18에 게시됨