黑马程序员——高新技术---类加载器、AOP

来源:互联网 发布:sql insert 多条 编辑:程序博客网 时间:2024/05/16 13:02

-------  android培训、java培训、期待与您交流! ----------

第一部分 类加载器

一.类加载器概述

1.简要介绍什么事类加载器和类加载器的作用

1.逐一编写如下代码来说明放置在不同位置的类确实由不同类加载器加载的。

System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());

输出:sun.misc.Launcher$AppClassLoader

//将上面语句的测试类改为System则抛NullPointException,这两个类存放位置不同。

<span style="white-space:pre"></span>System.out.println(System.class.getClassLoader().getClass().getName());

              改为

<span style="white-space:pre"></span>System.out.println(System.class.getClassLoader());

      结果为null

2.Java虚拟机中可以安装多个类加载器,系统默认3个主要的类加载器,每个负责加载 特定位置的类:BootStrap,ExtClassLoader,AppClassLoader

2.用下面的代码查看类加载器的层次结构关系

ClassLoader loader = ClassLoaderTest.class.getClassLoader();while (loader != null) {System.out.println(loader.getClass().getName());loader = loader.getParent();}System.out.println(loader);

结果为:BootStrap --> ExtClassLoader --> AppClassLoader

3.类加载器也是Java类,因为其他Java类的类加载器本身也要被类加载器加载,显然 必须有第一个类加载器不是Java类,这正是BootStrap

4.Java虚拟机中的所有类加载器都采用具有父子关系的树形结构进行组织,在实例化每 个类加载器对象时,需要为其指定一个父级类装载器或者默认采用系统类装载器为其父 级类加载器。

5.类加载器之间的父子关系和管辖范围图

 

注5.eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包,再eclipse中运行这个类,运行结果显示为ExtClassLoader。此时的环境状态是classpath 目录有ClassLoaderTtest.class,ext/itcast.jar中也有CLassLoaderTest.class,这时候我们就需 要了解类加载器的具体过程和原理了。

二.类加载器的委托机制

1.当Java虚拟机要加载一个类时,到底派出那个类加载器去加载呢?

>首先当前线程的类加载器去加载线程中的第一个类

>如果类A中引用了类BJava虚拟机将使用加载类A的类加载器来加载类B

>还可以直接调用ClassLoader.loadClass()方法来指定某个了加载器去加载某个类

2.每个类加载器加载类时,又先委托给其上级类加载器

>当所有祖宗来加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找那个呢?

>对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成JRE/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因

注二:每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他 的类加载器去加载类,这就是类加载器的委托模式。类加载器一级级委托到BootStrap 类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级的退回到子孙类 装载器去进行真正的加载。当回退到最初的类装载器时,如果他自己不能完成装载,那 就报告ClassNotFoundException异常。

有一道面试题,能不能自己写个类叫java.lang.System,为了不然我们写System类,类加载器采用委托机制,这样保证爸爸们优先,也就是使用爸爸们能找到的类,这样总是使用java系统提供的System

三.编写自己的类加载器

1.知识讲解

>自定义的类加载器必须继承ClassLoader

>laodClass方法(先找父类,再调用findClass)与findClass方法(自己干,覆盖)

>defineClass方法(把class文件内容转为字节码)

2.编程步骤

>编写一个对文件内容进行简单加密的程序

>编写一个自己的类加载器,可实现对加密过的类进行装载和解密

>编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中除了可以使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用class.forName().

3.实验步骤

>对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如:

java  MyClassLoader MyTest.class  F:\itcast

>运行加载类的程序,结果能够被正常加载,但打印出来的类加载器名称为AppClassLoader.  java MyClassLoader MyTest F:\itcast

>用加密后的类文件替换classpath环境下的类文件,再执行上一步操作就出问题了, 错误说明AppClassLoader类装载器失败

>删除classpath环境下的类文件,在执行上一步就没问题了

注三:加解密函数

void cypher(InputStream ips, OutputStream ops) throws IOException {int b = -1;while ((b = ips.read()) != -1) {ops.write(b ^ 0xff);}}

加密main工具

String srcPath = args[0];String destPath = args[1];FileInputStream fis = new FileInputStream(srcPath);FileOutputStream fos = new FileOutputStream(destPath);cypher(fis, fos);fis.close();fos.close();

四.一个了加载器的高级问题分析

1.编写一个能打印出自己的类加载器名称和当前类加载器父子结构关系的MyServlet 正常发布后看到打印结果为WebAppClassLoader

2.把MyServlet.class文件打成jar包,放到ext目录下,重启tomcat,发现“找不到 HttpServlet”的错误

3.把Servlet.jar也放到ext目录下,问题解决了,打印结果是ExtClassLoader

4.父级类加载器的类无法引用只能被子级类加载器加载的类,原理如下图:

 

思考两种情况下,HttpServlet分别由谁加载?

4:在MyServlet的方法中加入如下代码:

ClassLoader loader = this.getClass().getClassLoader();while (loader != null) {out.println(loader.getClass().getName());loader = loader.parent();}out.close();

注四:由于新配置了eclipse的工作环境,前面曾经将eclipse运行环境配置为了eclipse 自带的1.5,后来输出MyServletjar文件到了这个jdk1.5中,而tomcat运行时用的是 jdk1.6,所以实验出了点小意外。以后要注意这个问题。

如果按照Sun的建议,发布到lib/ext/itcast.jar中后,没有删除web-inf/classes目录下的MyServlet.class文件,则不应该出现问题啊。结果还是出问题了,这说明tomcat似乎没有按照Sun的建议做。可是根据以前的邮件视频中的列加载器,可以确定WebAppClassLoader时自己先加载,可以给大家看以前java邮件的视频。

第二部分 AOP

一.代理的概念与作用

1.生活中的代理

>武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想 总部买电脑,你觉得最终的主题业务目标有什么区别?基本上一样吧,都解决了核 心问题,但一点区别都没有吗?从代理商那里买真的一点好处都没有吗?

1:批发进货的成本和运输费用的优势,比你自己直接到北京总部买的总成本要低。

2.程序中的代理

>要为已存在的的多个具有相同接口的目标类的各个方法增加一些系统功能,例 如,异常处理、日志、计算方法的运行时间、事务管理等等,你准备如何做?

>编写一个与目标类具有相同接口的代理类,编写一个与目标方法具有相同借口的 代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能 的代码。如下图

 

>如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配 置文件中配置是使用目标类还是代理类,这样以后很容易切换。譬如,想要日志功 能时就配置代理类,否则配置目标类,这样增加系统功能很容易,以后运行一段时 间后,又想去掉系统功能很容易。

二.AOP概念

1.系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

 

注1:安全、事物、日志等功能要贯穿到多个模块中,所以它们就是交叉业务。

2.用具体的程序代码描述交叉业务:

 

3.交叉业务的编程问题即为面向方面的编程(Aspect Oriented Program,简称AOP)AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:

 

4.使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术

三.动态代理技术

1.要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代 理方式,将是件非常麻烦的事情!写成百上千个代理类是不是太累了!

2.JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即 动态代理

3.JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具 有相同借口的目标类的代理

4.CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

5.代理类的各个方法中通常除了要调用目标类的相应方法和对外返回目标返回的结果 外,还可以在代理方法中的如下四个位置加上系统共能代码:

>在调用目标方法之前

>在调用目标方法之后

>在调用目标方法前后

>在处理目标方法异常的catch块中

5:需要写一个示意代码进行辅助说明,例如:

class Proxy {void sayHello() {// . . . . . . . . . . . . . //此处try {target.sayHello();} catch (Exception e) {// . . . . . . . . . . . . //此处}// . . . . . . . . . . . . //此处}}

四.分析JVM动态生成的类

1.创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass()方法的 各个参数

1

Class clazz=Proxy.getProxyClass(ProxyTest.class.getClassLoader().Collection.class);System.out.println(clazz.getName());

2.编码列出动态类中的所有构造方法和参数签名

2

Constructor[] constructors = clazz.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());}

结果为:$Proxy0(java.lang.reflect.InvocationHandler)

3.编码列出动态类中的所有方法和参数签名

3

Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method.getName());}

4.创建动态类的实例对象

>用反射获得构造方法

>编写一个最简单的InvocationHandler

>调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对 象传进去

>打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他 有返回值的方法报告了异常。

>将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名 内部类

5.总结思考:让JVM创建动态类及实例对象,需要给他提供哪些信息?

>三方面

1>生成的类中有哪些方法通过通过让其实现哪些接口的方式进行报告;

2>产生的类字节码必须有一个关联的类加载器

3>生成的类内的方法是怎样的,也得由我们提供,把我们的代码写在一个约  定好了接口对象的方法中,把对象传给他,它调用我的方法,即相当于插入了 我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建 动态的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的 invoke方法中加一点代码,就可以看到这些代码被调用运行了。

6.Proxy.newProxyInstance方法直接一步就创建出代理对象

6

Collection proxy = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(),new Class[] { Collection.class }, new InvocationHandler() {public Object invoke(Object proxy, Method method,Object[] args) {return null;}});


Collection proxy = (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 Exception {Object retVal = method.invoke(target, args);return retVal;}});

五.猜想分析动态生成的内部类的内部代码

1.动态生成的内部类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。

2.构造方法接受了一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码回事什么样子的呢?

2

 

3.实现了Collection接口的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke()方法接受的参数又是什么意思?图解说明如下:

 

4.分析先前打印动态类的实例对象时,结果为什么是null呢?调用有基本类型返回值时为什么会出现NullPointException异常?

5分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?

>调用代理类对象的从object类继承的hashcode,equals或者toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法则不转发调用请求。

六.让动态生成的类成为目标类的代理

1.分析动态代理的工作原理图

1:动态代理的工作原理图:

 

2.怎样将目标类传进去?

>直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加 入日志代码

>InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式

>让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型 的引用变量

3.将创建代理的过程给位一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受 目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的 API

4.将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系 统功能代码以参数的形式提供?

>把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接 收者只要调用这个对象的方法,即等于执行了外界提供的代码?

>bind方法增加一个Advice参数

七.实现AOP功能的封装与配置

1.工厂类BeanFactory负责创建目标类或者代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象。如果参数字符串在配置文件中对应的类名不是ProxyFactoryBeab,则直接返回该类的实例对象,否则,返回该实例对象的getProxy方法返回的对象。

1:最好是为这个案例建一个子包,例如叫,aopframework然后将该案例涉及到的java源文件都放到这个字包下面。对于设计思想,刚开始要用在记事本中敲如下代码配置文件的方式进行比划,这样大家更容易理解需求和设计思想。

2.BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

#xxx=java.util.ArrayList

xxx=cn.itcast.ProxyFactoryBean

xxx.target=java.utilArrayList

xxx.advice=cn.itcast.MyAdvice

3.ProxyFactoryBean充当封装生成动态代理的工厂,需要工厂提供哪些配置参数信息?

>目标

>通知

4.编写客户端应用

>编写实现Advice接口的类和在配置文件中进行配置

>调用BeanFactory获取对象

 

0 0
原创粉丝点击