经过晚上和早上的努力,终于补上框架最后一块了,业务脚本侦听变化后自动编译jar包和调用,实现维护成本低,开发效率高的框架的基本体系。
实现自动编译jar包的类
package appcode; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.nio.file.Paths; import java.util.concurrent.ConcurrentHashMap; import java.util.*; import java.io.*; import org.apache.commons.io.FileUtils; import java.nio.charset.StandardCharsets; import java.nio.charset.*; public class AutoBuild { ///
/// 缓存路径和类型,允许多线程读一个线程写 /// private static ConcurrentHashMap
Building = new ConcurrentHashMap<>(); /// /// 编译指定路径的java代码 /// ///根路径///类代码路径///工程名称,不带后缀的///标准工程文件全路径///返回空成功,非空就是失败原因 public static String Build(String basePath,String codepath, String proname, String standprofilepath) { Process proc = null; try { //有编译的退出 if (Building.containsKey(codepath)) { System.out.println(codepath+"已经有进程在编译了,退出本次编译!"); return""; } try { if (!Building.containsKey(codepath)) { AutoBuild.HashTryAdd(Building, codepath, true); } } catch (Exception ex) { System.out.println("编译并发"+ex.getMessage()); } //编译临时总目录 String buildPath = Paths.get(basePath,"AutoBuildTmp").toString(); File directory = new File(buildPath); //没有编译的临时目录就创建 if (!directory.exists()) { directory.mkdirs(); } //构建项目编译文件夹 String proBuildPath = Paths.get(buildPath, proname).toString(); File directoryPro = new File(proBuildPath); //没有编译的临时目录就创建 if (!directoryPro.exists()) { directoryPro.mkdirs(); } File fi = new File(codepath); //编译目录类全名 String clsFullName = Paths.get(proBuildPath, fi.getName()).toString(); System.out.println("拷贝:"+ codepath +"到:"+ clsFullName); List lines = FileUtils.readLines(fi,"UTF-8"); StringBuilder sbNewCode=new StringBuilder(); String jarRootPath=proBuildPath; System.out.println("包名称:"+ proname); String [] arr=proname.split("\\."); String pakName=""; if(arr.length>1) { for(int p=0;p /// 解决多线程写并发 /// ///////// public static void HashTryAdd(ConcurrentHashMap hs, String key, Boolean value) { try { //更新类型 if (!hs.containsKey(key)) { hs.put(key, value); } } catch (Exception ex) { System.out.println("并发编译捕获的正常日志,忽略"+ex.getMessage()); //更新类型 if (!hs.containsKey(key)) { hs.put(key, value); } } } ///拷贝文件 public static void copyFile(File srcFile, File destFile) { //判断原文件是否存在 if (!srcFile.exists()) { throw new IllegalArgumentException("源文件:"+ srcFile +"不存在!"); } //判断原文件是否为一个文件 if (!srcFile.isFile()) { throw new IllegalArgumentException(srcFile +"不是一个文件!"); } try { FileInputStream in = new FileInputStream(srcFile); FileOutputStream out = new FileOutputStream(destFile); /** * 读取原文件,以字节流的形式读到byte数组,1024个字节=1KB * 循环读取 * in.read()读到bytes数组中,位置从0-bytes.length */ byte[] bytes = new byte[10 * 1024]; int b; //b为读取到的字节长度 while ((b = in.read(bytes, 0, bytes.length)) != -1) { //写入 out.write(bytes, 0, b); out.flush(); } //关闭 in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } ///删除文件和目录 public static void DeleteFile(File file){ //判断文件不为null或文件目录存在 if (file == null || !file.exists()){ return; } //取得这个目录下的所有子文件对象 File[] files = file.listFiles(); //遍历该目录下的文件对象 for (File f: files){ //判断子目录是否存在子目录,如果是文件则删除 if (f.isDirectory()){ DeleteFile(f); }else { f.delete(); } } } ///得到javac编译命令 ///basePath:根地址 ///standprofilepath:依赖jar包工程地址 ///javaName:java类文件名称 private static String GetJavacStr(String basePath,String standprofilepath,String javaName) { String retStr="javac -encoding UTF-8 -classpath"; try { //判断配置是否存在 File file = new File(standprofilepath); if (!file.exists()) { System.out.println(standprofilepath +"文件不存在,请确认!"); return""; } //解析xml DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(file); // 获得根节点 Element rootElement = document.getDocumentElement(); // 获得根节点下的所有子节点 NodeList students = rootElement.getChildNodes(); String classPath=""; for (int i = 0; i < students.getLength(); i++) { // 由于节点多种类型,而一般我们需要处理的是元素节点 Node childNode = students.item(i); // 元素节点就是非空的子节点,也就是还有孩子的子节点 if (childNode.getNodeType() == Node.ELEMENT_NODE) { Element childElement = (Element) childNode; //不是对象配置元素就忽略 if (childElement.getNodeName() !="orderEntry") { continue; } //解析得到包名 String name = childElement.getAttribute("name"); String oneJarPath=Paths.get(basePath,name+".jar").toString(); if(classPath=="") { classPath=oneJarPath; } else { classPath+=";"+oneJarPath; } } } retStr+=classPath+""+javaName; return retStr; } catch (Exception ex) { System.out.println(standprofilepath + ex.getMessage()); ex.printStackTrace(); } return""; } /** * 将文本写入文件 * * @param filePath 文件全路径 * @param text 文本 **/ public static void WriteText2File(String filePath, String text) { // 创建文件 File file = new File(filePath); if (!file.exists()) { try { // 创建文件父级目录 File parentFile = file.getParentFile(); if (!parentFile.exists()) { parentFile.mkdirs(); } // 创建文件 file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } // 将文本写入文件 WriteText2File(file, text); } /** * 将文本写入文件 * * @param file 文件对象 * @param text 文本 **/ public static void WriteText2File(File file, String text) { BufferedWriter writer = null; try { FileOutputStream writerStream = new FileOutputStream(file); writer = new BufferedWriter(new OutputStreamWriter(writerStream,"UTF-8")); writer.write(text); writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 然后实现目录Java监控调用编译
import java.io.*; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationObserver; //监控文件 public class FileListener extends FileAlterationListenerAdaptor { //启动 @Override public void onStart(FileAlterationObserver observer) { super.onStart(observer); } //新建目录 @Override public void onDirectoryCreate(File directory) { } //目录修改 @Override public void onDirectoryChange(File directory) { } //目录删除 @Override public void onDirectoryDelete(File directory) { } //创建文件 @Override public void onFileCreate(File file) { String compressedPath = file.getAbsolutePath(); if(compressedPath.contains("AutoBuildTmp")) { return; } if (file.canRead()) { String confStr = compressedPath.replace(MainInit.BllJavaBasePath,"").replace("\\","/").split("\\.")[0]; MainMiddleware.GetObjectByConfString(confStr,null,"",compressedPath); } } //修改文件 @Override public void onFileChange(File file) { String compressedPath = file.getAbsolutePath(); if(compressedPath.contains("AutoBuildTmp")) { return; } String confStr = compressedPath.replace(MainInit.BllJavaBasePath,"").replace("\\","/").split("\\.")[0]; MainMiddleware.GetObjectByConfString(confStr,null,"",compressedPath); } //删除文件 @Override public void onFileDelete(File file) { } //停止 @Override public void onStop(FileAlterationObserver observer) { super.onStop(observer); } } 文件侦听器
import org.apache.commons.io.monitor.FileAlterationListener; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.HiddenFileFilter; import java.io.File; //监控文件 public class FileMonitor { //监视器 private FileAlterationMonitor monitor; //构造函数 public FileMonitor(long interval) { monitor = new FileAlterationMonitor(interval); } /** * 给文件添加监听 * * @param path 文件路径 * @param listener 文件监听器 */ public void monitor(String path, FileAlterationListener listener) { IOFileFilter directories = FileFilterUtils.and( FileFilterUtils.directoryFileFilter(), HiddenFileFilter.VISIBLE); IOFileFilter files = FileFilterUtils.and( FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter(".java")); IOFileFilter filter = FileFilterUtils.or(directories, files); FileAlterationObserver observer = new FileAlterationObserver(new File(path),filter); monitor.addObserver(observer); observer.addListener(listener); } //停止 public void stop() throws Exception { monitor.stop(); } //启动 public void start() throws Exception { monitor.start(); } } 主初始化实现java脚本监控
import java.nio.file.Path; import java.nio.file.Paths; import java.io.IOException; import java.nio.file.*; import java.io.*; public class MainInit { //是否已经执行了初始化 private static boolean HasInit=false; //网站根地址 private static String webBasePath=""; //业务脚本根地址 public static String BllJavaBasePath=""; //执行初始化 public static void TryInit(String basePath) { //只初始化一次 if (HasInit == true) { return; } HasInit = true; webBasePath = basePath; File fiBase=new File(basePath); String parentPath=fiBase.getParent(); File fiParent=new File(parentPath); String codeBasePath=basePath; //开发环境 if(fiParent.getName().equals("artifacts")) { //到out一级 File fiParent1=new File(fiParent.getParent()); //到WebUI一级 File fiParent2=new File(fiParent1.getParent()); codeBasePath=Paths.get(fiParent2.toString(),"web").toString()+File.separator; } //用容器的配置xml初始化容器 LIS.Core.Context.ObjectContainer.InitIoc(basePath); try { BllJavaBasePath=codeBasePath; System.out.println("监控目录:"+codeBasePath); FileMonitor fileMonitor = new FileMonitor(5000); fileMonitor.monitor(codeBasePath, new FileListener()); fileMonitor.start(); } catch (Exception ex) { ex.printStackTrace(); } } } 主中间件调整
import appcode.IBaseHttpHandler; import java.io.*; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.concurrent.ConcurrentHashMap; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @javax.servlet.annotation.WebServlet(name ="MianMiddleware") public class MainMiddleware extends javax.servlet.http.HttpServlet { /// /// 缓存路径和类型,允许多线程读一个线程写 /// private static ConcurrentHashMap hsType = new ConcurrentHashMap<>(); ///网站根地址 public static String WebBasePath=""; ///执行post请求 @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //得到网站根路径 if(WebBasePath=="") { WebBasePath= getServletContext().getRealPath("/"); } //尝试执行初始化主逻辑 MainInit.TryInit(WebBasePath); response.setContentType("text/html"); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); String url=request.getRequestURI(); //解析得到类名 String className = url.split("\\.")[0]; PrintWriter writer = response.getWriter(); //取第一部分 if (className.charAt(0) == '/') { className = className.substring(1); } int index=className.indexOf("/"); className=className.substring(index+1); //反射得到类型 Object objDeal = GetObjectByConfString(className,writer,WebBasePath,""); //转换处理接口 if(objDeal!=null) { //转换成接口 appcode.IBaseHttpHandler baseDeal=(appcode.IBaseHttpHandler)objDeal; baseDeal.ProcessRequest(request,response); } else { Write(writer,"未找到名称为:"+className+"的处理类"); } } /// /// 通过配置得当对象 /// ///配置UI/login/ashx/AshDemo///输出///Web根///是否是编译/// public static Object GetObjectByConfString(String confStr,PrintWriter writer,String basePath,String javaBllClsPath) { try { //根 if(basePath=="") { basePath=WebBasePath; } System.out.println("confStr:"+confStr); //不包含类型或者要强行编译就进行编译 if (!hsType.containsKey(confStr)||javaBllClsPath!="") { String [] nameArr=confStr.split("/"); String classFullName =""; //类代码全路径 String classCodePath = basePath; for (int i = 0; i < nameArr.length; i++) { //类代码文件全名 classCodePath = Paths.get(classCodePath, nameArr[i]).toString(); //类带命名空间的全名 if(classFullName!="") { classFullName +="."+ nameArr[i]; } else { classFullName = nameArr[i]; } } //类代码地址,后面实现用脚本编译用 classCodePath = classCodePath +".java"; if(javaBllClsPath!="") { classCodePath=javaBllClsPath; } String standardPath=Paths.get(basePath,"Conf","StandAshxProj.iml").toString(); //编译返回 String buildRet=appcode.AutoBuild.Build(basePath,classCodePath,classFullName,standardPath); System.out.println("buildRet:"+buildRet); //编译的jar名字 String clsJarPath = Paths.get(basePath,"BinAshx", classFullName +".jar").toString(); System.out.println("加载jar包:"+clsJarPath); //自己生成jar包路径 URL url = new File(clsJarPath).toURI().toURL(); URL[] urls = new URL[]{url}; //加载程序集,这里很重要,一定要指定父加载器,否则加载的类和父加载器的类不认为是一个类 URLClassLoader loader = new URLClassLoader(urls, MainMiddleware.class.getClassLoader()); //加载类 Class c = loader.loadClass(classFullName); //先写死,后面执行编译和从jar包反射 hsType.put(confStr, c); } Class c = hsType.get(confStr); //创建对象 Object o = c.newInstance(); return o; } catch (Exception ex) { ex.printStackTrace(); } return null; } //get直接走post的逻辑 @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } ///输出数据到前台 private static void Write(PrintWriter writer,String str) { writer.println(str); writer.flush(); writer.close(); } } 自动编译的临时目录结构
自动生成的jar包
在idea里面改业务脚本代码后用浏览器访问就能立即生效,简单高效,适用于服务型系统。整个涉及到目录文件监控、驱动javac和jar打包、反射重复加载类、文件读写等。