在项目开发时,由于需求的更变,需要实现对jar文件的上传,解析,加载,卸载等功能
1.MyURLClassLoader.java
public class MyURLClassLoader extends URLClassLoader {
private List<JarURLConnection> cachedJarFiles = new ArrayList();
public MyURLClassLoader() {
super(new URL[] {}, findParentClassLoader());
}
/**
* 定位基于当前上下文的父类加载器
* @return 返回可用的父类加载器.
*/
private static ClassLoader findParentClassLoader() {
ClassLoader parent = MyURLClassLoader.class.getClassLoader();
if (parent == null) {
parent = MyURLClassLoader.class.getClassLoader();
}
if (parent == null) {
parent = ClassLoader.getSystemClassLoader();
}
return parent;
}
/**
* 将指定的文件url添加到类加载器的classpath中去,并缓存jar connection,方便以后卸载jar
* @param 一个可想类加载器的classpath中添加的文件url
*/
public void addURLFile(URL file) {
try {
// 打开并缓存文件url连接
URLConnection uc = file.openConnection();
if (uc instanceof JarURLConnection) {
uc.setUseCaches(true);
((JarURLConnection) uc).getManifest();
cachedJarFiles.add((JarURLConnection)uc);
}
} catch (Exception e) {
System.err.println("Failed to cache plugin JAR file: " + file.toExternalForm());
}
addURL(file);
}
/**
* 卸载jar包
*/
public void unloadJarFiles() {
List<JarURLConnection> tempDel = new ArrayList<>();
for (JarURLConnection url : cachedJarFiles) {
try {
tempDel.add(url);
System.err.println("Unloading plugin JAR file " + url.getJarFile().getName());
url.getJarFile().close();
url=null;
} catch (Exception e) {
System.err.println("Failed to unload JAR file\n"+e);
}
}
cachedJarFiles.removeAll(tempDel);
tempDel = null;
}
public int getCachedJarCount() {
return cachedJarFiles.size();
}
}
2.CustomFunctionJarManager.java
public interface CustomFunctionJarManager {
public void loadJar(String path,String loaderName);
public void unloadJar(String loaderName);
// public void tempParse(String path,JarParse jarParse);
public byte[] createChecksum(String filename) throws Exception;
public byte[] createChecksum(InputStream fis) throws Exception;
public String getMD5Checksum(String filename) throws Exception;
public String getMD5Checksum(InputStream fis) throws Exception;
public Object getLoader(String loaderName);
public void init();
public void destroy();
public boolean isloaded(String loaderName);
}
3.CustomFunctionJarManagerImpl.java
public class CustomFunctionJarManagerImpl implements CustomFunctionJarManager{
private static Map
loaderMap = new HashMap
();
public CustomFunctionJarManagerImpl(){
super();
}
@Override
public synchronized void loadJar(String path,String loaderName){
try {
String name = loaderName==null?path:loaderName;
unloadJar(name);
MyURLClassLoader loader = new MyURLClassLoader();
URL url = new File(path).toURI().toURL();
String jarStr = "jar:" + url.toExternalForm() + "!/";
loader.addURLFile( new URL(jarStr) );
this.loaderMap.put(name, loader);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
@Override
public synchronized void unloadJar(String loaderName){
MyURLClassLoader loader = (MyURLClassLoader) this.getLoader(loaderName);
if(null != loader) {
loader.unloadJarFiles();
this.loaderMap.remove(loaderName);
}
}
// @Override
// public synchronized void tempParse(String path,JarParse jarParse) {
// try {
// URL url = new File(path).toURI().toURL();
// String jarStr = "jar:" + url.toExternalForm() + "!/";
// loader.addURLFile( new URL(jarStr) );
//
// jarParse.parseJar(path, loader);
// } catch (MalformedURLException e) {
// e.printStackTrace();
// } finally {
// loader.unloadJarFiles();
// System.gc();
// }
// }
@Override
public synchronized byte[] createChecksum(String filename) throws Exception{
InputStream fis = new FileInputStream(filename);
byte[] buffer = new byte[1024];
int len;
MessageDigest complete = MessageDigest.getInstance("MD5");
while((len=fis.read(buffer)) > -1) {
complete.update(buffer, 0, len);
}
fis.close();
return complete.digest();
}
@Override
public synchronized byte[] createChecksum(InputStream fis) throws Exception{
byte[] buffer = new byte[1024];
int len;
MessageDigest complete = MessageDigest.getInstance("MD5");
while((len=fis.read(buffer)) > -1) {
complete.update(buffer, 0, len);
}
fis.close();
return complete.digest();
}
@Override
public synchronized String getMD5Checksum(String filename) throws Exception{
byte[] b = createChecksum(filename);
StringBuffer result = new StringBuffer("");
for (int i = 0,len = b.length; i < len; i++) {
result.append(Integer.toString((b[i] & 0xff) + 0x100).substring(1));
}
return result.toString();
}
@Override
public synchronized String getMD5Checksum(InputStream fis) throws Exception{
StringBuffer result = new StringBuffer("");
byte[] b = createChecksum(fis);
for (int i = 0,len = b.length; i < len; i++) {
result.append(Integer.toString((b[i] & 0xff) + 0x100).substring(1));
}
return result.toString();
}
@Override
public Object getLoader(String loaderName) {
return this.loaderMap.get(loaderName);
}
@Override
public void init() {
}
@Override
public void destroy() {
}
@Override
public boolean isloaded(String loaderName) {
MyURLClassLoader loader = (MyURLClassLoader) getLoader(loaderName);
return null!=loader;
}
}
4.CustomFunctionJarManagerFactory.java
public class CustomFunctionJarManagerFactory {
public static CustomFunctionJarManager manager = null;
public static synchronized CustomFunctionJarManager getCustomFunctionJarManager(String className){
if (null == manager) {
if(null != className && !"".equals(className)){
try {
manager = (CustomFunctionJarManager) Class.forName(className).newInstance();
} catch (Exception e) {
ARE.getLog().error("CustomFunctionJarManager获取失败", e);
manager = new CustomFunctionJarManagerImpl();
}
}
else {
manager = new CustomFunctionJarManagerImpl();
}
}
try {
manager.init();
} catch (Exception e) {
ARE.getLog().error("CustomFunctionJarManager初始化失败", e);
}
return manager;
}
public static synchronized void release() {
if (manager != null) {
try {
manager.destroy();
manager = null;
} catch (Exception e) {
ARE.getLog().error("CustomFunctionJarManager销毁失败", e);
}
}
return;
}
}
5.使用情况
ServletContext application = config.getServletContext();
Map<String,String> jarMap = new HashMap<String,String>();
Map<String,String> classMap = new HashMap<String,String>();
application.setAttribute("JarDetails", jarMap); //存储jar文件MD5值,表达式名
application.setAttribute("ClassMD5ValueMap", classMap); //存储表达式class文件对应MD5值
Connection conn = null;
try {
BizObjectManager bm=JBOFactory.getBizObjectManager("xxxxxxxxxxxxxxxxxxxxxxxxx");
String defaultPath = config.getServletContext().getRealPath("/");//项目工程根目录
String projectName = config.getServletContext().getContextPath();//项目工程名
final String filePath = "CUSTOM_FUNCTION_UPDOAD_FILE_PATH";
final String jarPath = "CUSTOM_FUNCTION_JAR_NAME";
String rootParse = ARE.getProperty(filePath,defaultPath.substring(0, defaultPath.length() - projectName.length()) + "/cfClasses"); //jar包位置
String jarName = ARE.getProperty(jarPath, "thread.jar");//jar包名
CustomFunctionJarManager manager = CustomFunctionJarManagerFactory.getCustomFunctionJarManager(null);
synchronized(manager) {//线程安全
File file = new File(rootParse, jarName);
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if(!file.exists()){
jarMap.put("MD5Value", "");
jarMap.put("FuncList", "");
bm.createQuery("delete from O");
return;
}
else {
manager.loadJar(file.getAbsolutePath());
jarMap.put("MD5Value", manager.getMD5Checksum(file.getAbsolutePath()));
JarFile jarFile = null;
jarFile = new JarFile(file.getAbsolutePath());
Enumeration<JarEntry> entryList = jarFile.entries();
StringBuffer sbf = new StringBuffer("");
while(entryList.hasMoreElements()) {//在jar文件中查找xxx.xxx.xxx包下的所以类文件class
JarEntry jarEntry = entryList.nextElement();
String tempName = jarEntry.getName();
if(tempName.startsWith("xxx/xxx/xxx/") && tempName.endsWith(".class")) {
Pattern p = Pattern.compile("[^0-9a-zA-Z/.]");//对掉内部类等情况
Matcher m = p.matcher(tempName);
if(!m.find()){
classMap.put(tempName.substring(tempName.lastIndexOf("/") + 1, tempName.lastIndexOf(".")), manager.getMD5Checksum(jarFile.getInputStream(jarEntry)));
sbf.append(tempName.substring(tempName.lastIndexOf("/") + 1, tempName.lastIndexOf(".")));
sbf.append(",");
}
}
}
if(!"".equals(sbf.toString())) {
jarMap.put("FuncList", sbf.substring(0, sbf.length() - 1));
}
else {
jarMap.put("FuncList", "");
return;
}
List<BizObject> funcNames = bm.createQuery("select funcName from O").getResultList(false);
StringBuffer funcNamesBuffer = new StringBuffer(",");
for (BizObject bizObject : funcNames) {
funcNamesBuffer.append(bizObject.getAttribute("funcName").getString());
funcNamesBuffer.append(",");
}
String funcNamesStr = funcNamesBuffer.toString();
String[] sbfArr = sbf.substring(0, sbf.length() - 1).split(",");
MyURLClassLoader loader = (MyURLClassLoader) manager.getLoader();
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "craw", "craw");
conn.setAutoCommit(false);
String sql = "INSERT INTO xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx VALUES(?,?,?,?,?)";
String sql2 = "DELETE FROM xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx WHERE funcName=?";
PreparedStatement prest = conn.prepareStatement(sql);
PreparedStatement prest2 = conn.prepareStatement(sql2);
for (int i = 0; i < sbfArr.length; i++) {//jar文件中的表达式类名与数据库比较,jar中存在,数据库中不存在的,批量新增
if(!funcNamesStr.contains(","+sbfArr[i]+",")){
boolean existFlag = false;
Class cls = loader.loadClass("com.thread.lock."+sbfArr[i]);
Method[] methods = cls.getMethods();
String templateName = sbfArr[i] +"(";
for (Method method : methods) {
if("action".equals(method.getName())) {
existFlag = true;
int argsCount = method.getGenericParameterTypes().length;
for (int n = 1; n < argsCount; n++) {
templateName += "${" + n + "},";
}
if (argsCount > 1) {
templateName = templateName.substring(0, templateName.length() - 1);
}
templateName += ")";
prest.setString(1, sbfArr[i]);;
prest.setString(2, templateName);
prest.setString(3, "1");
prest.setString(4, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
prest.setString(5, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
prest.addBatch();
}
}
if(!existFlag) {
classMap.remove(sbfArr[i]);
}
cls = null;
templateName = "";
}
else {
funcNamesStr.replace(","+sbfArr[i]+",", ",");
}
}
loader = null;
prest.executeBatch();
if(funcNamesStr.length() > 1) {//jar中不存在,数据库中存在的,批量删除
String[] noExistArr = funcNamesStr.substring(1, funcNamesStr.length() - 1).split(",");
for (int i = 0; i < noExistArr.length; i++) {
prest2.setString(1, noExistArr[i]);
prest2.addBatch();
}
prest2.executeBatch();
}
conn.commit();
conn.close();
Set set = classMap.keySet();
for (Object object : set) {
System.out.print(object + " : ");
System.out.println(classMap.get(object));
}
}
}
} catch (Exception e) {
ARE.getLog().error("初始化储存jarDetails信息出错!", e);
} finally {
if(null != conn) {
try {
conn.close();
} catch (SQLException e) {
ARE.getLog().error("数据库连接关闭失败", e);
}
}
}
说明
MyURLClassLoader类主要是实现了jar文件的加载和卸载(由于原有的URLClassLoader类对jar文件的卸载功能不支持,所以需要早我实现一个MyURLClassLoader类).在使用时,通过一个管理类CustonFunctionJarManager来使用,而管理类通过工厂获得,并保证全局唯一,以控制线程安全.使用情况是在项目的启动时,需要到指定位置读取指定jar包,并解析jar包中在某一包下的所以类的类名,同时存储对于的class文件的MD5值在全局.之后从数据库中查出所以在数据库中的类名.将在jar中存在的,数据库中不存在的进行批量新增,将数据库中存在的,jar中不存在的,进行批量删除.之后在对jar包的上传更新时,可以从全局中取到jar包的MD5值,来判断是否需要更新,而表达式class文件的MD5值的作用是,在jar需要更新时,判断数据库中对应的表达式名是否需要更新(表达式名即为jar包中固定包下的类名).