跟朋友聊天,无意中聊起了敏捷开发,扯到了[i]约定优于配置[/i]原则,我想到我最近做的一个电影下载网站,没用任何框架,纯servlet+jsp实现 。因为是个个人网站,用的tomcat,一切都用约定来代替配置,下面让我说说我的思路。
我想下面的代码大家一定都写腻了,再写都想吐了:
ArticleAddServlet
xxx.xxx.ArticleAddServlet
ArticleAddServlet
/article_add.do
/messages.jsp
其实我们仔细观察,这个很有规律可循,完全可以依靠约定优于配置实现:
只是将 /action_name/method_name.do 映射到 xxx.xxx.ActionName 的 methodName 方法而已
完全可以依靠反射实现,我们只需要配置一个Controller servlet,然后类似如下代码:
...
//取得请求的路径,获取要调用的action类和方法
String contextPath = req.getContextPath();
String requestURI = req.getRequestURI();
String actionStr = requestURI.substring(contextPath.length()+"/".length(),requestURI.length()-".do".length());
String[] actions = actionStr.split("/");
String objName = actions[0];
objName = "xxx.xxx." + objName.substring(0, 1).toUpperCase() + objName.substring(1) + "Action";
String methodName = actions[1];
Class objCls = Class.forName(objName);
Method method = objCls.getMethod(methodName, Class.forName("javax.servlet.http.HttpServletRequest"),
Class.forName("javax.servlet.http.HttpServletResponse"));
//通过java反射调用action方法
Object obj = objCls.newInstance();
method.invoke(obj, new Object[]{req,res});
...
这样我们就可以很轻松地利用约定简略了繁复枯燥的配置文件。
然后,我们再来看看如何处理请求参数
我再一个工具类中实现了这样一个绑定请求参数到bean的方法 bind(HttpServletRequest req, Object bean, String[] paramNames, ...){},以下是方法体的代码片段:
...
for (String paramName : paramNames) {
//通过反射取得set方法
Class beanCls = bean.getClass();
Field field = beanCls.getDeclaredField(paramName);
Class fieldCls = field.getType();
String setMethodName = "set" + paramName.substring(0, 1).toUpperCase() + paramName.substring(1);
Method method = beanCls.getDeclaredMethod(setMethodName, fieldCls);
//转换字符串类型的请求参数为Java对象,执行set方法绑定到bean
String value = req.getParameter(paramName);
if (fieldCls.equals(String.class)) {
method.invoke(bean, new Object[]{value});
} else {
Object convertValue = convertController.convert(value, fieldCls);
method.invoke(bean, new Object[]{convertValue});
}
//使用bean validation来验证参数合法性
Locale locale = req.getLocale();
Validator validator = getValidator(locale);
Set> constraintViolations;
constraintViolations = validator.validateProperty(bean, paramName);
}
...
//具体实现复杂一些,比如绑定到集合,绑定上传的文件对象。
//在以上方法体中我们可以将参数的转换、绑定、验证消息写到消息对象
然后我们在action的方法中这样调用就可以了
paramUtil.bind(req, bean, new String[]{"paramName1", "paramName2","..."}, ...);
bind方法建议返回一个代表参数是否完全合法的boolean值,由action方法决定后续操作。
然后执行业务逻辑。
这里再说一下jdbc的查询参数和结果集的绑定。其实apache有工具类可直接用,这里我贴上我的简陋实现,抛砖引玉吧,
绑定结果集我这里用了几个静态方法,贴上核心的:
//将结果集绑定到bean,这里需要约定查询字段和bean的字段名一致
static public void bind0(ResultSet rs, Object o) throws SQLException, IllegalAccessException, InvocationTargetException {
ResultSetMetaData rsmd = rs.getMetaData();
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
String name = rsmd.getColumnLabel(i);
Object value = rs.getObject(name);
BeanUtils.setProperty(o, name, value);
}
}
绑定命名参数我使用了一个类
public class PreparedStatementWarper {
private PreparedStatement stmt;
Map params;
public PreparedStatementWarper(Connection conn, String namedParamSql) throws SQLException{
params = new HashMap<>();
StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile(":\\w+");
Matcher m = p.matcher(namedParamSql);
int i = 1;
while (m.find()) {
String paramName = m.group().substring(1);
params.put(paramName, i);
m.appendReplacement(sb, "?");
i++;
}
m.appendTail(sb);
stmt = conn.prepareStatement(sb.toString());
}
public void bind(Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException{
for(String paramName : params.keySet()){
Object paramValue = PropertyUtils.getProperty(bean, paramName);
getStmt().setObject(params.get(paramName), paramValue);
}
}
public PreparedStatement getStmt() {
return stmt;
}
}
//调用代码片段
String sql = "insert into `article` (`xx1`,`xx2`,`xx3`,`xx4`) values(:xx1,:xx2,:xx3,:xx4);";
PreparedStatementWarper stmtWarper = new PreparedStatementWarper(conn, sql);
stmtWarper.bind(movie);
stmtWarper.getStmt().executeUpdate();
到这里,该结束了,最后,action方法可以返回结果字符串交回Controller跳转,当然,还是建议约定优于配置。
到这里,我贴上使用以上我的方法构建的完整网站例子,一个电影在线观看和下载的网站:
[url=http://hdgang.com/]海盗港电影[/url]