Spring实战之Spring动态加载jar模块和卸载模块

加载

Spring提供了下面这些类来加载外部jar。

在这里插入图片描述

classPathResorce可以用来加载类路径上的jar。
一般推荐使用

            classLoader = new UrlClassLoader(new URL[] {
    
    resource.getURL()}, parentClassLoader);

来创建一个classLoader。其中parentClassLoader可以通过BeanClassLoaderAware从当前Spring上下文中拿到。
一般,我们会写一MyClassPathXmlApplicationContext继承ClassPathXmlApplicationContext。

    public MyClassPathXmlApplicationContext(String[] paths, ClassLoader classLoader, ApplicationContext parent)
            throws BeansException {
    
    
        super(parent);
        Assert.notNull(paths, "Path array must not be null");
        Assert.notNull(classLoader, "Class argument must not be null");
        this.classLoader = classLoader;
        this.configResources = new Resource[paths.length];
        for (int i = 0; i < paths.length; i++) {
    
    
            this.configResources[i] = new ClassPathResource(paths[i], classLoader);
        }
    }

我们主要是为了记录下当前的classLoader。和需要加载的Resource。同时也传入我们自己需要加载的配置文件。

parent的SpringContext可以通过ApplicationContextAware接口来拿到。
为了加载我们jar的内容,我们需要覆盖loadBeanDefinitions方法



    @Override
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    
    
        // 使用系统cl先加载通用spring配置文件
        reader.setBeanClassLoader(Thread.currentThread().getContextClassLoader());
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
    
    
            reader.loadBeanDefinitions(configLocations);
        }
        // 使用决策jar对应的cl加载我们自己的配置文件
        reader.setBeanClassLoader(classLoader);
        // 加载决策jar中的指定URL
        if (configResources != null) {
    
    
            reader.loadBeanDefinitions(configResources);
        }
    }

我们自己实现的xml配置文件可以继承ComponentNamespaceHandler来解析。

        registerBeanDefinitionParser("gdl_tag", new GdlBeanDefinitionParser());

前面是xml节点,后面则是生成一个BeanDefinitionParser,可以用来生成一个BeanDefinition。
里面是经典的建造者模式,供Spring生成bean

public class AntMpsReaderComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {
    
    

    @Override
    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    
    
        String bid = BeanDefinitionReaderUtils.getElementAttr(element, "id");
        BeanDefinitionBuilder builder = BeanDefinitionReaderUtils.getGenericBeanDefinition(parserContext, element, bid,
                GDL.class, true);

        // 注入
        builder.addPropertyValue("mpsServiceId", gdl1);
        builder.addPropertyValue("mpsVersion", gdl2);
        builder.addPropertyValue("mpsGroup", gdl3);
        builder.addPropertyValue("mpsParams", gdl4);
        return builder.getBeanDefinition();
    }


}

卸载

既然可以加载,也必然必须能卸载。除了要卸载对象,必须也要把类加载也卸载掉。JVM判断类卸载的条件是

+ classLoader已经不存在
+ 该class的引用和实例已经不存在

我们会在后台循环尝试调用我的destroy方法


   protected volatile     boolean            destroy            = false; //表示是否已经成功销毁
    protected volatile     boolean            init               = false;//表示是否已经初始化


    @Override
    public void destroy() throws Exception {
    
    
        if (!init) {
    
    
            logger.warn("component[" + getId() + "] not initialized yet.");
        }
        if (!destroy) {
    
    
            synchronized (lock) {
    
    
                if (!destroy) {
    
    
                    //其他销毁内容
                    ...
                    
                    if (classLoader != null) {
    
    
                        //清除spring中因为内省锁保存的classLoader
                        clearClassLoaderCache(classLoader);
                        //清除context中的所有bean。
                        if (applicationContext != null && (applicationContext instanceof DisposableBean)) {
    
    
                            ((DisposableBean) applicationContext).destroy();
                        }
                        //将classLoader设置为null。
                        classLoader = null;
                    }
                    destroy = true;
                    logger.warn("decision unit[" + getId() + "] destroyed.");
                }
            }
        }
    }

其实上面的代码并不完美。一个简单的例子,如果加载一个极为复杂的jar。有如下情况。

  • 该jar有内部线程。导致线程堆栈中所持有的所有对象都无法被释放。
  • 该类通过一些操作,让自己的类被外部类引用

这些都要求我们在开发动态加载jar时,特别是引入第三方依赖时,仔细杜绝这些问题。

猜你喜欢

转载自blog.csdn.net/define_us/article/details/111052819