java中的类加载器,与tomcat自己又做了一些类加载器的不同

来源:互联网 发布:c语言求完全数 编辑:程序博客网 时间:2024/05/23 21:23

52.  现在来讲一下java中的类加载器的原理(双亲委派机制),在java中主要有三个加载器,启动类加载器,还有扩展类,还有系统类或者叫应用类加载器都行。不同的加载器啊,他找的东西不一样。


53.  那些核心啦,比如  java核心 java.开头那些,
那些api他都是BootStrap加载的。

54.  Ext扩展类加载器,他可以加载,你到java的安装目录下   jdk\jre\ext   这个目录下的东西都扩展类加载器加的,里面有dnsns.jar  localedate.jar  sunjce_provider.jar  sumkcs11.jar当然他的安装的是jdk1.5的。


55.  还有我们平时说的classpath,我们自己设置的classpath,就是把我们的类设置到classpath里面,把我们的jar包,加载到classpath里面,加载到classpath里面的东西是System加载器加载的。


56.  为什么加载器这样分了一些级呢?是这样的,java的加载,你看啊,在我们的核心库里面有一个String,这个String是用BootStrap加载器加载的。

现在假设我自己写的一个String和rt.jar里面的String是一模一样的。包括包名之类的,位于哪里呢?位于我们的classpath路径下,那么java的类加载器是这样加载的,他先请求他的父加载器,也就是说他先请求Ext加载器,看看他能不能加载,请示到这的时候,他还不能加载就再调用父加载器。你写的这个东西永远都加载不了,所以这样能起到一个安全性的作用,你破坏不了java虚拟机他的类加载机制,也就是你不能写一个String把java中的String给替了,因为他使用了一种机制叫   双亲委派机制。

想要加载一个东西的时候,他先委派他的双亲,往上面看,看看他能不能加,不能加再往上面看,除非你把java的api给改了。把他的jar包打开,改了再保存进去。

BootStrap

Ext

System

自定义加载器


57.  你不能像上面说的那样改啊,你这种权力都有了,那他的各种安全性就没有了。


58.  另外,你可以自己定义类加载器,你可以继承相关的类,自己定义一套加载策略,这个加载策略,你可以从url加载,就我给你一个url地址,然后你通过url把那个类从远程传过来加载,其实就像那个applet,其实这个现在用得比较少,在我们的ie里面可以嵌套applet.他这个我们可以看作是一种远程加载,因为applet呢,使用的话,你的本地是要有虚拟机的,虚拟机其实他就是一个url把远程的class加载过来,就你自己可以写加载器,我给你一个url你把class加载进来,加到你内存里来用。


59.  现在咱们写一个例子,看能不能覆盖java中的String   
包为  java.lang
public class String{
    public  int length(){
        int 100;
    }
}你在测试的时候
String s = new String();
System.out.println(s.length()); 打印0,说明调用的是java中的类,而非我自己写的类。


60.  之后我们再来看,tomcat加载机制就不太一样,这种web服务器,包括weblogic这种应用服务器,他们都有自己的类加载机制,就是把加载机制给扩展了,自己定义了一套加载机制,就像我们现在这个tomcat这个加载机制是这样的,

Bootstrap
Ext
System  前面肯定是java的类加载机制,然后下面    tomcat对java的类加载机制进行了扩展,
    Common  下面平行两个 catalina  shared
catalina  Shared
      Web App1  Web App2    shared下面是两个并行的Web App1  Web App2 

意思就是drp6.3有6.3的类加载机制,drp6.2有6.2的加载器。


61.  所以在两个web项目里,我们可以用相同的类,他自己用自己的,不会冲突,


62.  上面的层次不就是tomcat的目录结构不,注意啦,如果实在找不出错误,可能同事在tomcat的公共目录里面放入了一个jar,而你自己的项目中也有jar ,可能会造成冲突。


63. 他说什么使用递归打印tomcat里面类加载器的层次,就像上面说的,从下往上打印类加载器


64.  写了一个DbUtil类,这个工程一运行,打印如下
WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@54172f

org.apache.catalina.loader.StandardClassLoader@54172f
sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$ExtClassLoader@addbf1
null     最上层加载器



65.  工程名叫  test_webcalssloader,一运行,在控制台可以看到最后面的一个打印语句,打印一个null,其实应该指的就是最上面那个加载器,最上层的加载器是java的启动类加载器,这个加载器是加载java核心的,这个加载器不是用java实现的,他应该是用c++什么之类 的实现的,这个加载器没有名字,没有名字也有他一个好处,不至少于进行破坏,因为你不知道叫什么名字,很多人想要破坏java虚拟机,他就破坏不了,


66.  sun.misc.Launcher$ExtClassLoader@addbf1扩展类加载器用java实现的。
sun.misc.Launcher$AppClassLoader@19821f
对应上面的   System 
org.apache.catalina.loader.StandardClassLoader@54172f对应上面的   Common 

org.apache.catalina.loader.StandardClassLoader@54172f  对应上面的   Shared
再接下来的
WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@54172f
这个就对应我们的  webapp加载器了。


67.  上面列的tomcat的类加载器,其中common加载器主要就是加载   tomcat 下common目录下的东西,
shared加载器主要加载shared目录下的东西,
catalina加载器主要加载server目录下的东西,


68.  他这里搞一下,common目录下的classes目录下放一个文件夹下面放一个DbUtil2,share目录下的classes放一个文件夹下面放一个DbUtil
  
反正现在的情况就是
Common   DbUtil1
Shared   DbUtil2
Web App  DbUtil3
你说现在输出谁,肯定输出3,因为在Web App里面有他就会先加载3,这个他和java中的加载器他不一样,java是先加载自己的,比如上面写一个java.lang.String类没有用啊,他还是加载java的。

 

 

21.  类的加载机制,他采用了双亲委派机制,他为什么采用这种机制呢?安全性,java这里面他有String或者他的那些其他核心类,你想覆盖是覆盖不了的,因为你自己写的类他都属于classpath这个路径下,而classpath的加载器就是System即系统类加载器,你现在要使用String他首先会请求上面的加载器,你的永远加载不上。
bootstarp
ext
system
自定义加载


22.  在tomcat中的类加载器只不过是把自定义类加载器给扩充了。
bootstarp
ext
system
common     从这到最下面都 是自定义加载器
catalina  shared
web app1  web app2

23.  在tomcat里面有点不一样了,如果在webapp1里有一个test,在common里面也有一个test,一运行话,他先找自己的,如果web app1自己能加载,他就使用自己的来加载,这和java一上来就去找上面的加载器不一样了。

 

 

 

 

 

        今天看了张老师的类加载器的相关知识。说实在的,以前根本不了解类加载器的知识,所有看起来有的费劲。看完一遍视频还是对类加载器糊里糊涂的。因此有必要进行复习或者找资料学习相关知识。

       类加载器的第一个视频时类加载器及其委托机制的深入分析。

由张老师的讲解课知,Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader。类加载器也是一个Java类,因为其他是Java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。Java虚拟机中的所有类转载器采用具有父子关系的树形结构进行组织,在实例化每个类转载器对象时,需要为其指定一个父级类转载器对象或者默认采用系统类转载器为其父级类加载。例如:如果我们想要看某个类加载他的类加载器及其父级类加载器,我们可以这样:

       定义一个类ClassLoaderTest,该类里面有一个main方法,如果我们要查看该类的类加载器的名称,我们可以这么做:

       System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());,运行结果为:sun.misc.Launcher$AppClassLoader,可见是AppClassLoader加载该类。如果需要查看加载系统类的类加载器,则:System.out.println(System.class.getClassLoader());结果为:null。如果要查看加载该类的类加载器以及其父级类加载器,则如下:

       ClassLoader loader = Test1.class.getClassLoader();

            while(loader != null) {

                  System.out.println(loader);

                  loader = loader.getParent();   

            }

该段代码为首选获取该类的类加载器,然后输出该类的类加载器并循环输出不为空的父级类加载器。结果为:sun.misc.Launcher$AppClassLoader@82ba41

sun.misc.Launcher$ExtClassLoader@923e30

每个类加载器都有自己对应的管辖范围。AppClassLoader对应得管辖范围是CLASSPATH指定的jar或目录,ExtClassLoader对应的管辖范围是JRE/lib/ext/*.jar,BootStrap对于的管辖范围是JRE/lib/rt.jar.这时我们就知道加载我们的测试类的类加载器为什么是AppClassLoader,因为AppClassLoader对应的管辖范围是CLASSPATH指定的jar或目录,而我们的类只在我们指定的CLASSPATH指定的目录存在,所以只能用AppClassLoader加载我们的类。那么如果ExtClassLoader对应的管辖范围存在我们的类即JRE/lib/ext目录下存在含有我们的测试类的jar,这时会是什么情况呢?我们将我们的测试类导出为jar到JRE/lib/ext目录下,再测试该类,得到了结果如下:

sun.misc.Launcher$ExtClassLoader@923e30

也就是加载该类的类加载器为ExtClassLoader,而其父级类加载器值为null。这也就验证了如果父级类加载器存在类时会优先加载。

以上就是类加载器及其委托机制的原理。

       既然了解了类加载器委托机制的原理,我们来编写一个自己的类加载器。要写我们自己的类加载器,我没写的类必须继承ClassLoader这个抽象类,然后实现findClass方法和defineClass方法。

在编写我们自己的类加载器前我们来编写个工具类来将java编译过的类进行加密,我们举一个简单的加密方法,就是将class文件的说有字节去反,我们编写一个方法来实现这个功能。我们定义一个方法copy,方法代码如下:

       private static void copy(InputStream is, OutputStream os)

throws Exception {

            int data = -1;

            while ((data = is.read()) != -1) {

                  os.write(data ^ 0XFF);      //取反操作

            }

       }

通过这个方法可以将class进行简单的加密操作。为了测试的方便,我们将类存在的目录硬编码。并定义存放加密后的class文件的存放的目录,然后根据文件输出流和文件输入流进行简单加密操作,具体代码如下:

public static void main(String[] args) throws Exception {

String in = "D:\\java\\mypro\\bin\\com\\base\\classloader\\ClassLoaderAttechment.class";   //加密前的class文件的路径

String out = "D:\\java\\mypro\\src\\myclass"; //加密后class存放的目录

String fileName = in.substring(in.lastIndexOf('\\')+1);

String toFile = out + "\\" + fileName;

FileInputStream fis = new FileInputStream(new File(in));

FileOutputStream fos = new FileOutputStream(new File(toFile));

copy(fis, fos);

fis.close();

fos.close();

}

运行后,在目录D:\\java\\mypro\\src\\myclass下就会生成加密后的class文件。此时class文件基本加密成功。

之后,我们开始写我们自己的类加载器的类。该类要继承ClassLoader类,并实现findClass这个方法。首先我们在类中定义一个私有属性:classDir,就是要加载的类的目录,并提供一个带参数的构造函数,以及getter和setter方法。然后提供一个解密方法,由于我们加密时只是将class文件的所有字节取反,所以解密的时候只需要将加密的class取反就行,因此还可以用到上面的copy方法。在findClass方法中,我们定义一个文件输入流来获取要加载的类的二进制数据,并定义一个字节数组输出流存放解密后的字节数据,并用defineClass方法将字节数组输出流生成类,另外如果在没加载到class时,我们还可以用父级的类加载器加载数据(如果父级类加载器的管辖范围存在测试类时)。findClass方法代码如下:

protected Class<?> findClass(String name) throws ClassNotFoundException {

            String file = this.getClassDir() + "\\" + name +".class";

            try {

                  FileInputStream fis = new FileInputStream(new File(file));

                  ByteArrayOutputStream abos = new ByteArrayOutputStream();

                  copy(fis , abos);

                  byte bytes [] =abos.toByteArray();

                  return this.defineClass(null, bytes, 0, bytes.length);

            } catch (Exception e) {

                  e.printStackTrace();

            }

            return super.findClass(name);

}

最后我们编写一个测试类:

public class ClassLoaderAttechment {

       @Override

       public String toString() {

            return "hello World";

       }

}

在测试类中调用自己的类加载器:

public static void main(String[] args) throws Exception {

Class clazz = new MyClassLoader("D:\\java\\mypro\\src\\myclass").findClass("ClassLoaderAttechment");

            Object d = (Object)clazz.newInstance();

            System.out.println(d);

}

需要注意的是此时实例化时应将实例化的对象强制转换为Object类,用类去应用。最后就能打印出:hello World。至此,我们的类加载器测试就成功了!

0 0
原创粉丝点击