天天看点

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

<a target="_blank" href="http://www.javahelp.com.cn/">java帮帮-it资源分享网</a>

1、类加载器

一、概述:

1、定义:简单说,类加载器就是加载类的工具。

当出现一个类,用到此类的时候,java 虚拟机首先将类字节码加载进内存,通常字节码的原

始信息放在硬盘上的 classpath 指定的目录下。

2、类加载器作用:将.class 文件中的内容加载进内存进行处理,处理完后的结果就是字节码。

3、默认类加载器:

1)java 虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位

置的类:bootstrap、extclassloader、appclassloader

2)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 文件中的。

编写自己的类加载器(需要再仔细看一遍,不是太懂)

知识讲解:

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

loadclass 方法(直接继承,省去委托机制的编写)与 findclass 方法(覆盖这个就行了)

defineclass 方法

编程步骤:

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

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

编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译

器无法识别这个类。程序中可以除了使用 classloader.load 方法之外,还可以使用设置线程

的上下文类加载器或者系统类加载器,然后再使用 class.forname。

实验步骤:

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

myclassloader 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 = new

file(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,outputstream

ostream) 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);

fos.close();

//类加载器不能加载这种非public的类

exception in thread "main" java.lang.illegalaccessexception: class

myclassloader

can 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());// 打 印 代 理 的 名 字

$proxy0

system.out.println("----------begin constructors

list----------");

/*想打印出如下格式:

* $proxy0()

$proxy0(invocationhandler,int)*/

constructor[] constructors =

clazzproxy1.getconstructors();//获取代理的构造函数

for(constructor constructor : constructors){

string name = constructor.getname();//获取代理的构造函数的

name

stringbuilder sbuilder = new stringbuilder(name);

sbuilder.append('(');

class[] clazzparams = constructor.getparametertypes();//

获取代理的构造函数的参数

for(class clazzparam : clazzparams){

sbuilder.append(clazzparam.getname()).append( ',');

if(clazzparams!=null &amp;&amp; clazzparams.length != 0)//稳妥的判

断是否是一个参数,不是就删掉最后的,

sbuilder.deletecharat(sbuilder.length()-1);

sbuilder.append(')');

system.out.println(sbuilder.tostring());

//$proxy0(java.lang.reflect.invocationhandler)

system.out.println("----------begin methods

/*$proxy0()

method[] methods = clazzproxy1.getmethods();//获取代理身上的方

for(method method : methods){

string name = method.getname();

class[] clazzparams = method.getparametertypes();

if(clazzparams!=null &amp;&amp; clazzparams.length != 0)

system.out.println("----------begin create instance

object----------");

//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 {

//方式二:匿名内部类

collection proxy1 = (collection)constructor.newinstance(new

myinvocationhandler1());

system.out.println(proxy1);//没有错误

proxy1.clear();//没有错误

//proxy1.size(); 报 错 , 因 为 , 代 理 调 用 size 方 法 , 其 实 是 调 用 了

myinvocationhandler1中的invoke,他的返回值是null

//system.out.println("111111111111111");//调试用的

//用了匿名内部类的方法实现

collection proxy2 = (collection)constructor.newinstance(new

invocationhandler(){

});

/*下边这部分代码非常重要和精辟*/

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());

system.out.println(proxy3.getclass().getname());

private static object getproxy(final object target,final advice

advice) {

//方式三,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)

/*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);

advice.aftermethod(method);

return retval;

);

return proxy3;

---------------------------------------------

public interface advice {

void beforemethod(method method);

void aftermethod(method method);

//创建实现advice接口的子类

public class myadvice implements advice {

long begintime = 0;

public void aftermethod(method method) {

system.out.println("从传智播客毕业上班啦!");

system.out.println(method.getname() + " running time of " +

(endtime - begintime));

public void beforemethod(method method) {

system.out.println("到传智播客来学习啦!");

begintime = system.currenttimemillis();

8、实现类似 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 进行测试。

//创建beanfactory类

package cn.itcast.test3.aopframework;

import java.io.ioexception;

import java.io.inputstream;

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;

//由类的字节码获取对象

class clazz = class.forname(classname);

bean = clazz.newinstance();

} catch (exception e) {

//判断bean是特殊的bean即proxyfactorybean还是普通的bean

if(bean instanceof proxyfactorybean){

object proxy = null;

//是proxyfactorybean的话,强转,并获取目标和通告

proxyfactorybean proxyfactorybean =

(proxyfactorybean)bean;

//获取advice和target

advice 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();

// todo auto-generated catch block

//是proxyfactorybean的话,返回proxy对象

return proxy;

//否则返回普通bean对象

return bean;

//创建proxyfactorybean类

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实现相同的接口

new invocationhandler() {

//通过契约,使用其方法--before和after方法

object value = method.invoke(target, args);

return value;

//创建测试类aopframeworktest

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());