스스로 Spring AOP (二) JDK 대리 실현 AOP

23946 단어 springJSpringAOP
머리말
지난 글 은 스스로 Spring AOP (1) 환경 구축 과 지식 준 비 를 실현 했다. 나 는 프로젝트 를 구축 하고 예 를 들 었 다. 그 다음 에 나 는 이 사례 중의 AOP 기능 을 실현 할 것 이다.
Spring 에서 대상 이 인 터 페 이 스 를 실현 하면 기본적으로 JDK 의 동적 에이전트 로 AOP 를 실현 하고 대상 이 인 터 페 이 스 를 실현 하지 못 하면 CGLib (Code Generation Library) 방식 을 사용 해 야 합 니 다. 다음은 JDK 의 동적 에이전트 로 구현 하 겠 습 니 다.
준비 작업
AOP 기능 을 실현 하기 전에 테스트 할 관련 종 류 를 준비 하 세 요.
대상 관련 클래스 와 인 터 페 이 스 를 준비 하 세 요.
UserDao 인터페이스
package edu.jyu.dao;

public interface UserDao {
    public void add(String user);

    public String getUser(String id);
}

UserDaoImpl 클래스, UserDao 인터페이스 구현
package edu.jyu.dao;

public class UserDaoImpl implements UserDao {

    @Override
    public void add(String user) {
        System.out.println("add " + user);
    }

    @Override
    public String getUser(String id) {
        System.out.println("getUser " + id);
        return id + ":Jason";
    }
}

사전 통지
아니면 사전 알림 을 예 로 들 면 스프링 을 모방 해서 사전 알림 인터페이스 MethodBeforeAdvice 를 만 들 겠 습 니 다. 사전 알림 을 사용자 정의 하려 면 반드시 이 를 실현 해 야 합 니 다. 그 안에 before 방법 이 있 습 니 다. 바로 목표 방법 이 실행 되 기 전에 실 행 된 것 입 니 다.
package edu.jyu.aop;

import java.lang.reflect.Method;

/**
 *       
 * 
 * @author Jason
 */
public interface MethodBeforeAdvice {

    /**
     * 
     * @param method
     *                
     * @param args
     *                     
     * @param target
     *                
     */
    void before(Method method, Object[] args, Object target);

}


그리고 사전 알림 클래스 MyBeforeAdvice 를 사용자 정의 하여 MethodBeforeAdvice 인 터 페 이 스 를 실현 합 니 다.
package edu.jyu.advice;

import java.lang.reflect.Method;

import edu.jyu.aop.MethodBeforeAdvice;

/**
 *         
 * 
 * @author Jason
 */
public class MyBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) {
        System.out.println("    ");
    }

}

ProxyFactoryBean
지난 장 에서 제 가 사용 한 그 예 를 기억 하 시 나 요? 프 록 시 대상 을 만 들 때 지정 해 야 할 class 종 류 를 설정 합 니 다.
id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

그래서 저도 org.springframework.aop.framework.ProxyFactoryBean 류 를 정의 해서 전문 적 으로 생 성 된 대리 대상 을 정 의 했 습 니 다. 아직 방법 이 없습니다.
package edu.jyu.aop;
/**
 *           
* @author Jason
 */
public class ProxyFactoryBean {

}

배치 하 다.
AOP 와 관련 된 클래스 와 인터페이스 가 준비 되 어 있 습 니 다. 이제 ProxyFactoryBean 파일 에 생 성 프 록 시 대상 을 설정 해 야 합 니 다.

<beans>
    
    <bean name="userDao" class="edu.jyu.dao.UserDaoImpl">bean>

    
    <bean name="beforeAdvice" class="edu.jyu.advice.MyBeforeAdvice">bean>

    
    <bean name="userDaoProxy" class="edu.jyu.aop.ProxyFactoryBean">

        
        <property name="target" ref="userDao" />

        
        <property name="proxyInterface" value="edu.jyu.dao.UserDao" />

        
        <property name="interceptor" ref="beforeAdvice" />
    bean>
beans>

이 프로필 은 이전 장의 예 와 비슷 합 니 다. 비교적 많은 차이 점 은 프 록 시 생 성 대상 을 설정 하 는 것 입 니 다. 설정 이 목표 알림 에 들 어가 면 Spring 의 applicationContext.xmlname 이 고 제 것 은 interceptorNames 입 니 다. 간단 하기 위해 서 저 는 하나의 목표 가 이미지 에 대한 통 지 를 강화 할 수 있 도록 하려 고 합 니 다.
그러면 이 프로필 interceptor 파일 에 따라 다음 과 같이 수정 해 야 합 니 다.
package edu.jyu.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *           
 * 
 * @author Jason
 */
public class ProxyFactoryBean {
    //     
    private Object target;
    //   
    private Object interceptor;
    //        
    private String proxyInterface;

    //   setter       

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setInterceptor(Object interceptor) {
        this.interceptor = interceptor;
    }

    public void setProxyInterface(String proxyInterface) {
        this.proxyInterface = proxyInterface;
    }
}


개발 기능
현재 대상 을 만 드 는 클래스 ProxyFactoryBean 는 수정 이 필요 합 니 다. 대상 을 만 들 때 두 가지 상황 을 얻 을 수 있 기 때 문 입 니 다. 하 나 는 일반 대상 을 만 드 는 것 이 고 다른 하 나 는 프 록 시 대상 을 만 드 는 것 입 니 다.우 리 는 그것 의 edu.jyu.core.ClassPathXmlApplicationContext 방법 만 수정 해 야 한다.
/**
 *   bean       bean  
 * 
 * @param bean
 * @return
 */
private Object createBeanByConfig(Bean bean) {
    //   bean      
    Class clazz = null;
    Object beanObj = null;
    try {
        clazz = Class.forName(bean.getClassName());
        //   bean  
        beanObj = clazz.newInstance();
        //   bean    property  
        List properties = bean.getProperties();
        //   bean    property  ,     value  ref   bean   
        for (Property prop : properties) {
            Map params = new HashMap<>();
            if (prop.getValue() != null) {
                params.put(prop.getName(), prop.getValue());
                //  value    bean   
                BeanUtils.populate(beanObj, params);
            } else if (prop.getRef() != null) {
                Object ref = context.get(prop.getRef());
                //                      
                if (ref == null) {
                    ref = createBeanByConfig(config.get(prop.getRef()));
                }
                params.put(prop.getName(), ref);
                //  ref    bean   
                BeanUtils.populate(beanObj, params);
            }
        }

        //           
        if (clazz.equals(ProxyFactoryBean.class)) {
            ProxyFactoryBean factoryBean = (ProxyFactoryBean) beanObj;
            //       
            beanObj = factoryBean.createProxy();
        }

    } catch (Exception e1) {
        e1.printStackTrace();
        throw new RuntimeException("  " + bean.getClassName() + "    ");
    }
    return beanObj;
}

코드 도 고치 지 않 고 다음 몇 마디 코드 만 새로 추 가 했 습 니 다.
//           
if (clazz.equals(ProxyFactoryBean.class)) {
    ProxyFactoryBean factoryBean = (ProxyFactoryBean) beanObj;
    //       
    beanObj = factoryBean.createProxy();
}

만 든 대상 의 클래스 가 createBeanByConfig 인지 판단 하 는 것 입 니 다. 그렇다면 이 대상 을 ProxyFactoryBean 유형 대상 으로 강하 게 전환 한 다음 ProxyFactoryBean 방법 으로 프 록 시 대상 을 만 들 고 이 프 록 시 대상 을 최종 결과 createProxy() 로 합 니 다.
이때 beanObj 아직 ProxyFactoryBean 방법 이 없 기 때문에 지금 이 방법 을 만 들 고 완성 해 야 할 기능 을 실현 합 니 다.코드 는 다음 과 같다.
/**
 *       
 * 
 * @return
 */
public Object createProxy() {
    //        proxyInterface,      CGLib  
    if (proxyInterface == null || proxyInterface.trim().length() == 0)
        return createCGLibProxy();
    //   JDK    
    return createJDKProxy();
}

이 방법 은 매우 간단 하 다. 지정 createProxy() 에 따라 어떤 동적 프 록 시 방식 으로 프 록 시 대상 을 생 성 하 는 지 판단 하 는 것 이다. 인터페이스 가 없 으 면 CGLib 를 사용 하고 있 으 면 JDK 의 프 록 시 를 사용한다.
그럼 현재 CGLib 로 프 록 시 대상 을 만 드 는 proxyInterface 방법 과 JDK 로 프 록 시 대상 을 만 드 는 createCGLibProxy() 방법 이 없습니다. 우 리 는 먼저 이 두 가지 방법 을 만 들 고 createJDKProxy() 방법 을 다음 장 에 남 겨 두 었 다가 실현 해 야 합 니 다. 지금 먼저 createCGLibProxy() 방법 을 실현 해 보 겠 습 니 다.
/**
 * JDK        
 * 
 * @return
 */
private Object createJDKProxy() {
    Class> clazz = null;
    try {
        clazz = Class.forName(proxyInterface);//      
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        throw new RuntimeException(proxyInterface + "   ,       ");
    }
    // JDK         
    Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object result = null;

                    //     interceptor       ,            
                    //   interceptor         
                    if (interceptor instanceof MethodBeforeAdvice) {
                        MethodBeforeAdvice advice = (MethodBeforeAdvice) interceptor;
                        //                 
                        advice.before(method, args, target);
                        //       
                        result = method.invoke(target, args);
                    }
                    return result;
                }
            });
    return proxyInstance;
}

JDK 를 사용 하여 프 록 시 대상 을 만 드 는 방법 도 어렵 지 않 습 니 다. 주로 createJDKProxy() 에 관심 을 가지 고 있 습 니 다. 저 는 먼저 InvocationHandler 이 어떤 알림 유형 에 속 하 는 지 판단 해 야 합 니 다. 서로 다른 알림 유형 은 목표 방법의 집행 순서 가 다 르 기 때 문 입 니 다. 예 를 들 어 사전 통 지 는 목표 방법 전에 실 행 됩 니 다. 나중에 통 지 는 목표 방법 후에 실 행 됩 니 다. 현재 의 실현 은 문제 가 있 습 니 다.예 를 들 어 내 가 사후 통 지 를 새로 추가 했다 면 나 는 분기 판단 interceptor 이 사후 통지 인지 아 닌 지 를 추가 해 야 한다.이 문 제 는 다음 장 에서 내 가 해결 할 것 이다.
제 가 interceptor 전체 코드 를 다시 붙 여 드릴 게 요.
package edu.jyu.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *           
 * 
 * @author Jason
 */
public class ProxyFactoryBean {
    //     
    private Object target;
    //   
    private Object interceptor;
    //        
    private String proxyInterface;

    //   setter       

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setInterceptor(Object interceptor) {
        this.interceptor = interceptor;
    }

    public void setProxyInterface(String proxyInterface) {
        this.proxyInterface = proxyInterface;
    }

    /**
     *       
     * 
     * @return
     */
    public Object createProxy() {
        //        proxyInterface,      CGLib  
        if (proxyInterface == null || proxyInterface.trim().length() == 0)
            return createCGLibProxy();
        //   JDK    
        return createJDKProxy();
    }

    /**
     * JDK        
     * 
     * @return
     */
    private Object createJDKProxy() {
        Class> clazz = null;
        try {
            clazz = Class.forName(proxyInterface);//      
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException(proxyInterface + "   ,       ");
        }
        // JDK         
        Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;

                        //     interceptor       ,            
                        //   interceptor         
                        if (interceptor instanceof MethodBeforeAdvice) {
                            MethodBeforeAdvice advice = (MethodBeforeAdvice) interceptor;
                            //                 
                            advice.before(method, args, target);
                            //       
                            result = method.invoke(target, args);
                        }
                        return result;
                    }
                });
        return proxyInstance;
    }

    /**
     * CGLib        
     * 
     * @return
     */
    private Object createCGLibProxy() {
        return null;
    }

}

테스트
여기까지 만 해도 JDK 방식 으로 AOP 를 실현 하면 됩 니 다. 이제 테스트 클래스 테스트 를 작성 할 수 있 습 니 다.
package edu.jyu.aop;

import org.junit.Test;

import edu.jyu.core.BeanFactory;
import edu.jyu.core.ClassPathXmlApplicationContext;
import edu.jyu.dao.UserDao;

public class TestProxy {

    @Test
    public void testJDKProxy(){
        BeanFactory ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserDao userDao = (UserDao) ac.getBean("userDaoProxy");
        System.out.println(userDao.getClass());
        userDao.add("Jason");
        String user = userDao.getUser("132");
        System.out.println(user);
    }
}

출력 결과
class com.sun.proxy.$Proxy4
    
add Jason
    
getUser 132
132:Jason

첫 번 째 줄 의 출력 은 JDK 가 생 성 한 프 록 시 대상 을 확인 한 다음 에 실행 ProxyFactoryBean 하 는 userDao 방법 과 add 방법 전에 도 사전 알림 방법 을 실 행 했 고 마지막 getUser 의 결과 도 틀 리 지 않 았 다.
현재 전체 프로젝트 가 절반 을 완 성 했 고 나머지 절반 은 CGLib 방식 으로 프 록 시 대상 을 만 들 고 구 조 를 최적화 하 는 것 이다.
JSpring AOP 프로젝트 가 Github 에 올 라 왔 습 니 다.https://github.com/HuangFromJYU/JSpring-AOP

좋은 웹페이지 즐겨찾기