加载
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时,特别是引入第三方依赖时,仔细杜绝这些问题。