您的位置:首页 > 编程语言 > Java开发

JDK的动态代理为什么必须要使用接口与使用CGLIB动态代理

2015-04-28 16:30 302 查看
一.JDK的动态代理为什么必须要使用接口

JDK的代理Proxy必须要使用接口,才可以实现对方法的拦截。为什么呢?先让我们看一个JDK动态代理的示例:

接口类:

public
interface
IPerson {
public
void
sayHi(String nm);
}

接口实现类:

public
class
Person implements IPerson{
public Person(){//构造
}
private String
name;
public Person(String name){//构造
this.name=name;
}
public
void
sayHi(String str){
System.err.println(name+" Hello:"+str);
}
}

使用JDK代理Person类的方法:

@Test
public
void
testJdkProxy() throws Exception{
final Dog dog =
new Dog();
//以下必须返回接口,因为Proxy内部,会根据接口创建一个IAnimal的子类,
//创建的这个子类是Dog类的兄弟关系。子类可以转换成父类,但对于平行的两个转换将失败,
//这就是为什么必须要使用接口的原因
IAnimal dog2 = (IAnimal)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
dog.getClass().getInterfaces(),
newInvocationHandler() {
public Objectinvoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.err.println("Before...");
Object o =method.invoke(dog, args);
return o;
}
});
dog2.eat();
boolean boo = Proxy.isProxyClass(dog2.getClass());
System.err.println("是否是被代理对象:"+boo+","+dog2.getClass());
}

代码说明:Proxy返回的对象必须要强转为Ianimal的类型。如果要转成Dog类型则会抛出ClassCastException。

Proxy代理可以使用以下简图加以说明:



上图中,最关键,也是最重要的部分就是Proxy在接收到Person类的实例以后,创建了IPerson类的一个子类取名为$Proxy$,此时$Proxy$类与Person类为同级兄弟关系。所以如果使用以下代码将会抛出ClassCastException:

Person person = (Person)Proxy.newInstance(…..);

但使用接口就不会出现异常,即:

IPerson peson = (IPerson)Proxy.newInstance(…..);

1、为了更加清楚为什么会抛出转换异常,我再做如下测试:



声明一个IAnimal接口类:

public
interface
IAnimal {

public Dog(String food){
public void eat();
}

实现IAnimal接口的Dog类:

public
class
Dog implements IAnimal {
private String
food;
this.food=food;
}
public
void
eat() {
System.err.println("小狗吃:"+food);
}
}

实现IAnimal接口的Cat类:

public
class
Cat implements IAnimal {
private String
food;
public Cat(String food){
this.food=food;
}
public
void
eat() {
System.err.println("小猫吃:"+food);
}
}

测试是否可以将Dog类的实例转换成Cat,转换时,将会抛出ClassCastException:

IAnimal dog = new Dog("骨头");
IAnimal cat = new Cat("小鱼");
Dog dog2 = (Dog) cat;//cat与dog是兄弟关系不能转换成功,抛出ClassCastException
    dog = cat; //此时将转换成功,因为dog是IAnimal类型,可以指向自己的子类
2、然后再测试使用反射

IAnimal dog = new Dog("骨头");
IAnimal cat = new Cat("小鱼");
boolean boo = dog.getClass()==cat.getClass();
System.err.println(boo);
Method m = dog.getClass().getMethod("eat");//从dog中获取
m.invoke(cat);//不成功,说明反射从哪儿获取方法,就应该调用谁的实例

//但如果是从最高接口中获取到的方法,则可以执行,如下:
Method m2 = IAnimal.class.getMethod("eat");
m2.invoke(dog);//执行成功
System.err.println("---");
m.invoke(cat);//执行成功

上例中:

Methodm = dog.getClass.getMethod(“eat”);

m.invoke(dog);只可以执行dog的方法,如果填入cat则会执行不成功。因为Dog类拥有自己的字节码。

而如果修改成:

Method m = IAnimal.class.getMethod(“eat”);

m.invoke(dog);

m.invoke(cat);//两个调用都可以执行成功因为,Dog和Cat拥有相同的父类接口,而IAnimal字节码只有一份。

二.使用CGLIB动态代理

Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类。使用CGLIB即使被代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:

使用CGLIB需要导入以下两个jar文件:

asm.jar– CGLIB的底层实现。

cglib.jar– CGLIB的核心jar包。

CGLIB的核心类:

net.sf.cglib.proxy.Enhancer– 主要的增强类

net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现

net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:

Objecto = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

费话少说,上代码:

1、使用CGLIB的代理:

以下测试代理一个没有实现任何接口的Person类:

@Test
public
void
testProxy1() throws Exception {
final Person p1 =
new Person();   //Person类没有实现任何接口
Enhancer en = new Enhancer();    //声明增加类实例
en.setSuperclass(Person.class);   //设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
en.setCallback(new MethodInterceptor() {    //设置回调函数,即一个方法拦截
public Object intercept(Object target, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
Object o = method.invoke(p1,args);    //注意参数p1,仍然为外部声明的源对象,且Method为JDK的Method反射
System.err.println("After...");
return o;
}
});
Person p = (Person) en.create();    //通过create方法返回Person类的代理
System.err.println(p.getClass());//被代理的对象
p.sayHi("Hello");
}

2、以下测试代理一个拥有接口的类:

IAnimal是接口,Dog是实现类,具体代码如下:

@Test
public
void
testProxy2() throws Exception {
final Dog dog =
new Dog();
//声明被代理对象
Enhancer en = new Enhancer();
//声明CGLIB增强类
en.setSuperclass(IAnimal.class);
//设置接口类,也可以设置成dog实现类,会影响create返回的对象
en.setCallback(new MethodInterceptor() {
public Object intercept(Object target, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
System.err.println("Before...");
Object o = method.invoke(dog, args);
System.err.println("After...");
return o;
}
});
//Dog dog2 = (Dog)en.create();//必须转型为接口,否则抛出ClassCastException
IAnimal dog2 = (IAnimal)en.create();
dog2.eat();
}

说明:

由于上例中,设置了en.setSuperclass(IAnimal.class),所以en.create()方法,返回的对象,必须要转换成IAnimal接口。如果转换成Dog则会抛出ClassCastException。

3、将CGLIB再做一个简单的包装:

class CglibProxy
implements MethodInterceptor{
private Object
srcTarget;
private CglibProxy(Object o){
this.srcTarget = o;
}
@SuppressWarnings("unchecked")
public
static
<T>T proxyTarget(T t){
Enhancer en= new Enhancer();
en.setSuperclass(t.getClass());
en.setCallback(new CglibProxy(t));
T tt = (T) en.create();
return tt;
}
@Override
public Object intercept(Object obj, Method method, Object[]args,
MethodProxy proxy) throws Throwable {
System.err.println("拦截前...");
Object o = method.invoke(srcTarget, args);
System.err.println("拦截后....");
return o;
}
}

包装以后的调用代码如下,主要是快速的实现获取被代理类:

Person p = CglibProxy.proxyTarget(new Person());
p.sayHi("HJello");
IAnimal dog = CglibProxy.proxyTarget(new Dog());
dog.eat();

4、使用静态方法代理一个没有接口的对象

以下代码,包含在一个测试方法或是main方法中运行:

final Person
src = new Person();
//直接使用静态方法代理一个对象
Person p = (Person) Enhancer.create(Person.class,newMethodInterceptor(){
public Object intercept(Object proxyedObj, Method method, Object[]args,
MethodProxy proxy) throws Throwable {
System.err.println("Hello");
//使用原生的方法调用,注意里面的src
//Object oo= method.invoke(src,
args);
//使用MethodProxy调用父类的代码,同样有效
Object oo = proxy.invokeSuper(proxyedObj, args);
return oo;
}
});
System.err.println(p.getClass());
p.abc();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: