| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- frontend
- 일감관리
- AOP
- 검색
- 인덱스
- 비전공개발자
- spring
- redmine
- 엘라스틱서치
- 오라클
- elasticsearch
- db
- Database
- Si
- 데이터베이스
- 프로젝트관리
- 스프링
- PM
- java
- 웹프론트
- 레드마인
- 백엔드
- PreparedStatement
- 이슈관리
- 자바
- spring aop
- It
- 개발자
- kibana
- backend
- Today
- Total
리타의 저장소
Spring AOP | JDK Dynamic Proxy 본문

JDK Dynamic Proxy
JDK Dynamic Proxy는 Interface가 존재하는 경우에만 Proxy를 만들 수 있다. 객체 자체가 아닌 Interface와 Java Reflection API를 이용해서 Proxy 객체를 생성하는 것이다. Interface에 대한 검증 로직을 거친 후, ProxyFactory에 의해 실제 구현하려는 Interface와 InvocationHandler를 포함해 Proxy 객체를 생성한다.
JDK Dynamic Proxy 의 경우,Proxy.newProxyInstance(...) API를 통해 프록시를 생성하고, 여기서 핵심은 InvocationHandler 그리고 Proxy이다.
Java Platform SE 8
docs.oracle.com
Java 공식문서에서 확인할 수 있듯 InvocationHandler 그리고 Proxy의 경우, Reflection API 패키지 안에 포함되어있어, JAVA Reflection API의 확장선에 있다.
JDK Dynamic Proxy에서 Proxy 클래스를 통해 생성된 프록시 인스턴스는 모든 메서드 호출을 InvocationHandler 인터페이스의 invoke() 메서드로 위임한다.
InvocationHandler는 동적으로 생성된 프록시 객체의 메소드가 호출되면 동작하는 메소드를 정의하는 interface이다.
java.lang.reflect.InvocationHandler
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
JAVA 공식 문서
invoke(Object proxy,Method method,Object[] args)
Description : Processes a method invocation on a proxy instance and returns the result.
invoke() 라는 한개의 메서드만 갖고 있고, 이 인터페이스의 구현체는 각각, 혹은 전체의 메서드의 확장 기능을 구현할 수 있고, 호출된 메서드 정보와 입력값을 파라미터로 받게된다. 즉, 프록시 대상이 되는 인터페이스 각각의 메서드에 사용될 확장기능을 구현한다. Handler를 구현하게되면, Proxy를 이용해서 프록시 객체를 만들게 된다.
java.lang.reflect.Proxy
package java.lang.reflect;
public class Proxy implements Serializable {
private static final long serialVersionUID = -2222568056686623797L;
private static final Class<?>[] constructorParams = { InvocationHandler.class };
private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();
protected InvocationHandler h;
...
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (caller != null) {
checkNewProxyPermission(caller, cons.getDeclaringClass());
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
}
}
JAVA 공식 문서
Parameters:loader - the class loader to define the proxy classinterfaces - the list of interfaces for the proxy class to implementReturns:a proxy class that is defined in the specified class loader and that implements the specified interfacesThrows:IllegalArgumentException - if any of the restrictions on the parameters that may be passed to getProxyClass are violatedSecurityException - if a security manager, s, is present and any of the following conditions is met: • the given loader is null and the caller's class loader is not null and the invocation of s.checkPermission with RuntimePermission("getClassLoader") permission denies access. • for each proxy interface, intf, the caller's class loader is not the same as or an ancestor of the class loader for intf and invocation of s.checkPackageAccess() denies access to intf.NullPointerException - if the interfaces array argument or any of its elements are null
the given loader is null and the caller's class loader is not null and the invocation of s.checkPermission with RuntimePermission("getClassLoader") permission denies access.
for each proxy interface, intf, the caller's class loader is not the same as or an ancestor of the class loader for intf and invocation of s.checkPackageAccess() denies access to intf.
코드에서 확인할 수 있듯, 프록시를 생성하기 위해 requireNonNull(Invacationhandler h) 를 요구하고 있고, 파라미터로 Interface를 받는 것을 확인할 수 있다.
원본 객체로의 실행 진입점이 되는 InvocationHandler에서 어떤 함수를 실행시켰는지, 각 함수에 따라서 어떻게 동작해야 할 지에 대한 로직이나 올바르지 않은 타겟이 주어진 경우에 대한 검증 로직이 포함되어야 하는 이유가 여기에 있다.
JDK Proxy는 Interface에 대한 Proxy만 만들어주기 떄문에, 실제 Object를 잘못 주입하는 경우가 생길 수도 있다. 또 원본 객체에 여러 기능이 포함되어있어, 다양한 확장기능이 필요할 수도 있어서, 주의가 필요하다.
→ 즉, InvocationHandler에서 어떤 메서드가 호출됐는지에 따른 분기 처리 / 로깅 / 권한 체크 / 트랜잭션 / 예외처리 등을 모두 해야 하는 구조.
→ JDK Proxy의 경우 인터페이스만 알 뿐, 그 구현 객체가 어떤 타입인지 강제하지 않는다. 그러니까, InvocationHandler에서의 검증이 중요한 것. 실제 원본 객체가 해당 인터페이스를 제대로 구현한건지 런타임에서 확인해야하고, 잘못 주입된 경우 예외를 통해 방어처리를 해줘야함.
결론
⇒ 기능이 많은 객체일수록 InvocationHandler는 복잡해진다 ~
'Dev > Backend' 카테고리의 다른 글
| Spring AOP | 돌고돌아 JDK Dynamic Proxy vs CGLIB Proxy (0) | 2025.10.11 |
|---|---|
| Spring AOP | CGLIB Proxy (0) | 2025.10.11 |
| Spring AOP | JDK Dynamic Proxy & CGLIB Proxy (0) | 2025.10.11 |
| Spring AOP | Spring AOP 주요 개념 요약 (0) | 2025.10.11 |
| Spring AOP | 서비스 호출 전에 로깅, 꼭 AOP에서 해야만 하나? (0) | 2025.10.11 |
