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

来源:互联网 发布:工业打标机软件 编辑:程序博客网 时间:2024/05/22 10:39

---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

不得不吐槽,最后模拟那个Spring Bean工厂,难受。

代理:


理解:生活中的代理:例如从某地区的人从不同地区的代理商买东西。
程序中的代理:
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如异常处理,日志,计算方法的运行时间,事务管理,等等。
准备如何做呢?
例如我们需要获得一段程序的运行时间
class x {
  void sayHello(){
  syso:hello;
  }
} //当我们没有源代码的时候,如何进行呢,通过代理
xproxy{
Starttime
X.sayHello();
endtime;
}
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方式时加上系统功能的代码。


如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类,带是代理类,这样以后很容易切换,
譬如,想要日志功能时就配置代理类,否者配置目标类,这样,增加系统功能就很容易,以后运行一段时间后,又想去掉系统功能也很容易。
重要原则:不要将供货商暴露给你的客户。


AOP:面向方面的程序设计
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,
交叉业务的编程既为面向方面的编程Aspect Oriented Program 简称AOP
其目标就是要使交叉业务模块化,可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。
只要是面向对象的编程,就要涉及到代理。
————————————————————————————动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态的代理方式,将是一件非常麻烦的事,写成百上千个代理类,是不是太累。
JVM可以在运行期动态生成类的字节码,这种动态生成的类往往被用作代理类,既动作代理类。
JVM生成的动态代理类必须实现一个或多个接口,所以,JVM生成的动态代理类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码。
1在调用目标方法之前。
2在调用目标方法之后。
3在调用目标方法前后。
4在处理目标方法异常的catch块中。


——————————————————分析JVM动态生成的类。
创建实现了Collection接口的动态类和查看其名称,分析proxy.getProxyClass方法的各个参数。
编码列出动态类中的所有构造方法和参数签名。
编码列出动态类中的所有方法和参数签名。

package 代理;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 静态方法,返回代理类的字节码对象指定一个类加载器,指定多个类的接口 以字节码的形式。// proxy此类表示用于设置代理。Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);// 一般看到前缀为clazz的,就知道这个变量代表的是一份字节码。既然是一份字节码,那么就可以获取它的名字。System.out.println(clazzProxy1.getName());System.out.println("begin constructor list--------------------------------");/* * $proxy0() %proxy0(InvocationHandler,int) */// 首先我们先看看获取的这个类身上有什么构造方法Constructor[] constructors = clazzProxy1.getConstructors();for (Constructor constructor : constructors) {String name = constructor.getName();StringBuilder sBuilder = new StringBuilder(name);// 获得参数列表Class[] clazzParams = constructor.getParameterTypes();// 获取每一个sBuilder.append('(');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--------------------------------");/* * $proxy0() %proxy0(InvocationHandler,int) */// 首先我们先看看获取的这个类身上有什么构造方法Method[] methods = clazzProxy1.getMethods();for (Method method : methods) {String name = method.getName();StringBuilder sBuilder = new StringBuilder(name);sBuilder.append('(');// 获得参数列表Class[] clazzParams = method.getParameterTypes();// 获取每一个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 create instance  object--------------------------------");// 需要创建实例对象// clazzProxy1.newInstance(); //虽然是创建实例的方法,但是是无参数的,我们要获取构造信息。Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);// 但是InvocationHandler是一个接口,我们只能创建一个类去实现它,才能实例化。class MyInvocationHandler implements InvocationHandler {// 一次性的内部类@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// 重写方法return null;}}// 因为之前指定的接口是collectionCollection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler());System.out.println(proxy1.toString());// 无论是proxy1还是proxy1.toStirng都得到的是null,是究竟没有创建成功呢,其实是toString返回的本身就是null。proxy1.clear(); // 无返回值,这是集合的一个方法,用于清除集合。 如果运行中没有报空指针异常,则说明确实成功创建了一个实例。// proxy1.size(); //有返回值的方法,运行报错,跟invoke方法得到的null冲突。// 刚才创建了一个内部类,实际上还可以简化。Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method,Object[] args) throws Throwable {return null;}}); Collection proxy3=(Collection)Proxy.newProxyInstance( Collection.class.getClassLoader(), new Class[]{Collection.class},   //不能用可变参数,可变参数必须为最后一个。给数组的方式给值。 new InvocationHandler(){      ArrayList target = new ArrayList();  //指定目标 public Object invoke(Object proxy, Method method,Object[]args) throws Throwable { //每调用一次方法,就执行invoke方法一次。 // Collection不能直接作为集合使用  long beginTime =System.currentTimeMillis(); Object retVal=method.invoke(target,args);  long endTime =System.currentTimeMillis(); System.out.println(method.getName()+"running time is"+(endTime-beginTime)); return retVal; } //return method.invoke(Proxy,args);  如果这样调用,就是死循环  }); proxy3.add("zxx");proxy3.add("bxd");proxy3.add("flx");System.out.println(proxy3.size());System.out.println(proxy3.getClass().getName());//为什么得到的还是$proxy0,而不是targer //因为只有toString hashcode equals三个方法委托给了Handler。final ArrayList target = new ArrayList();Collection proxy4 =(Collection) getProxy(target,new MyAdvice());//这里传入封装的系统功能}//下面这行代码将Collection换成Object ,参数也进行的替换。这样不仅仅对集合也可以进行封装,对其他类型也可以进行封装。private static Object getProxy(final Object target,final Advice advice) {//增加了一个系统功能的参数Object proxy3 = (Object) Proxy.newProxyInstance(target.getClass().getClassLoader(),/*new Class[] { Collection.class },*/ target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method,Object[] args) throws Throwable {// Collection不能直接作为集合使用/*long beginTime = System.currentTimeMillis();Object retVal = method.invoke(target, args);long endTime = System.currentTimeMillis();System.out.println(method.getName() + "running time is"+ (endTime - beginTime));return retVal;*///上面代码演变吊下面一种 advice.beforeMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy3;}}



创建动态类的实例对象:
用反射获得构造方法。
编写一个最简单的InvocationHandler类。
调用构造方法创建类的实例对象,并将编写的InvocationHandlerd类的实例对象传进去、
打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其它有返回值的方法报告给异常,
将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类。
小知识,Stringbuilder和Stringbuffer区别
用法基本一样,作用都是对字符串进行动态添加。
区别是在单线程上,StringBuilder效率高。
StringBuffer安全但是效率低
StringBuffer之所以慢是因为他考虑了安全问题。


思考:让jvm创建动态类及其实例对象,要给它提供哪些信息?
1生成的类中有哪些方法。通过让其实现哪些接口的方式进行告知。
2产生的类字节码必须有一个关联的类加载器对象。
3生成的类中的方法是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,
它调用我的方法,既相当于插入了我的代码,提供执行代码的对象就是哪个InvocationHandler对象。它是在创建
动态类的实例对象的构造方法时传递进去的,在上面的InvocationHandler对象的invoke方法中加一点代码。
就可以看到这些代码被调用运行了。


分析动态类生成的类的内部代码:
动态生成的类实现Collection接口(可以实现若干个接口),生成的类有Collection接口中的所有方法和一个
如下解释InvocationHandler参数的构造方法。
构造方法接受一个InvocationHandler对象,接受了对象干什么呢?该方法内部的代码会是什么样的呢?
InvocationHandler handler.
public $Proxy0(InvocationHandler hander){
this.handler=handler;
}
接受了对象是为了调用方法,我们生成的哪些方法内部都是在调用传递经来的哪些handler对象的invoke方法
实现Collection接口中的各个方法的代码又是怎样的呢?
int size(){
return handler.invoke(this,this.getClass().getMethod("size"),null);
}
void clear(){
    handler.invoke(this,this.getClass().getMethod("clear"),null);
}
InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?
client程序调用objProxy add("abc")方法时,涉及三个要素,objProxy对象,add方法,“abc”参数。
Class Proxy${
add(Object object){
return handler.invoke(Object Proxy(Proxy对象),Method method(方法),Object[]args(方法使用的参数));
}
}


之前的代码是以硬编码的形式存在,若是我要将我所需要的系统功能,以参数的形势传递进来。该怎么办呢?
可不可以将我们要运行的系统功能以一个字符串或者一个对象的形式传递进来呢?
我们可以把系统功能封装成对象,极大的提高了复用性和灵活性。这就是面向切面编程。
如何做呢,InvocationHandler有两个对象,一个是我们要实现的系统功能,一个是目标对象。
在上面的代码中对代码进行抽取封装后,还可以对系统功能进行改造。
让他们变成接口,更加的灵活。

package 代理;import java.lang.reflect.Method;public class MyAdvice implements Advice {long beginTime;//由于两个方法都要用到这个变量,所以得变成成员变量。@Overridepublic void beforeMethod(Method method) {// TODO Auto-generated method stubbeginTime = System.currentTimeMillis();System.out.println("开始学习");}@Overridepublic void afterMethod(Method method) {// TODO Auto-generated method stublong endTime = System.currentTimeMillis();System.out.println("学习结束");System.out.println(method.getName() + "running time is"+ (endTime - beginTime));}}

package 代理;import java.lang.reflect.Method;public interface Advice {//默认抽象方法。void beforeMethod(Method method);void afterMethod(Method method);}


以后若是要生成代理,只要把目标和建议传过来就成了


实现AOP功能的封装与配置:
工厂类的BeanFactory负责创建目标类或代理类的实例对象,并透过配置文件实现切换。其getBean方法根据参数字符串
返回一个相应的实例对象,如果参数字符串在配置文件中不是ProxyFactoryBean,则直接返回该类的实例对象,否者,返
会该类实例对象的getProxy方法返回的对象。


BeanFactory的构造方法代表接收配置文件的输入流对象,配置文件格式如下:
#xxx = java.util.ArrayList
xxx = cn.itcast.ProxyFactoryBean
xxx.target = java.util.ArrayList
xxx.advice = cn.itcast.MyAdvice


getBean内部这么写呢?它有两种可能:如果它发现这个类不是ProxyFactoryBean这个类。那他就直接创建它的实例对象。
如果它是ProxyFactoryBean这个类,它就不返回,而是调用它的一个方法,返回它创建出的一个代理。
写要分连个步骤
1是写BeanFactory,  (专用于产生Bean)
2是写ProxyFactoryBean (这是工厂中特殊的一个,专用于创建代理)


模拟Spring  一个是Bean 工厂,一个是AOP框架
ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息? 
1:目标。  2 :通知。
编写客户端应用:
编写实现Advice接口的类和在配置文件中进行配置。
调用BeanFactory获取对象。

package 代理.Aopframework;import java.io.IOException;import java.io.InputStream;import java.util.Properties;import 代理.Advice;public class BeanFactory {//工厂创建JavaBean的时候要找一个配置文件,所以它的构造方法要接受一个配置文件。Properties props = new Properties();public BeanFactory(InputStream ips){  //配置文件是properties格式的try{props.load(ips);//从输入流中读取属性列表}catch(IOException e){e.printStackTrace();}};public Object getBean(String name) throws Exception{Object bean = null;  //抽出来是因为别的地方还要用到。String className=props.getProperty(name);//根据名字,拿到对应的类名。try {Class clazz = Class.forName(className);bean = clazz.newInstance(); //对于JavaBean来说,必须要有一个不带参数的构造方法。} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}if (bean instanceof ProxyFactoryBean) { //判断,假如它是后面这种类型,就用他自己的方法创建一个代理,并返回代理//创建代理前,还有些事情要做Object proxy=null;ProxyFactoryBean ProxyFactoryBean = (ProxyFactoryBean)bean;try {Advice advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance();Object target = Class.forName(props.getProperty(name+".target")).newInstance();ProxyFactoryBean.setAdvice(advice);ProxyFactoryBean.setTarget(target);proxy = ProxyFactoryBean.getProxy();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}  return proxy;}return bean;}}

package 代理.Aopframework;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import 代理.Advice;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 getProxy() {// TODO Auto-generated method stubObject proxy3 = (Object) Proxy.newProxyInstance(target.getClass().getClassLoader(),/*new Class[] { Collection.class },*/ target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method,Object[] args) throws Throwable {// Collection不能直接作为集合使用/*long beginTime = System.currentTimeMillis();Object retVal = method.invoke(target, args);long endTime = System.currentTimeMillis();System.out.println(method.getName() + "running time is"+ (endTime - beginTime));return retVal;*/advice.beforeMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy3;}}

package 代理.Aopframework;import java.io.InputStream;import java.util.Collection;public class AopFramworkTest {/** * @param args * @throws Exception  */public static void main(String[] args) throws Exception {// TODO Auto-generated method stubInputStream ips = AopFramworkTest.class.getResourceAsStream("config.properties");Object bean = new BeanFactory(ips).getBean("xxx");System.out.println(bean.getClass().getName());//((Collection)bean).clear();}}
最后是完成配置文件的设置
xxx=java.util.ArrayListxxx=\u4EE3\u7406.Aopframework.aopframworktest.ProxyFactoryBeanxxx.advice=\u4EE3\u7406.Aopframework.MyAdvicexxx=target=java.util.ArrayList



---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------详细请查看:http://edu.csdn.net

0 0