JAVA代理

目錄
1.1爲什麼要用代理
1.2 JDK動態代理玩法詳解
1.3 cglib代理的各種玩法詳解
一 爲什麼要用代理
package com.javacode2018.lesson001.demo15;

public interface IService {
void m1();
void m2();
void m3();
}
package com.javacode2018.lesson001.demo15;

public class ServiceA implements IService {
@Override
public void m1() {
System.out.println(“我是ServiceA中的m1方法!”);
}

@Override
public void m2() {
    System.out.println("我是ServiceA中的m2方法!");
}

@Override
public void m3() {
    System.out.println("我是ServiceA中的m3方法!");
}

}
package com.javacode2018.lesson001.demo15;

public class ServiceB implements IService {
@Override
public void m1() {
System.out.println(“我是ServiceB中的m1方法!”);
}

@Override
public void m2() {
    System.out.println("我是ServiceB中的m2方法!");
}

@Override
public void m3() {
    System.out.println("我是ServiceB中的m3方法!");
}

}
來個測試用例來調用上面類的方法,如下:

package com.javacode2018.lesson001.demo15;

import org.junit.Test;

public class ProxyTest {
@Test
public void m1() {
IService serviceA = new ServiceA();
IService serviceB = new ServiceB();
serviceA.m1();
serviceA.m2();
serviceA.m3();

    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}

}
上面的代碼很簡單,就不解釋了,我們運行一下m1()方法,輸出:

我是ServiceA中的m1方法!
我是ServiceA中的m2方法!
我是ServiceA中的m3方法!
我是ServiceA中的m1方法!
我是ServiceA中的m2方法!
我是ServiceA中的m3方法!
上面是我們原本的程序,突然領導有個需求:調用IService接口中的任何方法的時候,需要記錄方法的耗時。

此時你會怎麼做呢?

IService接口有2個實現類ServiceA和ServiceB,我們可以在這兩個類的所有方法中加上統計耗時的代碼,如果IService接口有幾十個實現,是不是要修改很多代碼,所有被修改的方法需重新測試?是不是非常痛苦,不過上面這種修改代碼的方式倒是可以解決問題,只是增加了很多工作量(編碼 & 測試)。

突然有一天,領導又說,要將這些耗時統計發送到監控系統用來做監控報警使用。

此時是不是又要去一個修改上面的代碼?又要去測試?此時的系統是難以維護。

還有假如上面這些類都是第三方以jar包的方式提供給我們的,此時這些類都是class文件,此時我們無法去修改源碼。

比較好的方式:可以爲IService接口創建一個代理類,通過這個代理類來間接訪問IService接口的實現類,在這個代理類中去做耗時及發送至監控的代碼,代碼如下:

package com.javacode2018.lesson001.demo15;

// IService的代理類
public class ServiceProxy implements IService {
//目標對象,被代理的對象
private IService target;

public ServiceProxy(IService target) {
    this.target = target;
}

@Override
public void m1() {
    long starTime = System.nanoTime();
    this.target.m1();
    long endTime = System.nanoTime();
    System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
}

@Override
public void m2() {
    long starTime = System.nanoTime();
    this.target.m1();
    long endTime = System.nanoTime();
    System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
}

@Override
public void m3() {
    long starTime = System.nanoTime();
    this.target.m1();
    long endTime = System.nanoTime();
    System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
}

}
ServiceProxy是IService接口的代理類,target爲被代理的對象,即實際需要訪問的對象,也實現了IService接口,上面的3個方法中加了統計耗時的代碼,當我們需要訪問IService的其他實現類的時候,可以通過ServiceProxy來間接的進行訪問,用法如下:

@Test
public void serviceProxy() {
IService serviceA = new ServiceProxy(new ServiceA());//@1
IService serviceB = new ServiceProxy(new ServiceB()); //@2
serviceA.m1();
serviceA.m2();
serviceA.m3();

serviceB.m1();
serviceB.m2();
serviceB.m3();

}
上面代碼重點在於@1和@2,創建的是代理對象ServiceProxy,ServiceProxy構造方法中傳入了被代理訪問的對象,現在我們訪問ServiceA或者ServiceB,都需要經過ServiceProxy,運行輸出:

我是ServiceA中的m1方法!
class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):90100
我是ServiceA中的m1方法!
class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):31600
我是ServiceA中的m1方法!
class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):25800
我是ServiceB中的m1方法!
class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):142100
我是ServiceB中的m1方法!
class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):35000
我是ServiceB中的m1方法!
class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):32900
上面實現中我們沒有去修改ServiceA和ServiceB中的方法,只是給IService接口創建了一個代理類,通過代理類去訪問目標對象,需要添加的一些共有的功能都放在代理中,當領導有其他需求的時候,我們只需修改ServiceProxy的代碼,方便系統的擴展和測試。

假如現在我們需要給系統中所有接口都加上統計耗時的功能,若按照上面的方式,我們需要給每個接口創建一個代理類,此時代碼量和測試的工作量也是巨大的,那麼我們能不能寫一個通用的代理類,來滿足上面的功能呢?

通用代理的2種實現:

jdk動態代理
cglib代理
二 jdk動態代理
jdk中爲實現代理提供了支持,主要用到2個類:

java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
jdk自帶的代理使用上面有個限制,只能爲接口創建代理類,如果需要給具體的類創建代理類,需要用後面要說的cglib

java.lang.reflect.Proxy

這是jdk動態代理中主要的一個類,裏面有一些靜態方法會經常用到,我們來熟悉一下:

getProxyClass方法

爲指定的接口創建代理類,返回代理類的Class對象
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>… interfaces)
參數說明:
loader:定義代理類的類加載器
interfaces:指定需要實現的接口列表,創建的代理默認會按順序實現interfaces指定的接口
newProxyInstance方法

創建代理類的實例對象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
這個方法先爲指定的接口創建代理類,然後會生成代理類的一個實例,最後一個參數比較特殊,是InvocationHandler類型的,這個是個藉口如下:

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
上面方法會返回一個代理對象,當調用代理對象的任何方法的時候,會就被InvocationHandler接口的invoke方法處理,所以主要代碼需要卸載invoke方法中,稍後會有案例細說。

isProxy方法

判斷指定的類是否是一個代理類
public static boolean isProxyClass(Class<?> cl)
getInvocationHandler方法

獲取代理對象的InvocationHandler對象
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
上面幾個方法大家熟悉一下,下面我們來看創建代理具體的2種方式。

創建代理:方式一

步驟

1.調用Proxy.getProxyClass方法獲取代理類的Class對象
2.使用InvocationHandler接口創建代理類的處理器
3.通過代理類和InvocationHandler創建代理對象
4.上面已經創建好代理對象了,接着我們就可以使用代理對象了
案例

先來個接口IService

package com.javacode2018.lesson001.demo16;

public interface IService {
void m1();
void m2();
void m3();
}
創建IService接口的代理對象

@Test
public void m1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 獲取接口對應的代理類
Class proxyClass = (Class) Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
// 2. 創建代理類的處理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“我是InvocationHandler,被調用的方法是:” + method.getName());
return null;
}
};
// 3. 創建代理實例
IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
// 4. 調用代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();
}
運行輸出

我是InvocationHandler,被調用的方法是:m1
我是InvocationHandler,被調用的方法是:m2
我是InvocationHandler,被調用的方法是:m3
創建代理:方式二

創建代理對象有更簡單的方式。

步驟

1.使用InvocationHandler接口創建代理類的處理器
2.使用Proxy類的靜態方法newProxyInstance直接創建代理對象
3.使用代理對象
案例

@Test
public void m2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 創建代理類的處理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“我是InvocationHandler,被調用的方法是:” + method.getName());
return null;
}
};
// 2. 創建代理實例
IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
// 3. 調用代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();
}
運行輸出:

我是InvocationHandler,被調用的方法是:m1
我是InvocationHandler,被調用的方法是:m2
我是InvocationHandler,被調用的方法是:m3
案例:任意接口中的方法耗時統計

下面我們通過jdk動態代理實現一個通用的代理,解決統計所有接口方法耗時的問題。

主要的代碼在代理處理器InvocationHandler實現上面,如下:

package com.javacode2018.lesson001.demo16;

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

public class CostTimeInvocationHandler implements InvocationHandler {

private Object target;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    long starTime = System.nanoTime();
    Object result = method.invoke(this.target, args);//@1
    long endTime = System.nanoTime();
    System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
    return result;
}

/**
 * 用來創建targetInterface接口的代理對象
 *
 * @param target          需要被代理的對象
 * @param targetInterface 被代理的接口
 * @param <T>
 * @return
 */
public static <T> T createProxy(Object target, Class<T> targetInterface) {
    if (!targetInterface.isInterface()) {
        throw new IllegalStateException("targetInterface必須是接口類型!");
    } else if (!targetInterface.isAssignableFrom(target.getClass())) {
        throw new IllegalStateException("target必須是targetInterface接口的實現類!");
    }
    return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
}

}
上面主要是createProxy方法用來創建代理對象,2個參數:
target:目標對象,需要實現targetInterface接口
targetInterface:需要創建代理的接口
invoke方法中通過method.invoke(this.target, args)調用目標方法,然後統計方法的耗時。
測試用例

@Test
public void costTimeProxy() {
IService serviceA = CostTimeInvocationHandler.createProxy(new ServiceA(), IService.class);
IService serviceB = CostTimeInvocationHandler.createProxy(new ServiceB(), IService.class);
serviceA.m1();
serviceA.m2();
serviceA.m3();

serviceB.m1();
serviceB.m2();
serviceB.m3();

}
運行輸出

我是ServiceA中的m1方法!
class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):61300
我是ServiceA中的m2方法!
class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):22300
我是ServiceA中的m3方法!
class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):18700
我是ServiceB中的m1方法!
class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):54700
我是ServiceB中的m2方法!
class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):27200
我是ServiceB中的m3方法!
class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):19800
我們再來的接口,也需要統計耗時的功能,此時我們無需去創建新的代理類即可實現同樣的功能,如下:

IUserService接口

package com.javacode2018.lesson001.demo16;

public interface IUserService {
/**
* 插入用戶信息
* @param name
*/
void insert(String name);
}
IUserService接口實現類:

package com.javacode2018.lesson001.demo16;

public class UserService implements IUserService {
@Override
public void insert(String name) {
System.out.println(String.format(“用戶[name:%s]插入成功!”, name));
}
}
測試用例

@Test
public void userService() {
IUserService userService = CostTimeInvocationHandler.createProxy(new UserService(), IUserService.class);
userService.insert(“路人甲Java”);
}
運行輸出:

用戶[name:路人甲Java]插入成功!
class com.javacode2018.lesson001.demo16.UserService.m1()方法耗時(納秒):193000
上面當我們創建一個新的接口的時候,不需要再去新建一個代理類了,只需要使用CostTimeInvocationHandler.createProxy創建一個新的代理對象就可以了,方便了很多。

Proxy使用注意

jdk中的Proxy只能爲接口生成代理類,如果你想給某個類創建代理類,那麼Proxy是無能爲力的,此時需要我們用到下面要說的cglib了。
Proxy類中提供的幾個常用的靜態方法大家需要掌握
通過Proxy創建代理對象,當調用代理對象任意方法時候,會被InvocationHandler接口中的invoke方法進行處理,這個接口內容是關鍵
三 cglib代理詳解

什麼是cglib

jdk動態代理只能爲接口創建代理,使用上有侷限性。實際的場景中我們的類不一定有接口,此時如果我們想爲普通的類也實現代理功能,我們就需要用到cglib來實現了。

cglib是一個強大、高性能的字節碼生成庫,它用於在運行時擴展Java類和實現接口;本質上它是通過動態的生成一個子類去覆蓋所要代理的類(非final修飾的類和方法)。Enhancer可能是CGLIB中最常用的一個類,和jdk中的Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創建一個被代理對象的子類並且攔截所有的方法調用(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由於Java final方法語義決定的。基於同樣的道理,Enhancer也不能對final類進行代理操作。

CGLIB作爲一個開源項目,其代碼託管在github,地址爲:

https://github.com/cglib/cglib
cglib組成結構
在這裏插入圖片描述
CGLIB底層使用了ASM(一個短小精悍的字節碼操作框架)來操作字節碼生成新的類。除了CGLIB庫外,腳本語言(如Groovy和BeanShell)也使用ASM生成字節碼。ASM使用類似SAX的解析器來實現高性能。我們不鼓勵直接使用ASM,因爲它需要對Java字節碼的格式足夠的瞭解。

spring已將第三方cglib jar包中所有的類集成到spring自己的jar包中,本系列內容都是和spring相關的,爲了方便,我們直接使用spring內部已集成的來講解

5個案例來演示cglib常見的用法

案例1:攔截所有方法(MethodInterceptor)

創建一個具體的類,如下:

package com.javacode2018.lesson001.demo17;

public class Service1 {
public void m1() {
System.out.println(“我是m1方法”);
}

public void m2() {
    System.out.println("我是m2方法");
}

}
下面我們爲這個類創建一個代理,代理中實現打印每個方法的調用日誌。

package com.javacode2018.lesson001.demo17;

import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibTest {

@Test
public void test1() {
    //使用Enhancer來給某個類創建代理類,步驟
    //1.創建Enhancer對象
    Enhancer enhancer = new Enhancer();
    //2.通過setSuperclass來設置父類型,即需要給哪個類創建代理類
    enhancer.setSuperclass(Service1.class);
    /*3.設置回調,需實現org.springframework.cglib.proxy.Callback接口,
    此處我們使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一個接口,實現了Callback接口,
    當調用代理對象的任何方法的時候,都會被MethodInterceptor接口的invoke方法處理*/
    enhancer.setCallback(new MethodInterceptor() {
        /**
         * 代理對象方法攔截器
         * @param o 代理對象
         * @param method 被代理的類的方法,即Service1中的方法
         * @param objects 調用方法傳遞的參數
         * @param methodProxy 方法代理對象
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("調用方法:" + method);
            //可以調用MethodProxy的invokeSuper調用被代理類的方法
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    });
    //4.獲取代理對象,調用enhancer.create方法獲取代理對象,這個方法返回的是Object類型的,所以需要強轉一下
    Service1 proxy = (Service1) enhancer.create();
    //5.調用代理對象的方法
    proxy.m1();
    proxy.m2();
}

}
上面代碼中的註釋很詳細,列出了給指定的類創建代理的具體步驟,整個過程中主要用到了Enhancer類和MethodInterceptor接口。
enhancer.setSuperclass用來設置代理類的父類,即需要給哪個類創建代理類,此處是Service1
enhancer.setCallback傳遞的是MethodInterceptor接口類型的參數,MethodInterceptor接口有個intercept方法,這個方法會攔截代理對象所有的方法調用。
還有一個重點是Object result = methodProxy.invokeSuper(o, objects);可以調用被代理類,也就是Service1類中的具體的方法,從方法名稱的意思可以看出是調用父類,實際對某個類創建代理,cglib底層通過修改字節碼的方式爲Service1類創建了一個子類。
運行輸出:

調用方法:public void com.javacode2018.lesson001.demo17.Service1.m1()
我是m1方法
調用方法:public void com.javacode2018.lesson001.demo17.Service1.m2()
我是m2方法
從輸出中可以看出Service1中的2個方法都被MethodInterceptor中的invoke攔截處理了。
案例2:攔截所有方法(MethodInterceptor)

在創建一個類,如下:

package com.javacode2018.lesson001.demo17;

public class Service2 {
public void m1() {
System.out.println(“我是m1方法”);
this.m2(); //@1
}

public void m2() {
    System.out.println("我是m2方法");
}

}
這個類和上面的Service1類似,有點不同是@1,在m1方法中調用了m2方法。
下面來採用案例1中同樣的方式來給Service2創建代理,如下:

@Test
public void test2() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service2.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(“調用方法:” + method);
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
});
Service2 proxy = (Service2) enhancer.create();
proxy.m1(); //@1
}
注意上面@1的代碼,只調用了m1方法,看一下輸出效果:

調用方法:public void com.javacode2018.lesson001.demo17.Service2.m1()
我是m1方法
調用方法:public void com.javacode2018.lesson001.demo17.Service2.m2()
我是m2方法
從輸出中可以看出m1和m2方法都被攔截器處理了,而m2方法是在Service1的m1方法中調用的,也被攔截處理了。

spring中的@configuration註解就是採用這種方式實現的,給大家上個@configuration案例眼熟一下:

package com.javacode2018.lesson001.demo17;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

@Bean
public C1 c1(){
    return new C1();
}
@Bean
public C2 c2(){
    C1 c1 = this.c1(); //@1
    return new C2(c1);
}
@Bean
public C3 c3(){
    C1 c1 = this.c1(); //@2
    return new C3(c1);
}

public static class C1{}

public static class C2{
    private C1 c1;

    public C2(C1 c1) {
        this.c1 = c1;
    }
}
public static class C3{
    private C1 c1;

    public C3(C1 c1) {
        this.c1 = c1;
    }
}

}
上面代碼中C2和C3依賴於C1,都是通過構造器注入C1,注意代碼中的@1和@2都是調用c1方法獲取容器中的C1,如何確保多次獲取的C1都是一個的?這個地方就是使用cglib代理攔截@Bean註解的方法來實現的。
案例3:攔截所有方法並返回固定值(FixedValue)

當調用某個類的任何方法的時候,都希望返回一個固定的值,此時可以使用FixedValue接口,如下:

enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return “路人甲”;
}
});
上面創建的代理對象,調用其任意方法返回的都是"路人甲"。
案例代碼如下:

創建一個類Service3,如下:

package com.javacode2018.lesson001.demo17;

public class Service3 {
public String m1() {
System.out.println(“我是m1方法”);
return “hello:m1”;
}

public String m2() {
    System.out.println("我是m2方法");
    return "hello:m2";
}

}
對用的測試用例:

@Test
public void test3() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service3.class);
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return “路人甲”;
}
});
Service3 proxy = (Service3) enhancer.create();
System.out.println(proxy.m1());//@1
System.out.println(proxy.m2()); //@2
System.out.println(proxy.toString());//@3
}
@1、@2、@3調用了代理對象的3個方法,運行輸出:

運行輸出:

路人甲
路人甲
路人甲
可以看出輸出的都是一個拱頂的值。
案例4:直接放行,不做任何操作(NoOp.INSTANCE)

Callback接口下面有個子接口org.springframework.cglib.proxy.NoOp,將這個作爲Callback的時候,被調用的方法會直接放行,像沒有任何代理一樣,感受一下效果:

@Test
public void test6() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service3.class);
enhancer.setCallback(NoOp.INSTANCE);
Service3 proxy = (Service3) enhancer.create();
System.out.println(proxy.m1());
System.out.println(proxy.m2());
}
運行輸出:

我是m1方法
hello:m1
我是m2方法
hello:m2
從輸出中可以看出,被調用的方法沒有被代理做任何處理,直接進到目標類Service3的方法中了。
案例5:不同的方法使用不同的攔截器(CallbackFilter)

有個類如下:

package com.javacode2018.lesson001.demo17;

public class Service4 {
public void insert1() {
System.out.println(“我是insert1”);
}

public void insert2() {
    System.out.println("我是insert2");
}

public String get1() {
    System.out.println("我是get1");
    return "get1";
}

public String get2() {
    System.out.println("我是get2");
    return "get2";
}

}
需求,給這個類創建一個代理需要實現下面的功能:

以insert開頭的方法需要統計方法耗時
以get開頭的的方法直接返回固定字符串歡迎和【路人甲java】一起學spring!
下來看代碼,然後再解釋:

@Test
public void test4() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service4.class);
//創建2個Callback
Callback[] callbacks = {
//這個用來攔截所有insert開頭的方法
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + “,耗時(納秒):” + (endTime - starTime));
return result;
}
},
//下面這個用來攔截所有get開頭的方法,返回固定值的
new FixedValue() {
@Override
public Object loadObject() throws Exception {
return “路人甲Java”;
}
}
};
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
return 0;
}
});
//調用enhancer的setCallbacks傳遞Callback數組
enhancer.setCallbacks(callbacks);
/**
* 設置過濾器CallbackFilter
* CallbackFilter用來判斷調用方法的時候使用callbacks數組中的哪個Callback來處理當前方法
* 返回的是callbacks數組的下標
/
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
//獲取當前調用的方法的名稱
String methodName = method.getName();
/
*
* 方法名稱以insert開頭,
* 返回callbacks中的第1個Callback對象來處理當前方法,
* 否則使用第二個Callback處理被調用的方法
*/
return methodName.startsWith(“insert”) ? 0 : 1;
}
});
Service4 proxy = (Service4) enhancer.create();
System.out.println("---------------");
proxy.insert1();
System.out.println("---------------");
proxy.insert2();
System.out.println("---------------");
System.out.println(proxy.get1());
System.out.println("---------------");
System.out.println(proxy.get2());

}
運行輸出:


我是insert1
public void com.javacode2018.lesson001.demo17.Service4.insert1(),耗時(納秒):15396100

我是insert2
public void com.javacode2018.lesson001.demo17.Service4.insert2(),耗時(納秒):66200

路人甲Java

路人甲Java
代碼說明:
由於需求中要對不同的方法做不同的處理,所以需要有2個Callback對象,當調用代理對象的方法的時候,具體會走哪個Callback呢,此時會通過CallbackFilter中的accept來判斷,這個方法返回callbacks數組的索引。
上面這個案例還有一種簡單的實現,見案例6

案例6:對案例5的優化(CallbackHelper)

cglib中有個CallbackHelper類,可以對案例5的代碼進行有環,CallbackHelper類相當於對一些代碼進行了封裝,方便實現案例5的需求,實現如下:

@Test
public void test5() {
Enhancer enhancer = new Enhancer();
//創建2個Callback
Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + “,耗時(納秒):” + (endTime - starTime));
return result;
};
//下面這個用來攔截所有get開頭的方法,返回固定值的
Callback fixdValueCallback = (FixedValue) () -> “路人甲Java”;
CallbackHelper callbackHelper = new CallbackHelper(Service4.class, null) {
@Override
protected Object getCallback(Method method) {
return method.getName().startsWith(“insert”) ? costTimeCallback : fixdValueCallback;
}
};
enhancer.setSuperclass(Service4.class);
//調用enhancer的setCallbacks傳遞Callback數組
enhancer.setCallbacks(callbackHelper.getCallbacks());
/**
* 設置CallbackFilter,用來判斷某個方法具體走哪個Callback
*/
enhancer.setCallbackFilter(callbackHelper);
Service4 proxy = (Service4) enhancer.create();
System.out.println("---------------");
proxy.insert1();
System.out.println("---------------");
proxy.insert2();
System.out.println("---------------");
System.out.println(proxy.get1());
System.out.println("---------------");
System.out.println(proxy.get2());

}
運行輸出:


我是insert1
public void com.javacode2018.lesson001.demo17.Service4.insert1(),耗時(納秒):9777500

我是insert2
public void com.javacode2018.lesson001.demo17.Service4.insert2(),耗時(納秒):50600

路人甲Java

路人甲Java
輸出效果和案例4一模一樣的,上面重點在於CallbackHelper,裏面做了一些封裝,有興趣的可以去看一下源碼,比較簡單。
案例6:實現通用的統計任意類方法耗時代理類

直接上代碼,比較簡單,如下:

package com.javacode2018.lesson001.demo17;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CostTimeProxy implements MethodInterceptor {
//目標對象
private Object target;

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

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    long starTime = System.nanoTime();
    //調用被代理對象(即target)的方法,獲取結果
    Object result = method.invoke(target, objects); //@1
    long endTime = System.nanoTime();
    System.out.println(method + ",耗時(納秒):" + (endTime - starTime));
    return result;
}

/**
 * 創建任意類的代理對象
 *
 * @param target
 * @param <T>
 * @return
 */
public static <T> T createProxy(T target) {
    CostTimeProxy costTimeProxy = new CostTimeProxy(target);
    Enhancer enhancer = new Enhancer();
    enhancer.setCallback(costTimeProxy);
    enhancer.setSuperclass(target.getClass());
    return (T) enhancer.create();
}

}
我們可以直接使用上面的靜態方法createProxy來爲目標對象target創建一個代理對象,被代理的對象自動實現方法調用耗時統計。
@1:調用被代理對象的方法獲取真正的結果。
使用非常簡單,來個測試用例,如下:

@Test
public void test7() {
//創建Service1代理
Service1 service1 = CostTimeProxy.createProxy(new Service1());
service1.m1();

//創建Service3代理
Service3 service3 = CostTimeProxy.createProxy(new Service3());
System.out.println(service3.m1());

}
運行輸出:

我是m1方法
public void com.javacode2018.lesson001.demo17.Service1.m1(),耗時(納秒):53200
我是m1方法
public java.lang.String com.javacode2018.lesson001.demo17.Service3.m1(),耗時(納秒):49200
hello:m1
四 CGLIB和Java動態代理的區別

Java動態代理只能夠對接口進行代理,不能對普通的類進行代理(因爲所有生成的代理類的父類爲Proxy,Java類繼承機制不允許多重繼承);CGLIB能夠代理普通類;
Java動態代理使用Java原生的反射API進行操作,在生成類上比較高效;CGLIB使用ASM框架直接對字節碼進行操作,在類的執行過程中比較高效

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章