文档章节

Java动态代理深入详解

wangjianme
 wangjianme
发布于 2017/07/23 17:56
字数 1870
阅读 145
收藏 1

声明:

原创 :一叶知秋(549051701)

 

1: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(),

new InvocationHandler() {

public Object invoke(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 void eat();

}

实现IAnimal接口的Dog类:

public class Dog implements IAnimal {

private String food;

 public Dog(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);//执行成功

上例中:

Method m = 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字节码只有一份。

2、使用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类的代理类,可以方便的实现对源对象方法的调用,如使用:

Object o = 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,new MethodInterceptor(){

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();

 

 

3:总结

1:对类的增强一般使用两种方式

一是包装,如C3p0连接池和dbcp连接池用的都是包装。包装的特点是代码量很大但运行速度快。

二是使用代理,如Spring中大量使用动态代理。动态代理的本质就是反射,所以,它的特点是代码量少但运行速度慢。不过,使用cglib代理可以解决运行速度慢的问题。

 

2:动态代理一般使用两种

   一是jdk的proxy动态代理。它的特点是被代理的类,必须要拥有接口。

   二是使用cglib由于它的低层是asm所以,它不需要被代理类有接口。同时运行速度方面也比jdk的proxy快。

 

© 著作权归作者所有

共有 人打赏支持
wangjianme
粉丝 25
博文 7
码字总数 7607
作品 0
济南
技术主管
加载中

评论(1)

一本正经的胡说八道韩
一本正经的胡说八道韩
老师写的很深刻
【目录导航】JAVA零基础进阶之路

【JAVA零基础入门系列】(已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day6 Java字符串 Day7 Java输入与输出...

MFrank
06/21
0
0
动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。 动态代理机制详解(JDK 和CGLIB,Javassist,ASM...

素雷
2017/10/19
0
0
Instrumentation 实践详解

利用 Java 代码,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,有了这样的功能,开发者就可以实现更为灵活的和 了,这样的特性实际上提供了,使得开发者无需对 ...

陶邦仁
2014/12/30
0
0
Spring AOP切点表达式详解

简介 面向对象编程,也称为OOP(即Object Oriented Programming)最大的优点在于能够将业务模块进行封装,从而达到功能复用的目的。通过面向对象编程,不同的模板可以相互组装,从而实现更为...

张旭峰
06/05
0
2
深入理解Java动态代理及手动实现

前言 文章目录如下,便于快速索引 一、什么叫代理? 二、什么叫动态代理? 三、动态代理有什么优势? 四、动态代理的JDK实现原理 4.1核心类/接口 4.2 代理类$Proxy0解析 4.3 动态代理的经典使...

HOT_POT
07/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

[雪峰磁针石博客]软件测试专家工具包1web测试

web测试 本章主要涉及功能测试、自动化测试(参考: 软件自动化测试初学者忠告) 、接口测试(参考:10分钟学会API测试)、跨浏览器测试、可访问性测试和可用性测试的测试工具列表。 安全测试工具...

python测试开发人工智能安全
今天
2
0
JS:异步 - 面试惨案

为什么会写这篇文章,很明显不符合我的性格的东西,原因是前段时间参与了一个面试,对于很多程序员来说,面试时候多么的鸦雀无声,事后心里就有多么的千军万马。去掉最开始毕业干了一年的Jav...

xmqywx
今天
3
0
Win10 64位系统,PHP 扩展 curl插件

执行:1. 拷贝php安装目录下,libeay32.dll、ssleay32.dll 、 libssh2.dll 到 C:\windows\system32 目录。2. 拷贝php/ext目录下, php_curl.dll 到 C:\windows\system32 目录; 3. p...

放飞E梦想O
今天
0
0
谈谈神秘的ES6——(五)解构赋值【对象篇】

上一节课我们了解了有关数组的解构赋值相关内容,这节课,我们接着,来讲讲对象的解构赋值。 解构不仅可以用于数组,还可以用于对象。 let { foo, bar } = { foo: "aaa", bar: "bbb" };fo...

JandenMa
今天
1
0
OSChina 周一乱弹 —— 有人要给本汪介绍妹子啦

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子 :分享水木年华的单曲《中学时代》@小小编辑 手机党少年们想听歌,请使劲儿戳(这里) @须臾时光:夏天还在做最后的挣扎,但是晚上...

小小编辑
今天
85
8

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部