首页 > 代码库 > 黑马程序员—张老师基础加强7-动态代理
黑马程序员—张老师基础加强7-动态代理
代理:
要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能。
例如:异常处理,日志和计算方法的运行时间,事物管理等,怎么做
如 在运行前后增加计算时间的方法。show(){
sop();
}
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
动态代理:
要为系统中的各种接口的类增加代理功能,那需要太多代理类。
jvm可以在运行期间动态生成出类的字节码,这种动态生成的类往往被调用作代理类,
即动态代理类。
jvm生成动态类必须实现一个或多个接口,所以,jvm生成的动态类只能作为相同接口的目标类的代理。
如果目标类没有接口,CGLIB库(不是jvm提供的)可以动态生成一个类的子类,一个类的子类,也可以用作该类的代理,所以,如果目标类没有接口,可以使用CGLIB
代理类除了调用目标的相应方法和返回目标的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中。
Proxy类:Proxy
提供用于创建动态代理类和实例的静态方法
方法全是静态:
常用方法:
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
练习:获取代理类Proxy的构造器和方法
//1.--------------创建Collection动态类,打印代理类的所有方法。 //创建代理类,传递的是类加载和代理实现的接口!!! Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); System.out.println(clazzProxy1.getName());//$Proxy0,代理类名 //获取构造方法,打印如$proxy0() $proxy0(String str,int i) Constructor[] constructors = clazzProxy1.getConstructors(); for(Constructor cons:constructors){ String name = cons.getName(); StringBuilder stringbuilder = new StringBuilder(name); stringbuilder.append("("); //添加参数 Class[] types = cons.getParameterTypes();//返回的是 Class<?> [] getParameterTypes() for(Class cl:types){ stringbuilder.append(cl.getName()+","); } if(types!=null&&types.length!=0) { //将最后一组参数的","去掉。前提构造函数有参数 stringbuilder.deleteCharAt(stringbuilder.length()-1); } stringbuilder.append(")"); System.out.println( stringbuilder.toString()); } Method[] methods = clazzProxy1.getMethods(); for(Method met:methods){ StringBuilder sb = new StringBuilder(met.getName()); sb.append("("); Class[] methodcls = met.getParameterTypes();//注意 for(Class cls:methodcls){ sb.append(cls.getName()+",");//注意 } if(methodcls.length!=0&&methodcls!=null){ sb.deleteCharAt(sb.length()-1);//stringbuilder无法索引最后一次出现字符的位置,只能是字符串。lastindexof } sb.append(")"); System.out.println(sb); }
练习:创建代理对象
//2.-------------------------------创建实例对象 //注意newInstence是调用无参构造函数,clazzProxy1没有 怎么办 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); Constructor constructor1 = clazzProxy1.getConstructor(InvocationHandler.class);//构造函数需要参数区分 //1.0 InvocationHandler是接口,只能new其子类。接口在子类必须覆盖父类的方法。 class myInvocationHandle1 implements InvocationHandler{ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } //1.1直接用匿名内部类的方式 Collection colls2 = (Collection) constructor1.newInstance(new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); //创建的对象是什么类型???动态类必须实现一个或多个接口,返回的是collection Collection colls1 = (Collection)constructor1.newInstance(new myInvocationHandle1());//获得构造方法参数是参数类型InvocationHandler.class,创建对象时真正的参数InvocationHandler对象。 System.out.println(colls1);//打印结果是null,说明colls1的返回值是null,不是对象本身是null,否则报空指针。 System.out.println(colls1.toString()); colls1.clear(); //colls1.size();//包空指针异常 /* Proxy的构造方法接受了InvocationHandler对象,到底要干什么? 当然是要在某个时候要用到。那什么时候用? 看代理中的add()方法。add中内部大概是: add(){ InvotationHandler.invoke(this,this.getClass.getMethod("add"),null) } InvotationHandler类:是代理实例调”用程序处理“的接口,当调用代理的方法时,会将方法进行编码并将任务指派的invoke()方法中。 invoke(Object proxy, Method method, Object[] args):因为代理同和代理的类实现了相同的接口,所有调用代理的方法时,就将 任务指派invoke方法中。 总结:调用代理的x方法,就去调用handler中invoke方法。而invoker有可以通过反射来调用目标中的x方法。 invoke中三个参数是:调用的哪个代理对象,调用代理对象的哪个方法,调用代理方法中的参数。代理三要素。谁,买,电脑,然后到总部提货。 */
练习:一步到位法创建代理对象 但这是硬编码
API文档例子;一步到位的方法。 Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] { Foo.class }, handler); Collection colls3 = (Collection) Proxy.newProxyInstance(loader, interfaces,//不能是可变参数,因为可变参数必须放在最后,只能用数组 handler ); //1.2一步到位创建Collection的动态类。动态类是collection类型。 Collection colls3 = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, //注意:可能会有多个接口,本例子只有一个。 new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); //挂到目标上。怎么挂:加到invoke方法中,谁是目标:ArrayList。 ArrayList dest = new ArrayList(); Object retval= method.invoke(dest, args);//调用代理传过来的方法。 long end = System.currentTimeMillis(); System.out.println(method.getName()+"运行时间"+(end-start));//(end-start)注意加括号 return retval; } } ); //add()方法的结果,就是Invoke返回的retval.这也是为什么第一次,调用size()时,会报错。因为第一次调用时返回的是null; colls3.add("biixnagdong");//去找Handler的invoke方法, 放在局部,每次add()的目标不一致。 colls3.add("binagdong"); colls3.add("binagdong"); //调用代理的方法时, System.out.println(colls3.size());//没有空指针了,,结果为0. 注:我们计算时间的方法是硬添加进来的,以后不可能只给一个用户。可以将用户目标的方法封装成对象,并作为参数传递进去。去调用代理中的管理方法。 那么在编框架是,就变成了四要素。要传两个对象:用户目标的程序封装的对象和要修改的系统功能的方法封装的对象。
练习:将系统功能和目标作为参数传递,获取动态代理,不再是硬编码
ArrayList<String> arrayproxy = new ArrayList<String>(); //对象的类型是Collection,和目标ArrayList无关 Collection<String> proxy = (Collection<String>)getProxy(arrayproxy, new myAdvice()); proxy.add("sdf"); proxy.add("sdf"); proxy.add("sdf"); proxy.size(); //将目标和系统功能作为参数传递(接口多态) 获取Collection的动态代理 public static Object getProxy(final Object target,final advice adv){//多态,内部类调用要final //一步到位发,获取代理对象 Object proxyobj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //挂上系统功能的代码 adv.beforeRun(); Object obj =method.invoke(target, args);//将目标传递进来。 adv.afterRun(); return obj; }}); return proxyobj; } }//获取时间的接口 系统功能 一般有四个方法。 interface advice{ public void beforeRun(); public void afterRun(); } class myAdvice implements advice{ private long start; private long end; @Override public void beforeRun() { System.out.println("开始运行!"); start= System.currentTimeMillis(); } @Override public void afterRun() { System.out.println("运行结束"); end= System.currentTimeMillis(); System.out.println(end-start); } }
练习:写一个代理框架
代理框架: import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyDemo2 { /**创建代理类 * loader - 定义代理类的类加载器 interfaces - 代理类要实现的接口列表 h - 指派方法调用的调用处理程序 应用:做代理框架,将目标对象和功能代码对象传入. * @param args */ public static void main(String[] args) { Server server= new Server(); ProxyTools proxytools = new ProxyTools(); ProxyInte ServerProxy = (ProxyInte) getProxy(server,proxytools);//实现的接口是什么类型,对象就是什么类型 ServerProxy.show(); } public static Object getProxy(final Object target,final Tools tool){//将客户和代理的管理工具传入 //创建Server的代理类,并调用获取时间的管理办法. //1.使用静态方法 Object obj = Proxy.newProxyInstance( target.getClass().getClassLoader(),//注意是谁的class文件 target.getClass().getInterfaces(),//注意接口是谁 new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//invoke方法该写什么??? //挂上系统功能的代码 tool.beforeRun(); Object retval = method.invoke(target, args);//重要,使用反射将客户的对象传入,运行代理传入的代码 tool.afterRun(); return retval; } }); return obj; } } //创建目标客户 class Server implements ProxyInte{ @Override public void show() { // TODO Auto-generated method stub System.out.println("客户运行的代码"); } } //定义获取运行时间的代理管理方法 interface Tools{//接口定义规则 void beforeRun(); void afterRun(); } class ProxyTools implements Tools{ long start; long end; public void beforeRun(){ start = System.currentTimeMillis(); } public void afterRun(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)); } } //目标类和代理类共同的接口 interface ProxyInte{ public void show(); }