AOP는 왜 존재하는가? 왜 필요한가?

태량·2023년 7월 11일
0
  1. 다이내믹 프록시의 생성 방식(Proxy의 정적 팩토리 메소드)으로는 스프링 빈으로 등록하기 힘든 이유가 무엇인가?

  2. 스프링이 빈을 생성하는 다양한 방법

    1. 리플렉션을 이용한 빈 생성 방법. 리플렉션의 newInstance( ) 메소드를 활용하는데, 이 메소드 자체가 대상 클래스의 기본 생성자를 호출하기 때문에, 기본 생성자가 없다면 사용 할 수 없다.
    2. 팩토리 빈을 통한 생성 방법.
      • factoryBean 인터페이스 getObject ( )라는 메소드를 통해 빈으로 만들고자 하는 객체를 반환 받는다. 그리고 bean id와 매칭시킨다.
  3. 토비의 스프링 AOP 파트 공부를 하다가, 프록시를 두개 중첩해서 사용하는 것에 대해 궁금증이 들어서 적용 해봤다.

    public interface Hello {
    
        String sayHello(String name);
    
        String sayHi(String name);
    
        String sayThankYou(String name);
    }
    
    프록시 대상이 될 인터페이스
    package proxy;
    
    public class HelloTarget implements Hello{
    
        @Override
        public String sayHello(String name) {
            return "Hello "+name;
        }
    
        @Override
        public String sayHi(String name) {
            return "Hi "+name;
        }
    
        @Override
        public String sayThankYou(String name) {
            return "ThankYou "+name;
        }
    }
    
    타겟이 될 Hello 인터페이스 구현체 HelloTarget
    package proxy;
    
    import lombok.Getter;
    import lombok.Setter;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    @Setter @Getter
    public class UppercaseHandler implements InvocationHandler {
    
        private Object target;
        private String pattern;
    
    		public UppercaseHandler() {
    
        }
    
        public UppercaseHandler(Object target) {
            this.target = target;
        }
    
        public UppercaseHandler(Object target, String pattern) {
            this.target = target;
            this.pattern = pattern;
        }
    
        
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            Object obj = method.invoke(target,args);
    
            if(obj instanceof String && method.getName().contains(pattern)){
                return ((String) obj).toUpperCase();
            }else return obj;
    
        }
    
    }
    
    -> 다시 보니 예전에 setter를 남발해서 적어놔서, 이번엔 생성자를 통해서만 접근하고 싶었는데, 생성자가 없어서 부랴부랴 세밀하지 못하게 생성자들을 만들었다.
    
    -> 무튼. 이 핸들러가 원래 1개만 사용되던 프록시 클래스
    package proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import lombok.extern.log4j.Log4j2;
    
    @Log4j2
    public class LogHandler implements InvocationHandler {
    
        private final Object target;
    
        public LogHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    log.info("LogHandler...........");
    
            return method.invoke(target,args);
        }
    }
    
    -> 거기에 추가해서 Log를 남기는 프록시를 만들어서 두개의 프록시를 적용하고자 했다.
    package learning;
    
    import java.lang.reflect.Proxy;
    import proxy.Hello;
    import proxy.HelloTarget;
    import proxy.LogHandler;
    import proxy.UppercaseHandler;
    
    public class ProxyEx {
    
        public static void main(String[] args) {
    
            Hello upperHello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),new Class[]{
                Hello.class},new UppercaseHandler(new HelloTarget(),""));
    
            Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),new Class[]{
                Hello.class},new LogHandler(upperHello));
    
            System.out.println(hello.sayHello("joinjun"));
        }
    
    }
    
    -> 결과는 올바르게 나오는데, 저 Proxy 정적 팩토리 메소드를 통해서 만드는 부분의 중복들이 너무 거슬린다. 프록시가 만약 더 늘면 저 코드들도 더 늘 것이 아닌가!!

    !! 스프링의 프록시가 출현 해야할 때가 왔다.

@Nullable
public Object getObject() throws BeansException {
    this.initializeAdvisorChain();
    if (this.isSingleton()) {
        return this.getSingletonInstance();
    } else {
        if (this.targetName == null) {
            this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
        }

        return this.newPrototypeInstance();
    }
}

싱글톤 타겟을 가지고 프로토 타입 프록시를 사용하면 어떻게 될까?

  • 이용하는 주체가 누구인지 파악이 안되지 않을까?
  1. 스프링 프록시의 멋진 해결
package proxy;

import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@Setter @Getter
public class UppercaseHandler implements InvocationHandler {

    private Object target;
    private String pattern;

		public UppercaseHandler() {

    }

    public UppercaseHandler(Object target) {
        this.target = target;
    }

    public UppercaseHandler(Object target, String pattern) {
        this.target = target;
        this.pattern = pattern;
    }

    

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object obj = method.invoke(target,args);

        if(obj instanceof String && method.getName().contains(pattern)){
            return ((String) obj).toUpperCase();
        }else return obj;

    }

}
public class UppercaseAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        Object obj = methodInvocation.proceed();

        return ((String)obj).toUpperCase();
    }
}

→ MethodInvocation이라는 클래스에 target 오브젝트와 메소드 객체를 넣어서 이 안에서 실행하게 해줌.

  1. 트랜잭션 코드를 서비스 코드에서 분리하고 싶다!! → 서비스 코드 인터페이스를 구현하는 프록시 클래스를 직접 구현해서 프록시를 통해 해결

    → 아…근데 프록시 클래스 매번 만드는거 귀찮고, 중복되는거 냄새나 → 그러면 Reflexion을 써서 클래스로 만들어두지 말고, 그때그때 동적으로 만들어!

    → 와.. 프록시 클래스를 안만들어서 좋긴 한데… UserTxProxy, MemberTxProxy 일일이 만들어 줘야 하는거 짜증나네 → JDK 너네가 관심사를 대충 분리하니깐 그렇지. 부가 기능하는 놈은 그것만 하게 하고, 타겟 클래스는 내가 만들어서 넣어주면 되고, 원래 같이 있던 메소드 선정 방법은 관심사가 다르니깐 분리해서 새로 만들자!

  2. JDK 다이나믹 프록시를 사용하면 프록시 팩토리 빈 자체를 부가기능 종류마다 새로 만들어야 하는 부담이 있음.

profile
좋은 영향력과 교류를 위하여

0개의 댓글