백엔드/Spring

[Spring] ProxyFactory

kwang2134 2024. 11. 6. 16:01
728x90
반응형
728x90

ProxyFactory

  • 스프링 프레임워크에서 프록시 객체를 생성하는 데 사용되는 유틸리티 클래스
  • 두 가지 종류의 프록시 생성 방법을 추상화하여 제공
    • JDK 프록시: 인터페이스 기반
    • CGLIB 프록시: 클래스 기반

구성 요소

target

  • 프록시가 감쌀 실제 객체
  • ProxyFactory 생성 시 생성자로 넘기거나 setTarget을 통해 지정 가능
//생성자
ProxyFactory proxyFactory = new ProxyFactory(new HelloImpl());

//setTarget
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new HelloImpl());

 

Advice

  • 프록시가 메서드 호출 시 실행할 로직
  • 실행 전 후 등 추가 로직이 구현되는 곳
//Advice - 실제 객체 호출 전 후 프록시에서 추가 구현 로직이 들어가는 곳
public class LogInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 메서드 호출 전 로깅
        log.info("Before method={}", invocation.getMethod().getName());

        // 실제 메서드 호출
        Object result = invocation.proceed();

        // 메서드 호출 후 로깅
        System.out.println("After method={}", invocation.getMethod().getName());

        return result;
    }
}

//Advice 지정
proxyFactory.addAdvice(new LogInterceptor());

 

Pointcut

  • 메서드의 특정 지점에서 Advice가 실행될지를 결정하는 표현식
  • 프록시에서 추가 로직(Advice)을 실행 여부를 검사하는 클래스
  • AspectJExpressionPointcut 클래스를 사용하던가 Pointcut을 상속받아 구현하여 사용
public class CustomPointcut implements Pointcut {

    //MethodMatcher 구현
    public class CustomMethodMatcher implements MethodMatcher {

        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            // 메서드 이름이 login* 또는 register*로 시작하는지 확인
            String methodName = method.getName();
            return methodName.startsWith("login") || methodName.startsWith("register");
        }

        @Override
        public boolean isRuntime() {
            return false;  // 런타임에서 결정되지 않음, 정적 판단
        }
    }

    //Pointcut 구현
    @Override
    public MethodMatcher getMethodMatcher() {
        return new CustomMethodMatcher();
    }

    @Override
    public ClassFilter getClassFilter() {
        return ClassFilter.TRUE;  // 모든 클래스를 허용
    }
}


//AspectJExpressionPointcut
public class AspectJPointcut {

    public AspectJExpressionPointcut getPointcut() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // AspectJ 표현식으로 login* 또는 register*로 시작하는 메서드 설정
        pointcut.setExpression("execution(void login*(..)) || execution(void register*(..))");
        return pointcut;
    }
}

 

Advisor

  • Pointcut과 Advice를 1개 씩 가지고 있는 클래스 
  • 해당 Pointcut을 만족하면 Advice 실행
  • 한 번에 여러 Advisor 적용 가능
Pointcut pointcut1 = new AspectJPointcut().getPointcut();				//AspectJPointcut 방식
Pointcut pointcut2 = new CustomPointcut();						//Pointcut 구현

LogInterceptor advice = new LogInterceptor();  						// Advice

DefaultPointcutAdvisor advisor1 = new DefaultPointcutAdvisor(pointcut1, advice);	//Advisor1
DefaultPointcutAdvisor advisor2 = new DefaultPointcutAdvisor(pointcut2, advice);	//Advisor2

ProxyFactory proxyFactory = new ProxyFactory(new HelloImpl());

proxyFactory.addAdvisor(advisor2);							//Advisor2 적용
proxyFactory.addAdvisor(advisor1);							//Advisor1 적용

//요청 흐름
client -> proxy -> advisor2 -> advisor1 -> target

 

사용 방법

  1. target  생성
    • 프록시 객체로 감쌀 실제 객체 생성
  2. ProxyFactory 생성
    • ProxyFatory 객체 생성과 프록시가 참조할 실제 target 객체 지정
  3. Advisor 생성 및 적용
    • Advice와 Pointcut 생성 후 Advisor 생성
    • ProxyFactory에 Advisor 지정
  4. 추가 설정
    • setProxyTargetClass(): CGLIB 프록시 사용 여부 설정 -> 인터페이스 및 실제 클래스가 존재하는 경우
    • setInterfaces(): 프록시가 구현할 인터페이스 지정 -> 실제 클래스가 구현한 인터페이스와 관계없이 프록시 객체가 다른 인터페이스를 구현 가능
    • setTargetSource(): target 객체 제공 방식 설정 
  5. 프록시 객체 생성
    • proxyFactory.getProxy()를 통해 프록시 객체 생성

장점

  • 유연한 적용 가능 -> 인터페이스만 존재하거나 클래스만 존재하는 모든 경우에 사용 가능
  • Pointcut과 Advice의 역할을 나누어 단일 책임 준수 

스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런 강의 내용 참고

728x90