黑马程序员-类加载器--我和小伙伴们都累坏了

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

 

  每一个class文件 每次我们用的时候 总是要先加载到内存上, 然后再编译成字节码文件.. 这时候涉及到了一个问题. 用什么来加载呢? 本文要叙述的问题就是这个, 关于类加载器
       首先了解3个东西,  bootStrap.  ExtClassLoader , appClassLoader     

l        类加载器也是 Java 类,因为其他是 java 类的类加载器本身也要被类加 载器加载,显然必须有第一个类加载器不是不是 java 类,这正是 BootStrap
       Java 虚拟机中的所有类装载器采用具有父子关系的树形结构进行组 织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器 对象或者默认采用系统类装载器为其父级类加载。  
       BootStrap返回的是一个null, BootStrap是跟节点, 如果在BootStrap里找到对应的加载对象, 将返回null
       默认的加载器是appClassLoader 
       类加载器的加载机制是由下往上加载,类加载器的委托机制,为什么说是由下往上, 因为BootStrap先加载上来的, 由BootStrap去加载各个加载器,但是默认的加载器又是appClassLoader, 所以成了由下往上加载, appClassLoader下就是自定义的类加载器, 如下图: 
              
        

        先看一个简单的类加载器配置读取文件的例子
 
 public static void main(String[] args) throws Exception{

    
         //首先我们需要配置config.properties文件className=java.util.HashSet

        
        InputStream ips = new FileInputStream("config.properties");  //其实个人还是比较习惯这种做法, 方便

        
        //java的类加载器提供了一个加载类文件流的方法叫做getResourceAsStream

        
        //getResourceAsStream  //在classPath 指定的目录下逐一的查找要加载的文件(张老师官方说法)

        
        //路径前面不能加 "/";

        
        InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties"); 

        
        //classn内部提供了getResourceAsStream直接加载文件的配置, 
        //底层也是调用了getClassLoader(); 用系统调用的可以省略不写绝对路径

        
        InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties");

        
        // "/" 路径问题,不多做解释, 绝对路径和相对路径都行
        

        InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties");

        
        Properties props = new Properties();  // Properties可以看成是一个map集合, 返回是key 跟 value ;

        
        props.load(ips); //加载一个文件读取流

        
        ips.close();

    
        String className = props.getProperty("className"); //获取到value

    }

 


 鉴于类加载器的委托机制,我们可以得出这样的结论:自定义类加载器也需要类加载器来加载,那么就由默认的类加载器,默认的加载器是appClassLoader,也就是说,自定义类加载器的父加载器是appClassLoader,这里我们需要考虑 到类加载器的委托机制,默认的发起者是appClassLoader。如果有存在继承或者实现接口的话,那么被继承或者被实现的那个类的加载器,是由继承或者实现类的这个类的类加载器来加载(有点拗口),如图, A类引用了B类,B类的加载器是使用A类的加载器比如说A类的加载器被ExtClassLoader加载到,A类继承了B类,那么B类也必须是被ExtClassLoader加载。类加载器还可以指定某个方法去加载某个类,他们的父加载器是app加载器,再次强调,自定义加载器被加载,他们的父加载器是appClassLoader。

         
看完上面的解释后,再来看下面这个自定义类加载器示例
 
思路:第一,首先要有一个加密的class文件。

public class ClassLoaderAttachment extends Date {

    
    @Override

    
    public String toString(){

        
        return "hello,itcast";

        
    } 
}


           第二,自定义类加载器必须继承ClassLoader,并且实现ClassLoader里的findClass方法,ClassLoader的默认加载器是
appClassLoader,所以,当自定义加载器由父加载器加载到子加载器appClassLoader后就不会再找自定义加载器了,换句话说自定义类加载器如果在BootShrap和ExtClassLoader里找不到的话, 一定是由appClassLoader返回结果。 如果appClassLoader也找不到的话,不会再返回自定义加载器了,直接抛出异常,classNotFindException。(码字好累)
        
         第三,在自定义类加载器里定义解密方法。好了,废话说到这,我和小伙伴们都累坏了。上代码

    //加密和解密的公用方法

    

    private static void cypher(InputStream ips ,OutputStream ops) throws Exception{

    
        int b = -1;

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

            
            ops.write(b ^ 0xff);
      }
    }

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

        
        String srcPath = args[0];  //由我们自己手动设置一个路径,不写死

        
        String destDir = args[1];  //目标目录,也不写死

        
        FileInputStream fis = new FileInputStream(srcPath);  //获取到要加密的那个class文件

        
        String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);   //获取到class文件的名字

        
        String destPath = destDir + "\\" + destFileName;    //放置在目录下

        
        FileOutputStream fos = new FileOutputStream(destPath);  //文件输出流, 不解释。

        
        cypher(fis,fos);

        
        fis.close();

        
        fos.close();

    }


以上是加密方法,好了,自定义类加载器读取上来解密,

public class MyClassLoader extends ClassLoader{

    private String classDir; //传入刚才那个加密的class文件的目录

    
    public MyClassLoader(){
    }

    
    public MyClassLoader(String classDir){ //为了省时,直接构造函数搞起

        
        this.classDir = classDir;

    }

    @Override

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

        
        System.out.println("我进来了");

        
        //name是解密的文件名

        
        String classFileName = classDir + "\\"  + name.substring(name.lastIndexOf('.')+1) + ".class";

        
        // classFileName 读取的路径名, itcastlib\ClassLoaderAttachment.class

        
        System.out.println(classFileName);

        
        try {

            
            FileInputStream fis = new FileInputStream(classFileName); //先读取上来

            
            ByteArrayOutputStream  bos = new ByteArrayOutputStream(); //读取到的是字节码文件, 放在字节数组流里面

            
            cypher(fis,bos); //再次异或, 解密操作

            
            fis.close();

            
            byte[] bytes = bos.toByteArray(); //创建一个字节数组

            
            return defineClass(null,bytes, 0, bytes.length); //将一个字节数组转换为 Class 类的实例,此方法由ClassLoader类加载器自带

            
        } catch (Exception e) {

            e.printStackTrace();

        }

        return null;
    }


运行看结果:

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

        
        //我们现在要加载指定目录下的class文件来使用, 那么我们就得把java虚拟机编译出来的class文件删掉,

    
        //如果不删掉,会被父加载器给加载到, 如果要让父加载器加密的文件, 我们可以先把那份加密的文件把生成的

        
        //未加密的class给覆盖成加密后的class文件, 然后父加载器加载的必须的带有包名的类

        
        Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");

        
        // Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");

        Date d1 =  (Date)clazz.newInstance(); 

        
        //这里只能用父类来引用, 虽然说加载上来的实例是ClassLoaderAttachment

        
        //但是ClassLoaderAttachment 加载上来虚拟机也不知道是什么东西,所以用父类来引用,

        
        //父类能被正常的类加载器加载上来,

        
        System.out.println(d1); // 结果是 hello, itcast

        
    }


 类加载器的高级应用, 在webService下的应用.

 需要搭建servlet  

public class classTest extends HttpServlet { //搭建servlet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        doPost(request, response);

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {

                
        response.setContentType("text/html");

        
        response.setCharacterEncoding("UTF-8"); //设置作用域的编码,不解释

        
        request.setCharacterEncoding("UTF-8");  //同上

        
        PrintWriter out  =  response.getWriter();//往页面上输出

        
        ClassLoader loader = this.getClass().getClassLoader(); //获取到当前类的加载器 webApp

        
        while(loader!=null){

            
            out.println(loader.getClass().getName()+"<br/><br/>"); //这里的类加载器由tomcat决定

            
            loader = loader.getParent();

            

        }
        out.print("我和小伙伴们都惊呆了<br/>");

        
        out.close();
    }
}

 



注意:如果将这个web项目导成jar包并且放在 lib/ext 目录下,那么被继承的Httpservlet 会报错,错误原因是,类加载器加载不到,这时候需要将HttpServlet 所在的jar包也放在ext下让webExt 类加载器加载即可解决
 
    PS: 我和我的小伙伴真的都累坏了。 
 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值