jdk代理和cglib代理區別和例子
阿新 • • 發佈:2019-01-02
代理目標
給某一個物件提供一個代理或佔位符,並由代理物件來控制對原物件的訪問,這句話的重點在於控制。
如何實現
既然要控制那麼如何實現呢?在方法執行之前、之後加上自己的邏輯處理就可以了。比如許可權檢查:如果一個方法在執行之前我們使用許可權代理去檢查一下這個人有沒有執行某個方法的許可權,如果有就執行,沒有就直接return返回或者報錯,這樣就可以達到控制的目的。
實現方式
定義一個介面和實現類
Hello介面:
package com.bsx.test.proxy;
public interface Hello {
void say(String name);
}
HelloImpl實現類:
package com.bsx.test.proxy;
class HelloImpl implements Hello {
@Override
public void say(String name) {
System.out.println("Hello! " + name);
}
}
1.靜態代理
package com.bsx.test.proxy;
public class StaticProxy implements Hello {
private Hello hello;
public StaticProxy(Hello hello) {
this.hello = hello;
}
private void before() {
System.out.println("static proxy before ...");
}
private void after() {
System.out.println("static proxy after ...");
}
@Override
public void say(String name) {
before();
hello.say(name);
after();
}
}
2.jdk 動態代理
package com.bsx.test.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 介面動態代理需要2步
* 第一步:定義額外的操作
* 通過實現 InvocationHandler 介面,來定義在執行代理物件方法前後自己的動作。
* 第二步:獲取代理物件
* 通過 Proxy.newProxyInstance 獲取代理物件,這一步的作用是根據指定的1.classLoader,2.要代理的介面,以及3.傳遞進來的處理者。來生成真正的代理物件。
*/
public class InterfaceProxy implements InvocationHandler {
// 獲取代理物件
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
private Object target;
public InterfaceProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object object = method.invoke(target, args);
after();
return object;
}
private void before() {
System.out.println("interface proxy before ...");
}
private void after() {
System.out.println("interface proxy after ...");
}
}
3.CGLIB動態代理
package com.bsx.test.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib 動態代理需要2步:
* 第一步:定義額外的操作
* 通過實現 MethodInterceptor 介面,來定義在執行代理物件方法前後自己的動作。
* 第二步:獲取代理物件
* 通過 Enhancer.create 獲取代理物件,因為這個只需要。
*/
public class CglibProxy implements MethodInterceptor {
// 獲取代理物件
public <T> T getProxy(Class<T> clazz) {
return (T) Enhancer.create(clazz, this);
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
private void before() {
System.out.println("cglib proxy before ...");
}
private void after() {
System.out.println("cglib proxy after ...");
}
}
4.測試例子
package com.bsx.test.proxy;
public class TestProxy {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl();
//1.靜態代理
StaticProxy staticProxy = new StaticProxy(helloImpl);
staticProxy.say("dada");
System.out.println("==========");
//2.jdk 動態代理
InterfaceProxy interfaceProxy = new InterfaceProxy(helloImpl);
// 注意動態代理返回的是介面型別的物件
// 所以,使用介面來標記改物件是可以的,
// 如果使用實現類來接收該型別是會報錯的
// 這就像是說人是動物是正確的,但是動物是人就不對了
Hello hello = interfaceProxy.getProxy();
hello.say("dada");
System.out.println("==========");
// 3.cglib 動態代理
CglibProxy cglibProxy = new CglibProxy();
// cglib 代理的是類,它的實現方式是通過繼承一個類作為它的子類來覆蓋父類中的方法
HelloImpl helloProxy = cglibProxy.getProxy(HelloImpl.class);
helloProxy.say("dada");
}
}
升級代理為單例模式
jdk 單利模式
package com.bsx.test.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 介面動態代理需要2步
* 第一步:定義額外的操作
* 通過實現 InvocationHandler 介面,來定義在執行代理物件方法前後自己的動作。
* 第二步:獲取代理物件
* 通過 Proxy.newProxyInstance 獲取代理物件,這一步的作用是根據指定的1.classLoader,2.要代理的介面,以及3.傳遞進來的處理者。來生成真正的代理物件。
*/
public class InterfaceProxy implements InvocationHandler {
private static final InterfaceProxy instance = new InterfaceProxy();
public static InterfaceProxy getInstance() {
return instance;
}
private InterfaceProxy() {
}
public <T> T getProxy(Object target) {
this.target = target;
return (T) Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
}
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object object = method.invoke(target, args);
after();
return object;
}
private void before() {
System.out.println("interface proxy before ...");
}
private void after() {
System.out.println("interface proxy after ...");
}
}
CGLIB代理
package com.bsx.test.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib 動態代理需要2步:
* 第一步:定義額外的操作
* 通過實現 MethodInterceptor 介面,來定義在執行代理物件方法前後自己的動作。
* 第二步:獲取代理物件
* 通過 Enhancer.create 獲取代理物件,因為這個只需要。
*/
public class CglibProxy implements MethodInterceptor {
private static final CglibProxy instance = new CglibProxy();
public static CglibProxy getInstance() {
return instance;
}
public <T> T getProxy(Class<T> clazz) {
return (T) Enhancer.create(clazz, this);
}
private CglibProxy() {
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
private void before() {
System.out.println("cglib proxy before ...");
}
private void after() {
System.out.println("cglib proxy after ...");
}
}
測試例子
從下面的測試更能夠清楚的看到,對於jdk代理來說,需要傳遞的引數就一個介面的實現類,而對於CGLIB代理來說,只需要傳遞被代理類的型別就可以了,因為jdk代理針對的是物件,而CGLIB代理針對的是類,所以不需要傳遞物件。
package com.bsx.test.proxy;
public class TestProxy {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl();
//1.靜態代理
StaticProxy staticProxy = new StaticProxy(helloImpl);
staticProxy.say("dada");
System.out.println("==========");
//2.jdk 動態代理
Hello hello = InterfaceProxy.getInstance().getProxy(new HelloImpl());
hello.say("dada");
System.out.println("==========");
// 3.cglib 動態代理
HelloImpl helloProxy = CglibProxy.getInstance().getProxy(HelloImpl.class);
helloProxy.say("dada");
}
}
jdk代理和CGLIB代理的區別
一、簡單來說:
JDK動態代理只能對實現了介面的類
生成代理,而不能針對類
。
CGLIB是針對類實現代理,主要是對指定的類生成一個子類
,覆蓋其中的方法(繼承
)。
二、Spring在選擇用JDK還是CGLiB的依據:
- (1)當Bean
實現介面
時,Spring就會用JDK的動態代理
。 - (2)當Bean
沒有實現介面
時,Spring使用CGlib
實現。 - (3)可以強制使用CGlib(在spring配置中加入)
三、CGlib比JDK快?
(1)使用CGLib實現動態代理,CGLib底層採用ASM位元組碼生成框架,使用位元組碼技術生成代理類,比使用Java反射效率要高。唯一需要注意的是,CGLib不能對宣告為final的方法進行代理
,因為CGLib原理是動態生成被代理類的子類。
(2)在對JDK動態代理與CGlib動態代理的程式碼實驗中看,1W次執行下,JDK7及8的動態代理效能比CGlib要好20%左右。