天天看点

类加载器什么是类加载器?类加载器的作用java虚拟机中三个主要的类加载器类加载器的委托机制 编写自己的类加载器------------ ( 有包名的类不能调用无包名的类。在帮助文档中一般看到protect方法多的就要想到用extends) 23种常用的设计模式之----------模板方法设计模式

      类加载器从 jdk 1.0 就出现了,最初是为了满足 java applet 的需要而开发出来的。java applet 需要从远程下载 java 类文件到浏览器中并执行。现在类加载器在 web 容器和 osgi 中得到了广泛的使用。一般来说,java 应用的开发人员不需要直接同类加载器进行交互。java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时间去调试<code>classnotfoundexception</code>和<code>noclassdeffounderror</code>等异常。本文将详细介绍 java 的类加载器,帮助读者深刻理解 java 语言中的这个重要概念。下面首先介绍一些相关的基本概念。

     顾名思义,类加载器是用来将java类加载到java虚拟机(jvm)中的。它是java语言的一个创新,也是java语言流行的重要原因之一。它使得java类可以被动态加载到java虚拟机中并执行。

     一般来说,java虚拟机使用java类的方式如下:java源程序(.java文件)----&gt;经过编译之后----&gt;变成java字节码(.class文件)。类加载器(classloader)负责读取java字节码,并转换成java.lang.class类的一个实例,每个这样的一个实例用来表示一个java类,通过此实例的newinstance()方法就可以创建出该类的一个对象。

        java 类加载器的作用就是在运行时加载类(把类的二进制加载到内存中),它可以在将类加载到虚拟机中的时候检查类的完整性。

      java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载特定位置的类。他们分别是:

bootstrap、extclassloader、appclassloader

     加载器也是一个java类,由于java类要由类加载器来加载,那第一个类加载器又是由谁来加载它呢?这个加载器是bootstrap。

     bootstrap------它不是一个java类,他是嵌套在jvm(java虚拟机)中的一个用c++编写的二进制代码。

  //得到我们自定义类的字节码,得到此字节码的类加载器,得到类加载器的字节码,得到字节码的名字

  system.out.println(myclassloadertest.class.getclassloader().getclass().getname());

  //得到我们自定义类的字节码,得到此字节码的类加载器,得到类加载器的字节码

  system.out.println(myclassloadertest.class.getclassloader().getclass());

运行输出:

sun.misc.launcher$appclassloader

class sun.misc.launcher$appclassloader

运行下面代码:

//得到系统类的字节码,得到此字节码的类加载器

system.out.println(system.class.getclassloader());-------------------------------------------------1

//得到系统类的字节码,得到此字节码的类加载器,得到类加载器的字节码,得到字节码的名字

system.out.println(system.class.getclassloader().getclass().getname());

null                                         

exception in thread "main" java.lang.nullpointerexception

at shipin44.myclassloadertest.main(myclassloadertest.java:16)

程序报空指针异常。

位置1的代码,运行结果为null,不代表它没类加载器,(如果没有那是谁加载它的呢?)而是加载它的加载器是bootstrap(一个特殊的类加载器,是c++写的二进制代码),bootstrap不是一个java对象,所以上面打印null。

类加载器什么是类加载器?类加载器的作用java虚拟机中三个主要的类加载器类加载器的委托机制 编写自己的类加载器------------ ( 有包名的类不能调用无包名的类。在帮助文档中一般看到protect方法多的就要想到用extends) 23种常用的设计模式之----------模板方法设计模式
类加载器什么是类加载器?类加载器的作用java虚拟机中三个主要的类加载器类加载器的委托机制 编写自己的类加载器------------ ( 有包名的类不能调用无包名的类。在帮助文档中一般看到protect方法多的就要想到用extends) 23种常用的设计模式之----------模板方法设计模式

public static void main(string[] args) {

  classloader cl = myclassloadertest.class.getclassloader();

  while (cl!=null) {

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

   //cl本来就是类加载器了,下面这样的写法是得到父加载器的父加载器多跳了1级

   cl = cl.getparent().getclass().getclassloader(); 

  }

  //打印第一个类加载器

  system.out.println(cl);

  system.out.println("------------正确输出-------------");

  cl = myclassloadertest.class.getclassloader();

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

   cl = cl.getparent(); 

 }

输出结果:

null

------------正确输出-------------

sun.misc.launcher$extclassloader

鼠标右键点击myclassloadertest类---&gt;选export---&gt;java---&gt;jar file---&gt;next---&gt;finish,然后将导出的包copy到jre\lib\ext目录下

运行输出:

zf.jar包中的myclassloadertest类中的代码如下:

  classloader cl = myclassloadertest.class.getclassloader();

  system.out.println("------------正确输出-------------");

      当第一个类加载器要加载类的时候,它先不直接加载类,而是交(委托)给它的父类加载器去加载,而父类加载器又交(委托)给它的父类加载器去加载,就这样一直往上的走,当到了bootstrap这个类加载器时,它没有父加载器,然后它就到自己所管辖的范围去找,找到了就加载出来,没找到它就交给它的儿子加载器去加载,这时候它的儿子加载器才会去管辖的范围找,如果又没找到,那就又给儿子找,就这样一直往下,当最后回到第一个类加载器的时候,如果还是没找到的话就报异常。

      图文解说(2级标题)

类加载器什么是类加载器?类加载器的作用java虚拟机中三个主要的类加载器类加载器的委托机制 编写自己的类加载器------------ ( 有包名的类不能调用无包名的类。在帮助文档中一般看到protect方法多的就要想到用extends) 23种常用的设计模式之----------模板方法设计模式

      它的好处是可以集中管理,有这样一个情况,我有一个类要加载,这时候myclassloader1找到了这个类并加载了此类,而myclassloader2也找到了这个类也加载了此类,这时候内存中就存在了2个一样的字节码,这样就很浪费资源。

       1-1、首先它会调用当前线程的类加载器去加载线程中的第一个类。

                thread a = thread.currentthread();

                system.out.println(a.getcontextclassloader().getclass().getname());

                运行上面的代码输出:

                sun.misc.launcher$appclassloader

       1-2、如果类a中引用了类b,java虚拟机将使用加载类a的加载器来加载类b。        

       1-3、还可以直接调用classloader.loadclass()方法来指定某个类加载器去加载某个类。

               thread a = thread.currentthread();

               system.out.println(a.getcontextclassloader().getclass().getname());

               a.setcontextclassloader(system.class.getclassloader());---------------------------------这里不写.getclass().getname())是因为null没有字节码,写了程序报空指针异常。

               system.out.println(a.getcontextclassloader());

               运行上面的代码输出:

               sun.misc.launcher$appclassloader

               null

      问:能不能自己写一个类叫java.lang.system?

      答:一把情况下不能,因为委托机制的原因,当你写了这个类时,到加载的时候,流程会一直想上走,当到bootstrap时,它发现自己的管辖范围有,于是他就直接加载一个system给你了,而这个system是rt.jar中的类。如果自己写个类加载器就可以了,或者类名相同但是包名不相同也是可以的,但是不能类名和包名都相同。比如楼主可以写个类叫:com.lang.string。

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

     1-2、一般尽量不要覆写已有的loadclass()方法中的委托逻辑

              一般在jdk 1.2之前的版本才这样做,而且事实证明,这样做极有可能引起系统默认的类加载器不能正常工作。在jvm规范和jdk文档中(1.2或者以后版本中),都没有建议用户覆写loadclass(…)方法,相比而言,明确提示开发者在开发自定义的类加载器时覆写findclass(…)逻辑。

        如果,覆盖loadclass方法,程序将不会用委托机制在创建代码,所以一般都覆盖findclass方法。

问题一:自定义一个类,然后编写一个简单的加密算法(把类的字节码中的0换成1,把1换成0),并输出到指定目录下,然后再自定义一个类加载器,用来加载加密过的类。

创建一个用来加密的原始类,此类继承java.util.date。并重写tostring()方法。代码如下

package shipin44;

import java.util.date;

public class classloaderattachment extends date {

 public string tostring(){

  return "hello,itcast";

}

编写加密方法。代码如下:

/**

  * 将传过来的二进制加密(把1变成0,把0变成1)

  * cypher:暗号的意思

  * @param ips:超类,为了能传多种输入流(里氏代换原则)

  * @param ops:超类,为了能传多种输出流(里氏代换原则)

  * @throws exception

  */

 public static void cypher(inputstream ips,outputstream ops) throws exception{

  int b = -1;

//  ips.read();帮助文档中的解释

//  read

//  public abstract int read()

//                    throws ioexception从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。

//  子类必须提供此方法的一个实现。 

//  返回:下一个数据字节;如果到达流的末尾,则返回 -1。 

//  抛出: ioexception - 如果发生 i/o 错误。

  while ((b = ips.read()) != -1) {

   ops.write( b ^ 0xff);

   //system.out.println(b+"\t");//打印输出的内容看不懂

   //system.out.println(b ^ 0xff);//打印输出的内容看不懂

编写执行加密操作的方法。代码如下

  * 开始做加密文件的工作

  * @param args:调用min方法时传进来的参数

  * @throws exception 

 public static void doworkcypher(string[] args) throws exception{

        //源文件路径

  string srcpath = args[0];

  //文件所在的目录

  string destdir = args[1];

  //获得文件名

  string destfilename = srcpath.substring(srcpath.lastindexof('\\')+1);

  //目标文件的相对路径

  string destpath = destdir + "\\" + destfilename;

        //输入流,把东西写入内存中,srcpath原文件的绝对路径

  fileinputstream fis = new fileinputstream(srcpath);

  //输入流,把东西从内存中写出来,写到硬盘上,destpath加密后的文件的相对路径

  fileoutputstream fos = new fileoutputstream(destpath);

  //加密类

  cypher(fis, fos);

  //关闭流

  fis.close();

  fos.close();

编写main方法。

public static void main(string[] args) throws exception{

  // todo auto-generated method stub

  doworkcypher(args);

调用main方法

1、鼠标右键点工程---&gt;new---&gt;folder---&gt;输入itcast

2、鼠标右键点以上方法所属的类---&gt;run as---&gt;java application,程序报错,因为调用min方法时没传相应的参数进去。

3、鼠标右键点以上方法所属的类---&gt;run as---&gt;run confugurarion---&gt;arguments---&gt;program(节目) arguments(参数)---&gt;输入classloaderattachment.class所在的绝对路径---&gt;空格---&gt;再输入上面的folder名---&gt;点apply---&gt;最后run。

4、最后点项目工程f5刷新后,会发现itcast文件夹下多了个classloaderattachment.class文件,此文件是加过密的文件。

测试结果

1、在一个min方法中打印system.out.println(new classloaderattachment().tostring());,输出结果是:

hello,itcast

这时候的加载的类是原始的类

2、用itcast文件夹下的classloaderattachment.class,把f:\java\bianchengruanjian\myeclipseworkspace\zhangxiaoxiangjichujiaqiang\bin\shipin44\classloaderattachment.class目录下的原始.class(未加过密的)替换掉,运行system.out.println(new classloaderattachment().tostring());,程序报错。

编写自定义类加载器,此类必须继承classloader。

重写findclass方法,不能重写loadclass方法。代码如下:

//目录名(itcast)

 string classdir;

 /**

  * 重写findclass方法

 @override

 protected class&lt;?&gt; findclass(string name) throws classnotfoundexception {

  // todo auto-generated method stub  

  string classfilename = classdir + "\\" + name + ".class";

  try {

   fileinputstream fis = new fileinputstream(classfilename);

//加密的.class的绝对路径,这样也可以,这位置的classloaderattachment.class是加过密的

//  classfilename = "c:\\users\\json\\desktop\\classloaderattachment.class";

   bytearrayoutputstream bos = new bytearrayoutputstream();

   cypher(fis, bos);

   fis.close();

   byte[] bytes = bos.tobytearray();

   return defineclass(bytes, 0, bytes.length);

  } catch (exception e) {

   // todo auto-generated catch block

   e.printstacktrace();

  return super.findclass(name);

 //构造函数

 public myclassloader(string classdir){

  this.classdir = classdir;

1、在一个min方法中运行system.out.println(new classloaderattachment().tostring());,打印输出:

2、将加过密后的classloaderattachment.class替换掉未加过密的classloaderattachment.class文件。在myeclipseworkspace\zhangxiaoxiangjichujiaqiang\bin\shipin44目录下运行程序报错,如下图:

类加载器什么是类加载器?类加载器的作用java虚拟机中三个主要的类加载器类加载器的委托机制 编写自己的类加载器------------ ( 有包名的类不能调用无包名的类。在帮助文档中一般看到protect方法多的就要想到用extends) 23种常用的设计模式之----------模板方法设计模式

3、用自定义类加载器myclassloader加载,加过密的classloaderattachment,在min方法中运行如下代码:

  class clazz = new myclassloader("itcast").loadclass("classloaderattachment");

  date d1 = (date) clazz.newinstance();

  system.out.println(d1.getclass().getclassloader().getclass().getname());

  system.out.println(d1.getclass().getclassloader().getparent().getclass().getname());

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

   输出结果:

shipin44.myclassloader

      模板方法模式(template method pattern),定义一个操作中的算法骨架,而将一些实现步骤延迟到子类当中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

      模板方法模式是比较简单的一种设计模式,但是它却是代码复用的一项基本的技术,在类库中尤其重要,它遵循“抽象类应当拥有尽可能多的行为,应当拥有尽可能少的数据”的重构原则。作为模板的方法要定义在父类中(并写完此方法),在方法的定义中使用抽象方法,而只看父类的抽象方法是根本不知道怎么处理的,实际具体处理的是子类,在子类中实现具体功能,因此不同的子类执行将会得出不同的实现结果,但是处理流程还是按照父类定制的方式。这就是模板方法的要义所在,定制算法骨架,让子类具体实现。

      1-1、一次性实现一个算法不变的部分,并将可变的行为留给子类来实现;

      1-2、各子类中具有公共行为的时候,应该被提取出来并集中到一个公共父类中以避免代码重复。

      1-3、当需要控制子类扩展的时候。模板方法在特定点调用钩子操作,这样就只允许在这些点进行扩展。

      下面简单地描述一下上班族正常一天的生活行为

类加载器什么是类加载器?类加载器的作用java虚拟机中三个主要的类加载器类加载器的委托机制 编写自己的类加载器------------ ( 有包名的类不能调用无包名的类。在帮助文档中一般看到protect方法多的就要想到用extends) 23种常用的设计模式之----------模板方法设计模式

我们看到,每个人吃早餐和乘坐交通工具的方式都是个性化行为,但是每个人的行为框架确实一致的,那就是:起床、吃早餐、乘坐交通工具、工作。而每个人吃早餐和乘坐交通工具的行为却是要单独实现的。

类加载器什么是类加载器?类加载器的作用java虚拟机中三个主要的类加载器类加载器的委托机制 编写自己的类加载器------------ ( 有包名的类不能调用无包名的类。在帮助文档中一般看到protect方法多的就要想到用extends) 23种常用的设计模式之----------模板方法设计模式

package com.demo;

public abstract class abstractpeople {

  * 起床

 public void getup(){

  system.out.println("起床");

  * 吃早餐

 public abstract void havebreakfast();

  * 抽象乘坐交通工具的方法

 public abstract void transport();

  * 工作

 public void dowork(){

  system.out.println("工作");

  * 模板方法(每天的行为)

 public void daylift(){

  system.out.println("-----------------------");

  getup();

  havebreakfast();

  transport();

  dowork();

  system.out.println("------------------------");

public class peoplea extends abstractpeople {

  * 具体吃早餐的方法

 public void havebreakfast() {

  system.out.println("吃三明治,喝牛奶");

  * 具体做交通工具的方法

 public void transport() {

  system.out.println("开私家车上班");

public class peopleb extends abstractpeople {

  system.out.println("喝粥,吃小菜");

  system.out.println("坐公交车上班");

public class peoplec extends abstractpeople {

  system.out.println("吃煎饼,喝豆浆");

  system.out.println("做地铁上班");

public class client {

  * @param args

 public static void main(string[] args) {

  abstractpeople peoplea = new peoplea();

  abstractpeople peopleb = new peopleb();

  abstractpeople peoplec = new peoplec();

  peoplea.daylift();

  peopleb.daylift();

  peoplec.daylift();

运行client类输出结果:

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

起床

吃三明治,喝牛奶

开私家车上班

工作

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

喝粥,吃小菜

坐公交车上班

吃煎饼,喝豆浆

做地铁上班