前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >类加载器的双亲委托模式

类加载器的双亲委托模式

作者头像
算法之名
发布于 2020-05-26 07:16:53
发布于 2020-05-26 07:16:53
84500
代码可运行
举报
文章被收录于专栏:算法之名算法之名
运行总次数:0
代码可运行

遵照之前解析反射 中,我们说到类的加载器ClassLoader在对类进行加载的时候,默认会使用双亲委托模式。系统会判断当前类是否已经被加载,如果已经被加载,就会直接返回可用的类,否则就会尝试加载,在尝试加载时,会先请求双亲处理,如果双亲请求失败,则会自己加载。

我们现在来对此加以验证,先写一个简单类HelloLoader

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class HelloLoader {
    public void print() {
        System.out.println("I am in Boot ClassLoader");
    }
}

再写一个调用类FindClassOrder

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class FindClassOrder {
    public static void main(String[] args) {
        HelloLoader loader = new HelloLoader();
        loader.print();
    }
}

这两个类放在同一个包下面,我这里的包名为com.guanjian.calculate.test

运行结果

I am in Boot ClassLoader

现在将生成的HelloLoader.class文件拷贝出来,放到我事先准备的文件夹下面,目录为/Users/admin/Downloads/com/guanjian/calculate/test,本人的系统为mac的,注意该文件夹不与我们的工程文件夹相同,我们之前到工程文件夹为/Users/admin/Downloads/calculate

此时修改HelloLoader的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class HelloLoader {
    public void print() {
        System.out.println("I am in App ClassLoader");
    }
}

此时运行FindClassOrder,结果如下

I am in App ClassLoader

现在给FindClassOrder添加JVM参数-Xbootclasspath/a:/Users/admin/Downloads/

再次运行,结果发生了改变

I am in Boot ClassLoader

给FindClassOrder添加一条代码,如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class FindClassOrder {
    public static void main(String[] args) {
        HelloLoader loader = new HelloLoader();
        loader.print();
        System.out.println(System.getProperty("sun.boot.class.path"));
    }
}

运行结果

I am in Boot ClassLoader

/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/classes:/Users/admin/Downloads/

从最后一部分/Users/admin/Downloads/我们可以看到,该目录已经加入到了启动的ClassPath中。

由此我们可以看出,HelloLoader的应用版本并没有运行,取而代之的是启动版本。当系统需要加载一个类时,会先从顶层的启动类加载器开始加载,逐层向下,直到找到该类。

现在我们将FindClassOrder修改如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class FindClassOrder1 {
    public static void main(String[] args) throws Exception {
        //获取FindClassOrder1的类加载器,此处为一个应用类加载器
        ClassLoader cl = FindClassOrder1.class.getClassLoader();
        //将HelloLoader.class字节码转换成字节数组
        byte[] bHelloLoader = loadClassBytes("com.guanjian.calculate.test.HelloLoader");
        //ClassLoader的defineClass()方法可以将字节数组转换成Class
        Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        md_defineClass.setAccessible(true);
        //获取Class HelloLoader
        md_defineClass.invoke(cl, bHelloLoader, 0, bHelloLoader.length);
        md_defineClass.setAccessible(false);

        HelloLoader loader = new HelloLoader();
        //打印loader对象的类加载器
        System.out.println(loader.getClass().getClassLoader());
        loader.print();
    }

    private static byte[] loadClassBytes(String className) throws ClassNotFoundException {
        try {
            //获取class文件的完整路径
            String classFile = getClassFile(className);
            FileInputStream fis = new FileInputStream(classFile);
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new
                    ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            while (true) {
                int i = fileC.read(buffer);
                if (i == 0 || i == -1) {
                    break;
                }
                buffer.flip();
                outC.write(buffer);
                buffer.clear();
            }
            fis.close();
            return baos.toByteArray();
        } catch (IOException fnfe) {
            throw new ClassNotFoundException(className);
        }
    }

    private static String getClassFile(String name) {
        StringBuilder sb = new StringBuilder("/Users/admin/Downloads/calculate/target/classes");
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + name);
        return sb.toString();
    }
}

添加JVM参数-Xbootclasspath/a:/Users/admin/Downloads/

运行结果如下

sun.misc.Launcher$AppClassLoader@18b4aac2

I am in App ClassLoader

由此我们可以看出,虽然定义了启动ClassPath,但是系统并没有加载启动版本的HelloLoader。当判断类是否需要加载时,是从底层的应用类加载器开始判断的,如果已经在应用类加载器加载过了,就不会请求上层类加载器了。

我们再将FindClassOrder做出修改

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class FindClassOrder2 {
    public static void main(String[] args) throws Exception {
        //获取FindClassOrder1的类加载器,此处为一个应用类加载器
        ClassLoader cl = FindClassOrder1.class.getClassLoader();
        //将HelloLoader.class字节码转换成字节数组
        byte[] bHelloLoader = loadClassBytes("com.guanjian.calculate.test.HelloLoader");
        //ClassLoader的defineClass()方法可以将字节数组转换成Class
        Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        md_defineClass.setAccessible(true);
        //获取Class HelloLoader
        md_defineClass.invoke(cl, bHelloLoader, 0, bHelloLoader.length);
        md_defineClass.setAccessible(false);
        //通过应用类加载器的双亲扩展类加载器进行加载
        //如果扩展类加载器加载不了,则会请求扩展类加载器的双亲启动类加载器进行加载
        Object loader = cl.getParent().loadClass("com.guanjian.calculate.test.HelloLoader").newInstance();
        //打印loader对象的类加载器
        System.out.println(loader.getClass().getClassLoader());
        //获取loader对象的print()方法
        Method m = loader.getClass().getMethod("print", null);
        //执行print()方法
        m.invoke(loader,null);
    }

    private static byte[] loadClassBytes(String className) throws ClassNotFoundException {
        try {
            String classFile = getClassFile(className);
            FileInputStream fis = new FileInputStream(classFile);
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new
                    ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            while (true) {
                int i = fileC.read(buffer);
                if (i == 0 || i == -1) {
                    break;
                }
                buffer.flip();
                outC.write(buffer);
                buffer.clear();
            }
            fis.close();
            return baos.toByteArray();
        } catch (IOException fnfe) {
            throw new ClassNotFoundException(className);
        }
    }

    private static String getClassFile(String name) {
        StringBuilder sb = new StringBuilder("/Users/admin/Downloads/calculate/target/classes");
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + name);
        return sb.toString();
    }
}

添加JVM参数-Xbootclasspath/a:/Users/admin/Downloads/

运行结果

null

I am in Boot ClassLoader

通过结果,我们可以看出,这里是调用了启动类加载器进行加载的,因为启动类加载器在Java中没有对象,是用C写的,所以为null;而打印的I am in Boot ClassLoader也正好说明加载的是启动ClassPath下的那个HelloLoader.class。虽然在扩展类加载器加载HelloLoader之前,该类已经在应用类加载器中了,但是扩展类加载器并不会向应用类加载器进行确认,而是只在自己的路径中查找,并最终委托给了启动类加载器,而非应用类加载器,从这里可以看到,在判断类是否已经加载时,顶层类加载器不会询问底层类加载器。判断类是否加载时,应用类加载器会顺着双亲路径往上判断,直到启动类加载器。但是启动类加载器不会往下询问,这个委托路线是单向的。

双亲委托模式的弊端

之前说的,检查类是否已经加载的委托过程是单向的。这种方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader所加载的类。

通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如在系统类中,提供了一个接口,该接口需要在应用类得以实现,该接口还绑定了一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。

双亲委托模式的补充

在Java平台中,把核心类(rt.jar)中提供外部服务,可由应用层自行实现的接口,通常可以称为Service Provider Interface.即SPI

我们来看一段这样的实现,它是实现javax.xml.parsers中XML文件解析功能的代码

在DocumentBuilderFactory中,这是一个抽象类,加载在启动类加载器中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static DocumentBuilderFactory newInstance() {
    //根据抽象父类来获取一个子类的对象,抽象父类是通过启动类加载器加载,而子类是通过应用类加载器加载
    return FactoryFinder.find(
            /* The default property name according to the JAXP spec */
            DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
            /* The fallback implementation class name */
            "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
}

在FactoryFinder中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static final SecuritySupport ss = new SecuritySupport(); //在获取系统资源的时候用于检查访问控制的安全支持类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static <T> T find(Class<T> type, String fallbackClassName)
    throws FactoryConfigurationError
{
    //获取类的名称
    final String factoryId = type.getName();
    dPrint("find factoryId =" + factoryId);

    // Use the system property first
    try {
        //从系统属性中获取资源名称
        String systemProp = ss.getSystemProperty(factoryId);
        //如果该资源名称不为空,则返回type类的实例化对象
        if (systemProp != null) {
            dPrint("found system property, value=" + systemProp);
            return newInstance(type, systemProp, null, true);
        }
    }
    catch (SecurityException se) {
        if (debug) se.printStackTrace();
    }

    //尝试从$java.home/lib/jaxp.properties文件读取资源
    try {
        if (firstTime) {
            synchronized (cacheProps) {
                if (firstTime) {
                    String configFile = ss.getSystemProperty("java.home") + File.separator +
                        "lib" + File.separator + "jaxp.properties";
                    File f = new File(configFile);
                    firstTime = false;
                    if (ss.doesFileExist(f)) {
                        dPrint("Read properties file "+f);
                        cacheProps.load(ss.getFileInputStream(f));
                    }
                }
            }
        }
        final String factoryClassName = cacheProps.getProperty(factoryId);
        //如果可以读取,则返回实例化type类的对象
        if (factoryClassName != null) {
            dPrint("found in $java.home/jaxp.properties, value=" + factoryClassName);
            return newInstance(type, factoryClassName, null, true);
        }
    }
    catch (Exception ex) {
        if (debug) ex.printStackTrace();
    }

    //尝试用SPI方式
    T provider = findServiceProvider(type);
    if (provider != null) {
        return provider;
    }
    if (fallbackClassName == null) {
        throw new FactoryConfigurationError(
            "Provider for " + factoryId + " cannot be found");
    }

    dPrint("loaded from fallback value: " + fallbackClassName);
    return newInstance(type, fallbackClassName, null, true);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static <T> T findServiceProvider(final Class<T> type) {
    try {
        return AccessController.doPrivileged(new PrivilegedAction<T>() {
            public T run() {
                //通过把type类实例,这里是DocumentBuilderFactory.class以及通过Thread.currentThread().getContextClassLoader()获取的应用类加载器
                //放入serviceLoader对象的惰性迭代器lookupIterator属性中
                final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
                
                final Iterator<T> iterator = serviceLoader.iterator();
                if (iterator.hasNext()) {
                    return iterator.next();
                } else {
                    return null;
                }
             }
        });
    } catch(ServiceConfigurationError e) {
        // It is not possible to wrap an error directly in
        // FactoryConfigurationError - so we need to wrap the
        // ServiceConfigurationError in a RuntimeException.
        // The alternative would be to modify the logic in
        // FactoryConfigurationError to allow setting a
        // Throwable as the cause, but that could cause
        // compatibility issues down the road.
        final RuntimeException x = new RuntimeException(
                "Provider for " + type + " cannot be created", e);
        final FactoryConfigurationError error =
                new FactoryConfigurationError(x, x.getMessage());
        throw error;
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static <S> ServiceLoader<S> load(Class<S> service) {
    //获取一个当前线程的上下文加载器,该上下文加载器就是应用类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private final Class<S> service; //已经被启动类加载器加载过的类,这里指的是DocumentBuilderFactory.class
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private final ClassLoader loader; //类加载器,用于判定获取的是哪一种类加载器,如启动类加载器或应用类加载器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private final AccessControlContext acc; //访问控制权限
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    //如果svc类已经被加载了,返回该类实例,否则抛出异常
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //传入的类加载器为应用类加载器,如果该应用类加载器不为空,则为应用类加载器,否则为启动类加载器
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); //按照实例化顺序的缓存
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private LazyIterator lookupIterator; //当前的惰性查找迭代器,ServiceLoader的内部类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void reload() {
    //清空加载缓存
    providers.clear();
    //将DocumentBuilderFactory.class以及应用类加载器传入惰性查找迭代器中生成对象
    lookupIterator = new LazyIterator(service, loader);
}

我们来看一下LazyIterator整个的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static final String PREFIX = "META-INF/services/";
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private class LazyIterator
    implements Iterator<S>
{

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    private boolean hasNextService() {
        //如果nextName不为空,表示有下一个类实例
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                //获取META-INF/services/加类全名组成的路径
                String fullName = PREFIX + service.getName();
                //如果应用类加载器为空,从加载类的搜索路径查找指定名称的资源
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                //如果应用类加载器不为空,启用双亲加载器查找资源
                else
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        nextName = pending.next();
        return true;
    }

    /**
     * 从资源文件加载子类
     */
    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            //使用应用类加载器loader加载子类c
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        //如果c不是DocumentBuilderFactory.class的子类,抛出异常
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            //将c的对象实例强转成DocumentBuilderFactory类型的对象
            S p = service.cast(c.newInstance());
            //添加进缓存
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }

    public boolean hasNext() {
        if (acc == null) {
            return hasNextService();
        } else {
            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                public Boolean run() { return hasNextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public S next() {
        if (acc == null) {
            return nextService();
        } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Iterator<S> iterator() {
    return new Iterator<S>() {
        //通过缓存获取已加载的迭代器集合
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();
        /**
         * 是否有加载的下一个类实例
         */
        public boolean hasNext() {
            //如果缓存中有下一个加载的类实例,返回true
            if (knownProviders.hasNext())
                return true;
            //如果缓存中没有,返回惰性查找迭代器中查找结果
            return lookupIterator.hasNext();
        }

        public S next() {
            //如果缓存中有下一个加载的类实例,直接从缓存中获取
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            //如果缓存中没有,返回惰性查找迭代器的下一个类实例
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

通过以上分析,我们可以看到,DocumentBuilderFactory.class的子类并不是由启动类加载器进行加载的,当然启动类加载器也加载不了这个子类,而是使用的Thread.currentThread().getContextClassLoader()上下文应用类加载器来进行加载的,该加载器成为了一个相对共享的实例。这样,即使是在启动类加载器中的代码也可以通过这种方式访问应用类加载器中的类了。

突破双亲的限制

当我们使用自定义类加载器的时候,当对类进行加载的时候,虽然它自己加载不了,会委托双亲应用类加载器进行加载,但是当我们来看这个类实际的加载器的时候,我们会看到是自定义加载器加载的。(有点玄幻)

首先我们先写一个OrderClassLoader继承于ClassLoader,重写父类的loadClass()和findClass()方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@AllArgsConstructor
public class OrderClassLoader extends ClassLoader {
    private String fileName;

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> re = findClass(name);
        if (re == null) {
            System.out.println("I can't load the class:" + name + " need help from parent");
            return super.loadClass(name, resolve);
        }
        return re;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        Class<?> clazz = this.findLoadedClass(className);
        if (clazz == null) {
            try {
                byte[] bytes = loadClassBytes(className);
                clazz = defineClass(className,bytes,0,bytes.length);
            } catch (Exception e) {
//                e.printStackTrace();
                return null;
            }
        }
        return clazz;
    }

    private byte[] loadClassBytes(String className) throws ClassNotFoundException {
        try {
            //获取class文件的完整路径
            String classFile = getClassFile(className);
            FileInputStream fis = new FileInputStream(classFile);
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new
                    ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            while (true) {
                int i = fileC.read(buffer);
                if (i == 0 || i == -1) {
                    break;
                }
                buffer.flip();
                outC.write(buffer);
                buffer.clear();
            }
            fis.close();
            return baos.toByteArray();
        } catch (IOException fnfe) {
//            throw new ClassNotFoundException(className);
            return null;
        }
    }

    private String getClassFile(String name) {
        StringBuilder sb = new StringBuilder(fileName);
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + name);
        return sb.toString();
    }
}

调用这个自定义加载器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class OrderClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        OrderClassLoader myLoader = new OrderClassLoader("/Users/admin/Downloads/");
        Class<?> clz = myLoader.loadClass("com.guanjian.calculate.test.HelloLoader");
        System.out.println(clz.getClassLoader());

        System.out.println("==== Class Loader Tree ====");
        ClassLoader cl = clz.getClassLoader();
        while (cl != null) {
            System.out.println(cl);
            cl = cl.getParent();
        }
    }
}

运行结果

I can't load the class:java.lang.Object need help from parent

com.guanjian.calculate.test.OrderClassLoader@85ede7b

==== Class Loader Tree ====

com.guanjian.calculate.test.OrderClassLoader@85ede7b

sun.misc.Launcher$AppClassLoader@18b4aac2

sun.misc.Launcher$ExtClassLoader@63961c42

从结果可以看出,光靠自定义加载器没办法把字节数组转换成Class实例,而且自定义加载器的defineClass()方法一直找到了HelloLoader的祖先类Object.class。最后使用super.loadClass(name, resolve)应用类加载器来加载成功。但我们打印clz.getClassLoader()得到的却是自定义加载器OrderClassLoader@85ede7b。

热替换

热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。对于Java来说,热替换并不是天生就支持的特性,如果一个类已经加载到了系统中,通过修改类文件,并无法让系统再来加载并重新定义这个类。因此,在Java中实现这一功能的一个可行方法就是灵活运用ClassLoader。

我们还是用前面写的OrderClassLoader自定义加载器来说明。我们新写一个调用类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class OrderClassLoaderTest1 {
    public static void main(String[] args) {
        while (true) {
            try {
                OrderClassLoader myLoader = new OrderClassLoader("/Users/admin/Downloads/");
                Class<?> clz = myLoader.loadClass("com.guanjian.calculate.test.HelloLoader");
                Object demo = clz.newInstance();
                Method print = demo.getClass().getMethod("print", new Class[]{});
                print.invoke(demo,new Object[]{});
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行以后会处于无限循环,每三秒打印一次

I can't load the class:java.lang.Object need help from parent

I can't load the class:java.lang.System need help from parent

I can't load the class:java.io.PrintStream need help from parent

I am in Boot ClassLoader

现在我们将工程中targets/classes里面的文件替换掉/Users/admin/Downloads/com/guanjian/calculate/test中的HelloLoader.class。现在我们在没有停止程序运行的情况下打印结果如下,每三秒打印一次

I can't load the class:java.lang.Object need help from parent

I can't load the class:java.lang.System need help from parent

I can't load the class:java.io.PrintStream need help from parent

I am in App ClassLoader

这里有一点需要注意,当我们实例化对象的时候不能写成如下的方式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class OrderClassLoaderTest1 {
    public static void main(String[] args) {
        while (true) {
            try {
                OrderClassLoader myLoader = new OrderClassLoader("/Users/admin/Downloads/");
                Class<?> clz = myLoader.loadClass("com.guanjian.calculate.test.HelloLoader");
                HelloLoader demo = (HelloLoader)clz.newInstance();
                Method print = demo.getClass().getMethod("print", new Class[]{});
                print.invoke(demo,new Object[]{});
                Thread.sleep(10000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

这么写的话,会抛出如下的异常

java.lang.ClassCastException: com.guanjian.calculate.test.HelloLoader cannot be cast to com.guanjian.calculate.test.HelloLoader

代码语言:txt
AI代码解释
复制
 at com.guanjian.calculate.test.OrderClassLoaderTest1.main(OrderClassLoaderTest1.java:11)

这是因为myLoader每一次都是一个新的自定义加载器对象,所以每次都是不同的加载器,不同的加载器加载同一个类会被虚拟机认为是不同的类。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
SOAP 是什么东西? 介绍介绍「建议收藏」
如果你的安装路径(installation paths)和上面使用的不同,你需要更正它们,然后关闭和重启Tomcat以使它们生效。这样,你就有为运行SOAP作好了准备。但是现在,我要忘记有关的技术部分,来学一点理论知识。
全栈程序员站长
2022/09/15
9820
Python量化学习-提取证券数据
不知道公众号有多少读者买基金或者炒股,分享一下如何用python获取证券信息 1、网易财经 import requests from lxml import etree headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' } def parse_url(url):
用户9925864
2022/07/27
9480
Python量化学习-提取证券数据
建立自己的Web service(SOAP篇)
  Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。
那一叶随风
2018/08/22
2.3K0
建立自己的Web service(SOAP篇)
php使用NuSoap产生webservice结合WSDL让asp.net调用
类别:PHP 评论:0 浏览:513 发表时间:2009-09-10 16:59:38
Java架构师必看
2021/03/22
3.3K0
php使用NuSoap产生webservice结合WSDL让asp.net调用
bwappxss_α·pav
使用 htmlspecialchars()函数把特殊字符(& ’ ” < >)转义为HTML实体无法桡过
全栈程序员站长
2022/09/29
1.1K0
干货 | 各种WAF绕过手法学习
https://github.com/danielmiessler/SecLists/tree/master/Fuzzing
Power7089
2021/04/30
4K0
Fiddler 显示客户端请求时间、请求耗时、服务器地址
打开 CustomRules.js  (目录:C:\Users\UsersName\Documents\Fiddler2\Scripts):
卓越笔记
2023/02/17
4.1K0
Fiddler 显示客户端请求时间、请求耗时、服务器地址
ColdFusion-命令速查与日常使用-CheatSheet Pt 1
首先要求 XML 代码最外层必须被一个 root tag 包围, 否则会报以下错误:
szhshp
2022/09/21
8750
Design issues - Sending small data segments over TCP with Winsock
When you need to send small data packets over TCP, the design of your Winsock application is especially critical. A design that does not take into account the interaction of delayed acknowledgment, the Nagle algorithm, and Winsock buffering can drastically effect performance. This article discusses these issues, using a couple of cases studies, and derives a series of recommendations for sending small data packets efficiently from a Winsock application.
用户4766018
2022/08/19
4780
一、爬虫的基本体系和urllib的基本使用 先进行一个简单的实例:利用有道翻译(post请求)另外一个简单的小实例是:豆瓣网剧情片排名前20的电影(Ajax请求)
爬虫   网络是一爬虫种自动获取网页内容的程序,是搜索引擎的重要组成部分。网络爬虫为搜索引擎从万维网下载网页。一般分为传统爬虫和聚焦爬虫。 爬虫的分类   传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。通俗的讲,也就是通过源码解析来获得想要的内容。   聚焦爬虫的工作流程较为复杂,需要根据一定的网页分析算法过滤与主题无关的链接,保留有用的链接并将其放入等待抓取的URL队列。然后,它将根据一定的搜索策略
酱紫安
2018/04/16
1.1K0
一、爬虫的基本体系和urllib的基本使用	 先进行一个简单的实例:利用有道翻译(post请求)另外一个简单的小实例是:豆瓣网剧情片排名前20的电影(Ajax请求)
一个简单Python脚本,实现轻量应用服务器防火墙自动添加客户端公网IP访问
当需要设置轻量级服务器的防火墙策略时,一般用户都是需要手动去设置开放指定公网IP访问轻量服务器,这样也是比较麻烦的。就此作者想了下,如何避免每次手动去修改公网IP,这样也是非常麻烦,如果腾讯云轻量服务器控制台能增加一个安全选项,实现浏览器获取当前用户公网IP并一键设置允许常用端口访问。这样大大的增加用户安全体验下。
邓鹏
2024/04/26
2800
使用Python轻松获取股票实时数据
近年来,股市并未迎来大牛市,相反,我们正面临着一个熊市,行情相当不佳。尽管股市一在3000点的心理阻力,左右徘徊,但随后又出现了下跌的趋势,让投资者备受挑战。
用户11122129
2024/05/15
8770
Python Fiddler抓包工具教学,获取公众号(pc客户端)数据
前言 今天来教大家如何使用Fiddler抓包工具,获取公众号(PC客户端)的数据。 Fiddler是位于客户端和服务器端的HTTP代理,是目前最常用的http抓包工具之一。 开发环境 python 3.8 运行代码 pycharm 2021.2 辅助敲代码 requests 第三方模块 Fiddler 汉化版 抓包的工具 微信PC端 如何抓包 配置Fiddler环境 先打开Fiddler,选择工具,再选选项 在选项窗口里点击HTTPS,把勾选框都勾选上 在选项窗口里点击链接,把勾选框都勾选上,然后点
松鼠爱吃饼干
2023/03/08
3.4K0
Python Fiddler抓包工具教学,获取公众号(pc客户端)数据
Python实现的食谱生成器
想象一下:你正在超市或者菜市场寻找晚餐灵感,但想到昨天餐馆点的好吃的菜,但并不知道那个配方。
mariolu
2024/03/18
3780
Python3爬虫学习.md
(1) urllib 简单的爬取指定网站 (2) Scrapy 爬虫框架 (3) BeautifulSoup 爬虫解析
全栈工程师修炼指南
2020/10/23
8240
Python3爬虫学习.md
Python之Urllib使用
如果我们把互联网比作一张大的蜘蛛网,那一台计算机上的数据便是蜘蛛网上的一个猎物,而爬虫程序就是一只小
鱼找水需要时间
2023/02/16
4460
Python之Urllib使用
Elasticsearch 中文分词、全文搜索、分布式集群搭建和java客户端操作
分词就是指将一个文本转化成一系列单词的过程,也叫文本分析,在Elasticsearch中称之为Analysis。 举例:我是中国人 --> 我/是/中国人
不愿意做鱼的小鲸鱼
2022/09/24
2.2K0
Elasticsearch 中文分词、全文搜索、分布式集群搭建和java客户端操作
Python 小型项目大全 76~81
井字棋是一种在3 × 3网格上玩的经典纸笔游戏。玩家轮流放置 X 或 O 标记,试图连续获得三个。大多数井字棋都以平局告终,但如果你的对手不小心,你也有可能智胜他们。
ApacheCN_飞龙
2023/04/12
1.2K0
Python 小型项目大全 76~81
Python爬虫urllib详解
学习爬虫,最初的操作便是模拟浏览器向服务器发出请求,那么我们需要从哪个地方做起呢?请求需要我们自己来构造吗?需要关心请求这个数据结构的实现吗?需要了解 HTTP、TCP、IP 层的网络传输通信吗?需要知道服务器的响应和应答原理吗?
仲君Johnny
2024/02/05
3190
Python爬虫urllib详解
[翻译]盲SSRF利用链术语表
SSRF(Server-Side Request Forgery:服务请求伪造)是一种由攻击者构造,从而让服务端发起请求的一种安全漏洞,它将一个可以发起网络请求的服务当作跳板来攻击其他服务,SSRF的攻击目标一般是内网。当服务端提供了从其他服务器获取数据的功能(如:从指定URL地址获取网页文本内容、加载指定地址的图片、下载等),但是没有对目标地址做过滤与限制时就会出现SSRF。
天钧
2021/10/08
2.2K0
[翻译]盲SSRF利用链术语表
推荐阅读
相关推荐
SOAP 是什么东西? 介绍介绍「建议收藏」
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验