Blog 搬家囉,這個地方不會再使用了.....
/**
* @deprecated 本 blog 已經停用請改用下面的網址
*
* @link 新Blog:JavaWorld jroller
*/
public void viewXexexBlog() ;
The page is mainly for sharing Java experience. 再加上一些不知所云的二三事。 而且,如你所見,內容是半中半英的
/**
* @deprecated 本 blog 已經停用請改用下面的網址
*
* @link 新Blog:JavaWorld jroller
*/
public void viewXexexBlog() ;
我們新專案已完成 phase I 了,爽!
Spring ORM 替 Hibernate 的 API (Session and Query) 做了一番修整,也就是 HibernateTemplate ,它最主要的目的是 resource 控管及 exception 的轉換。有了這個 persistence layer 的 code 就可以大量的減少並且降低出錯的機會。然而這個 API 卻抹剎了 Hibernate API 的簡單易用優點,舉例來說,如果藉由 hibernateTemplate 控制 Hibernate Session (ex. Criteria),唯一的做法就是寫很難看的 Callback:
public MyData findByNo(final String no) {
final String hql = " select data from MyData data "
+ " where data.no = :no ";
HibernateCallback hc = new HibernateCallback() {
public Object doInHibernate(Session session) {
Query q = session.createQuery(hql);
q.setString("no", no);
return q.uniqueResult();
}
};
return (MyData) hibernateTemplate.execute(hc);
}
接下來就是 AutoProxyCreator,當它遇到 bean 的 class 為 HibernateDAOWrapper 的 subclass 時,它就會自動替該 bean 做 proxy。
public class HibernateDAOWrapperMethodInterceptor
implements MethodInterceptor {
private HibernateTemplate hibernateTemplate;
public Object invoke(final MethodInvocation methodInvocation)
throws Throwable {
try {
return hibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
HibernateDAOWrapper.currentSession.set(session);
try {
return methodInvocation.proceed();
} catch (Throwable e) {
if (e instanceof HibernateException) {
throw (HibernateException) e;
} else if (e instanceof SQLException) {
throw (SQLException) e;
} else {
//must re-throw as RuntimeException, and let
//hibernateTemplate deal with resource management
throw new ExceptionCarrier(e);
}
}
}
});
} catch (ExceptionCarrier e) {
throw e.throwable;
}
}
private static class ExceptionCarrier extends RuntimeException {
private Throwable throwable;
public ExceptionCarrier(Throwable t) {
this.throwable = t;
}
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
}
public class HibernateDAOWrapperAutoProxyCreator extends
AbstractAutoProxyCreator {
protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass,
String beanName, TargetSource customTargetSource)
throws BeansException {
if (HibernateDAOWrapper.class.isAssignableFrom(beanClass)) {
return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
} else {
return DO_NOT_PROXY;
}
}
}
<bean id="hibernateDAOWrapperAutoProxyCreator"
class="org.bioinfo.util.spring.dao.HibernateDAOWrapperAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>hibernateDAOWrapperMethodInterceptor</value>
</list>
</property>
<!-- for use CGlib to create proxy,
(default to false, which will use Proxy if it detects interface presence) -->
<property name="proxyTargetClass">
<value>true</value>
</property>
</bean>
<bean id="hibernateDAOWrapperMethodInterceptor"
class="org.bioinfo.util.spring.dao.HibernateDAOWrapperMethodInterceptor">
<property name="hibernateTemplate">
<ref local="hibernateTemplate"/>
</property>
</bean>
/**
* A special HibernateTemplate wrapper for DAO. You need to do:
*
* (1) Extends this class and use getSession() to obtain Hibernate3 Session
*
* <code>
* public class YourDAO extends HibernateDAOWrapper {
*
* public MyProject findByProjectCode(String code) {
* String hql = "select prj from MyProject prj where prj.code = :code";
*
* return (MyProject) getSession().createQuery(hql)
* .setString("code",code").uniqueResult();
* }
* }
* </code>
*
* (2) At your spring *.xml, just write one line:
*
* <code>
* <bean id="yourDAO" class="some.thing.YourDAO"></bean>
* </code>
*
* done !!
* @author ingram
*
*/
public abstract class HibernateDAOWrapper {
final static ThreadLocal currentSession = new ThreadLocal();
protected final Session getSession() {
return (Session) currentSession.get();
}
}
最近又看了一本 Pro Spring,喔~ 裡面的第十一章 (Designing and implementating spring-based applications) 真是太棒囉!很多建議都相當的實用,解決了許多困惑我很久的疑問。這樣子差不多可以整理出一個通用的開發架構了:
去年以來個人一直在 team 裡疾呼 unit-test 的重要。現在整個 team 裡除了幾個不曾跟 heavy-tester 一起合作的人之外,幾乎都了解 test 的好處和重要 (有些人是沒寫test 吃了大虧,有些人先是被逼著寫,後來嘗到了甜頭 )。去年的開發幾乎完全都是個人單打獨鬥,然後單純互相討論,今年這個新專案由六個人組成,好玩的是分工完之後就自動兩兩成對 pair 了。記得去年前 pair 時大家都哇哇叫,今年好像因為個別的不同理由不得不 pair 啊,目前 pair 的分配是:
Struts 的 Dispatch Action 已經行之有年了,有 Dispatch, Lookup, MappingDispatch... 好幾種可以用。但設定方法都很囉嗦 (網頁,程式,struts-config三者都要 hard-code String 在上面),而且適用的了 A ,就不能用在 B。例如雖然 DispatchAction 可以寫在 URL 上: /foo.do?method=saveOrder ,但是遇到一個 form 需要利用多個 submit button dispatch 時,就不行了。你得轉用 LookupDispatch,可是 LookupDispatch 又需要在 Action 定義一個很醜的 getKeyMethodMap() (裡面全部是 hard-code string)。接下來要分享一個通用 URL/submit button/struts-config.xml 三者設定的 SimpleDispatchAction:
嘿嘿,有了這個統合的 Dispatch,日子就好過多啦!我也另外用同樣的邏輯寫了個 SimpleDispatchActionForm,搭配起來用不錯。
package org.bioinfo.util.struts;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.springframework.web.struts.ActionSupport;
/**
* 簡化的 DispatchAction,並支援Spring的 ActionSupport (如不需要可以換回 Action)
*
* 使用方法:
*
* 繼承此 class,並定義各個 action method,比方說有兩個 method 分別做儲存和刪除:
*
* <code>
* public ActionForward save(ActionMapping mapping, ActionForm form,
* HttpServletRequest request, HttpServletResponse response)
* throws Exception {
* // orderService.save(....)
* }
*
* public ActionForward delete(ActionMapping mapping, ActionForm form,
* HttpServletRequest request, HttpServletResponse response)
* throws Exception {
* // orderService.delete(....)
* }
* </code>
*
* 有三種方式可以設定 dispatch 到哪個 method 上。
*
* (1) submit button 法,直接寫在 submit 的 property 上:
*
* <code>
* <html:form action="/some/work">
* .... some thing ....
*
* <html:submit property="dispatch=save" value="儲存"/>
* <html:submit property="dispatch=delete" value="刪除"/>
* </html:form>
* </code>
*
* 注意 property 裡面的值是 'dispatch=xxxx' 記得要寫等號與 method 名稱,
* 而且大小寫要對,不能空白。當網頁按下 "儲存" 時,則會執行 /some/work.do
* 的 save(...) 的 method。 如果按下 "刪除" 則執行 delete(...),
*
* 建議 -- 這種寫法通常是用在一個 Action 有多個 dispatch method,而每個 method
* 都共用同個 ActionForm
*
* (2) URL 法,接在 URL 後面:
*
* <code>
* <html:form action="/some/work?dispatch=save">
* 或是用 link 也可以
* <html:link action="/some/work?dispatch=save" />
* </code>
*
* 建議 -- 通常用在不需要 ActionForm 的 Action,或者是要將 submit button 法
* 寫成 url 時使用。
*
* (3) struts-config 法,直接寫死在 parameter='dispatch=foo' 上
*
* <code>
* <action
* path="/saveOrder"
* name="SaveOrderForm"
* type="antar.order.web.OrderDispatchAction"
* parameter="dispatch=save" >
* </action>
* <action
* path="/deleteOrder"
* name="DeleteOrderForm"
* type="antar.order.web.OrderDispatchAction"
* parameter="dispatch=delete" >
* </action>
* </code>
*
* 建議 -- 這種寫法通常是為了讓 Action 中每個 dispatch method 使用不同
* 的 ActionForm。一旦寫死在 struts-config 裡,該 mapping 的 path 就不能
* 與 URL 法 或是 submit button 法同時使用。
*
* 最後請注意同一個 request 上,URL 法不能與 submit button 法同時使用:
*
* <code>
* ...........錯誤範例...........
* <html:form action="/some/work?dispatch=save">
* .... some thing ....
* <html:submit property="dispatch=delete" value="刪除"/>
* </html:form>
* </code>
*
* @author ingram
*
*/
public abstract class SimpleDispatchAction extends ActionSupport {
private static final String KEY_VALUE_SEPERATOR = "=";
private final static String KEY = "dispatch";
private static final String VALID_PARAMETER_NAME_PATTERN = KEY + "\\"
+ KEY_VALUE_SEPERATOR + "[a-zA-Z0-9_]+";
private Class clazz = this.getClass();
private Class[] argTypes = new Class[] { ActionMapping.class,
ActionForm.class, HttpServletRequest.class,
HttpServletResponse.class };
private Map dispatchMethods = new HashMap();
public final ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String methodName = getMethodName(request, mapping);
Method method = null;
try {
Object[] args = { mapping, form, request, response };
method = obtainDispatchMethod(methodName);
return (ActionForward) method.invoke(this, args);
} catch (NoSuchMethodException e) {
throw dealWithMethodProblem(methodName, e);
} catch (IllegalAccessException e) {
throw dealWithMethodProblem(methodName, e);
} catch (InvocationTargetException e) {
throw dealWithMethodProblem(methodName, e);
}
}
private RuntimeException dealWithMethodProblem(String methodName,
Exception e) {
return new RuntimeException(
"can not access dispatching method:["
+ methodName
+ "]. ", e);
}
private Method obtainDispatchMethod(String methodName)
throws NoSuchMethodException {
Method method = (Method) dispatchMethods.get(methodName);
if (method == null) {
method = clazz.getMethod(methodName, argTypes);
dispatchMethods.put(methodName, method);
}
return method;
}
static String getMethodName(HttpServletRequest request,
ActionMapping mapping) {
final List gatherAllMethodNames = new ArrayList();
for (Enumeration e = request.getParameterNames();
e.hasMoreElements();) {
String parameterName = (String) e.nextElement();
addMatchedParameter(gatherAllMethodNames, parameterName);
}
final String[] values = request.getParameterValues(KEY);
if (values != null) {
for (int i = 0; i < values.length; i++) {
gatherAllMethodNames.add(values[i]);
}
}
if (mapping.getParameter() != null) {
addMatchedParameter(gatherAllMethodNames
, mapping.getParameter());
}
if (gatherAllMethodNames.isEmpty()) {
throw new IllegalArgumentException(
"no 'dispatch=methodName' found in parameter");
} else if (gatherAllMethodNames.size() > 1) {
throw new IllegalArgumentException(
"\nMultiple dispatch parameter: " + gatherAllMethodNames
+ " Only one parameter is allowed.");
} else {
return (String) gatherAllMethodNames.iterator().next();
}
}
private static void addMatchedParameter(List gatherAllMethodNames,
String parameterName) {
if (parameterName.matches(VALID_PARAMETER_NAME_PATTERN)) {
gatherAllMethodNames
.add(parameterName.split(KEY_VALUE_SEPERATOR)[1]);
}
}
}
靠!真的想自殺,居然誤解了 EasyMock 這麼久!
mockSubscriber.expects(once()).method("receive").with( eq(message) );
就是這麼簡單,唯一的限制是需要非 final 的 public constructor,不過這是小事啦!
private MockControl control;
private OrderDAO mockOrderDAO;
protected void setUp() throws Exception {
//MockClassControl 可以替 class 建立 Mock
control = MockClassControl.createStrictControl(OrderDAO.class);
mockOrderDAO = (OrderDAO) control.getMock();
}
public void testSaveOrder() {
//開始預錄
Order order = new Order();
//執行真正的 method 來錄製,而不是用 method name
mockOrderDAO.saveOrder(order);
control.replay();
//錄製完成
OrderServiceImpl service = new OrderServiceImpl();
service.setOrderDAO(mockOrderDAO);
service.saveCustomerOrder(order);
//比對錄製結果
control.verify();
}