标签:
/**
* 代理和AOP(一)
*
* 1、分析代理类的作用与原理及AOP的概念
*
* 1.1 代理类:一个代理类通常有自己的代理目标类,代理类是对目标类的代理,一般代理类的方法和目标类的方法签名一致,是对
* 目标类的包装,代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标的方法返回的结果之外,
* 还可以在代理类的方法中 加入一些系统功能代码,添加的位置有四个地方:
* 分别是目标方法调用前,目标方法调用后,目标方法调用前后,在处理目标方法异常的catch块中。
* 例如:
* class Proxy //代理类
* {
* void sayHello()
* {
* ........ //功能代码
* try
* {
* target.method() //目标方法的调用
* }catch(Exception e)
* {
* ........ //功能代码
* }
* .......... //功能代码
* }
* }
*
* 代理类的背景:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如异常处理、日志、计算方法的运行时间、
* 事物管理等等,在你拿不到已存在类的源代码的时候,也就是不允许修改已存在类的源代码,这时候该怎么做?使用代理类
*
* 代理类的作用:代理技术的一个主要应用就是在AOP(Aspect Oriented Programming)中,扩展了目标类的功能。
*
* 代理类在程序中的应用原理:
* 客户端程序 目标类 代理类 接口
* 在设计时,要使目标类和代理类实现相同的接口,这样才可以在写客户端程序的时候采用面向接口(父类)的方式编程
* ,面向父类或者接口编程的好处是,当采用工厂设计模式和配置文件的方式进行管理的时候,就不需要修改已经编写好的
* 客户端程序,如果想要切换目标类到代理类,只需要在配置文件中配置一下就可以了,比如,想要日志功能时,就配置
* 代理类,不想要时就配置目标类,这样为程序增加系统功能很容易,去掉系统功能也很容易。
*
* 代理类可以采用自定义的方式,即手动写一个类A的代理类B,但是当要为系统中各个接口的类增加系统功能,即为这些类写代理类,如果全部
* 采用静态代理的方式,为程序中的成百上千个类写代理类,就会非常麻烦。
* 所以JDK提供了动态生成代理类的方式。
*
* 1.2 动态类:这种动态生成代理类的方式基于的技术是生成动态类,即在程序运行期间由JVM在内存中构造出一份字节码,
* 也就是一个类。动态生成的类可以被用来创建对象,可以用来做其他的用途,作为代理类只是其用途之一。
* JVM生成的动态类必须实现一个或者多个接口,所以动态类只能用作具有相同接口的目标类的代理,对于没有实现任何接口的类,
* 可以采用第三方类库,CGLIB库,CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以可以使用
* CGLIB库为一个没有实现任何接口的类生成动态代理类。
*
*
* java.lang.reflect.Proxy :Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
* 创建一个动态类:
* public static Class getProxyClass(ClassLoader loader,Class<?> ...interfaces)
* //该方法返回动态类的Class对象,即字节码文件。需要指定生成的动态类的类加载是谁,以及这个动态类要实现的接口,可以是多个接口
* //生成的动态类是不需要类加载加载的,也就是说本质上讲它没有类加载器,但是为了统一,需要为其指定一个类加载器,通过传递参数的
* //方式指定,传递的参数是什么,它的类加载器就是什么。
*
* 创建的动态类的类名一般为:com.sun.proxy.$Proxy0 、com.sun.proxy.$Proxy1、com.sun.proxy.$Proxy2 之类
*
* 该动态类中只有一个构造方法,并且是带有参数的:
* com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
* //该动态类不存在无参数的构造方法,所以不能使用Class.newInstance()创建对象
* //否则会抛出实例化异常java.lang.InstantiationException
*
* 该动态类中的方法:继承自Object的方法、继承自Proxy的方法、实现的接口Collection的方法
*
* InvocationHandler接口:是代理类的构造方法要接受的实例对象的类要实现的接口,接口中只有一个方法是invoke。
*
* Object invoke(Object proxy, Method method, Object[] args) //在代理类的实例对象上处理方法调用并返回结果
* 。
* //proxy:就是被调用的代理类实例,method:代理对象被调用的方法,args:代理对象被调用方法的实际参数
*
* //利用反射在代理类proxy的目标类target上调用method方法,method方法的参数是args。
* //当调用代理类的实例的方法时,就会将该方法调用请求转发到构造时传入的InvocationHandler对象的Invoke方法上,由
* //InvocationHandler对象的Invoke方法来处理调用请求,所以InvocationHandler中文意思是“调用处理者”,将该代理类的
* //实例和调用的方法以及方法的参数传入到invoke中。
* InvocationHandler的子类,一般由程序员自定义,一般采用匿名内部类的方式定义,并且在内部类中定义代理类的目标类的引用(为了通用,
* 定义为Object类型,这样就可以接受任何目标类的对象了),以InvocationHandler构造函数的参数的形式传入目标类的对象。
* 在invoke方法中再来调用传入的目标对象的对应方法。并将目标对象的对应方法的返回值返回回去。invoke的返回值就会作为
* 代理对象的方法的返回值返回。
*
* 调用流程图及伪代码 :
* InvocationHandler handler = new MyInvocationHandler(目标类对象);
* proxy = new 代理类对象(InvocationHandler handler);
* proxy.method1(); <==> handler.invoke(proxy,method1,args) ---> target.method1(args);
*
* 代理类(proxy) ---------> InvocationHandler实现类(匿名对象) ------> 目标类(target)
* { { Object target; {
* method1(){} --> invoke(proxy,method,args) --> method1(){}
* {
* method2(){} --> target.method(args); --> method2(){}
* }
* } } }
*
* 1.3 AOP(Aspect Oriented Programming):即是面向切面编程,或者面向方面编程
*
* AOP产生的背景:在软件开发中,会发现不同的模块被同一个交叉业务所贯穿,即不同的模块的某些方法中的代码有相似的部分,
* 也就是一系统功能(交叉业务)的代码需要在不同的模块中的某些方法中来实现,即这个系统功能不是独立的一个类,而是
* 一些分散在其他模块方法中的代码的集合,这些代码们看上去像一个切面,为了解决这个切面的问题,
* 即将切面代码抽离出来,形成单独的功能模块,封装到一个类中,就有了面向切面编程AOP。
*
* 专业回答:系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下:
* 安全 事务 日志 (各个交叉业务、系统功能)
* StudentService ------|-------|-------|------------------ (模块1)
* CourseService ------|-------|-------|------------------ (模块2)
* MiscService ------|-------|-------|------------------ (模块3)
*
*
*
* 用具体的程序代码描述交叉业务现象,如下:
*
* method1 method2 method3 原各个目标类的方法
* { { {
* -------------------------------------------------------- 切面(交叉业务)
* .... .... .... 目标类中的代码
* -------------------------------------------------------- 切面(交叉业务)
* } } }
*
* 这种交叉业务的存在,使得系统中不同位置有若干功能相似的代码出现,而且对于功能切换很不方便,添加和去除一些系统功能时,
* 需要修改源代码,一切都归结于这个交叉业务(这个系统功能)没有模块化,没有被封装成一个类。
*
* 交叉业务的编程问题就是面向方面的编程(AOP)要解决的问题,AOP的目标就是要使交叉业务模块化,可以采用将切面代码
* 移动到目标类的原始方法的周围的方法来实现交叉业务模块化,这与直接在方法中编写切面代码的运行效果是一样的。如下:
*
* -------------------------------------------------------- 切面(交叉业务)
* method1 method2 method3 原各个目标类的方法
* { { {
*
* .... .... .... 目标类中的代码
*
* } } }
* -------------------------------------------------------- 切面(交叉业务)
*
* 使用代理技术正好可以解决这种切面问题,代理是实现AOP功能的核心和关键技术
*
*/
package com.itheima; 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 AOPDemo { public static void main(String[] args)throws Exception { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);//JVM创建动态类的字节码对象 System.out.println(clazzProxy.getName()); System.out.println(clazzProxy.getClassLoader());//打印出代理类的类加载器,指定的是Collection的类加载器, //是顶级类加载器,所以打印是null。顶级类加载器不是java类,是 //C++编写的类 Constructor[] constructors = clazzProxy.getConstructors();//反射获得构造方法 System.out.println("----------构造方法列表----------------"); for(Constructor constructor : constructors) //打印所有构造方法列表 { String constructorName =constructor.getName(); StringBuilder sBuilder = new StringBuilder(constructorName); sBuilder.append(‘(‘); Class[] clazzParameters = constructor.getParameterTypes();//获得该构造方法的参数列表中的各个参数的类型 for(Class clazzParameter : clazzParameters) { sBuilder.append(clazzParameter.getName()).append(‘,‘); } if(clazzParameters!=null && clazzParameters.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(‘)‘); System.out.println(sBuilder); } Method[] methods = clazzProxy.getMethods(); //获得所有方法 System.out.println("----------方法列表----------------"); for(Method method : methods) //打印所有方法列表 { String methodName =method.getName(); StringBuilder sBuilder = new StringBuilder(methodName); sBuilder.append(‘(‘); Class[] clazzParameters = method.getParameterTypes(); //获得该方法的参数列表的各个参数的类型 for(Class clazzParameter : clazzParameters) { sBuilder.append(clazzParameter.getName()).append(‘,‘); } if(clazzParameters!=null && clazzParameters.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(‘)‘); System.out.println(sBuilder); } //Collection p = (Collection) clazzProxy.newInstance(); //错误代码,报异常 //创建动态类的实例对象 System.out.println("----------创建实例对象----------------"); //定义代理对象的调用处理程序类 //在调用处理程序类中指定目标类和要添加的系统功能类,使得这个InvocationHandler类对象可以应用在任何代理类上 //使用时只需在InvocationHandler的构造方法中传入要被代理类代理的目标类的对象,和要在目标对象各个方法上添加 //的系统功能的对象即可。目标类必须和代理类实现了相同的接口,这样调用时才会找到相应的方法,才不会出错。 class MyInvocationHandler implements InvocationHandler { Object target ; // Advice advice ; MyInvocationHandler(Object target,Advice advice) { this.target = target ; this.advice = advice ; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(); Object retVal = null; try { System.out.print("目标类被调用了 ::: "); retVal = method.invoke(target, args); }catch(Exception e) { advice.exceptionCatch(); } advice.endMethod(); return retVal; } } Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);//JVM创建动态类的字节码对象 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);//用代理类的字节码获得构造方法 Object target = new ArrayList();//创建目标类对象,该目标类必须和代理类实现相同接口,目标类是被代理类所实现的接口限制的 Advice advice = new MyAdvice();//创建你想添加的系统功能类的对象,通过对象方法调用的方式还添加想要被执行的代码 InvocationHandler handler = new MyInvocationHandler(target,advice);//创建调用处理程序实例对象 Collection proxy = (Collection) constructor.newInstance(handler);//绑定该调用处理程序对象到指定的代理对象上 //一个代理类可以代理多个目标类,代理类可以代理与其实现相同接口的那些目标类,这些目标类具体是代理哪个,由handler指定 proxy.add("zhangsan");//调用代理对象方法 proxy.add("lisi"); proxy.add("wangwu"); //proxy.lastIndexOf("lisi"); // ArrayList中有lastIndexOf方法,但是proxy中有的是Collection接口中的方法,即proxy没有该方法,报错 System.out.println(AOPDemo.class.getClassLoader()); //打印内部类所在类的类加载器 System.out.println(handler.getClass().getClassLoader()); //内部类是由内部类所在类的类加载器加载的 for(Object str : proxy) { System.out.println(str); } } } interface Advice //通告接口,系统功能的统一接口 { void beforeMethod();//在目标方法前添加系统功能代码 void endMethod(); //在目标方法后添加系统功能代码 void exceptionCatch();//在处理目标方法的异常catch块中添加系统功能代码 } //定义一个的通告,定义一个系统功能 class MyAdvice implements Advice { long time ; public void beforeMethod() { System.out.println("开始计算运行时间"); time = System.currentTimeMillis(); //添加要加入的功能代码,这段代码就是从目标类中的各个方法中抽离出来的交叉业务代码 } public void endMethod() { time = time - System.currentTimeMillis(); //添加要加入的功能代码,这段代码就是从目标类中的各个方法中抽离出来的交叉业务代码 System.out.println("运行时间 :"+time); System.out.println("结束计算运行时间"); } public void exceptionCatch() { System.out.println("目标出现了异常");//添加要加入的功能代码,这段代码就是从目标类中的各个方法中抽离出来的交叉业务代码 } }
标签:
原文地址:http://www.cnblogs.com/wllbelief-win/p/4438868.html