1。原理
java的自定义classloader可以加载自定义的类,实现动态加载。
可以通过集成classloader来实现,如下;
public Class<?> loadClass(String name) throws ClassNotFoundException
会调用下面的loadClass方法
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
(resolve java Links the specified class. 执行static块)
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
} 推荐使用
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
自己实现字节码加载 因此可以 进行字节码 增删改
spring可以动态加载xml配置文件,结合weblogic的jndi数据源,可以动态配置数据源到项目中。
调用loadBean方法加载一个新的xml就可以刷新bean容器了
public void loadBean(String configLocation) throws BeansException {
XmlBeanDefinitionReader beanDefinitionReader = getXmlBeanDefinitionReader();
beanDefinitionReader.setResourceLoader(getSpcxt());
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(getSpcxt()));
try {
beanDefinitionReader.loadBeanDefinitions(getSpcxt().getResources(configLocation));
logger.debug("load bean:" + configLocation + " ok");
} catch (BeansException e) {
logger.warn("loadBean failed", e);
throw e;
} catch (IOException e) {
logger.warn("loadBean failed", e);
throw new RuntimeException(e.getMessage());
}
}
public void loadBeans(String tempFilePath) throws BeansException {
String beanLocations = tempFilePath + "/classes/spring";
if (!Validate.isNull(beanLocations)) {
File locationsFolder = new File(beanLocations);
if (locationsFolder.isDirectory()) {
for (File file : locationsFolder.listFiles()) {
loadBean("classpath:" + "spring/" + file.getName());
}
} else {
for (String beanLocation : beanLocations.split(","))
loadBean(beanLocation);
}
}
}
private XmlBeanDefinitionReader xmlBeanDefinitionReader;
private XmlBeanDefinitionReader getXmlBeanDefinitionReader() {
if (xmlBeanDefinitionReader != null) {
return xmlBeanDefinitionReader;
}
xmlBeanDefinitionReader = new XmlBeanDefinitionReader(
(BeanDefinitionRegistry) ((AbstractApplicationContext) getSpcxt()).getBeanFactory());
return xmlBeanDefinitionReader;
}
2.实现方式
1)自定义CLassLoader的子类,实现读取jar的字节码,使用
java.util.jar.JarEntry;
java.util.jar.JarInputStream;
2)使用开源的jyaml的jar,并且自定义chain方式来执行
下面是一个类似于流程控制的yml文件
start: RequestHandler.validateParams
error: sandbox.error.ftl
exception: ExceptionHandler.doException
chainMaps:
RequestHandler.validateParams:
ok: RequestHandler.getAppInfo
RequestHandler.getAppInfo:
ok: RequestHandler.resolveParams
RequestHandler.resolveParams:
ok: RequestHandler.doAppEncode
RequestHandler.doAppEncode:
ok: RequestHandler.getReqUrl
RequestHandler.getReqUrl:
ok: RequestHandler.getApiIps
RequestHandler.getApiIps:
ok: RequestHandler.doApiEncode
RequestHandler.doApiEncode:
ok: RequestHandler.getRequestParams
RequestHandler.getRequestParams:
ok: RequestHandler.getTempCode
RequestHandler.getTempCode:
ok: RequestHandler.getResult
RequestHandler.getResult:
ok: sandbox.return.ftl
ExceptionHandler.doException:
ok: sandbox.exception.ftl
classMap:
RequestHandler: com.yundaex.yml.chain.request.RequestHandler
ExceptionHandler: com.yundaex.yml.chain.common.ExceptionHandler
ErrorHandler: com.yundaex.yml.chain.common.ErrorHandler
configMap:
config1: 100000999999999
这里的classMap中value指向某个类,这个时候,可以通过自定义classLoader来加载
如classLoader.findClass
然后,将这些class文件通通丢进一个jar包中,而同样通过public InputStream getResourceAsStream(String path)方法将yml文件也丢进jar包中。
3)spring mvc启动时候,将这些需要交给自定义classLoader的jar进行加载,然后以yml文件作为key进行一个接口单元缓存。
4)而在controller层,就可以这样写。
5)网页后台管理
通过网页后台进行统一jar包管理和jndi数据源管理。
6)配置共享
利用数据库进行一些诸如message和url的配置,然后利用redis等缓存程序共享jar包之间的数据,当然jar包之间不能相互依赖,只能共享配置。
7)后台线程的开发
因为线程需要一个激活和销毁,否则就会导致weblogic上出现线程销毁不掉的情况。
package com.yundasys.mobi.common.thread;
import java.util.HashMap;
import java.util.Map;
import com.yundasys.mobi.common.logger.LoggerUtil;
import com.yundasys.mobi.common.logger.LoggerUtil.Level;
public class ThreadManager {
private static Map<String, Thread> threads = new HashMap<String, Thread>();
public static void registerRunnable(NamedPollingTask runnable) {
if (threads.get(runnable.getName()) != null) {
threads.get(runnable.getName()).interrupt();
}
Thread thread = new Thread(runnable, runnable.getName());
threads.put(runnable.getName(), thread);
LoggerUtil.log(Level.INFO, runnable.getName() + " start");
thread.start();
}
public static void interruptAll() {
for (Thread thread : threads.values()) {
thread.interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
registerRunnable(new NamedPollingTask() {
@Override
public String getName() {
return "Thread 1";
}
@Override
public long getSleepMills() {
return 500;
}
@Override
public void polling() {
System.out.println("Thread 1");
}
});
Thread.sleep(2000);
registerRunnable(new NamedPollingTask() {
@Override
public String getName() {
return "Thread 1";
}
@Override
public long getSleepMills() {
return 500;
}
@Override
public void polling() {
System.out.println("Thread 111111");
}
});
}
}
这是一个自定义的简单线程池。
线程池通过覆盖安装的方式进行线程start。
但是,由于利用classloader不能马上执行object的代码,所以,不能再后台上传jar的同时立马启动线程。
所以,权宜之计,将线程的注册,写到了static代码块中,等到发布的接口被第一次调用的时候,启动线程。
package com.yundasys.mobi.core.ydwechat.chain.common;
import com.yundasys.mobi.common.context.ComxContext;
import com.yundasys.mobi.common.logger.LoggerUtil;
import com.yundasys.mobi.common.logger.LoggerUtil.Level;
import com.yundasys.mobi.common.persist.ComxPagingDAO;
import com.yundasys.mobi.common.thread.ThreadManager;
import com.yundasys.mobi.core.ydwechat.thread.PushCacheMsgTask;
import com.yundasys.mobi.core.ydwechat.thread.PushWaybillTask;
import com.yundasys.mobi.core.ydwechat.thread.RemoveCacheMsgTask;
import com.yundasys.mobi.core.ydwechat.thread.UpdateAreaCodeTask;
import com.yundasys.mobi.core.ydwechat.thread.GetOpenBranchTask;
import com.yundasys.mobi.util.ydwechat.YdUtil;
import com.yundasys.yml.model.IHandler;
public class BaseHandler implements IHandler {
protected static ComxPagingDAO cadao = ComxContext.getContext().getCadao();
protected static ComxPagingDAO branchdao = ComxContext.getContext().getDao("branchdao");
/**
* 服务器启动时候的后台托管线程
*/
static {
LoggerUtil.log(Level.INFO, YdUtil.refresh(cadao, branchdao));
// 推送物流信息的线程
ThreadManager.registerRunnable(new PushWaybillTask());
// 推送用户接收失败(连续48小时离线)的客服消息
ThreadManager.registerRunnable(new PushCacheMsgTask());
// 移除过期的推送失败消息缓存
ThreadManager.registerRunnable(new RemoveCacheMsgTask());
// 定时更新地址库
ThreadManager.registerRunnable(new UpdateAreaCodeTask());
// 定时更新开放网点
ThreadManager.registerRunnable(new GetOpenBranchTask());
}
}
这里也可以看出动态加载数据源的方式。
3性能测试
因为利用自定义classLoader可能会加载不少类,在后台管理jar的时候,因为更新jar的时候,可能会产生一些垃圾的class,需要进行性能测试,查看内存和线程的情况。
1)经过几个版本的更新,通过自定义线程池的方式,基本上自定义的线程没有重复出现,但是log4j的线程却因为weblogic的连续部署产生了,暂时不知道怎么解决。
2)对于垃圾的class,gc的效果还是比较理想的,而且值得一提的是,因为考虑到了提交的jar的不可运行性,如果jar中的yml配置有错,将仍然使用上次内存中的正确的jar,不会使用本次的jar,基本到达了事务的要求。
3)因为对yml文件进行了缓存处理,通过刷新yml缓存,来刷新接口,可以很方便的增删改接口,而且因为多是在后台管理界面上传的时候进行加载,虽然大量利用反射,性能的损失并不明显。
关于性能测试,可以参考 http://blog.csdn.net/namesliu/article/details/5830822
顺便上传了ymlchain的实现,有源代码和测试类,希望可以和大家一起多多交流。