java语言反射与动态代理学习笔记(动态代理部分)。(学习材料张龙:“反射机制与动态代理”、jdk帮助文档、book:JAVA.2核心技术)
一、代理模式:
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式一般涉及到的角色有
抽象角色:声明真实对象和代理对象的共同接口
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装(面向对象的三大特性(继承、多态、封装)之一:)
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
代理模式也是《设计模式》(由4个人书写很有名)中的23中模式之一。
1、程序Subject.java
// 抽象角色
abstract public class Subject
{
abstract public void request();
}
2、程序RealSubject.java
public class RealSubject extends Subject
{
public RealSubject()
{
}
public void request()
{
System.out.println("From real subject.");
}
}
3、程序ProxySubject.java
//代理角色
public class ProxySubject extends Subject
{
private RealSubject realSubject; // 以真实角色作为代理角色的属性
public ProxySubject()
{
}
public void request() // 该方法封装了真实对象的request方法
{
preRequest();
if (realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.request(); // 此处执行真实对象的request方法
postRequest();
}
private void preRequest()
{
// something you want to do before requesting
}
private void postRequest()
{
// something you want to do after requesting
}
}
4、程序Client.java
//客户端调用
public class Client
{
public static void main(String[] args)
{
Subject sub = new ProxySubject();
sub.request();
}
}
二、另外,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个 代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
1、Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
(1)Interface InvocationHandler:该接口中仅定义了一个方法
public object invoke(Object obj,Method method, Object[] args)
在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。 这个抽象方法在代理类中动态实现。
(2)Dynamic Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容
protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。h是实现了 InvocationHandler 接口的对象。
static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):h是实现了 InvocationHandler 接口的对象,返回代理类的一个实例,返回后的动态代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)以下实例中的Client.java类中就是用了该方法。
(3)所谓Dynamic Proxy动态代理类是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该类的实例当作这些interface中的任何一个来用。(此处就是运用了类的动态多态性,用类的实例来赋值给该类的某个接口的意思)当然,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个h,由它接管实际的工作。
(4)所以在使用动态代理类时,我们必须实现接口InvocationHandler(即重写invoke方法)
程序 Subject.java
//抽象角色(之前是抽象类,此处应改为接口):
public interface Subject
{
abstract public void request();
}
程序 RealSubject.java
//具体角色
public class RealSubject implements Subject
{
public RealSubject()
{
}
public void request()
{
System.out.println("From real subject.");
}
}
程序 DynamicSubject.java
//代理处理器
/**
* 该代理类的内部属性为Object类,实际使用时通过该类的构造函数DynamicSubject(Object obj)对其赋值;
* 此外,在该类还实现了invoke方法,该方法中的 method.invoke(sub,args);
* 其实就是调用被代理对象的将要被执行的方法,方法参数sub是实际的被代理对象,
* args为执行被代理对象相应操作所需的参数。
* 通过动态代理类,我们可以在调用之前或之后执行一些相关操作
*/
public class DynamicSubject implements InvocationHandler
{
private Object sub;
public DynamicSubject()
{
}
public DynamicSubject(Object obj)
{
sub = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable//该方法java会去调用,我们程序中没有调用到
{
System.out.println("before calling " + method);//可以自己写一些方法执行前操作
method.invoke(sub, args);
System.out.println("after calling " + method);//可以自己写一些方法执行后的操作!
return null;
}
}
程序 Client.java
//客户端
public class Client
{
static public void main(String[] args) throws Throwable
{
RealSubject rs = new RealSubject(); // 在这里指定被代理类
InvocationHandler ds = new DynamicSubject(rs);//生成一个上文所说的h
Class<?> cls = rs.getClass();
// 以下是一次性生成代理
Subject subject = (Subject) Proxy.newProxyInstance(
cls.getClassLoader(), cls.getInterfaces(), ds);
subject.request();//调用过程:request--->上一行的ds------>ds会动态调运它invoke----->invoke方法内真实对象
}
}
Subject subject 指的是代理类
通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系
动态代理是指客户通过代理类来调用其它对象的方法
动态代理使用场合:
a、调试
b、远程方法调用(RMI)
调运顺序:客户---->代理接口(代理)---->接口(对象)
2总结:动态代理步骤
(1).创建一个实现接口InvocationHandler的类,它必须覆写invoke方法(一般为建立一个DynamicSubjet类)
(2).创建被代理的类以及接口
(3).通过Proxy的静态方法
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)创建一个代理
(4).通过代理调用方法
3.程序实践
VectorProxy.java
public class VectorProxy implements InvocationHandler
{
private Object proxyobj;
public VectorProxy(Object obj)
{
proxyobj = obj;
}
public static Object factory(Object obj)
{
Class<?> cls = obj.getClass();
return Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(), new VectorProxy(obj));
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
System.out.println("before calling " + method);
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
System.out.println(args[i] + "");
}
}
Object object = method.invoke(proxyobj, args);
System.out.println("after calling " + method);
return object;
}
public static void main(String[] args)
{
List<String> v = (List<String>) factory(new Vector<String>(10));
v.add("New");
v.add("York");
System.out.println(v.toString);
v.remove(0);
System.out.println(v);
}
}
若修改该程序的main函数中的System.out.println(v.toString);为System.out.println(v.getClass);发现程序在执行中不会再去执行invoke方法了,而是直接打印出结果而已
为什么呢?参考jdk5.0的文档可以发现在java.lang.reflect.Proxy类中说明如下:
An invocation of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of the Method object passed to invoke will be java.lang.Object. Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.lang.Object.
翻译如下:
在代理实例上的 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的调用将按照与编码和指派接口方法调用相同的方式进行编码,并被指派到调用处理程序的 invoke 方法,如上所述。传递到 invoke 的 Method 对象的声明类是 java.lang.Object。代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。
即对于 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的调用将按照invoke 方法,其他方法就像以前方法一样,正常进行,不代理。