Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
Tags
- jpa
- 스프링
- 그리디
- Servlet
- 자바
- Spring Boot
- 알고리즘
- 인프런
- Android
- Proxy
- 백준
- AOP
- JPQL
- springdatajpa
- QueryDSL
- JDBC
- 김영한
- kotlin
- 스프링 핵심 기능
- pointcut
- Exception
- java
- http
- Greedy
- transaction
- SpringBoot
- 스프링 핵심 원리
- spring
- Thymeleaf
- db
Archives
- Today
- Total
개발자되기 프로젝트
[동적프록시] JDK 동적프록시 - 소개 본문
- 지금까지 프록시를 적용하기 위해 적용 대상의 숫자 만큼 많은 프록시 클래스를 만들었다.
- 적용 대상이 100개면 프록시 클래스도 100개 만들었다.
- 그런데 앞서 살펴본 것과 같이 프록시 클래스의 기본 코드와 흐름은 거의 같고,
- 프록시를 어떤 대상에 적용하는가 정도만 차이가 있었다. 쉽게 이야기해서 프록시의 로직은 같은데,
- 적용 대상만 차이가 있는 것이다.
- 이 문제를 해결하는 것이 바로 동적 프록시 기술이다.
- 동적 프록시 기술을 사용하면 개발자가 직접 프록시 클래스를 만들지 않아도 된다.
- 이름 그대로 프록시 객체를 동적으로 런타임에 개발자 대신 만들어준다.
- 그리고 동적 프록시에 원하는 실행 로직을 지정할 수 있다.
- JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어준다.
따라서 인터페이스가 필수이다.
1. 예제 - 준비
- JDK 동적 프록시를 이해하기 위해 아주 단순한 예제 코드를 만들어보자.
- 간단히 A , B 클래스를 만드는데, JDK 동적 프록시는 인터페이스가 필수이다.
- 따라서 인터페이스와 구현체로 구분
public interface AInterface {
String call();
}
@Slf4j
public class AImpl implements AInterface{
@Override
public String call() {
log.info("A 호출");
return "A";
}
}
public interface BInterface {
String call();
}
@Slf4j
public class BImpl implements BInterface{
@Override
public String call() {
log.info("B 호출");
return "B";
}
}
2. JDK 동적 프록시 InvocationHandler
- JDK 동적 프록시에 적용할 로직은 InvocationHandler 인터페이스를 구련해서 작성하면된다.
- JDK 동적프록시가 제공하는 InvocationHandler
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
- Object proxy : 프록시 자신
- Method method : 호출한 메서드
- Object[] args : 메서드를 호출할 때 전달한 인수
3. 적용- InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@Slf4j
public class TimeInvocationHandler implements InvocationHandler {
private final Object target;
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime = {}", resultTime);
return result;
}
}
- TimeInvocationHandler 은 InvocationHandler 인터페이스를 구현한다.
- Object target : 동적 프록시가 호출할 대상
- method.invoke(target, args) : 리플렉션을 사용해서 target 인스턴스의 메서드를 실행한다.
- args는 메서드 호출시 넘겨줄 인수이다.
4. 사용예시
@Slf4j
public class JdkDynamicProxyTest {
@Test
void dynamicA(){
AInterface target = new AImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);
//Object proxy = Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
proxy.call();
log.info("targetClass = {}", target.getClass());
log.info("targetClass = {}", proxy.getClass());
}
@Test
void dynamicB(){
BInterface target = new BImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);
//Object proxy = Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
BInterface proxy = (BInterface) Proxy.newProxyInstance(BInterface.class.getClassLoader(), new Class[]{BInterface.class}, handler);
proxy.call();
log.info("targetClass = {}", target.getClass());
log.info("targetClass = {}", proxy.getClass());
}
}
- new TimeInvocationHandler(target) : 동적 프록시에 적용할 핸들러 로직이다.
- Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler)
- 동적 프록시는 java.lang.reflect.Proxy 를 통해서 생성할 수 있다.
- 클래스 로더 정보, 인터페이스, 그리고 핸들러 로직을 넣어주면 된다.
- 그러면 해당 인터페이스를 기반으로 동적 프록시를 생성하고 그 결과를 반환한다.
- newProxyInstance(ClassLoader, Instances, Handler)
- loader – the class loader to define the proxy class ->Proxy의 클래스 정의
- interfaces – the list of interfaces for the proxy class to implement -> Proxy가 구현하는 Interfaces
- proxy.call()을 호출하면, InvocationHandler의 invoke()가 호출된다.
- 이 때 handler에 현재 proxy와 호출된 메서드 call()이 인자로 넘어간다.
- proxy의 역할은 handler를 호출하면서 메서드 정보, proxy를 넘겨주는 것.
실행 결과
16:09:25.331 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 실행
16:09:25.338 [main] INFO hello.proxy.jdkdynamic.code.AImpl - A 호출
16:09:25.338 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 종료 resultTime = 0
16:09:25.341 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class hello.proxy.jdkdynamic.code.AImpl
16:09:25.341 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class com.sun.proxy.$Proxy9
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 실행
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.code.BImpl - B 호출
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 종료 resultTime = 0
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class hello.proxy.jdkdynamic.code.BImpl
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class com.sun.proxy.$Proxy10
5. 생성된 JDK 동적 프록시
- proxyClass=class com.sun.proxy.$Proxy10이 부분이 동적으로 생성된 프록시 클래스 정보이다.
- 이것은 우리가 만든 클래스가 아니라 JDK 동적 프록시가 이름 그대로 동적으로 만들어준 프록시이다.
- 이 프록시는 TimeInvocationHandler 로직을 실행한다.
6. 실행 순서
- 클라이언트는 JDK 동적 프록시의 call() 을 실행한다.
- JDK 동적 프록시는 InvocationHandler.invoke() 를 호출한다.
- TimeInvocationHandler 가 구현체로 있으로 TimeInvocationHandler.invoke() 가 호출된다.
- TimeInvocationHandler 가 내부 로직을 수행하고, method.invoke(target, args) 를 호출해서
- target 인 실제 객체( AImpl )를 호출한다.
- AImpl 인스턴스의 call() 이 실행된다.
- AImpl 인스턴스의 call() 의 실행이 끝나면 TimeInvocationHandler 로 응답이 돌아온다.
- 시간 로그를 출력하고 결과를 반환한다.
7. 정리
- 예제를 보면 AImpl , BImpl 각각 프록시를 만들지 않았다.
- 프록시는 JDK 동적 프록시를 사용해서 동적으로 만들고 TimeInvocationHandler 는 공통으로 사용했다.
- JDK 동적 프록시 기술 덕분에 적용 대상 만큼 프록시 객체를 만들지 않아도 된다.
- 그리고 같은 부가 기능 로직을 한번만 개발해서 공통으로 적용할 수 있다.
- 만약 적용 대상이 100개여도 동적 프록시를 통해서 생성하고,
각각 필요한 InvocationHandler 만 만들어서 넣어주면 된다. - 결과적으로 프록시 클래스를 수 없이 만들어야 하는 문제도 해결하고,
부가 기능 로직도 하나의 클래스에 모아서 단일 책임 원칙(SRP)도 지킬 수 있게 되었다. - JDK 동적 프록시 없이 직접 프록시를 만들어서 사용할 때와 JDK 동적 프록시를 사용할 때의 차이를
그림으로 비교해보자. - 점선은 개발자가 직접 만드는 클래스가 아니다.
8. GitHub : 211230 JDK Dynamic Proxy
'인프런 > [인프런] 스프링 핵심 원리 - 고급' 카테고리의 다른 글
[동적프록시] JDK 동적프록시 - 적용2, 필터 (0) | 2021.12.30 |
---|---|
[동적프록시] JDK 동적프록시 - 적용 (0) | 2021.12.30 |
[동적프록시] 리플렉션 (0) | 2021.12.30 |
인터페이스기반 프록시와 클래스 기반 프록시 (0) | 2021.12.30 |
구체 클래스 기반 프록시 - 적용 (0) | 2021.12.30 |
Comments