黑马程序员 十六、 类加载器、代理

时间:2023-02-12 09:12:58
Java帮帮-IT资源分享网 1、类加载器
一、概述:1、定义:简单说,类加载器就是加载类的工具。当出现一个类,用到此类的时候,Java 虚拟机首先将类字节码加载进内存,通常字节码的原始信息放在硬盘上的 classpath 指定的目录下。2、类加载器作用:将.class 文件中的内容加载进内存进行处理,处理完后的结果就是字节码。3、默认类加载器:1)Java 虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader2)BootStrap--*类加载器:类加载器本身也是 Java 类,因为它是 Java 类,本身也需要加载器加载,显然必须有第一个类加载器而不是 java 类的,这正是 BootStrap。它是嵌套在 Java 虚拟机内核中的,已启动即出现在虚拟机中,是用 c++写的一段二进制代码。所以不能通过 java 程序获取其名字,获得的只能是 null。4、Java 虚拟机中的所有类加载器采用子父关系的树形结构进行组织,在实例化每个类加载器对象或默认采用系统类加载器作为其父级类加载器。
黑马程序员 十六、 类加载器、代理 
 
2、类加载器的委托机制l 当 Java 虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?1.首先当前线程的类加载器去加载线程中的第一个类。2.如果类 A 中引用了类 B,Java 虚拟机将使用加载类 A 的类装载器来加载类 B。3.还可以直接调用 ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。l 每个类加载器加载类时,又先委托给其上级类加载器。1.当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有 getChild 方法,即使有,那有多个儿子,找哪一个呢?2.对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的 itcast.jar 包中后,运行结果为 ExtClassLoader 的原因。每个 ClassLoader 本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到 BootStrap 类加载器,当BootStrap 无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException 异常。有一道面试,能不能自己写个类叫 java.lang.System,为了不让我们写 System 类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java 系统提供的 System。把先前编写的类加入到 jdk 的 rt.jar 中,会有怎样的效果呢?不行!!!看来是不能随意将自己的 class 文件加入进 rt.jar 文件中的。编写自己的类加载器(需要再仔细看一遍,不是太懂)知识讲解:自定义的类加载器的必须继承 ClassLoaderloadClass 方法(直接继承,省去委托机制的编写)与 findClass 方法(覆盖这个就行了)defineClass 方法编程步骤:编写一个对文件内容进行简单加密的程序。编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用 ClassLoader.load 方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用 Class.forName。实验步骤:对不带包名的 class 文件进行加密,加密结果存放到另外一个目录,例如: javaMyClassLoader MyTest.class F:\itcast运 行加载类 的 程序 ,结 果能 够 被正 常加 载 ,但 打印 出来 的 类装 载器 名称 为AppClassLoader:java MyClassLoader MyTest F:\itcast用加密后的类文件替换 CLASSPATH 环境下的类文件,再执行上一步操作就出问题了,错误说明是 AppClassLoader 类装载器装载失败。删除 CLASSPATH 环境下的类文件,再执行上一步操作就没问题了。import java.io.*;import java.lang.reflect.*;public class MyClassLoader extends ClassLoader{private String path = null;public MyClassLoader(String path) throws Exception//检查文件是否存在{File f = new File(path);if(!f.isDirectory()){throw new RuntimeException(path + " is not a directory");}this.path = path;}public Class findClass(String name) //throws Exception //为什么不能抛出{try{File f = newFile(path,name.substring(name.lastIndexOf('.')+1) + ".class");FileInputStream fis = new FileInputStream(f);ByteArrayOutputStream bos = new ByteArrayOutputStream();cypher(fis,bos);byte [] buf = bos.toByteArray();fis.close();bos.close();return defineClass(name,buf,0,buf.length);}catch(Exception e){throw new ClassNotFoundException(name + " is not found!");}return null;}public static void cypher(InputStream istream,OutputStreamostream) throws Exception{//下面这段代码可能遇到255的字节,当成byte就成了-1/*byte b = 0;while((b = (byte)istream.read()) != -1){ostream.write(b ^ 0xff);}*/int b = 0;while((b = istream.read()) != -1){ostream.write(((byte)b) ^ 0xff);}}public static void main(String [] args) throws Exception{//下面省略了错误检查if(!args[0].endsWith("class")){ClassLoader loader = new MyClassLoader(args[1]);Class cls = loader.loadClass(args[0]);/*让自定义类继承Date类System.out.println(cls.getClassLoader().getClass().getName());java.util.Date d = (java.util.Date)cls.newInstance();System.out.println(d.toString());*///Method m = cls.getMethod("test",null);//在jdk1.5中报警告,为什么?Method m = cls.getMethod("test");//m.invoke(cls.newInstance(),null);m.invoke(cls.newInstance());//((Test)cls.newInstance()).test();return;}else{FileInputStream fis = new FileInputStream(args[0]);File f = new File(args[1], new File(args[0]).getName());//不用检查目录最后是否有目录分割符FileOutputStream fos = new FileOutputStream(f);cypher(fis,fos);fis.close();fos.close();}}}//类加载器不能加载这种非public的类/*Exception in thread "main" java.lang.IllegalAccessException: ClassMyClassLoadercan not access a member of class MyTest with modifiers ""*//*class MyTest{public void test(){System.out.println("hello,www.it315.org");}}*/
 
3、一个类加载器的高级问题分析编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的 MyServlet,正常发布后,看到打印结果为 WebAppClassloader。把 MyServlet.class 文件打 jar 包,放到 ext 目录中,重启 tomcat,发现找不到 HttpServlet 的错误。把 servlet.jar 也放到 ext 目录中,问题解决了,打印的结果是 ExtclassLoader 。父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:
 
4、代理的概念与作用1、引入:为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。2、代理类的优点:如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。目标类: 代理类:class X{ Xproxy{void sayHello(){ void sayHello(){syso:Hello; startTime} X. sayHello();} endTime;}}一般用接口来引用其子类,如:Collectioncoll = new ArrayList();AOP系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:安全 事务 日志StudentService ------|----------|------------|-------------CourseService ------|----------|------------|-------------MiscService ------|----------|------------|-------------用具体的程序代码描述交叉业务:method1 method2 method3{ { {------------------------------------------------------切面.... .... ......------------------------------------------------------切面} } }交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称 AOP),AOP 的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:------------------------------------------------------切面func1 func2 func3{ { {.... .... ......} } }------------------------------------------------------切面使用代理技术正好可以解决这种问题,代理是实现 AOP 功能的核心和关键技术。安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务重要原则:不要把供货商暴露给你的客户
 
5、动态代理技术1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!2、JVM 可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。3、JVM 生成的动态类必须实现一个或多个接口,所以,JVM 生成的动态类只能用作具有相同接口的目标类的代理。4、CGLIB 库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用 CGLIB 库。5、代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:1)在调用目标方法之前2)在调用目标方法之后3)在调用目标方法前后4)在处理目标方法异常的 catch 块中
 
6、分析 JVM 动态生成的类总结思考:让 jvm 创建动态类及其实例对象,需要给它提供哪些信息?三个方面:1、生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;2、产生的类字节码必须有个一个关联的类加载器对象;3、生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个 InvocationHandler 对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的 InvocationHandler 对象的 invoke 方法中加一点代码,就可以看到这些代码被调用运行了。用 newProxyInstance()方法直接一步就创建出代理对象。总结分析动态代理类的统计原理和结构:1、怎样将目标传进去:1)直接在 InvocationHandler 实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是毫无意义。2)为 InvocationHandler 实现类注入目标的实例对象,不能采用匿名内部类的形式了。3)让匿名内部类的 InvocationHandler 实现类访问外面的方法中的目标类实例对象的 final类型的引用变量。2、动态代理的工作原理:1)Client(客户端)调用代理,代理的构造方法接受一个 InvocationHandler,client 调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的 handler 对象,又把各请求分发给目标的相应的方法。就是将 handler 封装起来,其中 this 引用了当前的放(发来什么请求就接受哪个方法)。猜想分析动态生成的类的内部代码:1、动态生成的类实现了 Collection 接口(可以实现若干接口),生成的类有 Collection 接口中的所有方法和一个如下接受 InvocationHandler 参数的构造方法。2、构造方法接受一个 InvocationHandler 对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?实现 Collection 接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler 接口中定义的 invoke 方法接受的三个参数又是什么意思?图解说明如下:分析为什么动态类的实例对象的 getClass()方法返回了正确结果呢?为何动态类的实例对象的 getClass()方法返回了正确结果,而没调用 invoke 方法:因为代理类从 Object 上继承了许多方法,其中只对三个方法(hashCode、equals 和 toString)进行开发,委托给 handler 去自行处理,对于它身上其他方法不会交给代理类去实现,所以对于 getClass()方法,还是由 Object 本身实现的。即 proxy3.getClass(),该是什么结果还是什么结果,并不会交给 invoke 方法处理。
 
7、综合示例代码(用到三个.java 文件,张老师的源码,自己加了注释,自己敲精简版的代理)package javaenhance.src.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//接收两个参数,一个是后边参数的字节码的加载器,一个是所要实现代理的接口的字节码Class clazzProxy1 =Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);System.out.println(clazzProxy1.getName());// 打 印 代 理 的 名 字$Proxy0System.out.println("----------begin constructorslist----------");/*想打印出如下格式:* $Proxy0()$Proxy0(InvocationHandler,int)*/Constructor[] constructors =clazzProxy1.getConstructors();//获取代理的构造函数for(Constructor constructor : constructors){String name = constructor.getName();//获取代理的构造函数的nameStringBuilder 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());//$Proxy0(java.lang.reflect.InvocationHandler)}System.out.println("----------begin methodslist----------");/*$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 instanceobject----------");//Object obj = clazzProxy1.newInstance();//方式一:通过接口的子类创建对象Constructor constructor =clazzProxy1.getConstructor(InvocationHandler. class);//获取代理身上的构造函数//创建内部类MyInvocationHandler1,目的是传递给代理的构造器class MyInvocationHandler1 implements InvocationHandler{public Object invoke(Object proxy, Method method, Object[]args)throws Throwable {// TODO Auto-generated method stubreturn null;}}//方式二:匿名内部类Collection proxy1 = (Collection)constructor.newInstance(newMyInvocationHandler1());System.out.println(proxy1);//没有错误proxy1.clear();//没有错误//proxy1.size(); 报 错 , 因 为 , 代 理 调 用 size 方 法 , 其 实 是 调 用 了MyInvocationHandler1中的invoke,他的返回值是null//System.out.println("111111111111111");//调试用的//用了匿名内部类的方法实现Collection proxy2 = (Collection)constructor.newInstance(newInvocationHandler(){public Object invoke(Object proxy, Method method, Object[]args)throws Throwable {return null;}});/*下边这部分代码非常重要和精辟*/final ArrayList target = new ArrayList();Collection proxy3 = (Collection)getProxy(target,newMyAdvice());proxy3.add("zxx");proxy3.add("lhm");proxy3.add("bxd");System.out.println(proxy3.size());System.out.println(proxy3.getClass().getName());}private static Object getProxy(final Object target,final Adviceadvice) {//方式三,newProxyInstance这个方法需要三个参数,可以直接创建target的代理对象Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(),/*new Class[]{Collection.class},*///获取target上的接口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() + " runningtime of " + (endTime - beginTime));return retVal;*///把上边的代码封装到一个类中,让后调用该类的方法,就实现了方法的封装advice.beforeMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy3;}}---------------------------------------------import java.lang.reflect.Method;public interface Advice {void beforeMethod(Method method);void afterMethod(Method method);}---------------------------------------------package javaenhance.src.cn.itcast.day3;import java.lang.reflect.Method;//创建实现Advice接口的子类public class MyAdvice implements Advice {long beginTime = 0;public void afterMethod(Method method) {// TODO Auto-generated method stubSystem.out.println("从传智播客毕业上班啦!");long endTime = System.currentTimeMillis();System.out.println(method.getName() + " running time of " +(endTime - beginTime));}public void beforeMethod(Method method) {// TODO Auto-generated method stubSystem.out.println("到传智播客来学习啦!");beginTime = System.currentTimeMillis();}}
 
8、实现类似 spring 的可配置的 AOP 框架一、工厂类 BeanFactory:1、工厂类 BeanFactory 负责创建目标类或代理类的实例对象,并通过配置文件实现切换。2、getBean 方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是 ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy 方法返回的对象。3、BeanFactory 的构造方法接收代表配置文件的输入流对象的配置文件格式如下:#xxx=java.util.ArrayListxxx=cn.itcast.test3.aopframework.ProxyFactoryBeanxxx.advice=cn.itcast.test3.MyAdvicexxx.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 进行测试。//创建BeanFactory类package cn.itcast.test3.aopframework;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Properties;public class BeanFactory {Properties prop = new Properties();//创建对象时需要传入一个配置文件中的数据,所以需要在构造方法中接受一个参数public BeanFactory(InputStream ips) {try {//将配置文件加载进来prop.load(ips);} catch (IOException e) {e.printStackTrace();}}//创建getBean方法,通过配置文件中的名字获取bean对象public Object getBean(String name){//从配置文件中读取类名String className = prop.getProperty(name);Object bean = null;try {//由类的字节码获取对象Class clazz = Class.forName(className);bean = clazz.newInstance();} catch (Exception e) {e.printStackTrace();}//判断bean是特殊的bean即ProxyFactoryBean还是普通的beanif(bean instanceof ProxyFactoryBean){Object proxy = null;try {//是ProxyFactoryBean的话,强转,并获取目标和通告ProxyFactoryBean proxyFactoryBean =(ProxyFactoryBean)bean;//获取advice和targetAdvice advice =(Advice)Class.forName(prop.getProperty(name +".advice")).newInstance();Object target = Class.forName(prop.getProperty(name +".target")).newInstance();//设置目标和通告proxyFactoryBean.setAdvice(advice);proxyFactoryBean.setTarget(target);//通过类ProxyFactoryBean(开发中是作为接口存在)中获得proxy对象proxy = proxyFactoryBean.getProxy();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}//是ProxyFactoryBean的话,返回proxy对象return proxy;}//否则返回普通bean对象return bean;}}//创建ProxyFactoryBean类package cn.itcast.test3.aopframework;import java.lang.reflect.*;import cn.itcast.test3.Advice;public class ProxyFactoryBean {private Object target;private Advice advice;public Object getTarget() {return target;}public void setTarget(Object target) {this.target = target;}public Advice getAdvice() {return advice;}public void setAdvice(Advice advice) {this.advice = advice;}public Object getProxy() {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;}}//创建测试类AopFrameworkTestpackage cn.itcast.test3.aopframework;import java.io.InputStream;public class AopFramewrorkTest {public static void main(String[] args)throws Exception {//读取配置文件的数据InputStream ips =AopFramewrorkTest.class.getResourceAsStream("config.property");//获取bean对象Object bean = new BeanFactory(ips).getBean("xxx");System.out.println(bean.getClass().getName());}}
Java帮帮-IT资源分享网