巧用 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 [email protected]
 *
 */
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 框架上,界面成品如下图所示。
在这里插入图片描述

扫描二维码关注公众号,回复: 11429723 查看本文章

猜你喜欢

转载自blog.csdn.net/zhangxin09/article/details/105085232