程序员_Java高新技术<三>代理

来源:互联网 发布:阿里云金牌合作伙伴 编辑:程序博客网 时间:2024/06/06 01:49
 

 代理

一、生活中的代理:
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?
二、程序中的代理
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。 (参看下页的原理图)
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

三、代理架构图:



四、代理类的作用与原理及AOP概念:


AOP:Aspect oriented program 面向方面编程。
OOP:Object oriented program 面向对象编程。

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                              安全       事务         日志
StudentService  ------|----------|------------|-------------
CourseService   ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------
用具体的程序代码描述交叉业务:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1         func2            func3
{             {                { 
....            ....              ......
}             }                }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。



五、动态代理:
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
例如:
5.需要写一个示意代码进行辅助说明,
例如
Class proxy{
void sayHello(){
……….
try{
target.sayHello();
}catch(Exception e){
………..
}
………….
}
}




产生动态类的方法:
static Class<?>getProxyClass(ClassLoader,class<?>...interface)
//该方法返回一个类字节码,相当于生成了一个动态代理类,在方法中指定了该类的加载器,和实现的接口。
//该加载器通常用与接口相同的类加载器


示例:打印出动态代理类中的所有构造方法和方法
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package cn.itcast.day3;import java.lang.reflect.Constructor;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 ProxyTest {public static void main(String[] args)throws Exception{//getProxyClass()接收两个参数:加载器,实现的接口。Class clazzProxy1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);System.out.println(clazzProxy1.getName());//该类有什么构造方法System.out.println("----------begin constructor list");/*希望打印出的构造方法如下格式: * $Proxy() * $Proxy(InvocationHandldr,int) */Constructor[] constructors=clazzProxy1.getConstructors();for(Constructor  constructor:constructors){String name=constructor.getName();StringBuilder sBuilder=new StringBuilder(name);sBuilder.append('(');Class[] clazzParams=constructor.getParameterTypes();//获得参数类型,返回的是Class类型的数组for(Class clazzParam:clazzParams){sBuilder.append(clazzParam.getName()).append(',');}//如果得到的参数类型不为空,去掉最后一个逗号if(clazzParams!=null && clazzParams.length!=0)sBuilder.deleteCharAt(sBuilder.length()-1);sBuilder.append(')');System.out.println(sBuilder.toString());}System.out.println("----------begin methods list");/*希望打印出的构造方法如下格式: * $Proxy() * $Proxy(InvocationHandldr,int) * */Method[] methods=clazzProxy1.getMethods();for(Method  method:methods){String name=method.getName();StringBuilder sBuilder=new StringBuilder(name);sBuilder.append('(');Class[] clazzParams=method.getParameterTypes();//获得参数类型,返回的是Class类型的数组for(Class clazzParam:clazzParams){sBuilder.append(clazzParam.getName()).append(',');}//如果得到的参数类型不为空,去掉最后一个逗号if(clazzParams!=null && clazzParams.length!=0)sBuilder.deleteCharAt(sBuilder.length()-1);sBuilder.append(')');System.out.println(sBuilder.toString());}//由刚才的构造方法列表得:动态类只有一个有一个参数的构造方法:com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)//创建动态类 的实例对象及调用其方法。System.out.println("----------begin create instance list");//clazzProxy1.newInstance();因为动态类没有无参数的构造方法,所有这样new对象错误。//所以先得到构造方法Constructor constructor=clazzProxy1.getConstructor(InvocationHandler.class);//由得到的构造方法创建动态类的实例对象时需要指定一个InvocationHandler类型的对象,//但通过API查  询,InvocationHandler为一个接口,不能创建对象,所以要通过子类实现该接口,然后创建对象。class MyInvocationHandler1 implements InvocationHandler{@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stubreturn null;}}//创建代理对象方式1Collection proxy1=(Collection)constructor.newInstance(new MyInvocationHandler1());System.out.println(proxy1);//此处打印为null,原因可能有2个:1。该对象真的为null,但是这样会返回空指针异常//2.proxy1.toString()返回是null,显然是这种情况。//创建代理对象方式2/*对于上述通过实现接口来new对象,我们可以通过构造匿名内部类的方式,更简单*/Collection proxy2=(Collection)constructor.newInstance(new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stubreturn null;}});//创建代理对象方式3,不需要先创建代理类再创建对象,采用直接调用Proxy.newProxyInstance方式创建对象。Collection proxy3=(Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class},new InvocationHandler(){ArrayList target=new ArrayList();//此处的ArrayList起到了目标的作用,//暂时替代Collection对象。然后将运算后的结果返回给Collection对象,这样就相当于Collection对象自己调用一样。//代理对象,方法,传递的参数public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {long beginTime=System.currentTimeMillis();Object retVal=method.invoke(target, args);long endTime=System.currentTimeMillis();System.out.println(method.getName()+"running time of"+(endTime-beginTime));return retVal;}});proxy3.add("zxx");//调用代理内部的invoke方法,并把proxy对象,add方法,"zxx"参数传递进去,代理内部执行完毕后再返回值。proxy3.add("lhm");System.out.println(proxy3.size());//注意:对于代理身上的方法,只有hashCode(),equals(),toString()三个方法交给InvocationHandldr去做。//而其他方法的实现,代理会自己实现。/*编写可生成代理和插入通告的通用方法。 * 由上段代码我们可知:在代理类中新建对象的Invocation内部,代码是硬写上去的,不利于以后的修改。 * 我们可以采用在其内部接收一个对象的方式来进行构造,而这个对象是由外部一个类传过来的,这个类中任意定义需要的代码,框架思想。 * */final ArrayList target=new ArrayList();//注意:在内部类的方法中要访问局部变量,必须加finalCollection proxy4=(Collection)getProxy(target,new MyAdvice());//要想得到此方法,可以将上段产生代理对象的方法,Refactor-Extract method抽取方法得到。proxy4.add("ljx");   //proxy4.add("xyk");//System.out.println(proxy4.size());}private static Object getProxy(final Object target,final Advice advice) {Object proxy4=Proxy.newProxyInstance(target.getClass().getClassLoader(), /*new Class[]{Collection.class},*/target.getClass().getInterfaces(),new InvocationHandler(){public Object invoke(Object proxy, Method method, Object[] args)//代理,方法,参数throws Throwable {/*long beginTime=System.currentTimeMillis();Object retVal=method.invoke(target, args);long endTime=System.currentTimeMillis();System.out.println(method.getName()+"running time of"+(endTime-beginTime));return target;*/advice.beforeMethod(method);Object retVal=method.invoke(target, args);advice.afeterMethod(method);return target;}});return proxy4;}}</span>

六、  实现类似spring的可配置的AOP框架

一、工厂类BeanFactory:

1、工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。

2、getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy方法返回的对象。

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

#xxx=java.util.ArrayList

xxx=cn.itcast.test3.aopframework.ProxyFactoryBean

xxx.advice=cn.itcast.test3.MyAdvice

xxx.target=java.util. ArrayList

 

注意:其中的#代表注释当前行。

4、ProxyFactoryBean充当封装成动态的工厂,需为工厂提供的配置参数信息包括:

目标(target)

通告(advice)

5、BeanFactory和ProxyFactoryBean:

1)BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。

2)ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。

二、实现类似spring的可配置的AOP框架的思路:

1、创建BeanFactory类:

1)构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象获得。

2)创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。

3)通过其字节码对象创建实例对象bean。

4)判断bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普通类的实例对象。

2、创建ProxyFactoryBean(接口),此处用类做测试,其中有一个getProxy方法,用于获得代理类对象。

3、对配置文件进行配置,如上面配置一样。

4、作一个测试类:AopFrameworkTest进行测试。

主要程序:

<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">//创建BeanFactory类package cn.itcast.test3.aopframework;import java.io.*;import java.util.Properties;import cn.itcast.test3.Advice;public class BeanFactory {Properties prop = new Properties();//创建对象时需要传入一个配置文件中的数据,所以需要在构造方法中接受一个参数public BeanFactory(InputStream ips) {try {//将配置文件加载进来prop.load(ips);} catch (IOException e) {e.printStackTrace();}}//创建getBean方法,通过配置文件中的名字获取bean对象public Object getBean(String name){//从配置文件中读取类名String className = prop.getProperty(name);Object bean = null;try {//由类的字节码获取对象Class clazz = Class.forName(className);bean = clazz.newInstance();} catch (Exception e) {e.printStackTrace();} //判断bean是特殊的bean即ProxyFactoryBean还是普通的beanif(bean instanceof ProxyFactoryBean){Object proxy = null;try {//是ProxyFactoryBean的话,强转,并获取目标和通告ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;//获取advice和targetAdvice advice = (Advice)Class.forName(prop.getProperty(name + ".advice")).newInstance();Object target = Class.forName(prop.getProperty(name + ".target")).newInstance();//设置目标和通告proxyFactoryBean.setAdvice(advice);proxyFactoryBean.setTarget(target);//通过类ProxyFactoryBean(开发中是作为接口存在)中获得proxy对象proxy = proxyFactoryBean.getProxy();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();} //是ProxyFactoryBean的话,返回proxy对象return proxy;}//否则返回普通bean对象return bean;}}//创建ProxyFactoryBean类package cn.itcast.test3.aopframework;import java.lang.reflect.*;import cn.itcast.test3.Advice;public class ProxyFactoryBean {private Object target;private Advice advice;public Object getTarget() {return target;}public void setTarget(Object target) {this.target = target;}public Advice getAdvice() {return advice;}public void setAdvice(Advice advice) {this.advice = advice;}public Object getProxy() {Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),//这里的接口要和target实现相同的接口target.getClass().getInterfaces(),new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {//通过契约,使用其方法--before和after方法advice.beforeMethod(method);Object value = method.invoke(target, args);advice.afterMethod(method);return value;}});return proxy;}}//创建测试类AopFrameworkTestpackage cn.itcast.test3.aopframework;import java.io.InputStream;public class AopFramewrorkTest {public static void main(String[] args)throws Exception {//读取配置文件的数据InputStream ips = AopFramewrorkTest.class.getResourceAsStream("config.property");//获取bean对象Object bean = new BeanFactory(ips).getBean("xxx");System.out.println(bean.getClass().getName());}}</span>
 
原创粉丝点击