知识点七 类加载器
一、概述:
1、定义:类加载器的作用是获取字节码.class文件里面的内容,简单的说,类加载器就是加载类的工具。
当出现一个类,用到此类的时候,Java虚拟机首先将类字节码加载进内存,通常字节码的原始信息放在硬盘上的classpath指定的目录下。
2、类加载器作用:将.class文件中的内容加载进内存进行处理,处理完后的结果就是字节码。
3、默认类加载器:
1)Java虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader
2)BootStrap--顶级类加载器:
类加载器本身也是Java类,因为它是Java类,本身也需要加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,获得的只能是null。
4、Java虚拟机中的所有类加载器采用子父关系的树形结构进行组织,在实例化每个类加载器对象或默认采用系统类加载器作为其父级类加载器。
示意图:
示例:
package cn.itcast.text2;import java.util.Date;public class ClassLoadTest{ public static void main(String[] args) throws Exception{ System.out.println( ClassLoadTest.class.getClassLoader(). getClass().getName());//为AppClassLoader System.out.println( System.class.getClassLoader());//为null }}
二、类加载器的委托机制:
1、加载类的方式
当Java虚拟机要加载一个类时,到底要用哪个类加载器加载呢?
1)首先,当前线程的类加载器去加载线程中的第一个类。
2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。
3)还可直接调用ClassLoader的LoaderClass()方法,来制定某个类加载器去加载某个类。
2、加载器的委托机制:每个类加载器加载类时,又先委托给上级类加载器。
思考:每个类加载器加载类时,又先委托给其上级类加载器。 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢? 对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
每个ClassLoader本身只能分别加载特定位置和目录中的类,但他们可以委托其他类的加载器去加载,这就是类加载器的委托模式,类加载器一级级委托到BootStrap类加载器,当BootStrap在指定目录中没有找到要加载的类时,无法加载当前所要加载的类,就会一级级返回子孙类加载器,进行真正的加载,每级都会先到自己相应指定的目录中去找,有没有当前的类;直到退回到最初的类装载器的发起者时,如果它自身还未找到,未完成类的加载,那就报告ClassNoFoundException的异常。
简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再返回给其子级找,直到发起者,再没找到就报异常。
3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。
补充:面试题
可不可以自己写个类为:java.lang.System呢?
回答:第一、通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System。
第二、但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。
4.加载类的方式:
一、用当前线程的类加载器加载了线程中的第一个类,然后这个类引用到的其他类,那么那个类也用这个线程的这个类加载器进行加载。
二、可以直接调用ClassLoader.loadClass()方法来指定某个类加载器加载类。
三、每个类加载器加载类的时候,会先委托给上级类加载器,会从BootStrap开始查看这个类是否已经加载过,如果父类加载器没有加载过这个类,那么才会顺着继承关系逐级下来进行查看。
可以写一个java.lang.String类通过自己写的类加载器加载,但是这个类加载器就不是依托与虚拟机加载类了。因为如果依托于java虚拟机加载的话,java虚拟机中的类加载机制是委托机制,即最后会从BootStrap开始逐级查找要进行加载的类,如果在父类中先找到了这个类,java虚拟机就会加载这个类,那么我们写的这个类就根本不可能加载到。
数组类的 Class对象不是由类加载器创建的,而是由Java运行时根据需要自动创建。数组类的类加载器由Class.getClassLoader()返回,该加载器与其元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。
三、自定义类加载器
1、自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(String name)方法,而不用覆写loadClass()方法。
注意:自定义的类加载器必须继承ClassLoader
模板方法设计模式:loadClass:通过findClass方法按照委托机制查找对应的类,在通过父类加载器检查所请求的类后,如果找到了类,那么就立刻调用defineClass把找到的字节码转换为这个类的实例。如果经过父类的加载器检查所请求的类却没有找到,反而在自定义的类加载器中找到了这个类,那么就会通过自定义的类加载器中复写过的findClass来对类的字节码进行在操作,然后调用defineClass把找到的字节码转换为这个类的实例。
A. loadClass方法,不能覆盖
Class<?> loadClass(String name) 使用指定的类名来加载类。
protected Class<?> loadClass(Stringname,boolean resolve) 使用指定的二进制名称来加载类。
B. findClass方法,需要覆盖这个方法。该方法按照委托机制查找对应的类,在通过父类加载器检查所请求的类后,此方法将被loadClass方法调用
protected Class<?> findClass(Stringname)使用指定的二进制名称查找类。
2、覆写findClass(String name)方法的原因:
1)是要保留loadClass()方法中的流程,因为loadClass()中调用了findClass(String name)这个方法,此方法返回的就是去寻找父级的类加载器。
2)在loadClass()内部是会先委托给父级,当父级找到后就会调用findClass(String name)方法,而找不到时就会用子级的类加载器,再找不到就报异常了,所以只需要覆写findClass方法,那么就具有了实现用自定义的类加载器加载类的目的。
流程:
父级-->loadClass-->findClass-->得到Class文件后转化成字节码-->defind()。
3、步骤:
1)编写一个对文件内容进行简单加密的程序
2)编写好了一个自己的类加载器,可实现对加密过来的类进行装载和解密。
3)编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName得到字节码文件。
示例:
ClassLoaderAttachment类:
/* * MyClassLoader.java可以对这个类进行加密 */package cn.itcast.day2;import java.util.Date;public class ClassLoaderAttachment extends Date { public String toString(){ return "hello,itcast"; } }
MyClassLoader类:
/* 既是类加载器,又是加密工具 */package cn.itcast.day2;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;public class MyClassLoader extends ClassLoader{ /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //源目录 String srcPath = args[0]; //目标目录 String destDir = args[1]; FileInputStream fis = new FileInputStream(srcPath); //目标的名字 String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1); //目标的路径 String destPath = destDir + "\\" + destFileName; FileOutputStream fos = new FileOutputStream(destPath); cypher(fis,fos); fis.close(); fos.close(); } //加密算法 private static void cypher(InputStream ips ,OutputStream ops) throws Exception{ int b = -1; while((b=ips.read())!=-1){ ops.write(b ^ 0xff); } } private String classDir; @Override protected Class findClass(String name) throws ClassNotFoundException { // TODO Auto-generated method stub String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class"; try { FileInputStream fis = new FileInputStream(classFileName); //定义字节数组流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); cypher(fis,bos); fis.close(); System.out.println("aaa"); byte[] bytes = bos.toByteArray(); return defineClass(bytes, 0, bytes.length); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public MyClassLoader(){ } public MyClassLoader(String classDir){ this.classDir = classDir; }}
ClassLoaderTest类:
/* 特殊的类加载器 */package cn.itcast.day2;import java.util.Date;public class ClassLoaderTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub System.out.println( ClassLoaderTest.class.getClassLoader().getClass().getName() ); //字节码的类加载器为null不代表没有类加载器,而是代表他有特殊的类加载器bootstrap类加载器 System.out.println( System.class.getClassLoader() ); System.out.println("xxx"); //循环打印三个类加载器 ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while(loader != null){ System.out.println(loader.getClass().getName()); loader = loader.getParent(); } System.out.println(loader); //System.out.println(new ClassLoaderAttachment().toString()); System.out.println("xxx2"); Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment"); Date d1 = (Date)clazz.newInstance(); System.out.println(d1); }}
四、一个类加载器的高级问题分析(JavaWeb项目)
1.分析:
1)编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。 2)把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。 3)把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。 4)父级类加载器加载的类无法引用只能被子级类加载器加载的类
2.原理如下图:
注意:按照类加载器的委托机制,HttpServlet在两种情况下分别由WebAppClassLoader、ExtClassLoader加载
补充两个小知识点:
1.有包名的类不能调用无包名的类2.Myeclipse下查找问题的方式:Windows-showview-General-problems,调用problems选项卡即可查看错误。
知识点八 动态代理技术的深入讲解与应用
一 代理概述
一、概念:
1、生活中的代理:山东人从山东的代理商手中买联想电脑和直接跑到北京来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?批发进货的成本和运输费的优势,比你自己直接到北京总部买的总成本要低吧。
2、程序中的代理:要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等。
3、简单示例:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码,如:
目标类: 代理类:
class X{ Xproxy{
void sayHello(){ void sayHello(){
syso:Hello; startTime
} X. sayHello();
} endTime;}}
一般用接口来引用其子类,如:Collectioncoll = new ArrayList();
4、代理类的优点:
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类。这样以后很容易切换,如果想要日志功能时,就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想换掉系统功能也很容易。
5.代理架构图:
示例:
/* 创建动态类及查看其列表信息的方法 * */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 { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //使用Collection这个接口,就是用它的字节码 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println( clazzProxy1.getName()); /* $Proxy0(); $Proxy0(InvocationHandler,int);*/ //ctrl+shift+/对一段进行注释 System.out.println("----------begin constructors list-------"); //得到所有的构造方法 Constructor[] constructors=clazzProxy1.getConstructors(); for (Constructor constructor : constructors) { String name=constructor.getName(); StringBuilder sBuilder=new StringBuilder(name); sBuilder.append('('); //增加参数列表 Class[] clazzParams=constructor.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 methods list-------"); //得到所有的方法的列表 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 list-------"); //这样不可以,因为 会调用不带参数的够着方法,但是clazzProxy1没有不带参数的构造方法 //Object obj=clazzProxy1.newInstance(); //先要获得构造方法 Constructor constructor=clazzProxy1.getConstructor(InvocationHandler.class); //自己做一个实现InvocationHandler类 //构造内部类 class MyInvocationHandler1 implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } } //调用构造方法的无参数的构造方法 Collection proxy1=(Collection) constructor.newInstance(new MyInvocationHandler1()); //这个对象的toString方法返回的结果为null,而不是这个对象为null,如果是会报空指针异常 System.out.println(proxy1.toString()); //调用无参数的构造方法成功,返回值为null proxy1.clear(); //调用无参数的构造方法失败,报告空指针异常 //报错的原因,这里要返回一个整数,但是调用invoke方法返回的是null; //proxy1.size(); //构造匿名内部类 Collection proxy2=(Collection)constructor.newInstance(new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; }}); final ArrayList target=new ArrayList(); Collection proxy3 = (Collection) getProxy(target,new MyAdvice()); //把目标返回的结果返回到代理上面去 proxy3.add("zxx"); proxy3.add("lhm"); proxy3.add("bxd"); System.out.println(proxy3.size()); //调用目标的class方法,返回的应该是ArrayList System.out.println(proxy3.getClass().getName()); } private static Object getProxy(final Object target,final Advice advice) { Object proxy3=Proxy.newProxyInstance( target.getClass().getClassLoader(), /* new Class[]{Collection.class},*/ //与target实现相同的接口 target.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub /*//打印系统时间 long begintime =System.currentTimeMillis(); Object returnVal=method.invoke(target, args); //打印系统时间 long endtime = System.currentTimeMillis(); System.out.println(method.getName()+" running time of "+(endtime-begintime)); return returnVal; //return method.invoke(proxy, args); */ advice.beforeMethod(method); Object returnVal=method.invoke(target, args); advice.afterMethod(method); return returnVal; } } ); return proxy3; }}
二、AOP
1、简述:AOP(Aspect Oriented Program)即面向方面的编程。OOP(Object Oriented Program)即面向对象的编程。
2、示意图:
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|------------- CourseService ------|----------|------------|------------- MiscService ------|----------|------------|-------------安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。
3、用具体的程序代码描述交叉业务:1)代码实现:
method1 method2 method3 { { { ------------------------------------------------------切面 .... .... ...... ------------------------------------------------------切面 } } } 2)交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示: ------------------------------------------------------切面 func1 func2 func3 { { { .... .... ...... } } } ------------------------------------------------------切面 使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。重要原则:不要把供货商暴露给你的客户
一道面试题:StringBuilder与StringBuffer的区别1.它们在应用上基本上一样,都是往字符串动态的添加内容。2.StringBuilder在单线程的情况下效率比StringBuffer高一点3.在单线程的情况下要用StringBuilder 在多线程的情况下要用StringBuffer
二 动态代理技术
一、概述:
1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,这时就不能采用静态代理方式,需用动态代理技术。
2、动态代理类:JVM可在运行时,动态生成类的字节码,这种动态(不是代理,只是拿出来作为代理类)生成的类往往被用作代理类,即动态代理类。
注:JVM生成的动态类必须实现一或多个接口,所以JVM生成的动态代理类只能用作具有相同接口的目标类代理。
3、CGLIB库可以动态生成一个类的子类,一个类的子类也可以作为该类的代理,所以,如果要为一个没有实现接口的类生成动态代理,那么可以使用CGLIB库。
4、代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,还可以再代理方法中的如下位置上加上系统功能代码:
1)在调用目标方法之前
2)在调用目标方法之后
3)在调用目标方法前后
4)在处理目标方法异常的catch块中。
二、分析JVM动态生成的类
1、创建动态类的实例对象:
1)用反射获得构造方法
2)编写一个最简单的InvocationHandler的类
3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。
示例:
第一、打印创建的对象和调用对象的无返回值的方法和getClass方法,演示调用其他没有返回值的方法报告的异常
第二、将创建的动态类的实例对象的代理改写成为匿名内部类的形式编写。
示例:
import java.lang.reflect.*; import java.util.*; public class ProxyTest { public static void main(String[] args) throws Exception{ //创建动态代理类的三种方式 //方式一:通过接口的子类创建对象 Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler()); System.out.println(proxy1);//null System.out.println(proxy1.toString());//null proxy1.clear();//无异常 //proxy1.size();//异常 //方式二:匿名内部类 Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); //方式三: //通过代理类的newProxyInstance方法直接创建对象 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 { //测试程序运行时间 long beginTime = System.currentTimeMillis(); //调用目标方法,将其从return抽出来,加入代理所需的代码 Object retVal = method.invoke(target, args); long endTime = System.currentTimeMillis(); //测试 System.out.println(method.getName() + " run time of " + (endTime - beginTime)); return retVal; } } ); //通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法 //当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法 proxy3.add("sdfd"); proxy3.add("shrt"); proxy3.add("rtbv"); System.out.println(proxy3.size()); System.out.println(proxy3.getClass().getName()); } }
2、让JVM创建动态类需要提供的信息:
1)生成类中的哪些方法,通过让其实现哪些接口的方式进行告知。
2)产生的类字节码必须有一个关联的类加载器对象
3)生成的类中的方法的代码是怎么样的,也得由我们自己提供,把我们的代码写在一个约定好的子接口对象的方法中,把对象传给它,它调用我们的方法,即相当于插入了我们自己的代码。提供执行代码的对象就是InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的,在上面的InvocationHandler对象的invoke方法中,加一点代码就可以看到这些代码被调用运行了。
用Proxy.newInstance方法直接一步就创建出代理对象。
三、猜想分析动态生成的类的内部代码
1、构造方法接受一个InvocationHandler对象,接受此对象的用处:
接受一个handler参数是为了记录它,以便在之后的程序中运用它。
2、实现Collection接口的动态类中的各个方法的代码的解析:
1)InvocationHandler接口中定义的invoke方法接受三个参数的含义:
第一、Client(客户端)程序调用objProxy.add(“avc”)方法时,涉及到了三个参数,分别为:objProxy对象,add方法,”avc”参数。代码如下:
class Proxy${
add(Object obj){
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
第二、其中的Objectproxy 即为objProxy对象,Method method对应add方法,Object[] args就是”avc”参数。在使用newProxyInstance的方式创建代理对象实现时,当前正在调用代理对象(Object proxy),调用当前对象的哪个方法(Method method),调用此对象方法时传入的参数(Object[] args)。
3、调用代理涉及到三个因素:代理对象,代理对象的哪个方法,以及此方法接受的参数。要执行目标对象,只需要将代理对象作为目标对象即可。
4、对于上面代码中的Object retVal = method.invoke(target,args)的分析:
目标对象target执行完返回一个值为retVal,接着将值作为结果return回去,则代理方法就会收到一个返回值。其中还可以定义一个过滤器,对参数args(即当前对象的方法传入的参数)进行过滤(修改)。
5、对于上面代码中的proxy3.add(“sdfd”)的分析:
1)代理对象调用add方法,传递了sdfd参数。
2)add方法内部会找到InvocationHandler中的invoke方法,将代理对象proxy传进去,把add方法传入,将“sdfd”参数传入代理对象中的handler参数,返回了一个结果,就是给了add方法,add方法继续向外返回给调用的对象proxy3,即最终结果。
其中的handler的invoke方法返回又来自于目标target返回值,从而将此返回值返给Object invoke()方法,即作为handler参数位置上的值返回给add方法。add方法作为最后的结果返回。
四、问题:
1、在上面的方式一的代码中,调用无返回值的方法返回的是null,而调用有返回值方法的时候会报NullPointException异常的原因:
在proxy1.size()方法中,size()会调用Object invoke()方法,而对其覆写时的返回值是null,而size()本身会返回int类型,两者返回的类型不相等,所以就会报错。
而对于proxy3.size()不报错,是因为size()返回值与代理对象中handler参数返回值是一致的,当前目标返回什么,代理就返回什么,所以不会报错。
注意:目标返回值和代理返回值必须是同一类型。
2、为何动态类的实例对象的getClass()方法返回了正确结果,而没调用invoke方法:
因为代理类从Object上继承了许多方法,其中只对三个方法(hashCode、equals和toString)进行开发,委托给handler去自行处理,对于它身上其他方法不会交给代理类去实现,所以对于getClass()方法,还是由Object本身实现的。即proxy3.getClass(),该是什么结果还是什么结果,并不会交给invoke方法处理。
补充:
Proxy生成的动态类只有一个有参的构造方法,接受的参数为InvocationHandler。
$Proxy0(java.lang.reflect.InvocationHandler)
只有toString、hashcode、equals这三个方法才委托给Handler。
五、总结分析动态代理类的统计原理和结构:
1、怎样将目标传进去:
1)直接在InvocationHandler实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是毫无意义。
2)为InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。
3)让匿名内部类的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。
2、动态代理的工作原理:
1)Client(客户端)调用代理,代理的构造方法接受一个InvocationHandler,client调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。就是将handler封装起来,其中this引用了当前的放(发来什么请求就接受哪个方法)。
2)将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受目标,同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
在这里将InvocationHandler加入到Proxy的构造方法中,因此,在创建出来的对象,就会存有构造方法中InvocationHandler的一些功能和信息,因为我们把想要运行的代码封装在InvocationHandler对象,把它传入到构造函数中,那么就实现了代理对象每次调用目标方法(因为实现了同一接口)时,都会调用我们加入到InvocationHandler对象中的代码。这就保证了每次调用代理时,可以在目标上加入我们自己加入的功能。
3、把系统功能代理模块化,即切面代码也改为通过参数形式提供,怎么把要执行的系统功能代码以参数的形式提供:
1)把要执行的代码装到一个对象的某个方法中,然后把此对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外接提供的代码。
2)为bind方法增加一个Advice参数。
示意图:
示例:
package cn.itcast.test3; import java.lang.reflect.*; import java.util.*; public class MyProxy { public static void main(String[] args)throws Exception { //创建目标对象,并进行操作测试 final ArrayList target = new ArrayList(); Collection proxy = (Collection)getProxy(target,new MyAdvice()); proxy.add("sdf"); proxy.add("wgcd"); proxy.add("hgwe"); System.out.println(proxy.size()); } //作为一个通用的方法,就使用Object //传入一个目标,并传入一个接口,此接口作为通信的契约,才能调用额外的方法 private static Object getProxy(final Object target,final Advice advice) { 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; } } //创建实现Advice接口的子类 package cn.itcast.test3; import java.lang.reflect.Method; //实现Advice接口中方法的具体内容 public class MyAdvice implements Advice { long beginTime = 0; public void beforeMethod(Method method) { // TODO Auto-generated method stub System.out.println("从这里开始"); beginTime = System.currentTimeMillis(); } public void afterMethod(Method method) { // TODO Auto-generated method stub long endTime = System.currentTimeMillis(); System.out.println("从这里结束"); System.out.println(method.getName() + " run time of " + (endTime-beginTime)); } } //创建接口Advice import java.lang.reflect.Method; /*接口中需要实现四个方法 * 调用目标方法之前 * 调用目标方法之后 * 调用目标方法前后 * 在处理目标方法异常的catch块中 */ //这里只列出两个作为示例 public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); }三 实现类似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进行测试。
类似spring可配置的AOP框架示例:
分析(需要用到的包和配置文件):
cn.itcast.day3包下两个类:Advice类、MyAdvice类
cn.itcast.day3.aopframework包下三个类:BeanFactory类、ProxyFactoryBean类、AopFrameworkTest
cn.itcast.day3.aopframework包下一个配置文件:config.properties
①Advice类:
package cn.itcast.day3;import java.lang.reflect.Method;/** * Advice是Spring中的一个术语 * @author Administrator * *接口里面的方法默认就是public,抽象的 */public interface Advice { void beforeMethod(Method method); void afterMethod(Method method);}
② MyAdvice类:
package cn.itcast.day3;import java.lang.reflect.Method;public class MyAdvice implements Advice { long begintime=0; long endtime=0; public void beforeMethod(Method method) { // TODO Auto-generated method stub //打印系统时间,在before之前 System.out.println("到传智播客来学习了!"); begintime =System.currentTimeMillis(); } public void afterMethod(Method method) { // TODO Auto-generated method stub //打印系统时间,,在after之后 System.out.println("从传智播客毕业上班了!"); endtime = System.currentTimeMillis(); System.out.println(method.getName()+" running time of "+(endtime-begintime)); }}
③ BeanFactory类:
package cn.itcast.day3.aopframework;import java.io.IOException;import java.io.InputStream;import java.util.Properties;import cn.itcast.day3.Advice;public class BeanFactory { Properties props = new Properties(); //构造方法,接受配置文件 public BeanFactory(InputStream ips){ try { //加载配置文件 props.load(ips); } catch (IOException e) { // TODO Auto-generated catch block 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) { // TODO Auto-generated catch block e.printStackTrace(); } //instanceof代表是xxx的实例吗? 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 block e.printStackTrace(); } return proxy; } return bean; }}
④ ProxyFactoryBean类:
package cn.itcast.day3.aopframework;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import cn.itcast.day3.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 stub Object proxy3 = 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 retVal;*/ advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } } ); return proxy3; }}
⑤AopFrameworkTest类:
/** * 测试框架类 */package cn.itcast.day3.aopframework;import java.io.InputStream;import java.util.Collection;public class AopFrameworkTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //getResourceAsStream用来加载配置文件 InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties"); Object bean = new BeanFactory(ips).getBean("xxx"); System.out.println(bean.getClass().getName()); ((Collection)bean).clear(); }}
⑥ config.properties配置文件:
#xxx=java.util.ArrayListxxx=cn.itcast.day3.aopframework.ProxyFactoryBeanxxx.advice=cn.itcast.day3.MyAdvicexxx.target=java.util.ArrayList
说明:
1.如果传入的是java.util.ArrayList的话,返回的结果就是这个对象的名字. 2.如果是特殊的bean对象,那么返回的就是$Proxy这个名字,是一个ProxyFactoryBean对象。
补充:
1.写spring只需要写MyAdvice,把通告写进去,在配置文件里面配上目标targettarget advice 2.#xxx=java.util.ArrayList这样是在配置文件中的注释,如config.properties3.spring的精髓就是可以再配置文件中配置是否需要代理Proxy,可以自由的切换。4.spring的两大核心技术:bean工厂 aop框架最新最全的的java学习视频教程:
-------、、java学习型技术博客、期待与您交流! ----------
详细请查看: