gwt+smartgwt framework网站开发心得 2.前期准备integration of framework
作为一个初学者,也许我会弄错一些概念名词。比如上一章节标题,我曾把它叫做设计模式。看完博客园一篇博客,标题叫“请放过设计模式”,自己也脸红了。设计模式在英语表述是“Pattern Design”,而我想说的问题是"framework",应该是指框架设计。
不过,我得目标不是成为一个高级技工或者说是这个方向的专家,所以可能看起来我不够专业。确实也就是如此。所以我不能够一下子就能给我的项目找到一个最优的解决方案。凡事一步一步来。遇到问题(problem),理解问题所在(problematique),最后寻找问题的解决方案。有时候甚至是绕了一个大弯,发现答案就在几个月前看完的一个文档的下班章节。这个过程中Google成了我最好的老师。
那么现在就面对第一个问题,如何将smartgwt,MVP,rpc,hibernate,Gilead,SpringSecurity这些框架用黏合剂粘合在一起?
我很幸运的找到了下面这个链接,一个很有名的法语IT论坛,这个博文讲了如何将smartgwt,spring和Hibernate融合在一起。特别详细。相当感叹国外IT工作者的耐心和认真的态度。
http://hugo.developpez.com/tutoriels/java/gwt/utilisation-gwt-avec-spring-et-hibernate/
这篇文章对于那些想把这些框架整合到一起却又一下子找不到切入点的人,提供了很好的思路。当然如果你已经看了相关方面的google doc,那么我想里面的例子你应该已经很熟悉了。
作者首先比较了各种当年流行的的框架,然后理性的根据自己的需求找了合适自己的框架。
正如他所说,他曾在网上查阅了很多的的文档,参考,例子,教程,但是很多情况下,那些所举的例子都相当简单,以至于和现实情况相差甚远,没有什么参考价值。当你需要一个比较复杂的功能时,这些简单的例子反而有点像隐蔽的绊脚石,让你绊到在那个地方,却迟迟找不到石子在哪里。
其实,简单的例子固然需要,但是对于一个工程师来说,一个系统化方法化的实例对我们来说更为需求吧。俗话说“授人于鱼不如授人于渔”
下面是三点是我觉得此篇博文对我来说特别有用的地方。
1. SmartGWT 的DataSource在showcase里面只给client端的使用例子,如果DataSource需要和Server端交互时,如何实现"fetch","update","remove",“add”这些方法?这个时候,我们可以创建一个GwtRpcDataSource类继承DataSource来实现。具体如下:
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.types.DSDataFormat;
import com.smartgwt.client.types.DSProtocol;
/**
* Data source with ability to communicate with server by GWT RPC.
* <p/>
* SmartClient natively supports data protocol "clientCustom". This protocol
* means that communication with server should be implemented in
* <code>transformRequest (DSRequest request)</code> method. Here is a few
* things to note on <code>transformRequest</code> implementation:
* <ul>
* <li><code>DSResponse</code> object has to be created and
* <code>processResponse (requestId, response)</code> must be called to finish
* data request. <code>requestId</code> should be taken from original
* <code>DSRequest.getRequestId ()</code>.</li>
* <li>"clientContext" attribute from <code>DSRequest</code> should be copied to
* <code>DSResponse</code>.</li>
* <li>In case of failure <code>DSResponse</code> should contain at least
* "status" attribute with error code (<0).</li>
* <li>In case of success <code>DSResponse</code> should contain at least "data"
* attribute with operation type specific data:
* <ul>
* <li>FETCH - <code>ListGridRecord[]</code> retrieved records.</li>
* <li>ADD - <code>ListGridRecord[]</code> with single added record. Operation
* is called on every newly added record.</li>
* <li>UPDATE - <code>ListGridRecord[]</code> with single updated record.
* Operation is called on every updated record.</li>
* <li>REMOVE - <code>ListGridRecord[]</code> with single removed record.
* Operation is called on every removed record.</li>
* </ul>
* </li>
* </ul>
*
* @author Aleksandras Novikovas
* @author System Tier
* @version 1.0
*/
public abstract class GwtRpcDataSource extends DataSource
{
/**
* Creates new data source which communicates with server by GWT RPC. It is
* normal server side SmartClient data source with data protocol set to
* <code>DSProtocol.CLIENTCUSTOM</code> ("clientCustom" - natively supported
* by SmartClient but should be added to smartGWT) and with data format
* <code>DSDataFormat.CUSTOM</code>.
*/
public GwtRpcDataSource()
{
setDataProtocol(DSProtocol.CLIENTCUSTOM);
setDataFormat(DSDataFormat.CUSTOM);
setClientOnly(false);
}
/**
* Executes request to server.
*
* @param request
* <code>DSRequest</code> being processed.
* @return <code>Object</code> data from original request.
*/
@Override
protected Object transformRequest(DSRequest request)
{
String requestId = request.getRequestId();
DSResponse response = new DSResponse();
response.setAttribute("clientContext", request.getAttributeAsObject("clientContext"));
// Asume success
response.setStatus(0);
switch (request.getOperationType())
{
case FETCH:
executeFetch(requestId, request, response);
break;
case ADD:
executeAdd(requestId, request, response);
break;
case UPDATE:
executeUpdate(requestId, request, response);
break;
case REMOVE:
executeRemove(requestId, request, response);
break;
default:
// Operation not implemented.
break;
}
return request.getData();
}
/**
* Executed on <code>FETCH</code> operation.
* <code>processResponse (requestId, response)</code> should be called when
* operation completes (either successful or failure).
*
* @param requestId
* <code>String</code> extracted from
* <code>DSRequest.getRequestId ()</code>.
* @param request
* <code>DSRequest</code> being processed.
* @param response
* <code>DSResponse</code>. <code>setData (list)</code> should be
* called on successful execution of this method.
* <code>setStatus (<0)</code> should be called on failure.
*/
protected abstract void executeFetch(String requestId, DSRequest request, DSResponse response);
/**
* Executed on <code>ADD</code> operation.
* <code>processResponse (requestId, response)</code> should be called when
* operation completes (either successful or failure).
*
* @param requestId
* <code>String</code> extracted from
* <code>DSRequest.getRequestId ()</code>.
* @param request
* <code>DSRequest</code> being processed.
* <code>request.getData ()</code> contains record should be
* added.
* @param response
* <code>DSResponse</code>. <code>setData (list)</code> should be
* called on successful execution of this method. Array should
* contain single element representing added row.
* <code>setStatus (<0)</code> should be called on failure.
*/
protected abstract void executeAdd(String requestId, DSRequest request, DSResponse response);
/**
* Executed on <code>UPDATE</code> operation.
* <code>processResponse (requestId, response)</code> should be called when
* operation completes (either successful or failure).
*
* @param requestId
* <code>String</code> extracted from
* <code>DSRequest.getRequestId ()</code>.
* @param request
* <code>DSRequest</code> being processed.
* <code>request.getData ()</code> contains record should be
* updated.
* @param response
* <code>DSResponse</code>. <code>setData (list)</code> should be
* called on successful execution of this method. Array should
* contain single element representing updated row.
* <code>setStatus (<0)</code> should be called on failure.
*/
protected abstract void executeUpdate(String requestId, DSRequest request, DSResponse response);
/**
* Executed on <code>REMOVE</code> operation.
* <code>processResponse (requestId, response)</code> should be called when
* operation completes (either successful or failure).
*
* @param requestId
* <code>String</code> extracted from
* <code>DSRequest.getRequestId ()</code>.
* @param request
* <code>DSRequest</code> being processed.
* <code>request.getData ()</code> contains record should be
* removed.
* @param response
* <code>DSResponse</code>. <code>setData (list)</code> should be
* called on successful execution of this method. Array should
* contain single element representing removed row.
* <code>setStatus (<0)</code> should be called on failure.
*/
protected abstract void executeRemove(String requestId, DSRequest request, DSResponse response);
}
2. 如何将Spring和Gwt整合?答案:GWT-SL
事实上,要将Spring security 和GWT 整合, 第一个如何配置Servlet。因为Spring有自己的servlet"DispatcherServlet",而GWT也有自己的Servlet是“gwt-servlet”。DispatcherServlet是提供给MVC机制的。
首先让我们看看正常情况下,只考虑GWT RPC时,那么一般gwt-servlet是在web.xml中声明,比如。
<servlet>
<servlet-name>appService</servlet-name>
<servlet-class>com.google.gwt.app.example.server.AppServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>appService</servlet-name>
<url-pattern>/app/appService</url-pattern>
</servlet-mapping>
如果你对Spring特别感兴趣,那么第一步就是需要在web.xml中声明你的application context file。
<context-param>
<param-name> contextConfigLocation </param-name>
<param-value> classpath:applicationContext_GWT.xml </param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
然后在这个applicationContext_GWT.xml声明一些Bean。需要注意,这些bean的需要不依赖于DispatcherServlet的。在这里你可以做一个最基本的Security的操作了。但是Method Security还不行,因为它依赖于DispatcherServlet。
如果要添加DispatcherServlet到web.xml中,那么问题出现了,如何处理两个老死不相往来的Servlet的关系?
其实GWT-SL就是用来处理这两者的关系的。一个直觉的想法就是用dispatcherServlet 来dispatch gwt-servlet的rpc service。也就是说url匹配时,将rpc service过滤出来,然后又gwt-servlet来处理。GWT-SL中的GWT-handler就是用来做这个url dispatch的。
首先声明这个rpc service bean.
applicationContext_GWT.xml
<bean id="appService" class=" com.google.gwt.app.example.server.AppServiceImpl"></bean>
然后加上GWTHandler这个bean,负责url mapping,将*.rpc url请求对应到这个remote service implementation上。
applicationContext_GWT.xml
<bean id="urlProjectMapping" class="org.gwtwidgets.server.spring.GWTHandler">
<!-- Supply here mappings between URLs and services. Services must implement the RemoteService interface but are not otherwise restricted.-->
<property name="mappings">
<map><entry key="/app/appService.rpc" value-ref="appService" /> </map>
</property>
</bean>
当然,你不能忘记在web.xml中加上你的dispatcherServlet的声明,同时删掉关于之前appService的声明。记住,这个是Spring 自己的Servlet而非GWT-SL的。
web.xml
<servlet>
<servlet-name>handler</servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>handler</servlet-name>
<url-pattern>*.rpc</url-pattern>
</servlet-mapping>
那现在一个新的问题出现了,当我声明了一个叫handler的dispatcherServlet的时候,它就自动找一个“handler-servlet.xml”的application context 文件。我们需要做的是重新创建一个叫做handler-servlet.xml的文件,然后将下面这一段在“applicationContext_GWT.xml“内容放置到”handler-servlet.xml“中。
Handler-servlet.xml
<bean id="appService" class=" com.google.gwt.app.example.server.AppServiceImpl"></bean>
<bean id="urlProjectMapping" class="org.gwtwidgets.server.spring.GWTHandler">
<!-- Supply here mappings between URLs and services. Services must implement the RemoteService interface but are not otherwise restricted.-->
<property name="mappings">
<map> <entry key="/app/appService.rpc" value-ref="appService" /> </map>
</property>
</bean>
如果你的remote service没有.rpc后缀,千万要加上。
最后再在web.xml中添加下面的handler-servlet载入声明。
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value> /WEB-INF/handler-servlet.xml </param-value>
</init-param>
现在你的filter parttern将会滤除rpc服务。
(the above method has been checked)
3. hibernate 用gilead4gwt方案于gwt融合。
Gilead4GWT像一个管道一样,将hibernate的 persistent object变成gwt-serializable objet.需要进行一定配置,不十分难,不过需要注意版本一致,同时,翻看升级记录,看该版本下用什么方法。
I will talk about how to configure the gilead4gwt later.
(未完待续)