jdk 동적 에이전트 와 cglib 동적 에이전트 상세 설명


위의 그림 과 같이 프 록 시 모드 는 동적 에이전트 와 정적 에이전트 로 나 눌 수 있 습 니 다.저 희 는 동적 에이전트 중의 jdk 동적 에이전트 와 Cglib 대 리 를 자주 사용 합 니 다.예 를 들 어 spring 프레임 워 크,hibenate 프레임 워 크 에서 모두 JDK 동적 대 리 를 사 용 했 습 니 다.다음은 코드 를 결합 하여 두 가지 프 록 시 모델 의 사용 과 차 이 를 논술 하 겠 습 니 다.
정적 에이전트
정적 에이전트 의 에이전트 대상 과 피 에이전트 대상 은 에이전트 전에 이미 확정 되 었 고 모두 같은 인터페이스 나 같은 추상 류 를 계승 한다.정적 대리 모델 은 일반적으로 업무 실현 류 와 업무 대리 류 로 구성 되 고 업무 실현 류 에서 주요 한 업무 논 리 를 실현 합 니 다.업무 대리 류 는 업무 방법 호출 전후 에 필요 한 처 리 를 합 니 다.예 를 들 어 로그 기록,권한 차단 등 기능...업무 논리 와 업무 방법 밖의 기능 디 결합 을 실현 하여 업무 방법 에 대한 침입 을 감소 합 니 다.정적 대 리 는 계승 방식 과 취 합 방식 으로 세분 화 할 수 있다.
장면:재 고 를 미리 줄 이 는 작업 을 가정 하려 면 미리 줄 인 전후 에 로그 기록 을 추가 해 야 합 니 다.(저 는 springboot 프로젝트 입 니 다)
계승 기반 정적 에이전트 구현

/**
 *        
 */
public interface OrderService {
    //     
    void reduceStock();
}

/**
 *      
 */
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Override
    public void reduceStock() {
        try {
            log.info("     ……");
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

/**
 *    
 */
@Slf4j
public class OrderServiceLogProxy extends OrderServiceImpl{
    @Override
    public void reduceStock() {
        log.info("      ……");
        super.reduceStock();
        log.info("      ……");
    }
}

    /**
     *              
     */
    @Test
    public void testOrderServiceProxy(){
        OrderServiceLogProxy proxy = new OrderServiceLogProxy();
        proxy.reduceStock();
    }
출력 결과
14:53:53.769[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy-재고 예감 시작...
14:53:53.771[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl-재고 예감 중...
14:53:54.771[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy-재고 예감 종료...
이 를 통 해 알 수 있 듯 이 OrderServiceLogProxy 는 OrderServiceImpl 을 위 한 대 리 를 실 현 했 고 대리 류 의 동명 방법 을 통 해 업무 방법의 전후 논 리 를 강화 했다.
취 합 된 방식 으로 정적 대 리 를 실현 합 니 다.
취 합 이란 업무 류 를 대리 류 에 도입 한 것 이다.인터페이스 와 업무 실현 류 는 예전 의 OrderService,OrderServiceImpl 이 고 대리 류 는 다음 과 같다.

/**
 *           :         
 */
@Slf4j
public class OrderServiceLogProxy2 implements OrderService {
    private OrderServiceImpl orderService;
    public OrderServiceLogProxy2(OrderServiceImpl orderService) {
        this.orderService = orderService;
    }
    @Override
    public void reduceStock() {
        log.info("      ……");
        orderService.reduceStock();
        log.info("      ……");
    }
}

    /**
     *              
     */
    @Test
    public void testOrderServiceProxy2() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        OrderServiceLogProxy2 proxy2 = new OrderServiceLogProxy2(orderService);
        proxy2.reduceStock();
    }

테스트 출력 결 과 는 위의 결과 와 일치 합 니 다.
계승 과 취 합 방식 으로 이 루어 진 정적 에이전트 비교
위의 코드 를 결합 하면 프 록 시 기능 을 중첩 해 야 한다 면 저 는 프 록 시 로 그 를 기록 할 뿐만 아니 라 권한 차단 기능 도 추가 해 야 합 니 다.이 럴 때 계승 방식 을 사용 하면 프 록 시 클래스 를 새로 만들어 야 합 니 다.로그 와 권한 논 리 를 포함 합 니 다.그러면 대리 기능 을 하나 더 추가 하면 대리 류 도 추가 합 니 다.대리 기능 의 집행 순 서 를 바 꾸 려 면 대리 류 를 늘 려 야 한다.위의 분석 과 결합 하면 이렇게 하 는 것 은 분명 타당 하지 않 을 것 이다.그런데 만약 에 취 합 방식 을 사용한다 면?우 리 는 위 에서 사용 하 는 취 합 방식 으로 이 루어 진 정적 프 록 시 코드 를 약간 개조 합 니 다.
우선 로그 에이전트 코드 입 니 다.

/**
 *           --          
 */
@Slf4j
public class OrderServiceLogProxy3 implements OrderService {
    //  ,       
    private OrderService orderService;
    public OrderServiceLogProxy3(OrderService orderService) {
        this.orderService = orderService;
    }
    @Override
    public void reduceStock() {
        log.info("      ……");
        orderService.reduceStock();
        log.info("      ……");
    }
}
그리고 새로 추 가 된 권한 검증 프 록 시 코드 입 니 다.

/**
 *           --          
 */
@Slf4j
public class OrderServicePermissionProxy implements OrderService {
    //  ,       
    private OrderService orderService;
    public OrderServicePermissionProxy(OrderService orderService) {
        this.orderService = orderService;
    }
    @Override
    public void reduceStock() {
        log.info("      ……");
        orderService.reduceStock();
        log.info("      ……");
    }
}
테스트 용례

    /**
     *              -    
     */
    @Test
    public void testOrderServiceProxy3() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        OrderServiceLogProxy2 logProxy2 = new OrderServiceLogProxy2(orderService);
        OrderServicePermissionProxy permissionProxy = new OrderServicePermissionProxy(logProxy2);
        permissionProxy.reduceStock();
    }
테스트 결과
16:00:28.348[main]INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy-권한 검증 시작...
16:00:28.365[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy 2-재고 예감 시작...
16:00:28.365[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl-재고 예감 중...
16:00:29.365[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy 2-재고 예감 종료...
16:00:29.365[main]INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy-권한 검증 종료.....
다음 에 프 록 시 클래스 의 논리 적 실행 순 서 를 바 꾸 려 면(테스트 처럼)사용 할 때 실례 화 순 서 를 바 꾸 면 로그 기능 과 권한 검증 의 선후 집행 순 서 를 실현 할 수 있 습 니 다.계승 방식 처럼 계속 새 프 록 시 클래스 를 만 들 필요 가 없습니다.
동적 에이전트
위의 정적 대 리 를 보고 우 리 는 정적 대리 모델 의 대리 류 는 특정한 유형의 대 리 를 실현 한 것 을 발견 했다.예 를 들 어 위의 OrderServiceLogProxy 가 실현 한 OrderServiceimpl 의 대리,만약 에 내 가 UserService 가 로그 기록,권한 검사 기능 이 있다 면 두 부의 UserServiceLogProxy,UserServicePermission Proxy 대리 류 를 써 야 한다.안에 있 는 논 리 는 대부분이 똑 같 습 니 다.즉,대리 대상 의 방법 이 많 을 수록 중복 되 는 코드 를 많이 써 야 합 니 다.그러면 동적 대리 가 있 으 면 이 문 제 를 잘 해결 할 수 있 습 니 다.동적 대 리 는 대리 류 를 동태 적 으로 생 성하 고 서로 다른 유형의 서로 다른 방법 에 대한 대 리 를 실현 할 수 있 습 니 다.
JDK 동적 에이전트
jdk 동적 대 리 는 반사 체 제 를 이용 하여 프 록 시 인 터 페 이 스 를 실현 하 는 익명 류 를 만 들 고 업무 방법 을 호출 하기 전에 Invocation Handler 로 처리 합 니 다.프 록 시 클래스 는 InvocationHandler 인 터 페 이 스 를 실현 해 야 합 니 다.또한 JDK 동적 프 록 시 는 인터페이스의 클래스 만 대리 할 수 있 고 인터페이스 가 실현 되 지 않 은 클래스 는 JDK 동적 프 록 시 를 실현 할 수 없습니다.아래 코드 를 결합 해 보면 비교적 뚜렷 하 다.

import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * JDK      ,    InvocationHandler  
 * InvocationHandler          ,              
 */
@Slf4j
public class DynamicLogProxy implements InvocationHandler {
    //        
    private Object target;

    public DynamicLogProxy(Object target) {
        this.target = target;
    }
    /**
     * @param obj        
     * @param method     
     * @param args       
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        log.info("         ,    ……");
        //       
        Object invoke = method.invoke(target, args);
        log.info("         ,    ……");
        return invoke;
    }
}
사용 시 코드

    /**
     *   JDK            
     */
    @Test
    public void testDynamicLogProxy() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        Class<?> clazz = orderService.getClass();
        DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService);
        //  Proxy.newProxyInstance(    ,   s,      Handler)       
        OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler);
        os.reduceStock();
    }

출력 결과
16:35:54.584[main]INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy-여 기 는 로그 기록 절단면 입 니 다.로그 시작...
16:35:54.587[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl-재고 예감 중...
16:35:55.587[main]INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy-로그 기록 절단면 입 니 다.로그 끝...
JDK 동적 에이전트 클래스 기본 단계 사용 하기:
1.대리 가 필요 한 클래스 와 인 터 페 이 스 를 작성 합 니 다(저 는 여기 가 바로OrderServiceImpl、OrderService.
2.프 록 시 클래스(예 를 들 어 제 가 있 는DynamicLogProxy를 작성 하려 면 Invocation Handler 인 터 페 이 스 를 실현 하고 invoke 방법 을 다시 써 야 합 니 다.
3.사용Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)동적 으로 대리 류 대상 을 만 들 고 대리 류 대상 을 통 해 업무 방법 을 호출 합 니 다.
그렇다면 이 럴 때 내 가 대리 류 에 기능 을 중첩 해 야 한다 면 어떻게 해 야 합 니까?예 를 들 어 로그 뿐만 아니 라 권한 인증 도 추가 해 야 합 니 다.사고방식 은 위의 집합 방식 이 정적 에이전트 에 있 는 것 처럼 코드 를 붙 이 고 권한 인증 에이전트 클래스 를 추가 합 니 다.

import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 *   JDK              
 */
@Slf4j
public class DynamicPermissionProxy implements InvocationHandler{
    private Object target;
    public DynamicPermissionProxy(Object target) {
        this.target = target;
    }
    /**
     * @param obj        
     * @param method     
     * @param args       
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        log.info("         ,    ……");
        Object invoke = method.invoke(target,args);
        log.info("         ,    ……");
        return invoke;
    }
}
그리고 사용 할 때 는 조금 바 꿔 야 합 니 다.

    /**
     *   JDK         、       
     */
    @Test
    public void testDynamicLogAndPermissProxy() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        Class<?> clazz = orderService.getClass();
        DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService);
        OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler);
        // :                      
        DynamicPermissionProxy dynamicPermissionProxy = new DynamicPermissionProxy(os);
        OrderService os2 = (OrderService)Proxy.newProxyInstance(os.getClass().getClassLoader(),os.getClass().getInterfaces(),dynamicPermissionProxy);
        os2.reduceStock();
    }
위 와 같이 하면 되 고 뒤에 기능 대리 류 를 중첩 해 야 한다 면 위의 사고 에 따라 대리 대상 의 인 스 턴 스 를 순서대로 전달 하면 된다.
어떻게 HashMap 의 동적 대리 류 를 실현 합 니까?

public class HashMapProxyTest {
    public static void main(String[] args) {
        final HashMap<String, Object> hashMap = new HashMap<>();
        Map<String, Object> mapProxy = (Map<String, Object>) Proxy.newProxyInstance(HashMap.class.getClassLoader(), HashMap.class.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(hashMap, args);
            }
        });
        mapProxy.put("key1", "value1");
        System.out.println(mapProxy);
    }
}
Cglib 동적 에이전트
cglib 는 클래스 를 대상 으로 대 리 를 실현 합 니 다.목표 클래스 에 대해 대리 클래스 를 만 들 고 방법 차단 기술 을 통 해 부모 클래스 를 걸 러 내 는 방법 을 호출 합 니 다.프 록 시 하위 클래스 는 MethodInterceptor 인 터 페 이 스 를 실현 해 야 합 니 다.또한 Spring 프로필 형식 으로 개발 되 었 다 면 설명 을 표시 해 야 합 니 다.

<aop:aspectj-autoproxy proxy-target-class="true"/>
SpringBoot 개발 을 기반 으로 한다 면,일반적으로 시작 클래스 의 머리 에 주 해 를 추가 합 니 다. 

@EnableAspectJAutoProxy(proxyTargetClass = true)

import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
 *   Cglib        -    
 *           ,       ,CGlib          ,                 
 */
@Slf4j
public class DynamicCglibLogProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public Object getProxyObj(Class clazz) {
        //    
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        enhancer.setUseCache(false);
        return enhancer.create();
    }
    /**
     *             
     *
     * @param o               
     * @param method          
     * @param args            
     * @param methodProxy      
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        log.info("         ,    ……");
        //             
        Object result = methodProxy.invokeSuper(o, args);
        log.info("         ,    ……");
        return result ;
    }
}
테스트 용례

    /**
     *   Cglib       -    
     */
    @Test
    public void testGclibDynamicLogProxy(){
        DynamicCglibLogProxy dynamicCglibLogProxy = new DynamicCglibLogProxy();
        OrderServiceImpl orderService = (OrderServiceImpl)dynamicCglibLogProxy.getProxyObj(OrderServiceImpl.class);
        orderService.reduceStock();
    }
출력 결과
17:41:07.007[main]INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy-로그 기록 절단면 입 니 다.로그 시작...
17:41:07.038[main]INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl-재고 예감 중...
17:41:08.038[main]INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy-여 기 는 로그 기록 절단면 입 니 다.로그 끝...
JDK 와 Cglib 동적 에이전트 비교?
1.JDK 동적 대 리 는 인터페이스의 클래스 만 대리 할 수 있 고 인터페이스 가 실현 되 지 않 은 클래스 는 JDK 의 동적 대 리 를 실현 할 수 없습니다.
2.Cglib 동적 대 리 는 클래스 에 대한 대 리 를 실현 하 는 것 입 니 다.실행 할 때 동적 으로 대리 류 의 하위 클래스 를 생 성하 여 부모 클래스 를 차단 하 는 방법 으로 호출 되 기 때문에 final 형식의 클래스 와 방법 으로 대리 성명 할 수 없습니다.
동적 에이전트 와 정적 에이전트 의 차이 점 은?
1.정적 대 리 는 대리 하기 전에 어느 대상 을 대리 해 야 하 는 지 알 고 동적 대 리 는 실 행 될 때 알 수 있 습 니 다.
2.정적 대 리 는 보통 한 가지 종류 만 대리 할 수 있 고 동적 대 리 는 인터페이스의 여러 가지 종 류 를 대리 할 수 있다.
Spring 은 어떻게 두 가지 대리 모델 을 선택 합 니까?
1.대상 이 인 터 페 이 스 를 실현 하면 기본적으로 JDK 동적 대 리 를 사용 합 니 다.
2.대상 이 인 터 페 이 스 를 실현 하지 못 하면 Cglib 대 리 를 사용 합 니 다.
3.대상 이 인 터 페 이 스 를 실 현 했 으 나 Cglib 를 강제로 사용 한 경우 Cglib 를 사용 하여 대리
우 리 는 소스 코드 와 결합 하여 위의 선택 을 볼 수 있다.

보충 하 다
cglib 에서 실 현 된 MethodInterceptor 인 터 페 이 스 는 spring-core 패키지 에 있 습 니 다.도입 해 야 할 수도 있 습 니 다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.0.RELEASE</version>
</dependency>
@Slf4j 는 lombok 에서 제공 합 니 다.또한 intellij idea 개발 도구 에서 사용 하려 면 lombok 플러그 인 을 설치 해 야 합 니 다.
@Test 는 Junit 가 포장 한 것 입 니 다.도입 해 야 할 수도 있 습 니 다.

<dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.11</version>
     <scope>test</scope>
</dependency>
총결산
이 글 은 여기까지 입 니 다.당신 에 게 도움 을 줄 수 있 기 를 바 랍 니 다.또한 당신 이 우리 의 더 많은 내용 에 관심 을 가 져 주 기 를 바 랍 니 다!

좋은 웹페이지 즐겨찾기