---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------
1、 代理概述
(1)我们平时说的代理就是受委托的意思,如代理商,是厂商与最终消费者间的中介商,它对产品的一些方面有一定的控制作用。而这里说的是程序中的代理,准确的说是一种设计模式,即代理模式。
(2)程序中的代理:要为已存在的多个具有相同接口的目标类(委托类)的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理等等。
(3)关系:在这里我把目标类称之为委托类,为委托类的增加功能的类称之为代理类。代理类就相当于委托类加了一个壳,这个壳能控制外部对内部的访问。
且代理类与委托类之间需存在关联,这层关联便是要实现相同的接口。但代理类不是真正主要功能的实现者,只是对主要功能进行一定的控制,它还是得调用委托类的主功能,就像代理商一样,它不是产品的生产者,产品还是得用厂商的。
(4)来看一个简单的示例,怎么体现代理的概念的,它采用的静态代理,其概念稍后与动态代理一起说明,代码如下:
//定义一个接口,都要实现的接口 interface InterSay { public void sayHolle(); } //目标类(委托类) class Englisher implements InterSay { @Override public void sayHolle() { System.out.println("Hello"); } } //代理类 class ProxyClass implements InterSay { private InterSay iSay; public ProxyClass (InterSay iSay) { this.iSay = iSay; } @Override public void sayHolle() { System.out.println("学了中文,会两种问好"); //用的主要功能是委托类的,好比英国人说Hello才是母语 iSay.sayHolle(); //代理类增加的附加功能 System.out.println("你好"); } } public class ProxyDemo { public static voidmain(String[] args) { //不需提供额外的功能的 //也可以写成InterSay people = new Englisher();多态性 Englisher people =new Englisher(); people.sayHolle(); //需提供额外的功能,又不想改接口和委托类,就用代理类 ProxyClass pxy =new ProxyClass (people); pxy.sayHolle(); } }
(5)代理模式的一点好处
如果配合工厂模式和配置文件的方式进行管理,则不需要修改客户端的调用程序,只需在配置文件中配置实用委托类还是代理类就行了,这样容易进行切换。例如,想要程序有日志功能,就配置实用代理类,不想就使用委托类,这样增加和删除某些系统功能都很容易。
(6)代理模式和装饰模式区别
如果你了解,你会发现这两者十分相像,实现与目标类共同的接口,给目标类功能增强等等,稍微改点代码就互通了。但是它们在实现和主要功能上还是有区别的。
(a) 实现上静态代理几乎没区别,但是动态代理需要反射机制实现。
(b) 功能上虽说两者都可以起到附加的功能,但代理只是为了简单理解,实际代理主要是对目标对象的访问控制,而装饰主要就是增强功能。
(c) 代理会将目标对象固化到代理类中,更好的隐藏了目标对象的细节,而装饰模式,需要装饰时将目标对象作为一个参数传入装饰类的构造函数中。
2、 面向方面的编程(AOP)
面向方面的编程(Aspect oriented program ,简称AOP),即针对交叉业务的编程问题。要理解AOP,得先理解交叉业务是什么。
(1)一个交叉业务就是要切入系统中的一个方面,如下图:
那么在类A,B,C中的某些方面都会有考虑安全,事务,日志等功能,从图中看就是这些功能贯穿到了很多模块中,那么它们就是交叉业务,从代理模式的角度看,他们其实就是附加的系统功能。
(2)从具体的程序去描述,如下图所示:
图中方法分别处于三个类中,我们可以发现把每个方法都要处理交叉业务,横向切出来看,会产生一个有共同交叉业务的切面,我们就是要优化这些重复的业务代码,使这些交叉业务模块化,它不是整个类的处理,而是一个面的处理,那么针对交叉业务的编程问题即为面向方面的编程。
(3)我们发现一个问题,切面位于每个方法内部,提取这些交叉业务相对复杂,于是我们将这些切面移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一致的,看如下的示意图:
那么采用代理技术正好可以解决这种问题,因此只要是面向方面的编程就要涉及到代理,代理是实现AOP功能的核心和关键技术。
3、 动态代理类
(1) 先来理解下静态代理,静态代理即由程序员创建或特定工具自动生成的源代码,再对其编译。代理类的class文件在程序运行前就存在了,即必须对代理类进行显示的定义。简单说,不用到运行期,代理类的代码我就定义好了。
(2) 静态代理的弊端,若要为系统中的各种接口增加代理功能,那将需要太多的代理类,全部采用静态代理的方式,需要定义很多的代理类,这是一件代码繁多且麻烦的事。这时就的采用动态代理了。
(3) 动态代理:JVM可以在运行期动态生成出类的字节码,这种动态生成的类(即动态类,本质上非代理类)往往被用作代理类,那被用作代理后的类,即为动态代理类。
动态类需实现一个或多个接口,正好可以满足代理类的要求,所以JVM生成的动态类只能用作具有相同接口的委托类的代理。
(4) 动态类为什么要实现接口?动态类在生成时告知都需要生成什么方法,而且可以多实现,比起类的单继承,可进行扩展。
(5) CGLIB库(第三方库)可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,可以使用CGLIB库。
4、 动态代理的实现
我们先进行动态代理的分步实现,下面先了解一些知识点。
(1) Proxy类的getProxyClass(ClassLoader loader,Class<?>... interfaces)方法,接收一个类加载器和一组接口,其方法内部根据提供的接口生成一个动态类,由类加载器加载成字节码,返回该字节码的对象。那么其实就是创建了一个动态代理类,只是该类还没有实质的代理功能。其名字为$Proxy0。
(2) 通过反射获取(1)中代理类字节码中的构造函数,发现它只有唯一的一个带参的构造函数,参数为InvocationHandler接口类型。这个用反射获取该类的所有构造函数的方法可证明,方法较简单这里不赘述。
(3) 利用newInstance方法去构造代理对象。代理类其实并没有执行实质的代理工作,需在创建对象时提供需InvocationHandler的子类对象,如handler,再由handler去执行复写的invoke方法进行实质的代理工作。
看下面的方法示例代码,演示了从动态生成代理类到创建代理对象的分步骤过程:
public static void createObj() throws Exception
{
//实现接口生成动态类,返回一个代理类的字节码
Class clazzProxy =Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//获取构造函数,参数为InvocationHandler接口类型
Constructor cst =clazzProxy.getConstructor(InvocationHandler.class);
//为代码清晰,这里我定义了一个内部类来实现InvocationHandler接口,也可写成匿名内部类
class MyInvocationHandler implements InvocationHandler
{
@Override
public Object invoke(Objectproxy, Method method, Object[] args)throws Throwable
{
//代理类的实质代理工作,都在这里完成
return null;
}
}
//创建对象,接收InvocationHandler子类对象
Collection Proxy2 = (Collection) cst.newInstance(new MyInvocationHandler());
System.out.println(Proxy2.toString());
}
下面演示下动态代理一次性实现代理对象的方式,也是基本的动态代理示例,先了解几点:
(1)上述createObj()为分步实现代理对象,过程还是比较麻烦的,所以Proxy提供了newProxyInstance(ClassLoaderloader, Class<?>[] interfaces, InvocationHandler h)方法,让上述过程一次性完成,该方法其实就相当于createObj()。
(2) InvocationHandler是代理对象调用的处理程序的接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑和交叉业务逻辑编织在一起(看后面示例理解)。当我们动态生成一个代理类,它并不会做实质性的工作,在创建代理类对象时,需接收一个InvocationHandler子类对象,由该对象来提供实质的工作。简单说,InvocationHandler接口其主要功能就是代理对象实现给目标类方法进行系统功能附加的处理器,代理对象想要实现代理工作,都要调用该处理器进行,其主要方式是复写InvocationHandler的invoke方法。
看下具体示例:
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //自定义测试接口 interface iSubject{ public void request1(); public void request2(); } //委托类(目标类) class Target implements iSubject{ @Override public void request1(){ System.out.println("询问......."); } @Override public void request2(){ System.out.println("再次询问......."); } } //自定义类实现InvocationHandler接口,非代理类,是主要的代理工作处理器,被代理类使用的 class DynamicProxy implements InvocationHandler{ private Object target; DynamicProxy(){} DynamicProxy(Object target){ this.target = target; } //复写invoke方法,进行委托类方法的代理工作 @Override public Object invoke(Object proxy, Methodmethod, Object[] args)throws Throwable{ //附加功能,method.getName()目标方法名 System.out.println(method.getName()+"方法前处理(上切面)"); //调用目标类的方法,返回值要一致 Object retVal = method.invoke(target, args); System.out.println("方法后处理(下切面)"); return retVal; } } //主调用程序,测试类 public class ClientProxyDemo{ public static void main(String[] args)throws Exception{ //创建目标类 Target tg = new Target(); //创建InvocationHandler的子类对象,代理类构造时需要 //获得目标类的字节码,主要用来获取目标类的接口的 Class cls = tg.getClass(); InvocationHandler hand = newDynamicProxy(tg); //分步骤 /* Class c =Proxy.getProxyClass(cls.getClassLoader(),cls.getInterfaces() ); Constructor ct=c.getConstructor(newClass[]{InvocationHandler.class}); iSubject isub =(iSubject) ct.newInstance(newObject[]{hand}); */ //一次性生成 iSubject isub =(iSubject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),hand); System.out.println(isub.getClass().getName());//测试,代理对象名 System.out.println(isub.toString());//测试,返回目标类名 //调用方法 isub.request1(); isub.request2(); } }
我们来分析下动态生成的代理类是怎么实现与委托类同一个接口的:
按要求,动态生成的代理类也必须与目标类相同接口,比较下静态代理的示例,发现从代码上看并没用进行这一步的定义,其实newProxyInstance将这一步完成了,该方法提供了目标类的类加载器,目标类的接口,和一个生成代理对象时需要的InvocationHandler接口的子类对象,那么要作为代理类的动态类实现了目标类的接口,用的同一个类夹载器,于是返回的代理类对象完全满足要求。
5、 分析动态代理的代理方式
(1)我们知道一个动态生成的代理类名形式为$Proxy0,从上面示例的结果来看,其代理顺序应该为:
代理对象调用方法-->代理处理器(InvocationHandler)-->目标方法,那么代理类内部应该像如下形式:
class $Proxy0//动态生成的代理类 { private InvocationHandler handler; $Proxy0(InvocationHandler handler)//接收handler { this.handler= handler; } 方法1(Object[] objs)//代理方法 { return handler.invoke(proxy, method, objs);//调用handler的invoke } 方法2…… }
该结构类似于静态代理类,只不过方法代理处理过程转交给hander去实现了,并且该类是不可见的,是动态生成和处理的。
(2) InvocationHandler子类对象中复写的invoke方法,其原型是Object invoke(Object proxy,Method method, Object[] args) ,那么代理类在使用handler的该方法时,需要将代理对象proxy,方法对象,参数列表args都提供给invoke,在handler中已经取得了目标类的对象target,通过方法对象进一步取得目标类的方法,即method.invoke(target,args);
(3)关于invoke的返回值,其返回值的路线是:
由图可知,如果在invoke方法中将返回值写成returnnull,那么使用size方法将会出NullPointException异常,因为null不能转成int类型。对于无返回值的void型方法,返回null不会出错,但是建议让代理方法和目标方法返回值保持一致。
(4)从上面的实例代码语句:
System.out.println(isub.getClass().getName());//测试,代理对象名
System.out.println(isub.toString());//测试,返回目标类名
结果我们发现代理类的getClass()方法并未交给invoke,从而调用目标类的getClass,而是保留给了自己,而toString()却是使用的目标类的。查java文档知道,代理类除了接口继承的方法外,对与从Object继承的方法,它只将hashCode,toString和equals三个方法进行了代理,使用的是目标类的,其他方法保留给了自己使用。
6、 AOP的动态代理实现
从动态代理的实例代码来看,每一个代理方法都要调用handler的invoke方法,在方法中实现系统功能的附加,目标类的每一个方法都要增加相同的功能(或说交叉业务),这正好体现AOP的方法的横切面,那么我们需将这些交叉业务模块化。先看下图:
从图中看出,log()方法就是进入到每个方法的交叉业务,我们将其模块化,将实现代码从InvocationHandler的invoke方法中抽离出来,可以在外部进行实现,在这里我们在外部定义一个类专门用来处理这些交叉业务,只需将该类的对象传给handler就行了,那么可以在代理方法中的如下四个位置加上系统功能代码。
(a) 在调用委托类方法之前
(b) 在调用委托类方法之后
(c) 在调用委托类方法之前后
(d) 在处理委托类方法异常的catch块中
来看具体的实现代码:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; //该接口方法表示目标方法的前后两个横切面(交叉业务) interface iAdvice { public void beforeMethod(Method method); public void afterMethod(Method method); } //实现接口iAdvice class MyAdvice implements iAdvice{ @Override public void beforeMethod(Method method) { System.out.println("上横切面的业务"); } @Override public void afterMethod(Method method) { System.out.println("下横切面的业务"); } } //测试类(主类) public class AdviceClient { publicstatic void main(String[] args) { //用ArrayList做目标类 final ArrayList target = new ArrayList(); //就没有定义接口了,直接用了Collection的接口,提供MyAdvice对象是为了送入附加功能 Collection proxy = (Collection)getProxy(target, new MyAdvice()); proxy.add("aaaa"); proxy.add("bbb"); proxy.add("ccc"); System.out.println(proxy.size()); } //获取动态代理类的方法,其处理器InvocationHandler,直接用匿名内部类在方法内部实现了 private static Object getProxy(final Object target, final iAdvice adv){ //获取代理类 Object proxy = Proxy.newProxyInstance( //使用与目标类同一个类加载器 target.getClass().getClassLoader(), //获取目标类的接口 target.getClass().getInterfaces(), //用匿名内部类实现InvocationHandler接口的子类对象 new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { adv.beforeMethod(method);//目标方法前置功能 Object retVal = method.invoke(target, args);//目标方法 System.out.println("运行了目标类方法:...."+method.getName()); adv.afterMethod(method);//后置功能 return retVal; } }); //返回创建代理类对象 return proxy; } }
7、 实现类似spring的可配置的AOP框架
spring的两大核心技术Bean工厂和AOP框架,在这里进行一个类似的简单配置,从中去了解spring。
(1) 在原有动态代理的基础上增加了BeanFactory、ProxyFactoryBean和配置文件。
(2) BeanFactory类用于产生对象,根据配置文件的信息进行对象的生成,即是用目标类对象还是代理类对象,有它的getBean返回对象。
(3) ProxyFactoryBean类产生代理类对象,它需要提供一个目标类对象terget,和一个实现代理功能的iAdvace接口的实现子类对象,其内部就是上述实现的动态代理的过程。
(4) 新建一个文本,命名成config.properties,内容为:
#xxx=java.util.ArrayList
xxx=zzb.proxy.ProxyFactoryBean
xxx.advice =zzb.proxy.MyAdvice
xxx.target =java.util.ArrayList
注意:加#号的是被注释的内容,通过注释xxx=java.util.ArrayList和
xxx=zzb.proxy.ProxyFactoryBean来进行切换,xxx.advice和xxx.target等号后的路径是自定义的,分别指向了MyAdvice和ArrayList类。
下面是示例代码:
先用到上述示例用到的接口:
//该接口方法表示目标方法的前后两个横切面(交叉业务) interface iAdvice { public void beforeMethod(Method method); public void afterMethod(Method method); } //实现接口iAdvice class MyAdvice implements iAdvice{ @Override public void beforeMethod(Method method) { System.out.println("上横切面的业务"); } @Override public void afterMethod(Method method) { System.out.println("下横切面的业务"); } }
而后才是主要实现的代码:
import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Properties; import java.util.Collection; public class AOPProxy { public static void main(String[] args) throws Exception { //读入配置文件流 InputStream ips = AOPProxy.class.getResourceAsStream("config.properties"); //创建BeanFactory对象并调用getBean,配置键为xxx Object bean = new BeanFactory(ips).getBean("xxx"); System.out.println("使用的类:"+bean.getClass().getName()); ((Collection)bean).add("aaa"); System.out.println("数量:"+((Collection)bean).size()); } } class BeanFactory{ Properties props = new Properties(); public BeanFactory(InputStream ips) throws IOException{ props.load(ips); } public Object getBean(String name) throws Exception{ //根据配置的键获取值,即类名 String clazzName = props.getProperty(name); //加载clazzName所指的类,并创建对象 Class clazz = Class.forName(clazzName); Object bean = clazz.newInstance(); //对象是否为ProxyFactoryBean,是的话创建代理类对象 if(bean instanceof ProxyFactoryBean){ ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean; //根据配置文件的xxx.advice键,获取iAdvice对象 iAdvice adv = (iAdvice)Class.forName(props.getProperty(name+".advice")).newInstance(); //获取目标类 Object target =Class.forName(props.getProperty(name+".target")).newInstance(); //告知proxyFactoryBean对象 proxyFactoryBean.setAdv(adv); proxyFactoryBean.setTarget(target); //获取代理类并返回 Object proxy = proxyFactoryBean.getProxy(); return proxy; } //返回bean,非代理类 returnbean; } } class ProxyFactoryBean{ private iAdvice adv; private Object target; public iAdvice getAdv(){ return adv; } public void setAdv(iAdvice adv){ this.adv= adv; } public Object getTarget(){ return target; } public void setTarget(Object target){ this.target= target; } public Object getProxy(){ //获取代理类 Object proxy = Proxy.newProxyInstance( //使用与目标类同一个类加载器 target.getClass().getClassLoader(), //获取目标类的接口 target.getClass().getInterfaces(), //用匿名内部类实现InvocationHandler接口的子类对象 new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { adv.beforeMethod(method);//目标方法前置功能 Object retVal = method.invoke(target, args);//目标方法 System.out.println("运行了目标类方法:...."+method.getName()); adv.afterMethod(method);//后置功能 return retVal; } }); //返回创建代理类对象 return proxy; } }
设置配置文件,内容上面已说明,不过xxx=zzb.proxy.ProxyFactoryBean
和xxx.advice = zzb.proxy.MyAdvice的值需根据自己的情况定义,我这里是在zzb.proxy包下。
在测试时,若将将第一行注释掉,那么使用的是代理类,若将第二行注释掉,使用的是ArrayList。
---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------