您的位置:首页 > 职场人生

黑马程序员—Java动态代理详解

2013-08-05 19:48 363 查看
对动态代理的理解;

这个知识点很复杂,也不常用,传智的课程深度!!张孝祥java高新技术上有讲解,当时看了几遍模模糊糊,现在在JDBC的连接池遇到,又狠狠的看了几遍。

其实搜到的资料还是几乎一样,可能是书读百遍其义自现,以前看的还是不够,直到现在才理解!

动态代理概述:

JVM在运行期间动态生成出某类的兄弟类(代理类),代理类与原类实现:调用相同的类加载器,实现相同的接口;并且代理类绑定了一个InvocationHandler实例对象(代理类真正的操作者)。(注意这三条)这样,调用代理类实例化对象的时候就会去执行InvocationHandler接口的方法,invoke(),在invoke方法中我们可以进行任意的操作,包括原类的方法调用(因为实现了相同的类加载器,相同的接口),自定义操作等。相当于原类的增强。

代理类可以实现原类全部的方法调用,还可以进行自定义操作!

创建代理类对象Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

代理类的类加载器

代理类需要实现的接口

与代理类绑定的InvocationHandler对象(代理类真正的操作者)

核心关键点:代理类是某类的代理;实现相同的类加载器,相同的接口,绑定了操作对象;返回值为其实现的某一接口;可以实现比原类更加强大的功能;调用代理类对象就会去调用invoke方法!

怎么理解动态代理?

现在有一个接口,我们创建一个实现类!本来该用这个类的实例对象去调用接口的方法,

但是现在不这样,我们生成这个类的代理类,让他'冒充'这个类的兄弟类(也实现了这个接口),调用代理类的时候它会去执行与它关联InvocationHandler的invoke()方法。代理类可以完成更加强大的功能!

使用好处:见下文。

详解:

JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。(注意:这说明动态代理类的具体对象:具有相同接口的目标类的代理,比如对于下边的例子,目标类是Man,所以代理类对象就是具有相同接口的对象,代理类的返回值是目标类实现的某接口,注意获取代理类返回值是接口)

CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。(暂不考虑,会的人很少)

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:(也就是说除了目标的方法,还可以有自定义的方法)

1.在调用目标方法之前

2.在调用目标方法之后

3.在调用目标方法前后

4.在处理目标方法异常的catch块中

(也就是你想到的invoke中的任意位置)

与动态代理相关的两个类Proxy 与 InvocationHandler。(JDK中)通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。 Proxy 类主要用来获取动态代理对象,InvocationHandler 接口用来约束调用者行为。

Proxy.newProxyInstance()方法直接创建出代理类对象

InvocationHandler接口的invoke()方法约束行为

InvocationHandler对象作为处理者,是真正的执行者

查询api获取两个类的常用方法

Proxy

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

返回 一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

loader- 定义代理类的类加载器

interfaces- 代理类要实现的接口列表

h- 指派方法调用的调用处理程序

InvocationHandler

Object invoke(Object proxy, Method method, Object[] args)

在代理实例上处理方法调用并返回结果。

proxy - 在其上调用方法的代理实例

method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。

args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。

InvocationHandler中的invoke,实现方法调用的时候method.invoke(target,args)taregt:被创建代理类的类的实例对象(这样实现的是可以调用原有方法)不调用的话就是返回null;

我的理解:(核心关键点拓展)

动态:JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,注意:关键字,运行期动态生成!

一个接口,有一个实现类,这个实现类的实例对象可以调用接口的方法(最正常不过的),现在我们创建实现类的动态代理类对象(和实现类的实例对象一样,但是功能更加强大),在调用动态代理类对象(注意不是普通对象)的时候会去调用与之关联的InvocationHandler的invoke()方法(真正的执行者),在invoke方法里我们可以进行任意的操作,同时注意invoke的参数中包含(Method method, Object[] args),这样的话就知道我们就可以去调用普通对象的方法(也可以选择不调用,也可以判断是否调用,比如JDBC连接池的时候)。

按照创建动态代理类实例原理来说:创建每一个动态代理类的时候必须绑定对应的类加载器,所需实现的接口,和与之关联的InvocationHandler(真正的执行者)。

注意在创建动态代理类实例对象的时候获取代理类的类加载器和代理类所需要实现的接口的时候用的是反射获取字节码!

以这个程序为例:

本程序创建接口Human,方法是say和walk

创建实现类Man,

创建普通实现类Man的代理类(InvocationHandler在这里用到是匿名内部类,方便点),这样实现类Man与代理类就成了'兄弟类'!

调用代理类对象的时候就会去执行与它关联InvocationHandler的invoke()方法(先去调用代理类里面的invoke方法,可以在这个方法里进行任意操作,包括调用原本Human类方法)

我们创建的是Man类的代理类,返回值是Man实现的某接口!

package com.itheima;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

interface Human{

public void say(String s);

public void walk(String s);

}

class Man implements Human{

public void say(String s) {

System.out.println("我说了"+s);

}

public void walk(String s) {

System.out.println("我走到了"+s);

}

}

public class ProxyDemo {

public static void main(String[] args) {

final Human man = new Man();

/**

* 这里把InvocationHandler匿名内部类(或者说是它的方法)与生成的动态代理类相关联,也可以自己创建一个InvocationHandler实例对象(实现InvocationHandler接口就行)

* 使用匿名内部类的好处是方便点!

* Proxy.newProxyInstance(这里参数有:类加载器,需要实现的接口,InvocationHandler对象)

注意:获取的动态代理类类型是目标类实现的接口Human,获取动态代理类实例对象所需实现的接口的时候用的的反射,所以这里是man.getClass().getInterfaces(),前面的类加载器一样

*/

Human human =

(Human) Proxy.newProxyInstance(

man.getClass().getClassLoader(), //代理类的类加载器(这里是Man的)也可以写成是Man.class.getClassLoader();都是反射获取字节码的方法。

man.getClass().getInterfaces(), //代理类实现的接口,也可以写成是Man.class.getInterfaces(),也可以直接的全部写出来

new InvocationHandler() {

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

/*System.out.println("专为证明执行而生!");

return method.invoke(man, args);*/

String name = method.getName();

if(name.equals("say")){

System.out.println("这个是say方法");

return method.invoke(man, args);//注意此处调用method.invoke的时候需要目标参数(target,也就是对谁进行操作)

}

return null;

}

});

//这里无论访问哪个方法,都是会把请求转发到human.invoke(调用的是实例对象的方法)

human.say("数学");

human.walk("北京");

man.say("英语");

}

}

用了两个测试

invoke中的方法体:

1、a:System.out.println("专为证明执行而生!");

return method.invoke(man, args);//调用原有的方法

打印:专为证明执行而生!

我说了数学

专为证明执行而生!

我走到了北京

我说了英语//正常输出,作比较用

备注:这里在调用代理类对象的时候每次都执行了它自定义的操作("专为证明执行而生!")

下一行代码的man.say()正常输出!

b: System.out.println("专为证明执行而生!");

return null;//不会再调用原有的方法

此时打印:专为证明执行而生!

专为证明执行而生!

我说了英语//正常输出

备注:因为这时候返回的是null,所以没有执行调用与之相关联的InvocationHandler中的方法。

2、invoke中的方法体:

String name = method.getName();

if(name.equals("say")){

System.out.println("这个是say方法");

return method.invoke(man, args);

}

return null;//别的(不匹配的)就不会再调用原有的方法

里面执行的是say方法

我说了数学

我说了英语//正常输出

备注:这时候是在invoke方法内部进行判断(满足条件才执行!)满足方法名是say才执行!

哪些地方需要动态代理? (好处)

不允许直接访问某些类;对访问要做特殊处理等,只能想到这些。

比如在使用数据库连接池的时候

1)传统方式找DriverManager要连接,数目是有限的。

2)传统方式的close(),无法将Connection重用,只是切断应用程序和数据库的桥梁,即无发送到SQL命令到数据库端执行

3)项目中,对于Connection来说,不会直接使用DriverManager取得,而使用连接池方式。

使用连接池方式连接数据库,对已有类进行增强(主要是close方法进行增强,修改为不是关闭连接而是放回连接池)

这时候最好就是使用动态代理:

在与原本类的代理类相关联的InvocationHandler中的方法对方法名称进行判断:

调用的方法是close的话就放回连接池(而不是之前的关闭),不是close的话就执行原本类的方法。(我们也只需要修改close方法,别的不变!)

public Object invoke(Object proxy, Method method,Object[] args) throws Throwable

{

if (method.getName().equals("close")) {

pool.addLast(conn);

return null;//原有方法不调用

}

return method.invoke(conn, args);//别的方法(不匹配)正常调用

}

这样的话:调用原有的我们不需要改变的方法,修改我们想要修改的方法。

我们就省了很大的麻烦,不用去自己写Connection方法(有47个方法)

另附关于动态代理的黑马入学测试题

写一个ArrayList类的代理,其内部实现和ArrayList中完全相同的功能,并可以计算每个方法运行的时间。

package com.itheima;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.ArrayList;

import java.util.List;

//写一个ArrayList类的代理,其内部实现和ArrayList中完全相同的功能,并可以计算每个方法运行的时间。

public class ProxyDemoRetry {

public static void main(String[] args) {

final ArrayList arraylist = new ArrayList();//被内部类调用,必须使用final修饰(也可以在内部类中定义)

List proxyArrayList = (List) Proxy.newProxyInstance(

ArrayList.class.getClassLoader(),//ArrayList代理所需要调用的类加载器

ArrayList.class.getInterfaces(),

new InvocationHandler() {

public Object invoke(Object proxy, Method method,

Object[] args) throws Throwable {

Long before = System.currentTimeMillis();

Thread.sleep(1000);//进程暂停1秒,模拟运行过程

Long after = System.currentTimeMillis();

System.out.println(method.getName()+"运行的时间是"+(after-before));

return method.invoke(arraylist, args);

}

});

proxyArrayList.add(1);//这三个方法都是动态代理类对象的调用,都会去执行invoke方法

proxyArrayList.add(4);

proxyArrayList.remove(1);

}

}

/**代理类实现的接口,当然这里也可以写成是

*newObject[]{

Serializable.class,Cloneable.class,Iterable.class,Collection.class,List.class, RandomAccess.class}

*/

打印结果:

add运行的时间是1000

add运行的时间是1000

remove运行的时间是1000

哪里理解的不对,请高手指出,以便我的进步!

对别人理解有所帮助,是我的荣幸!

参考资料

1)张孝祥java高新讲解PPT;

2)网页链接:http://langyu.iteye.com/blog/410071

3)网页链接:http://www.blogjava.net/fancydeepin/archive/2012/08/27/java_proxy.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: