반응형
2022.09.26 - [프로그래밍 노트/SPRING] - [Spring] 프록시 활용 - 프록시 패턴, 데코레이터 패턴
앞 전에서 프록시 관련 내용을 살펴보았다. 프록시를 사용하면 대상 클래스에 부가 기능이나 접근제어를 적용할 수 있는 장점이 있었다.
부가 기능을 추가해야하는 클래스의 수가 많지 않으면 프록시를 구현하여 적용하면 되지만, 문제는 부가 기능을 추가해야하는 클래수가 많다면 그 수 만큼 프록시 클래스를 만들어야하는 단점이 존재한다. (클래스 수 만큼 프록시 클래스를 만드는 것은 역시 미친짓이다..)
이러한 단점을 해결하기 위해 프록시를 적용할 코드를 하나만 만들고 프록시 객체를 만드는(찍어내는?) 동적 프록시 기술이 존재한다.
- JDK Dynamic Proxy - JAVA 에서 제공
- CGLIB - 오픈소스 기술(Spring에서 사용하므로, Spring 의존관계가 있다면 사용할 수 있다.)
동적 프록시 기술을 이해하기 위해 리플렉션에 대해 간단히 알아보자.
리플렉션
java.lang.Class
- 실행중인 자바 어플리케이션의 클래스와 인터페이스의 정보를 가진 클래스
- JVM에 의해 자동으로 생성된다.
Class 가져오기
// 1. 클래스타입.class
Class<?> clazz = Cat.class;
// 2. 인스턴스.getClass()
Class<?> clazz = new Cat("냐옹이").getClass();
// 3. Class.forName("패키지를 포함한 클래스 이름")
Class<?> class = Class.forName("com.corn.Cat");
Field 가져오기
// get, set 가능
// private 접근이 필요하면 setAccessible(true) 필요
Field[] fields = clazz.getDeclaredFields();
Field field = clazz.getField("name");
Method 가져오기
Method[] methods = clazz.getDeclaredMethods();
Method method = clazz.getDeclaredMethod("bark", String.class);
// method 실행 가능
method.invoke(cat, "냐옹");
이정도면 리플렉션 마스터다. 끝
JDK Dynamic Proxy
- JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어준다. 따라서
인터페이스
가 필수다.
Animal.java
public interface Animal {
String bark();
}
Cat.java
public class Cat implements Animal{
@Override
public String bark() {
return "냐옹~냐옹~";
}
}
Jdk Dynamic Proxy에 적용할 로직은 InvocationHandler
인터페이스를 구현해서 작성한다.
TimeInvocationHandler.java
@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 {
// proxy - 프록시 자신
// method - 호출한 메서드
// args - 메소드를 호출할 때 전달한 인수
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;
}
}
@Test
void dynamicProxyTest() {
Animal target = new Cat();
// 동적 프록시에 적용할 핸들러 로직
TimeInvocationHandler handler = new TimeInvocationHandler(target);
// proxy 동적 생성 (java.lang.reflect.Proxy)
// 인터페이스를 기반(new Class[])으로 동작 프록시를 생성하고 그 결과를 반환한다.
Animal proxy = (Animal)Proxy.newProxyInstance(Animal.class.getClassLoader(), new Class[]{Animal.class}, handler);
proxy.bark();
log.info("targetClass={}, proxyClass={}", target.getClass(), proxy.getClass());
}
targetClass=class hello.proxy.jdkdynamic.code.Cat, proxyClass=class com.sun.proxy.$Proxy12
- Proxy 클래스는
class com.sun.proxy.$Proxy12
동적으로 생성된 클래스이다. 이 프록시는 TimeInvocationHandler 로직을 실행한다. - 적용 대상 만큼 프록시 객체를 만들지 않아도 되며, 부가 기능 로직(InvocationHandler)를 한번만 개발해서 공통으로 적용할 수 있다.
CGLIB(Code Generator Library)
- 바이트코드를 조작해서 동적으로 클래스를 생성하는 기술
- 인터페이스가 없어도 구체 클래스만 가지고 동적 프록시를 만들어낼 수 있다.
- 스프링을 사용한다면 별도의 외부 라이브러리를 추가하지 않아도 된다.
Cat.java
public class Cat {
public String bark() {
return "냐옹~냐옹";
}
}
Jdk Dynamic Proxy 에서 InvocationHandler
를 제공했듯이, CGLIB는 MethodInterceptor
를 제공한다.
TimeMethodInterceptor.java
@Slf4j
public class TimeMethodInterceptor implements MethodInterceptor {
// 프록시가 호출할 실제 대상
private final Object target;
public TimeMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// obj - CGLIB가 적용된 객체
// method - 호출된 메서드
// args - 메서드를 호출하면서 전달된 인수
// methodProxy - 메서드 호출에 사용
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = methodProxy.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
@Test
void cglibProxyTest() {
// 인터페이스가 없는 구체 클래스
Cat target = new Cat();
// Enhancer 를 사용하여 프록시를 생성한다.
Enhancer enhancer = new Enhancer();
// 구체 클래스를 상속받아 프록시를 생성할 수 있다.
enhancer.setSuperclass(Cat.class);
// 프록시에 적용할 실행 로직을 할당한다.
enhancer.setCallback(new TimeMethodInterceptor(target));
Cat proxy = (Cat)enhancer.create();
proxy.bark();
log.info("targetClass={}, proxyClass={}", target.getClass(), proxy.getClass());
}
targetClass=class hello.proxy.cglib.code.Cat, proxyClass=class hello.proxy.cglib.code.Cat$$EnhancerByCGLIB$$3ad8d5cd
- 대상클래스$$EnhancerByCGLIB$$임의코드
- 제약 조건
- 생성자 체크 - CGLIB는 자식 클래스를 동적으로 생성하기 때문에 기본 생성자가 필요하다.
- 클래스에 final 키워드가 붙으면 상속이 불가하다. - CGLIB 예외 발생
- 메서드 final 키워드가 붙으면 메서드 오버라이딩이 불가하다. - CGLIB 프록시 로직이 동작하지 않음
참고) 스프링 핵심 원리 - 고급편 (김영한님)
반응형
'프로그래밍 노트 > SPRING' 카테고리의 다른 글
[Spring] 프록시팩토리_2 (feat. Advisor) (0) | 2022.09.30 |
---|---|
[Spring] 프록시팩토리_1 (feat. Advice) (0) | 2022.09.28 |
[Spring] 프록시 활용 - 프록시 패턴, 데코레이터 패턴 (0) | 2022.09.26 |
[Spring] 스프링의 트랜잭션 기술 (0) | 2022.04.16 |
Master/Slave DB 라우팅/이용하기(feat. AbstractRoutingDataSource) (0) | 2022.04.03 |