2025. 11. 3. 13:41ㆍJava/Spring
❶ 프록시란?
프록시(Proxy)는 ‘대리인’이라는 뜻으로, 실제 객체(Real Object)를 감싸서 대신 메서드를 실행하고, 그 과정에서 공통 기능(전처리, 후처리 등)을 삽입하는 역할을 한다. 스프링은 트랜잭션, 캐시, 보안 같은 부가기능을 삽입할 때 이 프록시 방식을 사용한다.

❷ 코드 구조 요약
package com.mycom.myapp.proxy;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
MyIF myIF = new MyIFImpl();
String param1 = "abc";
String param2 = null;
MyIF proxy = (MyIF) Proxy.newProxyInstance(
myIF.getClass().getClassLoader(),
myIF.getClass().getInterfaces(),
new CheckNotNullInvocationHandler(myIF)
);
proxy.m(param1, param2);
proxy.m2(param1, param2);
}
}
여기서 핵심은 Proxy.newProxyInstance()이다. 이 메서드가 런타임에 새로운 프록시 객체를 생성하고, 호출이 일어날 때마다 InvocationHandler가 가로채서 제어한다.
---
❸ InvocationHandler의 핵심 동작
public class CheckNotNullInvocationHandler implements InvocationHandler {
private Object target;
public CheckNotNullInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
if (targetMethod.isAnnotationPresent(CheckNotNull.class)) {
return handleCheckNotNull(targetMethod, args);
}
return method.invoke(target, args); // 원본 호출
}
private Object handleCheckNotNull(Method method, Object[] args) throws Throwable {
CheckNotNull annotation = method.getAnnotation(CheckNotNull.class);
String[] parameterNames = annotation.parameterNames();
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
throw new IllegalArgumentException(
"Parameter " + parameterNames[i] + " is null (should be notnull)");
}
}
return method.invoke(target, args);
}
}
이 클래스는 스프링의 AOP 어드바이스(Advice) 역할을 한다. 즉, 메서드 실행 전에 공통 로직(여기서는 null 검사)을 수행하고, 문제 없으면 실제 target 메서드를 호출한다.
---
❹ 인터페이스와 구현 클래스
public interface MyIF {
void m(String param1, String param2);
void m2(String param1, String param2);
}
public class MyIFImpl implements MyIF {
@Override
@CheckNotNull(parameterNames = {"param1","param2"})
public void m(String param1, String param2) {
System.out.println("m() : " + param1 + ", " + param2);
}
@Override
public void m2(String param1, String param2) {
System.out.println("m2() : " + param1 + ", " + param2);
}
}
`m()`은 @CheckNotNull이 붙어 있기 때문에 프록시가 null 검사를 수행하지만, `m2()`는 애노테이션이 없어서 그냥 바로 실행된다.
---
❺ @CheckNotNull 애노테이션 정의
@Retention(RUNTIME)
@Target(METHOD)
public @interface CheckNotNull {
String[] parameterNames();
}
- @Retention(RUNTIME) : 런타임에도 유지되어 리플렉션으로 접근 가능 - @Target(METHOD) : 메서드에만 적용 가능 → 결국 이 애노테이션이 부가기능의 ‘트리거’ 역할을 한다.
---
❻ 실행 흐름
- Test에서 MyIFImpl 객체 생성
- Proxy.newProxyInstance()로 프록시 객체 생성
- proxy.m("abc", null) 호출 → InvocationHandler로 이동
- @CheckNotNull 확인 → null 발견 → 예외 발생
- proxy.m2() 호출 → 애노테이션 없음 → 원본 메서드 직접 실행
---
❼ 스프링 AOP와의 관계
스프링은 내부적으로 다음 두 가지 프록시 방식을 사용한다:
- JDK 동적 프록시: 인터페이스가 존재할 때 (→ 현재 예제)
- CGLIB 프록시: 인터페이스 없이 클래스 상속으로 구현
스프링에서 @Transactional, @Cacheable, @Async 같은 기능들이 바로 이런 프록시 객체를 통해 동작한다. 즉, 우리가 만든 CheckNotNull 예제는 스프링 AOP의 축소판이다.
---
❽ 주의할 점
- 파라미터 이름 배열과 실제 인자 수가 다르면 오류 발생 가능
- Self-invocation(자기 내부 메서드 호출) 시 AOP 적용 안 됨
- final 클래스/메서드는 CGLIB 프록시로도 가로채기 불가
---
❾ 정리
| 개념 | 역할 |
|---|---|
| Proxy 객체 | 원본 객체를 감싸 호출을 가로챔 |
| InvocationHandler | 실제 호출 시 부가기능 수행 |
| Annotation | 적용 대상을 선언적으로 지정 |
| 리플렉션(Reflection) | 런타임에 메서드·필드 접근 |
| AOP | 공통 로직을 필요한 지점에 삽입 |
---
🔑 핵심 요약
✅ 프록시는 런타임에 생성되어 대리로 메서드를 실행한다.
✅ InvocationHandler가 전·후처리 로직을 수행한다.
✅ 애노테이션은 부가기능을 선언적으로 적용하게 한다.
✅ 스프링의 AOP(@Transactional 등)는 동일 원리로 동작한다.
---
🧭 마무리
이 예제는 스프링의 프록시 기반 AOP를 이해하기 위한 완벽한 축소판이다.
단순히 코드 트릭이 아니라, 스프링이 애노테이션 하나로 다양한 기능을 삽입할 수 있는 근본 원리를 보여준다.
프록시의 작동 원리를 정확히 이해하면, 트랜잭션 경계, 예외 처리, DI 컨테이너 내부 구조까지도 자연스럽게 연결된다.
'Java > Spring' 카테고리의 다른 글
| [LG U+ 유레카3기]Spring MVC | HttpSession 로그인 → 유지 → 로그아웃 실습 정리 (0) | 2025.11.05 |
|---|---|
| [LG U+ 유레카 3기]Spring MVC | 요청 바인딩 + View/Model/Redirect 실습 정리 (0) | 2025.11.05 |
| Spring MVC 내부 동작 구조 (Deep Dive) (0) | 2025.11.04 |
| [LG U+ 유레카 3기]Spring MVC 요청 흐름 & 매핑 실습 — Boot 위에서 레거시 구조 이해하기 (0) | 2025.11.04 |
| [LG U+ 유레카 3기]Spring DI | XML → Annotation → Java Config → Has-A → Interface + Qualifier 실습 정리 (0) | 2025.11.03 |