Java 基礎【19】代理

   Java 代理(Proxy)模式與現實中的代理含義一致,如旅遊代理、明星的經紀人。java

   在目標對象實現基礎上,增長額外的功能操做,由此來擴展目標對象的功能。git

   JavaWeb 中最多見的過濾器、Struts 中的攔截器、Spring 中的 AOP...都有代理的應用。github

   此篇博客將編寫例子描述 Java 底層技術和開源類庫Cglib實現代理的方法,並對比各方法的優缺性。框架

   例子源碼:https://github.com/OrsonEx/proxy-demo.gitide

1.JDK 靜態代理

   抽象接口:測試

/**
 * 用戶服務抽象
 */
public interface UserService {

    /**
     * 用戶登陸
     *
     * @param userName 用戶名
     * @param pwd      密碼
     * @return 登錄結果
     */
    String login(String userName, String pwd);
}

   實現該接口:this

/**
 * 用戶服務實現
 *
 * @author Rambo 2019-03-01
 **/
public class UserServiceImpl implements UserService {


    @Override
    public String login(String userName, String pwd) {
        Console.log("進行登錄邏輯.........");

        return "登錄結果";
    }
}

   編碼代理類,實現該接口,代理目標做爲私有對象:編碼

/**
 * 用戶服務代理類
 *
 * @author Rambo 2019-03-01
 **/
public class UserServiceProxy implements UserService {
    private UserService userService;

    UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public String login(String userName, String pwd) {
        Console.log("登錄前擴展.....");
        userService.login(userName, pwd);
        Console.log("登錄後擴展.....");
        return "登錄結果";
    }
}

   編寫測試類:spa

    @Test
    public void testLogin() throws Exception {
        UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl());
        userServiceProxy.login("rambo","111111");
    }

   最原始實現代理的樣子,缺點也很明顯,當目標類方法調整後,須要同步維護代理類。且須要單獨編碼代理類,勢必致使冗餘。代理

2.JDK 動態代理(接口代理)

   代理核心方法 Proxy.newProxyInstance :

    /**
     * JDK 生成代理類
     * @param loader 當前目標對象使用類加載器
     * @param interfaces 目標對象實現的接口的類型
     * @param h 事件處理對象,經過反射執行目標對象的方法
     * @return 生成的代理類實例
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)

   代理工廠類:

/**
 * 代理工廠類
 *
 * @author Rambo 2019-03-01
 **/
public class JdkProxyFactory {

    private Object target;

    public JdkProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Console.log("執行目標前的擴展......");
                Object returnValue = method.invoke(target, args);
                Console.log("執行目標後的擴展......");
                return returnValue;
            }
        });
    }
}

   編寫測試類:

    @Test
    public void testGetProxyInstance() throws Exception {
        UserService proxyInstance = (UserService) new ProxyFactory(new UserServiceImpl()).getProxyInstance();
        proxyInstance.login("rambo", "111111");
    }

   目標對象須要實現接口,代理對象不用實現目標對象的接口。

   須要統一實現 InvocationHandler 接口中 invoke 方法,編碼實現目標方法先後的擴展操做。

   和靜態代理相比:代理對象經過反射動態生成,無需進行編碼。目標對象方法進行調整後,代理對象無需作任何調整。

3.Cglib 代理 (子類代理)

   當目標對象是個單獨的類,沒有實現任何接口,是沒法使用上述兩種代理方法,這時候怎麼辦?

   能夠使用 Cglib 代理(須要單獨引入 cglib 類庫),底層經過一個小而快的字節碼處理框架 Asm 來轉換字節碼並生成新的類。

   Cglib 經過自定義目標對象的子類進行目標對象的擴展,且這種擴展進行在 Jvm 運行期。

   不侷限目標類建模方式(有無繼承接口)、運行期加強目標類、底層精緻的 asm 字節碼框架,使 Cglib 成爲許多 AOP 框架生成動態代理的首選。

   代理工廠類:

/**
 * Cglib 動態代理工廠
 *
 * @author Rambo 2019-03-01
 **/
public class CgbProxyFactory implements MethodInterceptor {

    private Object target;

    public CgbProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        /*
          Enhancer類爲Cglib庫中的字節碼加強器,它能夠方便對你想要處理的類進行擴展;
          將被代理類 target 設置成父類,而後設置當前 intercept 爲代理攔截器;
          最後執行 enhancer.create() 動態生成一個代理類。
         */
        Enhancer en = new Enhancer();
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        return en.create();

    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Console.log("執行目標前的擴展......");
        Object returnValue = method.invoke(target, args);
        Console.log("執行目標後的擴展......");
        return returnValue;
    }
}

   編寫測試用例:

    @Test
    public void testGetProxyInstance() throws Exception {
        UserService proxyInstance = (UserService) new CgbProxyFactory(new UserServiceImpl()).getProxyInstance();
        proxyInstance.login("rambo","111111");
    }

   JDK InvocationHandler 、Cglib MethodInterceptor 具體實現的細節,如你有興趣可翻翻源碼,這裏就不贅述了。

   至此代理的幾種方式都已描述完畢,但願能幫助你對 java 代理有系統的瞭解。

相關文章
相關標籤/搜索