본문 바로가기

Spring Framework

InvocationHandler

InvocationHandler는 자바의 java.lang.reflect 패키지에 있는 인터페이스로, Java의 다이나믹 프록시(dynamic proxy) 메커니즘의 핵심 부분입니다. 다이나믹 프록시는 런타임에 특정 인터페이스를 구현하는 클래스의 인스턴스를 생성하는 기능을 제공합니다. 이 과정에서 InvocationHandler는 핵심적인 역할을 담당합니다.

InvocationHandler의 기능과 역할

  1. 메소드 호출의 중앙 처리: InvocationHandler 인터페이스는 단 하나의 메소드 invoke를 가지고 있습니다. invoke 메소드는 프록시 인스턴스의 메소드가 호출될 때마다 자동으로 실행됩니다. 즉, InvocationHandler는 프록시를 통해 호출된 모든 메소드를 중앙에서 처리합니다.
  2. 동적 프록시 생성: Proxy.newProxyInstance 메소드를 사용하여 다이나믹 프록시 객체를 생성할 때 InvocationHandler 구현체를 제공해야 합니다. 이 핸들러는 프록시 객체에 의해 호출되는 타겟의 모든 메소드에 대해 제어를 담당합니다.
  3. 유연한 메소드 처리: invoke 메소드는 프록시된 타겟 메소드의 호출을 가로채고, 이를 다양한 방식으로 처리할 수 있습니다. 예를 들어, 메소드 호출 전후에 특정 로직을 실행하거나, 원래의 메소드 호출을 변경하거나, 심지어 호출을 완전히 다른 동작으로 대체할 수도 있습니다.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

 

InvocationHandler 인터페이스에는 다음과 같은 메서드가 정의되어 있습니다:

 

1.  invoke(): 다이나믹 프록시가 가로챈 클라이언트의 타겟 메서드 호출을 처리하기 위해 호출되는 메서드입니다. 이 메서드를 구현하여 원하는 동작을 수행할 수 있습니다. invoke() 메서드는 다음과 같은 세 개의 파라미트를 정의 하고 있습니다.

  • Object proxy: 다이나믹 프록시 객체 자체입니다. invoke() 메서드 내에서 proxy 객체를 사용하여 프록시 객체와 관련된 작업을 수행할 수 있습니다.
  • Method method: 호출된 메서드에 대한 Method 객체입니다. method 객체를 통해 호출된 메서드의 이름, 매개변수 등의 정보에 접근할 수 있습니다.
  • Object[] args: 메서드에 전달된 인수들을 포함하는 객체 배열입니다. args 배열을 통해 메서드에 전달된 인수들에 접근할 수 있습니다.

invoke() 메서드는 호출된 메서드의 결과를 반환해야 합니다. 반환값은 호출된 메서드의 반환 타입에 맞게 설정해야 합니다. 만약 반환값이 없는 경우 null을 반환할 수 있습니다.

 

예를 들어, 다이나믹 프록시가 호출된 메서드의 이름을 출력하고 실제 대상 객체의 메서드를 호출하기 전후에 원하는 작업을 수행하고자 한다면, 다음과 같이 invoke() 메서드를 구현할 수 있습니다:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 메소드 호출 전 로직
        System.out.println("Before method: " + method.getName());

        // 실제 객체의 메소드 호출
        Object result = method.invoke(target, args);

        // 메소드 호출 후 로직
        System.out.println("After method: " + method.getName());

        return result;
    }

    public static void main(String[] args) {
        MyInterface original = new MyImplementation();
        MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                MyInterface.class.getClassLoader(),
                new Class[]{MyInterface.class},
                new DynamicProxyHandler(original));

        proxy.myMethod();
    }
}

 

위의 예제에서 invoke() 메서드는 호출된 메서드의 이름을 출력하고, method.invoke(target, args)를 사용하여 실제 대상 객체의 메서드를 호출합니다. 호출된 메서드의 결과를 리하고, 필요한 추가 동작을 수행한 후 리턴값을 리턴합니다.

다이나믹 프록시에서 InvocationHandler 인터페이스의 invoke() 메서드를 구현하여 원하는 동작을 정의할 수 있습니다. 이를 통해 프록시 객체의 메서드 호출을 가로채고 추가 동작을 수행할 수 있습니다.

 

고급 사용 방법

1. 조건부 로직 실행: invoke 메소드 내에서, 호출되는 메소드(Method 객체)의 이름이나 타입을 확인하여 조건에 따라 다른 로직을 실행할 수 있습니다. 이를 통해 특정 메소드에 대해서만 특별한 처리를 할 수 있습니다.

2. 예외 처리: invoke 메소드 내에서 예외 처리를 구현하여, 프록시된 메소드 호출 중 발생하는 예외를 적절히 처리하거나 변경할 수 있습니다.

3. 성능 모니터링: 메소드 호출 전후에 시간을 측정하여, 특정 메소드의 실행 시간을 모니터링하는 로직을 추가할 수 있습니다. 이는 성능 분석과 디버깅에 유용합니다.

4. 트랜잭션 관리: 데이터베이스 연결과 같은 경우, 메소드 호출 전후에 트랜잭션을 시작하고 커밋 또는 롤백하는 로직을 추가하여, 트랜잭션 관리를 자동화할 수 있습니다.

 

주의사항

1. 무한 재귀 호출 방지: invoke 메소드 내에서 프록시 객체의 메소드를 호출할 때는 주의가 필요합니다. 잘못 구현하면 무한 재귀 호출이 발생할 수 있습니다. 대신, 원래의 타깃 객체의 메소드를 호출해야 합니다.

2. 성능 고려: InvocationHandler를 사용하면 추가적인 메소드 호출 오버헤드가 발생합니다. 성능이 중요한 애플리케이션에서는 이러한 오버헤드를 고려해야 합니다.

실용적인 활용 예시

- 로깅 및 감사: 시스템에서 중요한 메소드 호출을 로깅하거나 감사하기 위해 사용될 수 있습니다.

- 보안 검증: 메소드 호출 전에 사용자 인증이나 권한 검사를 수행할 수 있습니다.

- 리소스 관리: 예를 들어, 데이터베이스 연결을 열고 닫는 것과 같은 리소스 관리 작업을 자동화할 수 있습니다.

InvocationHandler의 사용은 매우 유연하며, 다양한 상황에 맞게 커스터마이즈할 수 있습니다. 이러한 유연성은 Java 프로그래밍에서 매우 강력한 도구를 제공하지만, 복잡성과 성능 고려 사항을 이해하는 것이 중요합니다.

 

// 인터페이스 정의
public interface MyInterface {
    void performAction();
    void anotherAction();
}

// MyInterface 구현
public class MyImplementation implements MyInterface {
    @Override
    public void performAction() {
        System.out.println("Performing Action");
    }

    @Override
    public void anotherAction() {
        System.out.println("Performing Another Action");
    }
}

 

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 특정 메소드에 대한 조건부 로직
        if ("performAction".equals(method.getName())) {
            System.out.println("Special Handling for performAction");
        } else {
            System.out.println("Regular Handling for " + method.getName());
        }

        // 원래 객체의 메소드 호출
        return method.invoke(target, args);
    }
}

 

import java.lang.reflect.Proxy;

public class ProxyExample {
    public static void main(String[] args) {
        // 원래의 객체 생성
        MyInterface originalObject = new MyImplementation();

        // 동적 프록시 생성
        MyInterface proxyObject = (MyInterface) Proxy.newProxyInstance(
                MyInterface.class.getClassLoader(),
                new Class[]{MyInterface.class},
                new MyInvocationHandler(originalObject));

        // 프록시 객체를 통해 메소드 호출
        proxyObject.performAction();
        proxyObject.anotherAction();
    }
}

 

이 코드를 실행하면, performAction 메소드와 anotherAction 메소드가 호출될 때 각각 다른 로그 메시지가 출력됩니다. performAction에 대해서는 "Special Handling" 메시지가, anotherAction에 대해서는 "Regular Handling" 메시지가 출력되며, 이는 MyInvocationHandler invoke 메소드에서 설정한 조건부 로직에 의한 것입니다.

'Spring Framework' 카테고리의 다른 글

Factory Bean  (0) 2024.04.09
Java Dynamic Proxy와 Spring FactoryBean  (0) 2024.04.09
서비스 추상화  (0) 2024.04.09
Service Layer  (0) 2024.04.09
템플릿 콜백 패턴(Template Callback Pattern)  (0) 2024.04.09