经过晚上和早上的努力,终于补上框架最后一块了,业务脚本侦听变化后自动编译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 {
/// <summary>
/// 缓存路径和类型,允许多线程读一个线程写
/// </summary>
private static ConcurrentHashMap<String, Boolean> Building = new ConcurrentHashMap<>();
/// <summary>
/// 编译指定路径的java代码
/// </summary>
/// <param name="basePath">根路径</param>
/// <param name="codepath">类代码路径</param>
/// <param name="proname">工程名称,不带后缀的</param>
/// <param name="standprofilepath">标准工程文件全路径</param>
/// <returns>返回空成功,非空就是失败原因</returns>
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<String> 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<arr.length-1;p++)
{
jarRootPath=Paths.get(jarRootPath,arr[p]).toString();
//创建jar包目录结构
File directoryProJarChild = new File(jarRootPath);
//没有编译的临时目录就创建
if (!directoryProJarChild.exists()) {
directoryProJarChild.mkdirs();
}
if(pakName=="")
{
pakName=arr[p];
}
else
{
pakName+="."+arr[p];
}
}
}
sbNewCode.append("package "+pakName+";"+System.lineSeparator());
for (String line : lines) {
sbNewCode.append(line+System.lineSeparator());
}
WriteText2File(clsFullName,sbNewCode.toString());
//复制类代码
//copyFile(new File(codepath), new File(clsFullName));
String cmmand = "javac";
StringBuilder retsb = new StringBuilder();
//得到javac编译命令串
String cmdStr=GetJavacStr(basePath,standprofilepath,fi.getName());
System.out.println("编译命令:" + cmdStr);
System.out.println("运行路径:" + directoryPro);
// 创建进程并执行命令
Process process = Runtime.getRuntime().exec(cmdStr,null,directoryPro);
// 获取命令行程序的输出结果
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
retsb.append(line);
}
// 等待命令行程序执行完毕
int exitCode=process.waitFor();
// 关闭资源
reader.close();
System.out.println("编译返回:" + exitCode);
//编译完成
if (exitCode == 0)
{
// 获取目录中的所有文件和子目录
File[] files = directoryPro.listFiles();
// 遍历文件和子目录
for (File file : files) {
if (!file.isDirectory()) {
if (file.getName().endsWith(".class")) {
copyFile(new File(file.toString()),new File(Paths.get(jarRootPath,file.getName()).toString()));
}
}
}
String pakStr="jar cf "+proname+".jar"+" "+arr[0];
System.out.println("打包命令:" + pakStr);
System.out.println("打包运行路径:" + directoryPro);
//打包jar包
Process processPak = Runtime.getRuntime().exec(pakStr,null,directoryPro);
// 等待命令行程序执行完毕
int exitCodePak=processPak.waitFor();
System.out.println("打包返回:" + exitCodePak);
if (exitCodePak == 0)
{
String jarOutPath=Paths.get(proBuildPath,proname+".jar").toString();
String jarBinPath=Paths.get(basePath,"BinAshx",proname+".jar").toString();
System.out.println("从:" + jarOutPath+"拷贝到:"+jarBinPath);
//拷贝生成的jar包到BinAshx
copyFile(new File(jarOutPath), new File(jarBinPath));
}
//删除源文件
//DeleteFile(new File(proBuildPath));
return "";
}
String retstr = retsb.toString();
return retstr;
}
catch (Exception ex)
{
return ex.getMessage();
}
finally
{
if (Building.containsKey(codepath))
{
Building.remove(codepath);
}
}
}
/// <summary>
/// 解决多线程写并发
/// </summary>
/// <param name="hs"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void HashTryAdd(ConcurrentHashMap<String, Boolean> 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 {
/// <summary>
/// 缓存路径和类型,允许多线程读一个线程写
/// </summary>
private static ConcurrentHashMap<String, Class> 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+"的处理类");
}
}
/// <summary>
/// 通过配置得当对象
/// </summary>
/// <param name="confStr">配置UI/login/ashx/AshDemo</param>
/// <param name="writer">输出</param>
/// <param name="basePath">Web根</param>
/// <param name="isBuild">是否是编译</param>
/// <returns></returns>
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打包、反射重复加载类、文件读写等。