代码生成器(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 查看本文章