自定义的类加载器

自定义的类加载

自定义的类加载器很牛逼,也是因为他们,有了强大的系统,例如Tomcat的ClassLoader, 因为作为一个Web服务器需要考虑到许多,例如:

1、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现相互隔离。这是最基本的要求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相使用

2、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以相互共享。这个需求也很常见,比如相同的Spring类库10个应用程序在用不可能分别存放在各个应用程序的隔离目录中

3、支持热替换,我们知道JSP文件最终要编译成.class文件才能由虚拟机执行,但JSP文件由于其纯文本存储特性,运行时修改的概率远远大于第三方类库或自身.class文件,而且JSP这种网页应用也把修改后无须重启作为一个很大的优势看待

因为这些问题,Java提供给用户使用的ClassLoader就无法满足需求, 也需要我们自己研究

一个自定义类加载器需要继承于ClassLoader此抽象类,下面我们贴个源码分析下

/**
     * Loads the class with the specified <a href="#name">binary name</a>. 
     在方法注释中写到:以指定的二进制名字的文件加载类,即是我们的.class文件 
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        //获取锁, 类在内存中只有一份, 不可多线程操作
        synchronized (getClassLoadingLock(name)) {
            // 首先, 我们要检查此类是否已经被加载了,若是已经被加载,直接返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //使用父类加载器加载此类, 这也说明了此父委托机制的重点
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //使用根类加载器加载, 若能加载即返回对象,否则返回null
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
​
                if (c == null) {
                   //如果任然未找到,即用自个定义的findclass去找
                    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(String name)方法是我们自定义类加载器至关重要的一步,并且它是用protected所修饰的,也就是提醒我们赶紧重写

//这是ClassLoader中的findClass(String name), 啥都没, 就是提供重写
protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

 

 

下面来个基于磁盘的自定义类加载器的例子

package com.pjh.concurrent.part2.classLoader;
​
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
​
public class MyClassLoader extends ClassLoader {
    //定义一个默认的存放路径
    private final static Path DEFAULE_CLASSPATH = Paths
            .get("E:", "javaResource\\ThreadLearn\\src");
​
    private final Path classpathDir;
​
    public MyClassLoader(){
        super();
        this.classpathDir = DEFAULE_CLASSPATH;
    }
​
    public MyClassLoader(String classpath, ClassLoader parent){
        super(parent);
        this.classpathDir = Paths.get(classpath);
    }
​
    //传入指定class路径
    public MyClassLoader(String classpath){
        this.classpathDir = Paths.get(classpath);
    }
​
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes = this.readClassBytes(name);
        if(null == classBytes || classBytes.length == 0){
            throw new ClassNotFoundException("Can not load the class " + name);
        }
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }
​
    //以二进制形式读取文件至内存中\
    private byte[] readClassBytes(String name) throws ClassNotFoundException {
        //将报名分隔符转换为文件路径分隔符号
        String classpath = name.replace(".", "/");
        //完整路径
        Path fullPath = classpathDir.resolve(Paths.get(classpath + ".class"));
        if (!fullPath.toFile().exists()) {
            throw new ClassNotFoundException("The class " + name + " not found");
        }
        //二进制数组输出流
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            Files.copy(fullPath, baos);
        } catch (IOException e) {
​
        }
        return baos.toByteArray();
    }
​
​
}

下面来一个实体类

package com.pjh.concurrent.part2.classLoader;
​
public class HelloWorld {
    public HelloWorld() {
    }
​
    public String welcome() {
        return "welcome !!!!!!!!!!";
    }
​
    static {
        System.out.println("Hello world");
    }
}

再来个测试类

package com.pjh.concurrent.part2.classLoader;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
​
public class MyClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> aClass = myClassLoader.loadClass("com.pjh.concurrent.part2.classLoader.HelloWorld");
        System.out.println(aClass.getClassLoader());
        Object helloWorld = aClass.newInstance();
        System.out.println(helloWorld);
        Method method = aClass.getMethod("welcome");
        String result = (String) method.invoke(helloWorld);
        System.out.println("Result: " + result);
    }
}

输出结果

com.pjh.concurrent.part2.classLoader.MyClassLoader@74a14482
Hello world
com.pjh.concurrent.part2.classLoader.HelloWorld@677327b6
Result: welcome !!!!!!!!!!

但是要把目录下的HelloWorld.java删除了 否则会使用application加载器加载

如果需要破坏双亲委托机制, 重写loadClass即可

 

参考博客:https://www.cnblogs.com/szlbm/p/5504631.html

参考书籍:<<Java高并发详解>>汪文君著

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值