Proxy 객체


다음 구조의 클래스 파일이 있다.

 

[그림 1] 클래스 구조

 

Calculator.java

public interface Calculator { 
    public long factorial(long num); 
} 

 

ImpeCalculator.java (핵심 기능 구현 객체)

public class ImpeCalculator implements Calculator { 

    public long factorial(long num) { 
        long result = 1; 
        for (long i = 1; i <= num; ++i) 
            result *= i; return result; 
    } 
} 

 

RecCalculator.java (핵심 기능 구현 객체)

public class RecCalculator implements Calculator { 
    public long factorial(long num) { 
        if (num == 1) 
            return 1; 
            
        return num * factorial(num - 1); 
    } 
} 

 

ExeTimeCalculator.java (프록시 객체)

public class ExeTimeCalculator implements Calculator { 
    private Calculator delegate; 
    
    public ExeTimeCalculator(Calculator delegate) { 
        this.delegate = delegate; 
    } 
    
    public long factorial(long num) { 
        long start = System.nanoTime(); 
        long result = delegate.factorial(num); 
        long end = System.nanoTime(); 
        
        System.out.printf("%s.factorial(%d) 실행 시간 = %d\n", 
                delegate.getClass().getSimpleName(), num, (end - start)); 
                
        return result; 
    } 
} 

 

핵심 기능 구현 객체들의 실행시간을 파악하기 위하여 프록시 객체를 이용했다. Proxy 객체는 핵심 기능 외에 공통적인 기능을 담은 객체를 말한다. 이렇게 공통 기능 구현과 핵심 기능 구현을 분리하는 것이 AOP의 핵심이다.

 

 

 

AOP (Aspect Oriented Programming)


AOP(Aspect Oriented Programming): 여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법

 

위의 코드에서 팩토리얼 계산 기능(핵심 기능)의 코드의 수정없이 계산 시간 측정 기능(공통)을 프록시(ExeTimeCalculator)를 사용해서 구현할 수 있었다. Spring MVC도 프록시를 이용해서 AOP를 구현하고 있다.

 

AOP의 기본 개념은 핵심 기능에 공통 기능을 삽입하는 것. 핵심 기능의 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것이 AOP이다. Spring MVC의 AOP는 프록시 객체를 자동으로 만들어준다. 무슨 말이냐면, 상위 타입 인터페이스를 상속받거나 하는 등의 잡다한 일은 스프링이 알아서 해준다.

 

필수적으로 알아야AOP의 주요 용어는 다음과 같다.

    1. Aspect - AOP에서 공통 기능. 여러 객체에 공통으로 적용되는 기능.

    2. Advice - 공통 기능을 언제 핵심 기능에 삽입할지를 정의

    3. Joinpoint - 위의 Advide의 '언제'에 해당하는 부분의 리스트를 나타낸다. ex) 메서드 호출 전, 필드 값 변경 후 등

    4. Pointcut - Joinpoint의 부분 집합으로서 실제 Advice가 적용되는 Joinpoint를 나타낸다.

    5. Weaving - Advice를 핵심 로직 코드에 적용하는 것

 

 

개발자는 공통 기능을 제공하는 Aspect 구현 클래스를 만들고 자바 설정을 이용해서 Aspect를 어디에 적용할지 설정한다.

위의 프록시 객체를 스프링 AOP를 이용해서 새로 작성한 코드는 다음과 같다.

@Aspect // Aspect 구현 클래스임을 명시한다. 
public class ExeTimeAspect { 

    // 공통 기능을 적용할 대상을 설정한다. 
    @Pointcut("execution(public * chap07..*(..))") 
    private void publicTarget() { } 
    
    // Around Advice를 설정한다. 
    // publicTarget() 메서드에 정의한 Pointcut에 공통 기능을 적용한다. 
    @Around("publicTarget()") 
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable { 
        long start = System.nanoTime(); 
        try { 
            Object result = joinPoint.proceed(); 
            return result; 
        } finally { 
            long finish = System.nanoTime(); 
            Signature sig = joinPoint.getSignature(); 
            System.out.printf("%s.%s(%s) 실행 시간: %d ns\n", 
                    joinPoint.getTarget().getClass().getSimpleName(), 
                    sig.getName(), 
                    Arrays.toString(joinPoint.getArgs()), 
                    (finish - start)); 
        } 
    } 
}

 

measure() 메서드의 ProceedingJoinPoint 타입 파라미터는 프록시 대상 객체의 메서드를 호출할 때 사용한다. 22행의 ProceedingJoinPoint#proceed() 메서드를 실행하면 실제 대상 객체의 메서드가 호출된다.

 

스프링 설정 클래스에 등록하는 코드는 다음과 같다.

@Configuration 
@EnableAspectJAutoProxy 
public class AppCtx { 
    
    @Bean 
    public ExeTimeAspect exeTimeAspect() { 
        return new ExeTimeAspect(); 
    } 
    
    @Bean 
    public Calculator calculator() { 
        return new RecCalculator(); 
    } 
}