第一部分:代理的概念与作用
一、生活中的代理
贵州人从贵州代理商手中购买联想电脑,和到北京总部购买联想电脑的本质都是一样的,都解决了核心问题,但是代理提供了方便。
二、程序中的代理
1、要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如,异常处理、日志、计算方法的运行时间、事物管理等等。
2、编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法的同时加上系统功能的代码。
3、如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置时决定使用目标类、还是代理类,这样以后很容易切换。比如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后程序运行一段时间后,又想去掉功能也很容易。
第二部分:AOP(面向方面)
一、系统中存在交叉业务,一个交叉业务就是要切入系统中的一个方面,如下所示:
安全、事物、日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。
二、用具体的程序代码描述交叉业务
切面表示日志功能,安全功能等方面,但是不可能去改变目标的方法、深入到目标的方法内部。
三、交叉业务的编程问题即为面向方面的编程(Aspect oriented program,简称AOP,面向对象是OOP),AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面的运行效果是一样的,如下所示:
四、使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心与关键技术
第三部分:动态代理技术
一、概述
1、要为系统中的实现各种接口的类增加代理功能,那就需要太多的代理类,全部都采用静态代理的方式,将是一件非常麻烦的事情!
2、JVM可以在运行期间动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
以前都是手动的写一个java源程序用javac编译生成class文件,现在可以用JVM自动的生成出一份字节码,不需要自己去写。动态生成的类不是代理,只是用作别人的代理。
3、JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态代理只能用作具有相同接口的目标类的代理。
实现接口可以知道该类中有何方法。
4、CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理
如果想要为一个没有实现接口的类生成动态代理类,那么可以使用第三方的CGLIB库。
5、代理类的各个方法中通常除了想要调用目标的相应方法和对外返回目标的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1)在调用目标方法之前
2)在调用目标方法之后
3)在调用目标方法前后
4)在处理目标方法异常的catch块中
二、基础类与方法
1、Proxy(java.lang.reflect)
Proxy提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
构造方法:
protected Proxy(InvocationHandler h)
使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例
常用方法:
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回代理类的 java.lang.Class 对象,并向其提供类加载器(一般与接口相同)和接口数组
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
static InvocationHandler getInvocationHandler(Object proxy) 返回指定代理实例的调用处理程序
static boolean isProxyClass(Class<?> cl)
当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true
2、InvocationHandler(java.lang.reflect)
InvocationHandler 是代理实例的调用处理程序 实现的接口。
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
唯一方法(必须覆盖):
Object invoke(Object proxy, Method method, Object[] args) 在代理实例上处理方法调用并返回结果
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口
args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中
三、分析JVM生成的动态类
1、创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数
2、编码列出动态类中的所有构造方法和参数签名
3、编码列出动态类的所有方法和参数签名
4、创建动态类的实例对象
1)用反射获得动态方法
2)编写一个简单的InvocationHandler类
3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
4)打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常
5)将创建动态类的实例对象的代理改成匿名内部类的形式编写
5、总结:让jvm创建动态类及其实例对象,需要给它提供三个方面的信息
1)生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知
2)产生的类的字节码必须有一个关联的类加载器对象
3)生成的类中的方法是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被动用运行了。
注意:用Proxy.newProxyInstance方法直接一步就创建出代理对象
6、示例代码
指定需要实现的系统功能的接口:
实现具体功能的类:package day.day21; import java.lang.reflect.Method; /* * Advice来自spring * 给的通告与建议 * 希望执行的系统功能 * 功能 1、在调用目标方法之前 2、在调用目标方法之后 3、在调用目标方法前后 4、在处理目标方法异常的catch块中 */ public interface Advice { /*public void beforeMehthod(target,args,method) 正常写法*/ void beforeMethod(Method method); void afterMethod(Method method); }
package day.day21; import java.lang.reflect.Method; /* * 程序开发(spring)需要些的功能 * 功能 1、在调用目标方法之前 2、在调用目标方法之后 * 具体的系统功能类 */ public class MyAdvice implements Advice { long beginTime = 0; long endTime = 0; public void beforeMethod(Method method) { System.out.println("开始学习"); beginTime = System.currentTimeMillis(); } public void afterMethod(Method method) { System.out.println("结束学习"); endTime = System.currentTimeMillis(); System.out.println(method.getName()+"运行时间:"+(endTime-beginTime)); } }
生成代理类并运行实现了系统功能后的方法:
package day.day21; 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; /* * 分析JVM动态生成的类 */ public class ProxyTest { public static void main(String[] args) throws Exception{ //返回代理类的 java.lang.Class 对象(字节码,内存中加载)(动态类) Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println("-------begin constructors list-------"); /* 格式 * $Proxy0(...)*/ //获取动态类的所有构造方法 Constructor[] constructors = clazzProxy1.getConstructors(); for(Constructor constructor:constructors){ String name = constructor.getName(); StringBuilder sb = new StringBuilder(name); sb.append("("); //获取构造函数的参数类型 Class[] clazzParams = constructor.getParameterTypes(); for(Class clazzParam : clazzParams){ sb.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length!=0) sb.deleteCharAt(sb.length()-1); sb.append(")"); System.out.println(sb.toString()); } /* * 结果:只有一个构造方法: * com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler) */ //获取动态类的所有方法 System.out.println("-------begin methods list-------"); Method[] methods = clazzProxy1.getMethods(); for(Method method:methods){ String name = method.getName(); StringBuilder sb = new StringBuilder(name); sb.append("("); //获取方法的参数类型 Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam : clazzParams){ sb.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length!=0) sb.deleteCharAt(sb.length()-1); sb.append(")"); System.out.println(sb.toString()); } //通过动态类创建对象(通过反射) InvocationHandler是一个接口 System.out.println("-------begin create instance object-------"); //根据获得的构造函数的参数获取构造方法 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); //自定义InvocationHandler实现类--子类 class MyInvocationHandler1 implements InvocationHandler{ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } //方法一:通过动态类创建实例对象(指定参数) Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1()); System.out.println(proxy1);//运行结果为null,说明toString方法返回null proxy1.clear();//无返回值,运行正常,成功调用 // proxy1.size();//有返回值,抛出空指针异常,不能成功调用 //方法二:通过匿名内部类的方式创建动态类的实例对象 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); //在外部建立目标类。(内部类在局部时只能访问被final修饰的局部变量) final ArrayList target = new ArrayList(); //通过外界传递目标类及系统功能对象MyAdvice。只能转换为其实现的接口类型 Collection proxy3 = (Collection)getProxy(target,new MyAdvice()); //调用代理对象的方法 proxy3.add("zhangsan");//运行后调用handler的invoke方法,add方法的结果来自handler的invoke方法 proxy3.add("lisi"); proxy3.add("wangwu"); System.out.println(proxy3.size()); System.out.println(proxy3.getClass().getName());//运行结果为com.sun.proxy.$Proxy0 } private static Object getProxy(final Object target,final Advice advice) { /*方法三:通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法 * 一步创建(只是动态类,并未挂上目标)*/ Object proxy3 = Proxy.newProxyInstance( target.getClass().getClassLoader(), //第一个参数 /*new Class[]{Collection.class},第二个参数,数组*/ target.getClass().getInterfaces(), new InvocationHandler(){//第三个参数(对象-通过匿名内部类实现) /*为动态类指定目标(在invoke方法内部时,每运行一次add方法就创建一个新目标) ArrayList target = new ArrayList();不在内部定义目标类,改在外部,不再写死*/ 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()+"运行时间:"+(endTime-beginTime)); return retVal; return method.invoke(proxy,args);//改为返回该句,则死循环,因为代理执行这个自己的方法时,就是在调用handle对象的invoke方法,而该方法又会接着循环调用 */ advice.beforeMethod(method); Object retVal = method.invoke(target, args);//调用目标方法 advice.afterMethod(method); return retVal; } } ); return proxy3;//返回代理类(实际上就是自定义的该函数的功能新建一个对象并返回) } }
注意:
1)直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但是没有意义
2)为InvocationHandler实现类外部注入目标类的实例对象,
3)让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量
4)将系统功能代码模块化,即将切面代码也改为通过参数形式提供
把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!
5)在获取在代理类之后,只能强转为代理类实现的接口的子类,并调用该接口中的方法
7、分析动态生成的类的内部原理
1)动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接收InvocationHandler参数的构造方法(记住InvocationHandler对象,方便使用)
2)构造方法接收一个InvocationHandler对象,并通过该对象的invoke方法调用目标对象的同名函数,该类的内部构成及运行原理如下:
class &Proxy0//动态类 { InvocationHandler handler; public &Proxy0(InvocationHandler handler) { this.handler = handler; } //生成的Collection接口中的方法运行原理 int size() { return handler.invoke(this,this.getClass().getMethod("size"),null); } //add方法 add(Object args) { return handler.invoke(this,this.getClass().getMethod("add"),args); } }
3)Client程序调用objProxy.add("abc")方法时,涉及3个要素:objProxy对象,add方法,“abc"参数。
4)在代理实例上的 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的调用将委托给handler目标去实现;其他方法如getClass等从Object类中继承的其他方法自己实现,不交给handler。
8、图解
第四部分:实现类似spring的可配置的AOP框架
一、分析框架
1、工程类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
2、BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList xxx=day.day21.ProxyFactoryBean xxx.target=java.util.ArrayList xxx.advice=day.day21.MyAdvice
3、ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂提供目标与通知的配置信息。
4、编写客户端应用。编写实现Adive接口的类和在配置文件中进行配置
二、代码体现
1、工程类
package day.day21.aopframework; /* * 工程类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。 * 其getBean方法根据参数字符串返回一个相应的实例对象, * 如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象, * 否则,返回该类实例对象的getProxy方法返回的对象 */ import java.io.IOException; import java.io.InputStream; import java.util.Properties; import day.day21.Advice; public class BeanFactory { Properties props = new Properties(); //构造时加载配置文件 public BeanFactory(InputStream in){ try { props.load(in); } catch (IOException e) { e.printStackTrace(); } } public Object getBean(String name){ String className = props.getProperty(name); Object bean = null; try { Class clazz = Class.forName(className); /* 调用不带参数的构造方法,对javabean来说 必须要有一个不带参数的构造方法*/ bean = clazz.newInstance();//创建该类的实例对象 } catch (Exception e) { e.printStackTrace(); } //如果从配置文件中加载的类是代理,就从配置文件中加载代理的advice(功能)与代理的target(目标) 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中去 proxyFactoryBean.setAdvice(advice); proxyFactoryBean.setTarget(target); //生成代理 proxy = proxyFactoryBean.getProxy(); } catch (Exception e) { e.printStackTrace(); } return proxy; } //从配置文件中加载的类不是代理就直接返回 return bean; } }
2、动态代理的工厂
package day.day21.aopframework; /* * ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂提供目标与通知的配置信息。 */ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import day.day21.Advice; public class ProxyFactoryBean { private Advice advice; private Object target; public Advice getAdvice() { return advice; } //设置与获取 advice(系统功能对象) 与 target(代理的目标对象) public void setAdvice(Advice advice) { this.advice = advice; } 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(),//类实现的所有接口的class对象的数组 new InvocationHandler(){//InvocationHandler对象 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在原功能前后加上系统功能代码 advice.beforeMethod(method); Object retVal = method.invoke(target, args);//调用目标方法 advice.afterMethod(method); return retVal; } } ); return proxy; } }
3、配置文件
xxx=java.util.ArrayList #xxx=day.day21.aopframework.ProxyFactoryBean xxx.advice=day.day21.MyAdvice xxx.target=java.util.ArrayList
4、测试程序
package day.day21.aopframework; /* * 测试程序 * 编写客户端应用。编写实现Adive接口的类和在配置文件中进行配置 */ import java.io.InputStream; public class AopFrameworkTest { public static void main(String[] args) throws Exception{ //相对路径加载配置文件(class类中的getResourceAsStream方法(底层也是通过类加载器实现的) InputStream in = AopFrameworkTest.class.getResourceAsStream("config.properties"); Object bean = new BeanFactory(in).getBean("xxx"); System.out.println(bean.getClass().getName());//Collection c = (Collection)bean;(转化为接口类型) } }
运行:通过更改配置文件可以达到是运行本类还是代理类的功能,而不用更改源代码