1. 首先應該讀取domain對象,從而獲取field列表;
/**
* getDomainFields via jsp name, and should remove the super fields, like lastUpdatedBy, lastUpdatedDate
* @param name of jsp
* @return list of field
*/
public static List<Field> getDomainFields(String name) {
List<Field> ret = new ArrayList<Field>();
try {
Class clz = Class.forName(CodeEngineConfig.getDomainPackage() + "." +
CodeEngineConfig.getControllerDomain(CodeEngineConfig.getJspRef(name)));
for (Field field : clz.getDeclaredFields()) {
if(!"lastUpdateDate".equalsIgnoreCase(field.getName())
&& !"lastUpdateBy".equalsIgnoreCase(field.getName())){
ret.add(field);
// System.out.println("field.getName() + field.getType() = " + (field.getName() + field.getType()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
2. 將Field List傳遞給freemarker模板;之所以要傳遞field而不僅僅是name的原因是,因為希望在模板里根據field的type,從而生成不同的html,比如date可以生成使用js calendar的,boolean生成radio,或checkbox,其他的生成text;
a) 定義macro;
<#macro getHtml field>
<#if field.type?ends_with('Boolean')>
<input type="radio" id="${field.name}" name="${field.name}" class="inp_txt_30" size="80" value="${'$'}{${domain?uncap_first + '.' + field.name}}">
<#elseif field.type?ends_with('Date')>
<input type="text" id="${field.name}" name="${field.name}" readonly="readonly" class="inp_txt_30" size="17" value="<fmt:formatDate value="${'$'}{${domain?uncap_first + '.' + field.name}}" pattern="yyyy-MM-dd hh:mm"/>">
<button id="${field.name}Btn" class="button" >...</button>
<script type="text/javascript">
Calendar.setup(
{
inputField : "${field.name}", // id of the input field
ifFormat : "%Y-%m-%d %H:%M", // the date format
showsTime: true,
button : "${field.name}Btn" // id of the button
}
);
</script>
<#elseif field.type?ends_with('Text')>
<textarea id="${field.name}" name="${field.name}" rows="15" cols="80" style="width: 100%">${'$'}{${domain?uncap_first + '.' + field.name}}</textarea>
<#else>
<input type="text" id="${field.name}" name="${field.name}" class="inp_txt_30" size="80" value="${'$'}{${domain?uncap_first + '.' + field.name}}">
</#if>
</#macro>
b)調用macro生成html;
<#list fields! as field>
<tr>
<td class="txt_tit_s" ><spring:message code="${lbl + '.' + domain?uncap_first + '.' + field.name}"/></td>
<td><@getHtml field=field/></td>
</tr>
</#list>
不光要考慮開發的簡單,還要考慮日后的升級;
甚至足夠充分的文檔資料支持;還有現有團隊的技術能力;以及項目時間等;
MVC:第一要素我個人覺得是要簡單,因為在這個部分的中的代碼量,通常相對后端是很多的;一個容易上手,并且大家都熟悉并且不討厭;
SpringMVC,我個人覺得是很完備的mvc,有著很強的靈活性,但正是這種靈活性,讓很多人無所適從;
Struts 1 標簽很糟糕;form對象很別扭,繁瑣的配置;
Struts2 沒有用過,如果他還有form我就不打算用;
學習springside(以前)使用Spring 的MultiActionController,減少了很多沒有必要的配置;在一個controller里面可以寫多個Action;MultiActionController還可以很靈活的從request中綁定Domain對象,非常的方便;
MultiActionController 加 Controller可以滿足全部的需要;
JSP部分使用spring form tag;
Tiles 和 sitemesh; 考慮到使用Ajax,而sitemesh是利用filter來修飾;選擇Tiles;
ORM用ibatis,當前最實用,簡單的ORM;而且可以自動生成,又容易理解;何樂而不為;
FullTextSearch: compass + lucene;
Others:ActiveMQ + ApacheCXF
Form/Ajax Request > Controller > Manager/Service > GenericDao
其他輔助工具:
EMS for mysql;
SVN as version control;
DB/web server:mysql/resin
上一個項目使用的是spring MVC; 客戶需要做Ajax應用;所以就找了一些資料研究了一下,比如DWR,dojo, prototype,JSON-RPC, trimpath 等等,發現很多不適合我們,比如DWR要生成客戶端js,服務器端還要部署,麻煩;dojo又太慢了;經過一輪淘汰剩下了prototype和trimpath;所以最終就選這2個了;
Prototype在書寫普通的js時候,有很多好處,比如簡單,實用的很多函數;比如$()系列;
Trimpath提供一個客戶端的js模板,如果從服務器回來的數據很復雜,要動態改變Html元素是比較費力的事情;用trimpath就方便許多;
在模板語言的世界里,總有2個東西:模板和模板中的數據;trimpath的模板接受的數據是javascript object,模板則定義在一個不顯示的textarea里面;
所以有個問題就是:怎么讓ajax調用返回一個javascript對象?
后來,我終于發現了(想起了劉若英)JSON;發現json是個好東東;比xml輕量級,又可以很容易的轉換為javascript對象,而且還有java api;唉,開源的世界多美妙;
所以解決方案就是,在springmvc框架中,用response返回json string,給ajax 客戶端,然后生成javascript對象,然后,調用trimpath模板,然后,動態修改頁面。
代碼片段:
public ModelAndView getClient(HttpServletRequest request, HttpServletResponse response) throws Exception {
JSONObject jsonObject = new JSONObject();
Client client=clientMgr.getClientByPk(Long.parseLong(request.getParameter("clientId")));
jsonObject.add("client", client);
return ajaxResponse(jsonObject, response);
}
protected ModelAndView ajaxResponse(JSONObject jsonObject, HttpServletResponse response) throws Exception {
response.setContentType("application/x-json;charset=UTF-8");
response.getWriter().print(jsonObject);
return null;
}
利用xpath,freemarker等技術,使得用戶通過xml配置文件,模板等簡單的方式,快速開發代碼和根據需求調整模板;
Dom4j對實現了xpath 1.0, 非常的好用;
所需jar :dom4j-1.6.1.jar, jaxen-1.1-beta-7.jar
Code:
import junit.framework.TestCase;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import java.util.List;
import java.util.Iterator;
import java.io.File;
/**
* Created by IntelliJ IDEA.
* User: duanbin
* Date: 2007-8-15
* Time: 9:47:17
* To change this template use File | Settings | File Templates.
*/
public class XPathTest extends TestCase {
public void testXPathViaDom4jXpathV1() {
printSelectedNodeValue("D:""xpath""src""test.xml");
}
/**
* 利用XPath操作XML文件,打印指定節點或者屬性的值, using xpath 1.0
*
* @param filename String 待操作的XML文件(相對路徑或者絕對路徑)
*/
public void printSelectedNodeValue(String filename) {
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File(filename));
List list;
list = document.selectNodes("http://qn1:college/@leader[.!='leader1']");
for (Object aList : list) {
Attribute attribute = (Attribute) aList;
System.out.println("http://qn1:college/@leader: " + attribute.getValue());
}
//打印所有student節點的屬性age值,如果有的話
// list = document.selectNodes("/students/student/@age");
// for (Object aList : list) {
// Attribute attribute = (Attribute) aList;
// System.out.println("/students/student/@age:" + attribute.getValue());
// }
//打印所有college節點值,如果有的話
// list = document.selectNodes("/students/student");
// for (Object aList1 : list) {
// Element bookElement = (Element) aList1;
// Iterator iterator = bookElement.elementIterator("college");
// while (iterator.hasNext()) {
// Element titleElement = (Element) iterator.next();
// System.out.println("/students/student/college:" + titleElement.getText());
// }
// }
//測試節點的一些方法
// list = document.selectNodes("http://city");
// for (Object aList2 : list) {
// Element titleElement = (Element) aList2;
// System.out.print("http://telephone:getName:" + titleElement.getName());
// System.out.print(" ##getNodeType:" + titleElement.getNodeType());
// System.out.print(" ##getTextTrim:" + titleElement.getTextTrim());
// System.out.print(" ##getNamespaceURI:" + titleElement.getNamespaceURI());
// System.out.print(" ##getNodeTypeName:" + titleElement.getNodeTypeName());
// System.out.print(" ##getQualifiedName:" + titleElement.getQualifiedName());
// System.out.print(" ##getUniquePath:" + titleElement.getUniquePath());
// System.out.println(" ##getPath:" + titleElement.getPath());
// }
//打印所有name節點值,如果有的話,與上面college的取法不一樣
// list = document.selectNodes("/students/student/name");
// Iterator iter = list.iterator();
// for (Object aList3 : list) {
// Element titleElement = (Element) aList3;
// System.out.println("/students/student/name:" + titleElement.getText());
// }
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Test.xml:
<?xml version="1.0" encoding="UTF-8"?>
<students xmlns:qn1="http://qn1.com">
<student age="259911911911"><!--如果沒有age屬性,默認的為20-->
<qn1:name>崔衛兵</qn1:name>
<college>PC學院</college>
<telephone>62354666</telephone>
<notes>男,1982年生,碩士,現就讀于北京郵電大學</notes>
<addr><city>Beijing</city></addr>
</student>
<student>
<name>cwb</name>
<qn1:college leader="學院領導">PC學院</qn1:college><!--如果沒有leader屬性,默認的為leader-->
<telephone>62358888</telephone>
<notes>男,1987年生,碩士,現就讀于中國農業大學</notes>
</student>
<student>
<name>xxxxx</name>
<college leader="">xxx學院</college>
<telephone>66666666</telephone>
<notes>注視中,注釋中</notes>
</student>
<student age="9911911911">
<name>yyyyyy</name>
<qn1:college leader="leader1">yyyy學院</qn1:college>
<telephone>88888888</telephone>
<notes>注視中111,注釋中222</notes>
</student>
</students>
Freemarker基本示例:
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import java.io.File;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.FileOutputStream;
import java.util.Map;
import java.util.HashMap;
import org.apache.log4j.Logger;
/**
* Created by IntelliJ IDEA.
* User: duanbin
* Date: 2007-8-21
* Time: 22:30:13
* To change this template use File | Settings | File Templates.
*/
public abstract class BaseGenerator {
protected Configuration cfg;
protected final Logger logger = Logger.getLogger(this.getClass());
public BaseGenerator() {
try {
cfg = getConfiguration(CodeEngineConfig.getTemplateDir());
} catch (Exception e) {
e.printStackTrace();
}
}
public abstract void generate();
protected void generateFile(String template, Map root, String fileName){
try {
File dist = new File(fileName);
FileOutputStream fos = new FileOutputStream(dist);
Writer out = new OutputStreamWriter(fos);
// Writer out2 = new OutputStreamWriter(System.out);
cfg.getTemplate(template).process(root, out);
out.flush();
out.close();
logger.info("Generated File: " + fileName);
} catch (Exception e) {
e.printStackTrace();
}
}
protected Configuration getConfiguration(String tempDir) throws Exception {
Configuration cfg = new Configuration();
// Specify the data source where the template files come from.
// Here I set a file directory for it:
cfg.setDirectoryForTemplateLoading(
new File(tempDir));
// Specify how templates will see the data model. This is an advanced topic...
// but just use this:
cfg.setObjectWrapper(new DefaultObjectWrapper());
return cfg;
}
}
模板文件示例:
package ${package}.application.web;
import biz.web.framework.web.BaseController;
<#list managers! as mgr>
import ${package}.application.manager.${mgr};
</#list>
<#list services! as svc>
import ${package}.application.service.${svc};
</#list>
/**
* Created by IntelliJ IDEA.
* User: ${author!'admin'} "${r"${build.dir}"} "${'$'}{cfg.startDate}
* Date: Sep 1, 2006
* Time: 9:19:17 AM
* To change this template use File | Settings | File Templates.
*/
public class ${name}Controller extends BaseController {
<#list managers! as mgr>
protected ${mgr} ${mgr?uncap_first};
</#list>
<#list services! as svc>
protected ${svc} ${svc?uncap_first};
</#list>
<#list managers! as mgr>
public void set${mgr}(${mgr} ${mgr?uncap_first}) {
this.${mgr?uncap_first} = ${mgr?uncap_first};
}
</#list>
<#list services! as svc>
public void set${svc}(${svc} ${svc?uncap_first}) {
this.${svc?uncap_first} = ${svc?uncap_first};
}
</#list>
public ModelAndView list${name}(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mav = new ModelAndView("update_${name?uncap_first}");
Long user = getUserId();
mav.addObject("userId", user);
return mav;
}
}
初始的tomcat 5.0.28是無法使用manager
app的,因為沒有權限,找到/conf/tomcat-users.xml;
打開后編輯如下兩句,保存并啟動tomcat,
請求http://localhost:8080,
使用用戶名和密碼都是tomcat登錄即可;
加入:<role rolename="manager"/>
修改:<user username="tomcat"
password="tomcat" roles="tomcat"/> 為
<user username="tomcat"
password="tomcat"
roles="tomcat,manager"/>
之前一直以為我們搞定了中文,所以一直沒有懷疑我們的配置,今天和立國發現一個問題:
就是用ajax的方式去創建一個記錄,然后用jsp的方式去取數據,出現亂碼;
但是用ajax的方式取此記錄,正常;
后來發現,我們提交數據的方式,不管是ajax的,還是form表單提交,所使用的編碼通通沒有指定!!,雖然我們在頁面上加上了<%@
page contentType="text/html;charset=UTF-8" language="java"
%>,但這一句主要是負責response的數據顯示;
查了資料后才發現需要加上:<%@
page pageEncoding="utf-8"
%>;
而之前我們都沒有指定request的charset,所以按照servlet標準,大多數web
server(Resin,Tomcat)默認按照iso-8859-1來處理,而我們的數據庫是utf-8的,所以放到數據庫中的數據并不是utf-8的;所以用jsp顯示時候出錯;但用ajax的方式為什么沒出錯,還沒有搞明白;
解決方案:
1.ajax方式
#1:prototype.js
[line707]contentType:
'application/x-www-form-urlencoded;charset=UTF-8',
#2:BaseController
[line135] response.setCharacterEncoding("UTF-8");
以上兩行保證了ajax請求和相應的方式一致,并都是utf8;
2.form表單方式;
#3:在jsp加上:<%@ page
pageEncoding="utf-8" %>;
#4:和<%@ page
contentType="text/html;charset=utf-8" language="java"
%>
3.我在web。xml中加了一個encodefilter,保證了當請求中沒有指定charset的時候,使用utf-8方式,所以以上#1和#3處的指定charset是可選的!!
請大家注意更新以上3個文件;prototype.js,BaseController
.java, web.xml
同意以上所述,更新3個文件:prototype.js,
BaseController.java, web.xml ,頂。
我發現直接按照以上的配置resin還有問題,就是無法加載applicationContext_manager.xml;
我費了很大勁才搞明白,是resin用自己的xmlparser所以不識別spring2.0的xml
schema 配置;已經找到解決辦法:
1.將<web-app id="/wiczone"
document-directory="你自己的wiczone war
目錄G:/IdeaProjects/wiczone/trunk/wiczone/war"/> 改為:
<web-app id="/wiczone"
document-directory="D:/wiczone/trunk/wiczone/war">
<!-- xml -->
<system-property
javax.xml.parsers.DocumentBuilderFactory=
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"/>
<system-property javax.xml.parsers.SAXParserFactory=
"org.apache.xerces.jaxp.SAXParserFactoryImpl"/>
<!-- xslt -->
<system-property
javax.xml.transform.TransformerFactory=
"org.apache.xalan.processor.TransformerFactoryImpl"/>
</web-app>;
2.得最新web-inf\lib下的jar包;多加了一個xml解析器;
另外,\trunk\web
server\resin下面有resin服務器,和resin配置文件;
springside的文檔中有aop的配置;http://wiki.springside.org.cn/display/springside/Spring+Aop
里面有關于pointcut 表達式語言的表述;
里面也有官方文檔中文版的鏈接:http://www.redsaga.com/spring_ref/2.0/html/aop.html
我們用scheme-based aop 配置方式:
<aop:config> ...... </aop:config>
Advisor方式:
假設我們有一個MethodBeforeAdvice 叫TestAdvice
;用于打印將要執行的方面名;
public class TestAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object
object)
throws Throwable {
System.out.println("method.getName() = "
+ method.getName());
System.out.println("TestAdvice
testing..." );
}
}
配置如下:
<bean id="testAdvice"
class="biz.pxzit.application.service.TestAdvice"/>
<aop:config>
<aop:advisor pointcut="execution(*
biz..*Mgr.save*(..))"
advice-ref="testAdvice" order="1"/>
......
</aop:config>
order是可選的,指定執行的次序;上面的配置語意是,在執行biz開頭的package下面任意以Mgr結尾的manager的save開頭的方法時候,執行testAdvice,因為是MethodBeforeAdvice
所以在save開頭方法執行前執行;
Aspect方式:
public class TestAdvice2 {
public void goAfter(JoinPoint joinPoint) throws Throwable {
System.out.println("TestAdvice2.goAfter
testing..."+joinPoint.getTarget());
System.out.println("TestAdvice2.goAfter
testing..." );
}
}
配置如下:
<bean id="testAdvice2"
class="biz.pxzit.application.service.TestAdvice2"/>
<aop:config>
......
<aop:aspect
id="ddAspect" ref="testAdvice2">
<aop:after
method="goAfter"
pointcut="execution(* biz..*Mgr.*(..))"
/>
.......
</aop:aspect>
</aop:config>
注意TestAdvice2的方法參數,這里用的是JoinPoint
joinPoint,還有很多細節,具體可以看文檔;
要使用<aop:aspect >節點,還需asm.jar
(3個,在springframework的lib下有)否則會報noclassfound
exception;
注意我之前配置的pointcut表達式是錯的,正確的是execution(*
biz..*Mgr.*(..))";