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

黑马程序员——代理

2015-08-10 12:20 573 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1 概述

1.1 生活中的代理

比如我们要买电脑,有两种方式:第一种是直接找生厂商购买。这种购买方式虽然少了中间商抬高的价格,但是可能需要自己承担运输费用和运输过程中的风险——因为消费者和生产商可能在两个城市,甚至两个国家。第二种购买方式,就是找生产商的代理商购买。这种方式虽然需要负担一定的代理费用,但是却免去了自己承担运输商品的麻烦和费用。那么上述第二种购买商品的方式就是代理。所谓代理,我认为就是“代替你处理问题”,通过代理的方式处理问题,和自己解决问题的结果都是一样的,但是使用代理肯定更为简便,同时还具有其他的优势,才能吸引别人花费一定的代价去使用代理。

1.2 实际开发中的代理

正因为具有与现实生活中同样的好处,因此在实际开发中也会经常用到代理的方式进行开发。比如,类A是已经开发完毕,并能够正常使用的类,换句话说,我们手中只有类A的“.class”文件而没有源代码。在这种情况下,我们想为其添加一些功能,比如异常处理、日志、统计运行时间、事物管理等等功能,但是由于我们无法修改源代码,因此无法将这些功能硬编码写入到类A的“.class”文件中。从另一方面来说,即使能够修改源代码,也是一个非常麻烦的事情。
此时我们就可以通过代理的方式,为类A“添加”上述功能。既然我们要通过代理处理问题,自然需要一个类A的代理类,我们可以称之为“AProxy”。那么通常AProxy与A要实现相同的接口,这样才能具备相同的方法。因为我们说,无论是类A亲自去实现某个功能,还是代理类AProxy代替A去实现功能,主要的功能是相类似的,只不过代理类在基本功能的基础上还具有其他扩展功能,因此两者要具备相同的方法。这样做的好处是,对于调用类A方法的客户端类来说是非常方便的,因为客户端只需使用接口类型的引用,多态地接收代理类的对象,并调用从接口继承来的方法,就完全不需要修改源代码,扩展性非常好。此外,通常在创建AProxy对象的同时,要为其初始化一个A对象,因为代理不必完全由自己去实现功能,而是去调用A对象的原始方法实现基本的功能,而扩展功能只需在调用A对象原始方法的同时调用即可,就像下面的代码那样(这是一段伪代码)。
代码1:
//原始类A
class A implements Interface{
void fun(){
//---
}
}
//代理类AProxy
class XProxy implements Interface{
private A a;

XProxy(A a){
this.a = a;
}
void fun(){
//Otherfunction
a.fun();
//Otherfunction
}
}
就像代码1那样,目标类A与代理类AProxy都实现了共同的接口,因此具有相同的方法fun,都能够实现基本的功能。而代理类在调用A对象原始方法的同时,还可以定义其他代码(在两处注释位置处添加)用以扩展原始类的功能。
以上就是代理的基本原理,在不修改原始类源代码的基础上,定义与原始类实现了相同接口的代理类,通过原始类对象实现基本功能的同时,添加其他功能的方式称为代理。以下是代理机制的原理图,可以帮助大家更好的理解代理机制。



图1
根据上图所示,客户端程序起初是调用目标类Target的doSth方法,但由于需要对其进行功能扩展,因此需要一个代理类Pxory。目标类Target和代理类Proxy实现了共同的接口,并继承了相同的方法doSth。那么代理类在复写接口doSth方法的时,调用目标类Target的doSth方法来实现基本功能,并且在调用目标类对象doSth方法前后添加其他功能实现功能的扩展。客户端在调用代理类方法时,使用接口类型的引用指向代理类对象,多态地调用doSth方法,这样既不需要修改客户端程序,也不需要修改目标类程序,即可在实现基本功能的同时,进行功能扩展。当然,依旧可以通过配置文件的方式,令客户端程序在目标类和代理类之间进行切换。
举一个实际开发的例子,比如说刚刚完成了一个新的项目,需要对每个方法的执行效率进行监控,此时就要在目标类原始方法的基础上,添加监控代码执行效率的代码,此时就可以通过代理的方式,添加效率监控代码。随后,经过一段时间的测试发现该项目的测试结果良好,准备交付的时候,就可以通过修改配置文件,令客户端程序不再调用代理类,而是直接调用目标类,避免了大幅度修改源代码的麻烦。

1.3 AOP

目前在JavaEE编程中代理的应用十分广泛,因此出现了对应用代理编程的专业术语,称为面向切面编程,AspectOriented Programming,简称AOP。
在项目开发过程中,可能存在交叉业务,一个交叉业务就是要切入到另一个业务中的一个切面,如下图所示。



图2
比如说有学生管理系统、课程管理系统以及教师管理系统等多种管理系统,这些管理系统就是一个个独立的模块。。在这些模块中又都有安全系统、事务管理系统,以及日志系统,而由上图所示,安全、事务、日志等业务都是贯穿在学生、课程、教师等管理系统中的,那么它们就是不同模块中一个一个的切面。假如我们把学生管理、课程管理、教师管理系统简化为一个个的方法,然后将安全、事物日志等业务简化为一个个切面,就可以表示为下图的形式。



图3
图中每一个method就是一个业务模块,可以把它们理解为目标类的原始方法,每一个切面就是需要添加到原始方法的扩展功能,按照上图所示,就是将这些扩展功能硬编码写入到目标类的原始方法中。那么问题就来了,如果每创建一个新的模块就要重新定义不同的“切面”,是非常麻烦的,而且如果在后期还要添加其他的“切面”,只能通过修改源代码的方式,扩展性较差。那么根据AOP的思想,把不同的“切面”也模块化,只需要定义一次就可以应用到其他所有交叉业务中,而不是每次都要重新定义,这时候就需要通过代理来实现这一需求,如下图所示。



图4
上图中不再把作为扩展功能的“切面”以硬编码的方式定义到目标类(交叉业务)中,而是定义在代理类中,并在调用目标类原始方法的前后添加扩展功能,这样做的效果与图2的方式是一样的,但是图3的定义方式更为方便,并且具有良好的扩展性——如果其他业务中也需要添加相同的交叉功能,则不必重新定义,按照图3的方式即可达到效果。
那么综上所述,AOP就是利用代理思想的一种编程范式,它可以实现程序功能的统一维护。

1.4 动态代理技术

在实际开发中,我们可能要面对成百上千个类,为每个类都单独定义代理类是一件极为繁琐而庞大的工程。为了提高开发效率,人们又开发出了一种在运行期间动态生成代理类而不需要手动编写代理类源代码的技术,称为动态代理技术,通过动态代理技术生成的类称为动态代理类。动态代理技术简单说,就是通过调用某个类的某个方法,并传递代理类需要实现的一个或多个接口Class对象,那么方法返回的即是实现了指定接口的代理类实例对象,省去了手动编写源代码,以及编译生成“.class”文件的麻烦。
最后需要大家注意的是,有种可能就目标类没有实现任何接口,在这种情况下如果想要生成目标类的动态代理类,可以使用CGLIB库。他与java标准类库提供的方式类似,只不过生成动态代理类并不是指定需要实现的接口,而是指定需要继承的父类。

2 动态代理类的生成

以上的内容对代理的基本原理进行的简单说明,下面我们就来具体说说,如何将代理技术体现在代码上。

2.1 Proxy

Java标准类库中对外提供了实现动态代理技术的类——java.lang.reflect.Proxy,它是实现动态代理技术最为核心的类,我们先简单了解一下Proxy类以及相关方法。
API文档:Proxy提供了用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类(父类)。
方法:
public static Class<?> getProxyClass(ClassLoader loader,Class<?>… interfaces) throws IllegalArgumentException:向该方法中传递一个类加载器和包含有需要代理类实现的接口的数组,则返回代理类的Class对象。这个方法正是体现了前述动态代理的思想,给定若干接口,方法则会动态地返回(在运行期间)实现有指定接口的代理类Class对象,进而通过这个Class对象就可以创建代理类对象到了。由于每一个类的Class对象都是通过,类加载器加载到内存中创建出来的,因此还要为代理类Class对象的创建指定一个类加载器。

2.2 创建一个简单的动态代理类

下面我们将利用Proxy类,创建一个Collection接口的动态代理类Class对象,并列出该代理类的所有构造方法和方法。根据Proxy类的静态方法getProxyClass的说明,需要指定一个类加载器和若干接口,则会返回实现了指定接口的动态代理类Class对象。再通过Class对象的getConstructors和getMethod方法遍历该代理类的所有构造方法和方法。演示代码如下。
代码2:
i
mport java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class ProxyTest {
public static void main(String[] args) {
//获取到实现了Collection接口的代理类Class对象
Class<Collection> collectionProxyClazz = (Class<Collection>)Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(collectionProxyClazz);

//列出代理类所有的构造方法
listConstructor(collectionProxyClazz);
//列出代理类所有的方法
listMethod(collectionProxyClazz);
}
private static void listConstructor(Class<?> clazz){
System.out.println("-----Constructor List-----");

StringBuilder sb = new StringBuilder();
Constructor[] constructors = clazz.getConstructors();
for(Constructor constructor : constructors){
String modifier = Modifier.toString(constructor.getModifiers());
sb.append(modifier+" ");

String name = constructor.getName();
sb.append(name+"(");

Class[] parameterTypes = constructor.getParameterTypes();
if(parameterTypes.length != 0){
for(int i=0;i<parameterTypes.length; i++){
if(i !=parameterTypes.length-1)
sb.append(parameterTypes[i].getSimpleName()+",");
else
sb.append(parameterTypes[i].getSimpleName()+")");
}
}
else
sb.append(")");

Class[] exceptionTypes = constructor.getExceptionTypes();
if(exceptionTypes.length != 0){
sb.append(" throws");
for(int i=0;i<exceptionTypes.length; i++){
if(i !=exceptionTypes.length-1)
sb.append(exceptionTypes[i].getClass().getSimpleName()+",");
else
sb.append(exceptionTypes[i].getClass().getSimpleName());
}
}

System.out.println(sb.toString());
sb.delete(0, sb.length());
}
}
private static void listMethod(Class<?> clazz){
System.out.println("-----Method List-----");

StringBuilder sb = new StringBuilder();
Method[] methods = clazz.getMethods();
for(Method method : methods){
String modifier = Modifier.toString(method.getModifiers());
sb.append(modifier+" ");

String retType = method.getReturnType().getSimpleName();
sb.append(retType+" ");

String name = method.getName();
sb.append(name +"(");

Class[] parameterTypes = method.getParameterTypes();
if(parameterTypes.length != 0){
for(int i=0;i<parameterTypes.length; i++){
if(i != parameterTypes.length- 1)
sb.append(parameterTypes[i].getSimpleName()+",");
else
sb.append(parameterTypes[i].getSimpleName()+")");
}
}
else
sb.append(")");

Class[] exceptionTypes = method.getExceptionTypes();
if(exceptionTypes.length != 0){
sb.append(" throws");
for(int i=0;i<exceptionTypes.length; i++){
if(i != exceptionTypes.length- 1)
sb.append(exceptionTypes[i].getSimpleName()+",");
else
sb.append(exceptionTypes[i].getSimpleName());
}
}

System.out.println(sb.toString());
sb.delete(0, sb.length());
}
}
}

执行结果为:
class com.sun.proxy.$Proxy0
-----Constructor List-----
public com.sun.proxy.$Proxy0(InvocationHandler)
-----Method List-----
public final boolean add(Object)
public final boolean remove(Object)
public final boolean equals(Object)
public final String toString()
public final int hashCode()
public final void clear()
public final boolean contains(Object)
public final boolean isEmpty()
public final Iterator iterator()
public final int size()
public final Object[] toArray(Object[])
public final Object[] toArray()
public final Spliterator spliterator()
public final boolean addAll(Collection)
public final Stream stream()
public final void forEach(Consumer)
public final boolean containsAll(Collection)
public final boolean removeAll(Collection)
public final boolean removeIf(Predicate)
public final boolean retainAll(Collection)
public final Stream parallelStream()
public static boolean isProxyClass(Class)
public static transient ClassgetProxyClass(ClassLoader,Class[]) throws IllegalArgumentException
public static InvocationHandlergetInvocationHandler(Object) throws IllegalArgumentException
public static ObjectnewProxyInstance(ClassLoader,Class[],InvocationHandler) throwsIllegalArgumentException
public final void wait() throwsInterruptedException
public final void wait(long,int) throwsInterruptedException
public final native void wait(long) throwsInterruptedException
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
代码说明:
(1) 调用getProxyClass方法时,传递的类加载器对象是加载Collection接口Class对象的类加载器,这是通常的做法,也可以指定其他类加载器。
(2) 从getProxyClass的方法声明中可以看出,由于定义有可变参数列表因此可以传递多个接口Class对象。为了兼容JDK1.4也可以传递包含有多个接口Class对象的数组。
(3) 动态代理类唯一的公有构造方法需要传递一个InvocationHandler的对象, 它的主要功能就是用于定义扩展功能,因此非常重要,下面会详细进行介绍。
既然可以获得动态代理类的Class对象,那么离创建动态代理类的实例对象也就不远了。代码2的执行结果表明,动态代理类的构造方法接收一个InvocationHandler类型的对象,它实际上就是代理类在目标类原始方法基础上进行功能扩展的核心,因此我们首先要了解InvocationHandler接口。
InvocationHandler是位于java.lang.reflect包中的接口,其API文档描述如下。
API文档:
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法。当代理类的方法被调用时,将通过其内部的InvocationHandler对象,利用反射间接的调用方法。之所以要通过这样繁琐的方式,是因为除了目标类原始方法的功能以外,更重要的是还要定义额外功能,而这些额外功能就要定义在InvocationHandler接口实现类invoke方法中。
方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable:在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。每调用一次代理类对象的方法,都会自动通过调用此invoke方法来实现。由于是利用反射调用方法,因此需要传递被调用方法的对象(method),并指定调用哪个对象的指定方法(proxy),并传递调用方法所需的参数(args)。在invoke方法内部,我们可以调用代理类内部目标类的原始方法,并在原始方法前后添加额外功能,我们将在下面的内容中进行演示。
代码3:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
importjava.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class ProxyTest2 {
public static void main(String[] args) throws Exception {
Class<?> collectionProxyClazz = Proxy.getProxyClass(
Collection.class.getClassLoader(), Collection.class);
//获取接收InvocationHandler对象为参数的代理类构造方法对象
Constructor constructor = collectionProxyClazz.getConstructor(InvocationHandler.class);
/*
创建代理类实例对象
使用匿名内部类的方式创建InvocationHandler实现类对象
复写invoke方法
*/
Collection collection = (Collection)constructor.newInstance(new InvocationHandler(){

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}

});

System.out.println(collection);
System.out.println(collection.toString());
collection.clear();
collection.size();
}
}
执行结果为:
null
null
Exception in thread "main" java.lang.NullPointerException
atcom.sun.proxy.$Proxy0.size(Unknown Source)
atProxyTest2.main(ProxyTest2.java:34)
代码说明:
(1) 通过代理类构造方法对象创建代理类实例对象时,由于InvocationHandler是一个接口,因此必然要传递它的实现类实例对象,而由于只需要复写一个方法,因此采用匿名内部类的方式是最为方便的。
(2) 我们在复写invoke方法时,并没有定义任何实际内容,这是我们下面将要介绍的重点,这里仅仅演示一下创建代理类的简单格式。
(3) 由于我们指定代理类是Collection接口的实现类,因此代理类的实例对象可以通过Collection类型的引用变量接收。
(4) 直接打印代理类实例对象的结果为null,而打印显示调用toString方法的结果依然为null。这就表示代理类的实例对象并非为空,只是toString方法的返回值为空。这是因为代理类的所有方法都要间接地通过invoke方法进行调用,也就是说,每调用一个方法实际都是在调用invoke方法,而method参数就是被调用方法的Method对象。由于代码3中invoke方法并没有定义任何内容,因此返回值只能是null。
(5) 然而,在调用size方法时,还是抛出了异常,我们将在下面的内容中进一步进行解释。

2.3 利用InvocationHandler定义额外功能

实际上除了先获取代理类Class对象,再创建代理类实例对象的方法以外,Proxy类还提供了一个静态方法可以将以上两个步骤合并为一步——newProxyInstance,其API文档如下。
public static Object newProxyInstance(ClassLoader loader, Class<?> interface,InvocationHandler h) throws IllegalArgumentException:返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。该方法的底层原理与代码3是一样的,只不过在方法内部自动完成了代理类Class对象的创建、构造方法对象的获取,以及代理类实例对象的创建,并且所需的参数也是相同的。为了简化代码书写,我们将使用此方法进行演示。
正如第一部分中所述,在定义代理类时,一定要指定一个目标类对象,而目标类对象的作用就是实现方法的最基本功能,这正是代码3为什么调用方法仅返回null,或者直接抛出异常的原因。下面我们通过在InvocationHandler内部定义一个ArrayList对象作为目标类对象,通过该对象来实现原始功能,在此基础上添加一个计算方法执行时间的额外功能

代理类在目标类功能基础上添加的额外功能可以以多种形式添加,或者说额外功能相对原始功能的执行顺序可以是多样的,有以下四种情况:
(1) 在调用目标方法之前;
(2) 在调用目标方法之后;
(3) 在调用目标方法前后;
(4) 在处理目标方法异常的catch块中。
由于我们要计算方法的运行时间,因此额外功能的定义既要在调用目标方法前,又要在调用目标方法后。
代码4:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyTest3 {
public static void main(String[] args) {
Collection collection = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
//创建一个目标类对象
ArrayList target = newArrayList();

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在调用目标类原始方法前后添加额外功能
long startTime = System.currentTimeMillis();

//调用目标类的原始方法,实现基本功能
Object retValue = method.invoke(target,args);

//在调用目标类原始方法前后添加额外功能
long endTime =System.currentTimeMillis();

//计算每个方法的执行时间
System.out.println(method.getName()+": "+(endTime - startTime)+" ms");
return retValue;
}

});

collection.add("David");
collection.add("Peter");
collection.add("George");
System.out.println("size : "+collection.size());
}
}
执行结果为:
add : 0 ms
add : 0 ms
add : 0 ms
size : 0 ms
size : 3
代码说明:
(1) 在InvocationHandler实现类对象内,通过硬编码的方式创建了一个ArrayList对象作为目标类对象。那么每次通过反射的方式调用代理类对象方法时,实际就是在调用目标类的同名方法,来实现基本功能。这就是为什么代理类一定要实现与目标类相同接口的原因。由于代码4中指定了目标类对象,因此代理类对象的size方法能够得以正常执行。
(2) 正像前文所述,代码4中我们将额外功能定义在了基本功能前后,然后在方法结束之前计算并打印了方法执行时间。通过指定的目标类对象,来实现基本功能,或者说基本业务,然后在调用基本功能前后添加额外功能,或者说交叉业务。
(3) 当我们在调用代理类的方法时,所有方法的执行都是通过调用InvocationHandler实现类对象的invoke方法实现的。之所以这么设计,就是为了避免为代理类的每个方法都重新扩展功能的麻烦,只需将额外功能统一定义在invoke方法。那么每调用一个代理类方法,实际在底层是通过代理类Class对象获取到被调用方法的Method对象,传递给invoke方法,进而在invoke方法内部,去调用Method对象参数的invoke方法,但此时传递给Method对象的被调用对象是目标对象,而不是代理类对象了,以此实现基本功能。大家需要注意的是,在invoke方法内部调用Method对象的invoke方法时,千万不要再次传递代理类对象,否则会出现方法调用的无线循环。
从执行的结果来看,每调用一次add方法,都会打印方法执行时间,包括调用size方法也是一样的效果,其中“ms”表示的就是毫秒单位,由此也就证明了代理类方法调用均要通过invoke方法实现。
(4) 注意要将指向目标类对象的引用定义在InvocationHandler匿名内部类的成员位置,或者作为参数传入,否则如果定义在invoke方法内部,那么每次调用某个方法时,实际都是对不同的目标类对象进行操作。后面我们还会对代码4进行进一步修改,提高其扩展性和健壮性。

2.4 动态代理类底层实现原理

其实关于动态代理类的实现原理,上面已经说了很多,这一部分会从代码层面更为形象的进行说明。
以下列出了代理类,或者更准确称为$Proxy0类的底层实现代码,这是通过对以上创建动态代理类对象、定义InvocationHandler匿名内部类,以及复写其invoke方法等等过程进行反推得到的,只能反映大致的原理。
代码5:
import java.lang.reflect.InvocationHandler;

public class My$Proxy0 {
private InvocationHandler handler;

My$Proxy0(InvocationHandler handler){
this.handler = handler;
}

//这里假设此代理类实现了Collectioin接口
//仅以size方法为例
public int size() throws Throwable {
return ((Integer)handler.invoke(this, this.getClass().getMethod("size"), null)).intValue();
}
}
代码说明:
(1) 由于$Proxy0类仅有一个接受InvocationHandler类型对象的构造方法,因此其内部必然定义有如上所示的构造方法,并且,为了接受InvocationHandler类型的对象,还需在成员位置上定义有私有InvocationHandler类型的成员变量。
(2) 对于方法的定义,由于每个方法都需要通过invoke方法进行调用,因此代理类每个继承自指定接口的方法直接返回调用InvocationHandler对象invoke方法的返回值即可。根据invoke方法的参数列表,分别传递代理类本类对象,被调用方法的Method对象,以及调用该方法所需的参数列表,以上这三个参数也就是根据代码4中复写的invoke方法的三个参数。
(3) 调用代理类方法的返回值,直接地是来自调用InvocationHandler对象的invoke方法的返回值,而此返回值最终是来自调用目标类对象的方法的返回值。可以在InvocationHandler中的invoke方法中定义一个过滤器,对于从代理类中传递来的参数进行判断,如果不满足条件则不继续传递给目标类对象方法的调用中,或者对不满足条件的参数进行统一的修改等等。这是在代理类方法调用与目标类方法调用之间增加一个中间层(InvaocationHandler)的另一个好处。
(4) 这里就可以通过代码5解释为什么在代码3中,调用size方法会报错。由于InvocationHandler匿名内部类的invoke方法返回值是Object类型的,因此即使调用目标类对象的size方法返回一个int型常量,也会自动装箱为一个Integer对象。但是此Integer对象最终要作为代理类size方法的返回值,因此要将Integer对象转换为int型常量,需要像代码5那样先将Object对象强转为Integer,再调用intValue方法。但是在代码3中,由于InvocationHandler的invoke方法返回null,也就无法对其进行类型转换,根不能调用intValue方法,否则就会抛出异常。而add方法没有返回值,即使返回null,也不会抛出异常。
(5) 实际上,并非所有的代理类方法均由通过InvocationHandler的invoke方法实现,对于从Object类继承来的若干基本方法中,只有hashCoade、equals,以及toString方法通过invoke方法实现,而其他方法则都会进行具体的定义,比如getClass方法。无论在代码3还是在代码4中中执行代码
System.out.println(collection.getClass());
打印到控制台的均为“class com.sun.proxy.$Proxy0”,表明getClass方法的实现与invoke方法无关。
下图更为形象的说明了代理机制中,客户端、代理类、InvocationHandler以及目标类之间方法调用的方法调用关系。图中的otherFun方法代表了需要添加的额外功能。



图5

2.5 额外功能的封装

无论是代码4中计算方法执行时间的代码,还是图4中,otherFun()方法的调用,额外方法(或称为交叉业务),总是以硬编码的形式添加到了InvocationHandler匿名内部类的invoke方法中。这样做还是不够优化,因为可能需要为某个功能添加多个交叉业务,反过来某个业务也可能需要添加到多个功能中,如果采用硬编码的方式,就需要反复定义多个功能,这是十分繁琐的,最好的方式是将交叉业务封装为一个模块,或者说封装为一个类,在创建代理类对象的时候,为其初始化指定的交叉业务类对象,那么额外功能的实现仅仅需要调用交叉业务类的方法即可。那么对额外功能进行封装,才是真正将交叉业务转化为一个切面,体现了AOP的思想。
除此以外,目标类对象与额外功能类对象一样也是需要调用者指定的,因此也不应该硬编码写入到InvocationHandler的invoke方法中——因为一个接口可能有多个实现类,因此不能将实现类写死。
那么具体的实现方式就是在创建InvocationHandler对象的时候,为其初始化一个目标类对象和额外功能类对象,调用目标类原始方法,以实现基本功能,而调用额外功能类对象方法,来实现额外功能。这里的初始化并一定指的就是通过构造方法进行初始化,也可以将创建代理类对象的过程封装为一个方法,方法的两个参数分别是目标类对象和额外功能类对象。

1) 定义额外功能类

直接将额外功能类封装为一个单独类是不合适的,还需要考虑到扩展性的问题。因为可能要向代理类中添加不同的额外功能,那么每次更换功能,就都需要修改InvocationHandler类的invoke方法的代码,以调用不同额外功能类的不同方法。因此最好的方式就是为若干额外功能类定义一个共同的接口,那么这些额外功能相对目标类基本方法的调用顺序应是相同的。在invoke方法中使用接口类型的引用指向额外功能类对象,并调用接口中定义的统一方法,这样的做法具有较好的扩展性,代码如下。
代码6:
//额外功能类统一接口
import java.lang.reflect.Method;

public interface Advice {
public abstract void beforeMethod(Method method);
public abstract void afterMethod(Method method);
}
//额外功能类
import java.lang.reflect.Method;

public class MyAdvice implements Advice {
long startTime, endTime;

@Override
public void beforeMethod(Method method) {
startTime = System.currentTimeMillis();
}

@Override
public void afterMethod(Method method) {
endTime = System.currentTimeMillis();
System.out.println(method.getName()+" :"+(endTime-startTime)+" ms");
}
}
代码说明:
(1) 通常额外功能类的类名会定义为Advice,表明了这是对目标类对象原始方法的一个建议。实际开发中,Advice中会定义有4种方法,分别代表与目标类原始方法的4种相对调用顺序,也就是代表了4种不同的功能。
(2) 为了能够打印不同方法的方法名,需要在调用额外功能时传递目标类原始方法的Method对象。
代码7:
//测试类以及获取代理类对象的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyTest4 {
public static void main(String[] args) {
ArrayList target = new ArrayList();
MyAdvice advice = new MyAdvice();

Collection collection = (Collection)getProxyInstance(target,advice);
collection.add("SONY");
collection.add("Microsoft");
collection.add("Nintendo");
System.out.println(collection.size());
}
private static Object getProxyInstance(final Object target, final Adviceadvice) {
Object proxyInstance = Proxy.newProxyInstance(
//为代理类指定与目标类相同的类加载器
target.getClass().getClassLoader(),
//为代理类指定与目标类相同的接口
target.getClass().getInterfaces(),
new InvocationHandler(){

@Override
public Object invoke(Objectproxy, Method method, Object[] args) throws Throwable {

advice.beforeMethod(method);

Object retValue =method.invoke(target,args);

advice.afterMethod(method);

return retValue;
}
});

return proxyInstance;
}
}
执行结果为:
add : 0 ms
add : 0 ms
add : 0 ms
size : 0 ms
3
代码说明:
(1) 代码7实现了与代码4相同的功能,并且具有更好的扩展性。由于目标类对象和额外功能类对象均是以方法参数的形式进行指定,而不是硬编码的形式写入到源代码中,因此可以非常灵活的更改代理类所代理的目标类,以及在代理类中索要添加的额外功能。若要在代理类中添加其他额外功能只需,定义一个新的Advice接口实现类,并复写beforeMethod和afterMethod方法,然后在调用getProxyInstance方法时,传递新的Advice实现类即可。通过上述方法,则避免了修改getProxyInstance源代码(实际开发中就是客户端代码)的麻烦。
(2) 由于匿名内部类需要访问getProxyInstance方法的局部变量target和advice,因此这两个参数需要在参数列表中被final修饰。

3 动态代理综合应用——类Spring小框架

在上面的内容中,我们首先对AOP以及动态代理的基本原理进行了简单介绍,并根据这一原理,实现了动态创建指定目标类的代理类对象的功能,并为代理类添加了额外功能。下面我们将应用以上所学知识,自定义一个类似Spring的小框架。

3.1 自定义框架需求

定义一个能够返回JavaBean的工厂类——BeanFactory。该类定义有一个名为“getBean”的方法,调用该方法时传递一个字符串形式的标识符,BeanFactory则返回配置文件中与此标识符对应的JavaBean对象。若要操作不同的JavaBean对象,只需要修改配置文件即可。JavaBean类大体分为两种,一种是简单目标类,一种是代理类。因此BeanFactory需要对从配置文件中读取的JavaBean类名进行判断,如果是简单目标类类,则直接返回该类的实例对象;若是代理类,则继续读取配置文件中相关的目标类类名,以及额外功能类类名,并通过代理类对象,利用目标类和额外功能类,创建一个代理类对象并返回。
在该需求中我们将代理类类名定义为ProxyFactoryBean。因为代理类本身就是一种JavaBean,因此类名还是要以Bean结尾,而前面的ProxyFactory则表示了此JavaBean的功能——用于创建代理类。ProxyFactoryBean要与上面提到的BeanFactory进行区分,BeanFactory是专门用于返回用户指定JavaBean类名的JavaBean对象,而ProxyFactoryBean本身作为一个JavaBean,其功能是根据用于指定的目标类和额外功能类创建一个代理类,因此ProxyFactoryBean的作用也像是一种工厂。

3.2 BeanFactory

下面我们就根据以上需求自定一个类Spring的AOP框架。首先是BeanFactory类。
代码8:
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class BeanFactory {
private Properties prop = new Properties();

public BeanFactory(InputStream inStream){
try {
prop.load(inStream);
} catch (IOException e) {
e.printStackTrace();
}
}

public Object getBean(StringbeanName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String className = prop.getProperty(beanName);

Class<?> clazz = null;
Object bean = null;
try {
clazz = Class.forName(className);
bean = clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessExceptione) {
e.printStackTrace();
}

if(bean instanceof ProxyFactoryBean){
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;

Advice advice = (Advice)Class.forName(prop.getProperty(beanName+".advice")).newInstance();
Object target =Class.forName(prop.getProperty(beanName+".target")).newInstance();

proxyFactoryBean.setTarget(target);
proxyFactoryBean.setAdvice(advice);

Object proxy = proxyFactoryBean.getProxyInstance();

return proxy;
}
else
return bean;
}
}
代码说明:
(1) 这里的配置文件依旧是与一个字节读取流进行关联,然后装载到一个Properties集合中。然后使用表示JavaBean类名的字符串标识符去到Properties集合查找对应的JavaBan类名。
(2) 创建JavaBean对象的时候,代码8中使用的是JavaBean类的无参构造方法,因此通常在定义JavaBean类的时候,一定要定义一个无参构造方法。
(3) BeanFactory类中定义了一个用于返回JavaBean对象的方法——getBean。在此方法中对创建的JavaBean对象类型进行判断,如果是ProxyFactoryBean类型,则调用其getProxyInstance方法创建代理类对象,否则表示是一个简单类,直接返回。
(4) 当判断出创建的JavaBean对象为ProxyFactoryBean以后,若要创建代理类对象,还需指定目标类对象和额外功能类对象,因此要分别在Properties集合中查找“bean.target”和“bean.advice”这两个键对应的类名,并分别创建这两个类的对象。随后将这两个对象设置到ProxyFactoryBean中。本例中使用到的目标类依旧设定为ArrayList,而额外功能类也同样定义为Advice接口的实现类MyAdvice。

3.3 ProxyFactoryBean

下面定义ProxyFactoryBean类。该类是一个专门用于创建代理类对象的类,因此需要定义代码8中调用的getProxyInstance方法,由于其创建代理类对象的原理与代码7是一样的,因此这里不再进行详细说明。需要提一点的是,代码7中实现创建代理类对象的是一个方法,而这里我们将其封装为了一个类,因此目标类和额外功能类将被定义为ProxyFactoryBean的两个私有成员,并通过与这两个成员对应的set方法进行设置,代码如下。
代码9:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactoryBean {
private Advice advice;
private Object target;

public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}

public Object getProxyInstance() {
Object proxyInstance = Proxy.newProxyInstance(
//为代理类指定与目标类相同的类加载器
target.getClass().getClassLoader(),
//为代理类指定与目标类相同的接口
target.getClass().getInterfaces(),
new InvocationHandler(){

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

advice.beforeMethod(method);

Object retValue = method.invoke(target,args);

advice.afterMethod(method);

return retValue;
}
});

return proxyInstance;
}
}
至此,两个主要的类就都定义完毕。本演示示例中,创建代理类对象的JavaBean是ProxyFactoryBean,而将简单类设定为ArrayList,进行对比演示。

3.4 测试

下面我们再定义一个测试类,对以上自定义AOP框架进行测试。
代码10:
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;

public class AopFramewokTest {
public static void main(String[]args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String propPath = "config.properties";
InputStream inStream = AopFramewokTest.class.getClassLoader().getResourceAsStream(propPath);

BeanFactory bf = new BeanFactory(inStream);
Collection bean = (Collection)bf.getBean("bean");
bean.add("XBox");
bean.add("PlayStation");
bean.add("Wii");
System.out.println(bean.size());
}
}
//配置文件如下
bean=java.util.ArrayList
#bean=cn.tju.day3.aopframework.ProxyFactoryBean
bean.advice=cn.tju.day3.MyAdvice
bean.target=java.util.ArrayList
配置文件中bean键的值为ArrayList时的执行结果为:
3
当bean键的值为ProxyFactoryBean时的执行结果为:
add : 1 ms
add : 0 ms
add : 0 ms
size : 0 ms
3
相对于简单类,代理类能够计算并打印方法的执行时间。以上就是我们自定义的类Spring小框架,通过在配置文件中指定不同的JavaBean类名,即可由BeanFactory对象返回相对应的JavaBean对象。当配置文件中的JavaBean为代理类时,则能够自动生成配置文件中指定的目标类和额外功能对象,并据此创建出代理类对象。

3.5 小问题

张孝祥老师的代理部分视频的最后提到了一个问题:在BeanFactory类getBean方法中,如果在通过读取配置文件创建target对象时,即使将原本应传递表示traget类类名的键值“bean.target”误传为了表示额外功能类的键值“bean.advice”,也不会出现任何问题。
这个问题从两方面来说,一方面,如果将target对象指定为MyAdvice对象实际从原理来说是没有任何问题,只不过我们原本想创建一个ArrayList的代理类对象,却创建成了MyAdvice对象的代理类对象,并且为其添加的额外功能还是MyAdvice对象的额外功能而已。另一方面,这个问题最终没有暴露出来的原因是,在张孝祥老师的代码中,创建了代理类对象以后,既没有将该对象强转为Collection类型,也没有调用Collection接口的任何特有方法。如果尝试将类型转换就是抛出异常,并提示“com.sun.proxy.$Proxy0
cannot be cast tojava.util.Collection”,也就是说,实现了Advice接口的代理类无法转换为Collction类型对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: