前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义类加载器

自定义类加载器

作者头像
每天学Java
发布2020-06-02 10:13:05
1.6K0
发布2020-06-02 10:13:05
举报
文章被收录于专栏:每天学Java每天学Java

Java编译和加载

在初学Java的时候,我们都知道.java文件转换为.class文件的过程叫做编译。

编译的过程主要存在三步

  • 词法分析和输入到符号表
  • 注解处理
  • 语义分析和生成字节码

我们可以通过一些命令(javap)去查看.class文件的字节码

公众号其他相关文章:

Class类文件结构

JDK常用命令行工具

加载是虚拟机将.class文件加载到内存中供程序使用

注: 通常我们说的类加载是是类的生命周期中加载、连接、初始化三个阶段。但是在某一段程序中,我们引用第三方的类的不一定会触发类初始化(主动引用和被动引用大家可自行了解)。如下代码:

  class A{
  public final static int x = 10;
   static {
        System.out.println("1000");
    }
  }

我们在其他类中调用A,并不会初始化A

class B{
public static void main(String[] args) {
 System.out.println(A.x);
}
 }

执行结果就是A中静态代码块没有执行,只是输出10,没有输出1000。

类加载器

Java中类的加载都是在运行期执行,这种方式会带来一定的性能开销,但是也增加了应用程序的灵活性。 类的加载自然离不开类加载器,Java中类加载器分为启动类加载器,扩展类加载器,系统类加载器,自定义类加载器。其中启动类加载器为顶级加载器,自定义类加载器最低。

在说明自定义类加载器之前我们先去简单的看一下ClassLoader这个类的loadClass方法,双亲委派模式核心也在这里,里面有一个递归loadClass方法(个人是这样理解的)

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
         //同步
        synchronized (getClassLoadingLock(name)) {
            //findLoadedClass是检测class文件是否被加载,如果加载就调用本地方法findLoadedClass0返回class
            Class<?> c = findLoadedClass(name);
            //没有被加载
            if (c == null) {
            //类似System.currentTimeMillis()
                long t0 = System.nanoTime();
                try {
                //如果父加载器不为空,那么调用父加载器的loadClass方法加载类,如果父加载器为空,那么调用虚拟机的加载器来加载类
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
        
                }
//如果以上两个步骤都没有成功的加载到类,那么调用自己的findClass(name)方法来加载类。
                if (c == null) {
                    long t1 = System.nanoTime();
                    //这里是我们实现自定义类加载的关键
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

我们发现findClass是一段如下代码,意思很明显,如果没有重写就抛出ClassNotFoundException。在注释上也描述到 该方法应由类加载器实现重写。所以实现自定义类加载器离不开findClass方法

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

自定义类加载器好处

1.实现自定义类加载器后我们可以对于class文件进行加密

    @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
      //对于加密的class文件,我们先解密然后再交给加载器加载
      byte[] data = this.decrypt(name);
      return this.defineClass(name, data, 0, data.length);
  }

2. 加载其他途径来源的Class文件,因为默认类加载器只能加载固定路径下的class,如果有特定路径下的class,需要自定义

实现自定义类加载器

首先我们来写两个简单的类,供我们测试

public class TestDyna {
    public void test() {
        System.out.println("TestDyna");
        new Test2().sss();
    }
}
public class Test2 {
    static {
        System.out.println("Test2调用TestDyna开始");
        new TestDyna().test();
        System.out.println("Test2调用TestDyna结束");
    }
    public void sss() {
        System.out.println("Test2");
    }
}

如果我们不使用自定义类加载器,调用TestDyna的test方法,输出结果是什么呢?(类的类构造器中会收集所有的static块和字段并执行,static块只执行一次,由JVM保证其只执行一次)

TestDyna
Test2调用TestDyna开始
TestDyna
Test2
Test2调用TestDyna结束
Test2

下面我们来实现自定义的类加载器来执行TestDyna的test方法。

首先我们自定义类继承ClassLoader,构造器中super(null)是为了将ClassLoader中parent设置为null。这样才会执行自定义加载器,否则仍然会执行虚拟机提供的类加载器

class MyClassLoader extends ClassLoader {
    public MyClassLoader() {
        super(null);
    }
}

重写findClass方法

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("执行自定义的类");
        //获取文件
        File file = getClassFile(name);
        try {
            //这里是将class文件转为字节
            byte[] bytes = getClassBytes(file);
            //把字节码转化为Class
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }
        //执行失败,则又父类findClass执行--也就是抛出ClassNotFoundException
        return super.findClass(name);
    }

getClassFile/getClassBytes方法

    private File getClassFile(String name) {
        //前面是固定的文件路径,由于name为xx.xx.xx,我们要转换为xx/xx/xx.class
        File file = new File("/Users/chenlong/Documents/weaver/demo/ecology/classbean/" +
        name.replace(".", "/") + ".class");
        return file;
    }

    private byte[] getClassBytes(File file) throws Exception {
        // 这里要读入.class的字节,因此要使用字节流
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);
        while (true) {
            int i = fc.read(by);
            if (i == 0 || i == -1)
                break;
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return baos.toByteArray();
    }

到这里我们就实现了一个类加载器,我们来看一下执行,利用了反射的API

    public static void main(String[] args) throws Exception {
        MyClassLoader mcl = new MyClassLoader();
        Class<?> c1 = Class.forName("cl.test.TestDyna", true, mcl);
        Object obj = c1.newInstance();
        Method m = c1.getMethod("test");
        m.invoke(obj);
    }

结果:是跟我们上面普通的调用方式没什么区别。

执行自定义的类
TestDyna
执行自定义的类
Test2调用TestDyna开始
TestDyna
Test2
Test2调用TestDyna结束
Test2

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java编译和加载
  • 类加载器
  • 自定义类加载器好处
  • 实现自定义类加载器
相关产品与服务
命令行工具
腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档