黑马程序员——动态代理技术

来源:互联网 发布:写作软件哪个好 编辑:程序博客网 时间:2024/06/05 07:15

------- android培训、java培训、期待与您交流! ----------

相关概念

生活中的代理

生活中的代理随处可见:相比比从商品的源公司直接购买商品和获得服务,人们从代理商那购买商品和获得服务更加方便和节约时间、金钱成本。

程序中的代理

    需求:为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如:异常处理、日志、计算方法的运算时间、事物管理等。
    思路:
1)编写一个与目标类具有相同接口的代理类;
2)代理类的每个方法调用目标类的相同方法;
3)并在调用方法时加上系统功能的代码。

:图1:代理构架图(重要),需理解图的含义,并且会自己画。

面向切面编程AOP

    需求:安全、事务、日志等功能要贯穿到好多个模块中,所以它们就是交叉业务。

    原理:我们不可能去修改方法,深入到方法内部去修改,但是我们能让用户感觉到就是在方法前或后加了代码,就把切面代码转移到方法执行前和执行后。

    重要原则:“不要把供应商暴漏给你的客户”。

图2:面向切面编程AOP图解。

动态代理技术及CGLIB

    分析:要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,且全部采用静态代理方式将需要写成百上千的代理类;JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类;

    动态代理及CGLIB库:JVM生成动态类必须实现一个或多个接口,所以JVM生成的动态类智能用作具有相同接口的目标类的代理;而CGLIB库可以动态生成一个类的子类,一个类的子类也可以用用该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库;代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的四个位置加上系统功能代码:

1)在调用目标方法之前;

2)在调用目标方法之后;

3)在调用目标方法前后;

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

知识点与代码

Proxy类

Proxy类位于java.lang.reflect.Proxy包,它提供了一个核心方法 static Class<?> getProxyClass(ClassLoaderloader, Class<?>... interfaces):返回代理类的java.lang.Class对象,并向其提供类加载器和接口数组。

创建动态代理,代码

在代码通过演练分析来解决创动态建代理类的一个个问题,为分析、创建完善的动代理类做准备;其中通过三种方式的演变:

方式一:创建代理,再指定要实现的接口并得到接口的类加载器,单独写一个内部类获取实例对象;
方式二:采用内部类方式,合并创建代理类和实例对象;
方式三:用Foo f =(Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[]{Foo.class},handler);方法,一步创建出代理对象。

总结思考方式一到方式二

让JVM创建动态类及其实例对象,需要给它提供三个方面的信息:

1)生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;

2)产生的类字节码必须有一个关联的类加载器对象;

3)生成类的方法的代码是怎样实现的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码;提供执行代码的对象就是那个InvocationHandler对象,它是在创见动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。

4)而用Proxy.newProxyInstance方法直接一步就创建出代理对象。

总结思考方式二到方式三

java.lang.reflect.Proxy包下的类Proxy,方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) :返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于:

Proxy.getProxyClass(loader, interfaces).         getConstructor(new Class[] { InvocationHandler.class }).         newInstance(new Object[] { handler });

参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

抛出:

IllegalArgumentException - 如果违反传递到getProxyClass 的参数上的任何限制
NullPointerException - 如果interfaces 数组参数或其任何元素为null,或如果调用处理程序h 为null

分析InvocationHandler对象的运行原理

猜想分析动态生成的类的内部代码:

1)动态生成的类实现了Collection接口(可以实现多个接口),生成的类有Collection接口中的所有方法和一个如下接收InvocationHandler参数的构造方法;

2)构造方法接收一个InvocationHandler对象,接收对象了要干什么用呢?该方法内部代码是怎样实现的呢?

3)实现Collection接口的动态类中的各个方法的代码又是怎样的呢?

InvocationHandler接口中定义的invoke方法接收的三个参数分析如下:

Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象、add方法、“abc”参数;

Class Proxy${

    add(Objectobject){

        returnhandler.invoke(Object proxy, Method method, Object[] args);

    }

}

其他

1、当代码中打印一个对象为null时,可以有两种情况:
1)对象null;
2)toString方法返回值为null,当这种情况下,继续调用有返回值的方法就报错(如proxy.size()方法,不能传入值为null的参数),而没有返回值的方法则不报错(如clear()方法返回值为void,return null本来也相当于返回值为void)。
2、通过阅读JDK源码,发现Proxy类只是被委托了hashCode、equals、toString方法,别的方法都自己复写了?

分析动态代理的设计原理与结构

分析过程

1、在图中的log()小圈注释的地方,不写成硬性的代码,把(系统功能)代码作为一个参数传递进去,做成框架;那么可不可以将代码写成字符串传递进来?在Java script有这样的功能;Java script是一种动态语言,可以将代码在程序运行时,植入进去一段代码让程序执行——Java现在还没有这个功能(以后Java版本可能有这样功能?);


图3:动态代理的工作原理图。

2、那么在Java中怎么实现上述功能呢?分三步分析:
1)将目标类传进去:
    a、直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义;
    b、为InvocationHandler实现类侏儒目标类的实例对象,那就不能采取匿名内部类的形式了;
    c、则让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
2)将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标的同时返回代理对象,让调用者更“懒惰”、方便得调用,甚至调用者不用接触任何代理的API;
3)将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供呢?
    a、把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,就等于执行了外界提供的代码;
    b、为bind方法增加一个Advice参数。
3、这就是面向切面编程。
1)简单说来:上面的三步分析,不是传递代码进去,而是传递进去一个对象,让对象被执行;那么是执行这个对象的方法(不是对象),把要想执行的代码放在这个对象的方法里;执行这个对象的方法,就等效于执行了想要执行的代码。
2)那么给InvocationHandler要传递两个对象:内部要调用目标,则把目标传进去;把系统功能也做成对象传进去。

可生成代理和插入通告的通用方法,代码

通过上面的分析,在代码中实现:定义一个契约(接口Advice);实现这个契约的类(MyAdvice);生成黑匣子方法。

总结

小框架
上述代码就实现了一个小小的框架,Spring原理也就是实现上面的功能:
1)配置文件上配置目标、动态代理类、功能类等;
2)生成具体的代理;
3)给定目标、系统功能。
以后运用到Spring大致也是只完成一件事:写MyAdvice。
invoke 方法
java.lang.reflect包下的接口InvocationHandler提供了一个public abstract方法Object invoke (Object proxy,Method method, Object[] args)  throws Throwable: 在代理实例上处理方法调用并返回结果。
参数:
    proxy - 在其上调用方法的代理实例
    method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
    args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如java.lang.Integer或 java.lang.Boolean)的实例中。

返回:

        从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出


实现类似Spring的可配置的AOP框架分析

实现AOP功能的封装与配置

步骤分四步:

1)工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象;如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象;否则,返回该类实例对象的getProxy方法返回的对象;

2)BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

#xxx=java.util.ArrayList——符号“#”这里代表注释

xxx=cn.itcast.ProxyFactoryBean

xxx.target=java.tuil.ArrayList

xxx.advice=cn.itcast.MyAdvice

3)ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂类提供两个配置参数信息:目标、通知;

4)编写客户端应用:

    a、编写实现Advice接口的类和在配置文件中进行配置;

    b、调用BeanFactory获取对象。

代码

总结

    Spring的两大核心技术:BeanFactory和AOP框架都在上面的代码中体现了;根据需求选择想要代理和不想要代理,都是在配置文件里配。

0 0
原创粉丝点击