背景
假设一个web工程有个独立业务子模块userhomerpc个独立子模块也独立发布子模块独立发布后可以及时reloadweb工程的业务功能个子模块可以在任何服务器上也可以是个不同的公司来提供
一般我们的工程会采用spring来管理bean在这种情况下要稍微改造下看上去和热部署也有点类似
这次的知识点以这个背景为例来进行讲解
首先看下这样做的一些好处
业务模块的独立按需加载可以加快web容器的启动每次只需要启动时加载自己需要的内容
发布更新更快速独立发布子模块影响局部的业务功能
原理
我们先做一个子模块userjar(在共享里)
userjar里面就一个spring_userxml和Userjava
spring_userxml
<bean id=userclass=comwzucxdUser>
<property name=namevalue=xxx />
</bean>
Userjava
packagecomwzucxd;
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
thisid = id;
}
public String getName() {
return name;
}
public void setName(String name) {
thisname = name;
}
}
下面就是模拟父容器加载子模块的case
基本思路通过XmlBeanFactory先加载子模块的bean对象的BeanDefinition然后将BeanDefinition注册到父容易的beanFactory最后对加载bean的AppClassloader改造成需要的ClassLoader(这里改造成URLClassLoader)
详细代码以及示例如下
public class Test {
public static void main(String[] args) {
try {
//读取spring全局配置文件(这里是一个空内容的文件没有任何内容)即我们平时web project父容器上下文
//当然平时这里会用ClassPathXmlApplicationContext看文件所在位置了…
ApplicationContextapplicationContext = newFileSystemXmlApplicationContext(file://\\share\\ebook\\jse\\classloader\\spring_configxml)
//创建全局spring BeanFactory目的是将所有子模块的bean对象注册到这个父容器上下文中
DefaultListableBeanFactorybeanFactory = (DefaultListableBeanFactory)applicationContextgetAutowireCapableBeanFactory()
//独立模块的spring bean配置文件位置
String configurationFilePath = jar:file://\\share\\ebook\\jse\\classloader/userjar!/spring_userxml;
//这里可以做一个逻辑如果该配置文件不存在那么父容器启动的时候这个子模块就不加载
URL url = new URL(configurationFilePath)
//建立远程资源访问
UrlResource urlResource = new UrlResource(url)
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(urlResource)
String[] beanIds =xmlBeanFactorygetBeanDefinitionNames()
for (String beanId : beanIds) {
//获得的子模块bean对象
BeanDefinition bd =xmlBeanFactorygetMergedBeanDefinition(beanId)
//在这里将子模块bean对象注册到父容易上下文中完成bean对象的Definition
beanFactoryregisterBeanDefinition(beanId bd)
}
//接着要进行classloader的改变加载的class文件现在不在classpath下而是其他地方(远程共享http服务或者其他协议服务的机器上)
//这时候就需要将父容易中beanFactory的加载bean的classloader改变(父容易中beanFactory默认是AppClassLoader这种情况下改成用URLClassLoader)
// 以下这行设置BeanFactory的ClassLoader为URLClassLoader以加载外部类
setBeanClassLoader(beanFactory)
//以下是测试是否注入成功
//从父容器上下文中获取user对象
Object pluginBean =applicationContextgetBean(user)
//测试结果
String val = tryInvoke(pluginBean)
Systemoutprintln(val)
} catch (Exception exc) {
excprintStackTrace()
}
}
private static void setBeanClassLoader(
DefaultListableBeanFactorybeanFactory)
throws MalformedURLException {
//指明spring_userxml配置出现的bean对象所在jar位置
String jarFilePath = file://\\share\\ebook\\jse\\classloader\\userjar;
URL jarUrl = new URL(jarFilePath)
URL[] urls = new URL[] { jarUrl };
URLClassLoader cl = new URLClassLoader(urls)
beanFactorysetBeanClassLoader(cl)
}
private static String tryInvoke(Object bean) throws SecurityException
NoSuchMethodExceptionIllegalArgumentException
IllegalAccessExceptionInvocationTargetException {
Class<?> paramTypes[] = new Class[];
Method method =beangetClass()getDeclaredMethod(getName paramTypes)
Object paramValues[] = new Object[];
Object obj = methodinvoke(bean paramValues)
//……
return (String)obj;
}
}
这里如果我们不改变BeanClassLoader会有什么问题呢?会出现classnofound异常这个主要原因就是默认的beanfactory classloader是AppClassLoader
当然还有另外一种方式将jar中的类扫描出来自己创建个classloader一个个添加进去这种方式更加灵活多变其实还是使用spring的bean管理方式使用比较方便已经解决了主要场景
public static void main(String[] args) throws Exception {
URL url = new URL(jar:file:d:\\userjar!/)
URLClassLoader uc = new URLClassLoader(new URL[]{url})
Class<?> cls = ucloadClass(comwzucxdUser)
Object obj = clsnewInstance()
Systemoutprintln(obj)
}