巧用 ServletOutputStream 制作代码生成器

代码生成器(CodeGenerators)能够极大地提高工作效率。我们知道,模板技术推崇一种模式:输出=模板+数据。代码生成器的原理也可以同样如是,这里不妨借鉴 JSP 生成的思路,页面 HTML 换成 Java 语句,成为固定不变的内容,即是“模板”;数据就是动态获取的内容,即实体信息和数据库的表字段等信息。生成过程和标准 Servlet 的 MVC 模式没什么不同,只是自定义其中的 ServletOutputStream 对象,故本文立论于此。

生成的代码有 Bean、DAO、Service、Controller,皆是围绕数据库里面的某一个实体。即使是注释,也是读取数据库表上每个字段的注释。而且为简单起见,假设全部 Bean 字段与数据库的一致。

获取数据库表的信息

第一个任务是获取数据库表的信息,一般 MySQL 数据库提供了丰富的 SQL 命令返回各种表信息,例如:

  • 获取当前数据库下的所有表名称
  • 获得表的注释
  • 获取表的各个字段的名称、类型、是否允许为空和注释

以上逻辑封装在 DataBaseStruController 类中。调用者为 CodeGenerators 的 doGet() 方法,如下所示。

@POST
public String doGet(MvcRequest request, HttpServletResponse response) throws FileNotFoundException {
	request.setAttribute("packageName", request.getParameter("packageName"));

	Connection conn = JdbcConnection.getMySqlConnection(request.getParameter("dbUrl"),
			request.getParameter("dbUser", "root"), request.getParameter("dbPassword"));

	if (request.getParameter("getTable") != null) {
		Info info = new Info(request.getParameter("getTable"), request.getParameter("saveFolder", "C:\\temp"));

		if (request.getParameter("beanName") != null)
			info.setBeanName(request.getParameter("beanName"));

		render1(info, DataBaseStruController.getColumnComment(conn, info.getTableName()),
				DataBaseStruController.getTableComment(conn, info.getTableName()), request, response);
	} else {
		List<String> tables = DataBaseStruController.getAllTableName(conn);
		Map<String, String> tablesComment = DataBaseStruController.getTableComment(conn, tables);
		Map<String, List<Map<String, String>>> infos = DataBaseStruController.getColumnComment(conn,
				tables);

		for (String tableName : tables) {
			Info info = new Info(tableName, request.getParameter("saveFolder", "C:\\temp"));

			render1(info, infos.get(tableName), tablesComment.get(tableName), request, response);
		}
	}

	try {
		conn.close();
	} catch (SQLException e) {
		LOGGER.warning(e);
	}

	return "html::Done!<a href=\"" + request.getContextPath() + zipSave + "\" download>download</a>";
}

再进一步准备好数据。

private static void pareperRender(Info info, List<Map<String, String>> fields, String tableComment, MvcRequest request,
		HttpServletResponse response) {
	request.setAttribute("fields", fields);
	request.setAttribute("tableName", info.getTableName());
	request.setAttribute("beanName", info.getBeanName());
	request.setAttribute("tablesComment", tableComment);
	request.setAttribute("tablesCommentShortName", getName(tableComment));

	// 是否生成 model
	if (!request.hasParameter("isMap"))
		render(info.setType("pojo"), request, response);

	render(info.setType("dao"), request, response);
	render(info.setType("service"), request, response);
	render(info.setType("serviceImpl"), request, response);
	render(info.setType("controller"), request, response);
}

结合模板输出内容

有了这些数据后,第二个任务就是结合模板输出内容。一个典型的 Bean 模板如下(pojo.jsp)。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" import="java.util.*, com.ajaxjs.util.ReflectUtil"%><%
	String beanName = request.getAttribute("beanName").toString();
	List<Map<String, String>> fields = (List<Map<String, String>>)request.getAttribute("fields");
%><%!
	public static String getName(String sqlType) {
		String[] arr = sqlType.split(",|,|\\.|。");

		return arr[0];
	}

	public static String toJavaType(String sqlType) {
		String t = "void";

		if (sqlType.indexOf("varchar") != -1 || sqlType.indexOf("char") != -1 || sqlType.indexOf("text") != -1)
			t = "String";
		else if (sqlType.indexOf("datetime") != -1)
			t = "java.util.Date";
		else if (sqlType.indexOf("bigint") != -1)
			t = "Long";
		else if (sqlType.indexOf("int") != -1 || sqlType.indexOf("date") != -1)
			t = "Integer";
		else if (sqlType.indexOf("float") != -1)
			t = "Float";
		else if (sqlType.indexOf("double") != -1)
			t = "Double";
		else if (sqlType.indexOf("decimal") != -1)
			t = "java.math.BigDecimal";

		return t;
	}
%>package ${packageName}.model;

import com.ajaxjs.framework.BaseModel;

/**
 * ${tablesComment}
 */
public class <%=ReflectUtil.firstLetterUpper(beanName)%> extends BaseModel  {
	private static final long serialVersionUID = 1L;
	<%
		for (Map<String, String> i : fields) {
			String name = i.get("name");
			if("content".equals(name) || "name".equals(name) || "createDate".equals(name) || "stat".equals(name) 
					|| "updateDate".equals(name) || "id".equals(name) || "uid".equals(name))
				continue;
			
			request.setAttribute("info", i);
			request.setAttribute("name", getName(i.get("comment").toString()));
			request.setAttribute("type", toJavaType(i.get("type").toString()));
			request.setAttribute("firstLetterUpperName", ReflectUtil.firstLetterUpper(i.get("name").toString()));
				
	%>
	/**
	 * ${name}
	 */
	private ${type}${' '}${info.name};
	
	/**
	 * 设置${name}
	 
	 * @param ${info.name}  
	 */
	public void set${firstLetterUpperName}(${type}${' '}${info.name}) {
		this.${info.name} = ${info.name};
	}
	
	/**
	 * 获取${name}
	 
	 * @return ${name}
	 */	
	public ${type} get${firstLetterUpperName}() {
		return ${info.name};
	}
	<%
		}
	%>
}

用户可根据自己的需求修改这些模板。

接下来就是真正的渲染模板,即生成 Java 文件,在 render() 函数中完成。

/**
 * 替换为实际内容
 * 
 * @param info      请求页面地址,如 /sqlDoc.jsp  保存地址,如 c:\\sp42\\d.htm
 * @param request
 * @param response
 */
private static void render(Info info, HttpServletRequest request, HttpServletResponse response) {
//		MvcRequest r = new MvcRequest(request);
//		ZipHelper.toZip(saveFolder, r.mappath(zipSave));

	File save = new File(info.getSaveTarget());
	mkdir(save);
	RequestDispatcher rd = request.getServletContext().getRequestDispatcher(info.getJsp());

	try (ByteArrayServletOutputStream stream = new ByteArrayServletOutputStream();
			PrintWriter pw = new PrintWriter(new OutputStreamWriter(stream.getOut(), "UTF-8"));
			OutputStream out = new FileOutputStream(save);) {
		HttpServletResponse rep = new HttpServletResponseWrapper(response) {
			@Override
			public ServletOutputStream getOutputStream() {
				return stream;
			}

			@Override
			public PrintWriter getWriter() {
				return pw;
			}
		};

		rd.include(request, rep);
		pw.flush();

		stream.writeTo(out);
	} catch (IOException | ServletException e) {
		LOGGER.warning(e);
	}
}

注意 ByteArrayServletOutputStream 类是重点,它覆盖了原 ServletOutputStream 的某些方法。详见源码。

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;

import com.ajaxjs.util.logger.LogHelper;

/**
 * 自定义响应对象的输出流
 * 
 * @author sp42 frank@ajaxjs.com
 *
 */
public class ByteArrayServletOutputStream extends ServletOutputStream {
	private static final LogHelper LOGGER = LogHelper.getLog(ByteArrayServletOutputStream.class);

	/**
	 * 输出流
	 */
	private OutputStream out = new ByteArrayOutputStream();

	/**
	 * 创建一个 ByteArrayServletOutputStream 对象
	 */
	public ByteArrayServletOutputStream() {
	}

	/**
	 * 
	 * 创建一个 ByteArrayServletOutputStream 对象
	 * 
	 * @param os 输出流
	 */
	public ByteArrayServletOutputStream(ByteArrayOutputStream os) {
		this.out = os;
	}

	@Override
	public void write(byte[] data, int offset, int length) {
		try {
			out.write(data, offset, length);
		} catch (IOException e) {
			LOGGER.warning(e);
		}
	}

	@Override
	public void write(int b) throws IOException {
		out.write(b);
	}

	/**
	 * 
	 * @param _out
	 */
	public void writeTo(OutputStream _out) {
		ByteArrayOutputStream bos = (ByteArrayOutputStream) out;

		try {
			bos.writeTo(_out);
		} catch (IOException e) {
			LOGGER.warning(e);
		}
	}

	public OutputStream getOut() {
		return out;
	}

	@Override
	public boolean isReady() {
		return false;
	}

	@Override
	public String toString() {
		return out.toString();
	}

	@Override
	public void setWriteListener(WriteListener writeListener) {
	}
}

这样的话就不会像 JSP 输出到浏览器,而是保存在磁盘上成为 Java 文件。

成品

我们封装这功能在我们的 AJAXJS 框架上,界面成品如下图所示。
在这里插入图片描述

已标记关键词 清除标记
最近做即时通讯,要在openfire上开发插件保存离线文件。我的思路是客户端登陆后向服务器提交GET请求,然后服务器通过OutputStream将文件输出。由于可能有多个离线文件,所以我打算循环输出多个outputstream。但是客户端得到的只是第一个OutputStream。请问怎么解决? 服务器端代码: protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { System.out.println("this is uploadservlet"); if(req.getParameter("state").equals("online")&&req.getParameter("userTo")!=null){ List<OfflineFile> filelist = dboperator.getOfflineFile(req.getParameter("userTo")); Iterator<OfflineFile> iterator = filelist.iterator(); OfflineFile offlinefile; while(iterator.hasNext()) { offlinefile = iterator.next(); File dfile = new File(offlinefile.getFilepath()); //得到输入流 FileInputStream in = new FileInputStream(dfile); //获取文件名字,并对其进行编码,如果不这样会出现中文乱码。 String filename = dfile.getName(); filename = new String(filename.getBytes(),"iso8859-1"); //文件的发送者 String userFrom = new String(offlinefile.getUserFrom().getBytes(),"iso8859-1"); System.out.println(userFrom); //通过response对象获得输出流 OutputStream out = response.getOutputStream(); // response.setContentType("Application/Octet-stream;charset=utf-8"); // 下载文件的名字和发送者 response.addHeader("Content-Disposition", "attachment; filename="+filename); response.addHeader("userfrom", userFrom); response.setContentLength((int) dfile.length()); byte[] bs =new byte[1024]; int len = 0; while((len =in.read(bs))!=-1){ out.write(bs); } // 最后是流的关闭。 out.close(); in.close(); } } } ``` ``` 客户端代码: `private static List<InputStream> islist; public static InputStream getInputstream() { islist = new ArrayList<InputStream>(); InputStream inputstream=null; HttpURLConnection urlconnection=null; try { String urlpath="http://chen-pc:9090/plugins/upload/upload?state=online&userTo=hehe@chen-pc"; String urlcn=URLEncoder.encode("刘德华_冰雨.mp3","UTF-8"); System.out.println(urlpath+urlcn); URL url=new URL(urlpath); urlconnection=(HttpURLConnection)url.openConnection();//返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。 urlconnection.setConnectTimeout(6000);//设置超时时间 urlconnection.setDoInput(true); urlconnection.setRequestMethod("GET");//设置GET请求方式 System.out.println(new String(urlconnection.getHeaderField("Content-Disposition").getBytes("iso8859-1"),"gb2312")); System.out.println(new String(urlconnection.getHeaderField("userfrom").getBytes("iso8859-1"),"gb2312")); int responsecode=urlconnection.getResponseCode(); if(responsecode==200) { inputstream=urlconnection.getInputStream();//获得输入流 islist.add(inputstream); while(inputstream!=null) { inputstream=urlconnection.getInputStream();//获得输入流 if(inputstream!=null){ islist.add(inputstream); } } } } catch (IOException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } return islist.get(1); }`` ```
©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页