论坛首页 Java版

JERT学习笔记

浏览 25146 次
该帖已经被评为精华帖
作者 正文
最后更新时间:2006-09-12
robbin说过:“JERT是Hibernate/Spring/Webwork/Sitemesh/FreeMarker整合的范例”。对于我来说Jert是很好的学习教材。我原本使用Hibernate/Spring/OSWorkflow/Echo/EchoPoint做内网应用的开发,现在要做internet应用,Echo/EchoPoint不大合适(Echo2基于AJAX,或许可以)。因此,需要重新选择Web Framework。WebWork/SiteMesh/FreeMarker的组合是我的首选。

这个笔记是我学习过程的记录,我不清楚的地方用绿色,我的想法和建议用蓝色,我认为的bug用红色(目前发现了一个http://forum.javaeye.com/viewtopic.php?p=74043#74043)。请大家指正。

笔记版本0.1

05-05-06:新增user篇:http://forum.javaeye.com/viewtopic.php?p=74117#74117

05-05-06:新增pert篇:http://forum.javaeye.com/viewtopic.php?p=74123#74123

core篇
(core的版本是0.2)

package: core

Application
1、Singleton模式,保证只有一个Application对象。
2、包含一个容器:Container
3、应用
   1)core包:
a)DefaultApplicationSetupListener的contextInitialized()中,将Container的具体实现——SpringContainer赋值到Application的Container属性。而DefaultApplicationSetupListener作为一个listener定义到web.xml中,这样J2EE应用启动时,向Application加载Container的具体实例。
2)user包:无
3)jert包:使用Application获取Container实例,具体见Container笔记。


package: core.container

Container
1、Container是个接口,表示应用所使用的一个抽象容器。在core中,Container的具体实现是SpringContainer。Quake Wang对Container的解释是:“有一个Container的接口是为了能够用别的容器,比如要用nano的话,写一个NanoContainer就可以用了,我们可以不一定绑定在Spring上。”

2、接口方法:
public Object getComponent(Object key) throws ComponentNotFoundException; 
从容器中获取组件对象。

public void reload();
重新装载容器定义。
   
public void autowireComponent(Object bean);
自动装配组件对象的协作对象。(其中的bean,好像叫component更符合Container的命名习惯)

3、应用:
    1)core包:
a)ComponentAutowireInterceptor调用Container的autowireComponent(invocation.getAction())方法,这样可以按照Action中属性名称自动为Action装配协作对象(多半是一些在applicationContext中定义的Service, Manager等)。最终的效果是:在xwork.xml中定义的Action不再需要编写<external-ref>为Action指定外部的属性引用。
b)xwork-optional(https://xwork-optional.dev.java.net/)项目中提供了WebWork和Spring集成方案,效果是一样的。它的核心是ActionAutowiringInterceptor。

2)user包:无

3)jert包:
a)SetupDoneInterceptor调用Container的getComponent()方法获取UserManager实例。
b)UserPrefsInterceptor调用Container的getComponent()方法获取UserPrefsService实例。
c)com.javaeye.webtools.action.reload.ReloadContainer(是一个Action)的execute()中调用Container的reload()方法。

SpringContainer
1、Container的具体实现。
2、一个私有成员变量:ApplicationContext applicationContext。
    ApplicationContext是Spring的Bean工厂,通常用于J2EE环境。SpringContainer的所有方法都需要这个ApplicationContext。
3、两个构造方法:
    public SpringContainer(ServletContext servletContext)
public SpringContainer(ApplicationContext applicationContext)
它们的唯一职责是:设置applicationContext。
4、四个public方法:
三个public方法:分别实现了Container的接口方法。
还有一个public void close():用于关闭ApplicationContext。
由于close()方法不是Container的方法,使用该方法的对象必然直接依赖SpringContainer而不是Container。我猜测可能为了特殊的应用,例如test、destroy,因此搜索了一下,发现除了SpringContainer自己的reload()调用了这个close()外,没有其它的代码调用close()。
我的建议:
1)在SpringContainerTest的tearDown()中调用close()。
2)将close()定义到Container中。
5、应用
    1)core包:
a)在com.javaeye.core.web.listener.DefaultApplicationSetupListener的contextInitialized()中使用构造方法SpringContainer(ServletContext servletContext)创建整个应用唯一的SpringContainer对象。
b)在com.javaeye.core.container.SpringContainerTest的setUp()中使用构造方法SpringContainer(ApplicationContext applicationContext)创建测试用的SpringContainer对象。
c)ComponentAutowireInterceptor调用Container的autowireComponent(invocation.getAction())方法,这样可以按照Action中属性名称自动为Action装配协作对象(多半是一些在applicationContext中定义的Service, Manager等)。最终的效果是:在xwork.xml中定义的Action不再需要编写<external-ref>为Action指定外部的属性引用。

2)user包:无
3)jert包:无

ComponentNotFoundException
1、extends RuntimeException:说明ComponentNotFoundException是一个unchecked exception。
2、应用:
    1)core包:
       Container和SpringContainer中getComponent()使用ComponentNotFoundException。
SpringContainer中getComponent()抛出ComponentNotFoundException。
2)user包:无
3)jert包:无
   
最后更新时间:2006-09-12
package: core.domain

Entity
1、user, jert中所有实体类的基类。
2、以一个类型是Long,名为id的对象作为Entity的主键字。
3、覆写了equals(), toString()方法。
4、应用
    1)core包:无
2)user包:无
3)jert包:com.javaeye.jert.domain包及其子包中所有实体类(持久类)均继承Entity。
5、讨论:
1)Jert开源项目讨论 http://forum.javaeye.com/viewtopic.php?t=9797
robbin说:
Quake的设计中域对象有一个基类Entity,主键设定为Long型。对于这种设计,我个人认为这个Entity是多余的,如果是我,我会让所有域对象的主键类型为对象型(Long, Integer, String皆可),如果是对域对象抽象操作,那么我用java.io.Serializable接口即可统一表示。
Quake Wang说:
主键设定为Long型的基类Entity只是为了可以少写一些代码,目的是为了偷懒,我很多Entity都extends它,只是为了少写几行代码。并不是如你所想的那样做一个统一的Entity抽象操作,在com.javaeye.core package里面是没有任何代码是和这个对象有关系的。
2)我的想法
(1) 用一个Entity接口统一表示实体类(域对象)。
[code:1]public interface Entity implements Serializable {
  public Serializable getEntityId();
  public void setEntityId(Serializable id);
  public boolean isNew();
}[/code:1]
(2) 编写抽象类AbstractEntity,除了实现Entity接口外,覆写equals(), toString()方法。
(3) 编写LongIdEntity、IntIdEntity、StringIdEntity,它们都是AbstractEntity的派生类,同时也是抽象类。主要任务是实现id类型的转换。

package: core.hibernate

CriteriaResultsCounter
1、对于给定的Criteria,计算它返回结果的条数。
2、extends net.sf.hibernate.loader.CriteriaLoader
3、一个public static方法:int count(Session s, Criteria criteria) throws HibernateException
4、protected的构造函数。
5、两个protected方法:setSql()和doCount()方法。
6、覆写CriteriaLoader 的最顶层基类net.sf.hibernate.loader.Loader的list()方法。
7、应用:
    1)core包:AbstractService的find()方法调用CriteriaResultsCounter.count(),为分页做准备。
2)user包:无
3)jert包:无
8、我没有用过Hibernate的Criteria,不能深入理解CriteriaResultsCounter的源码。(TODO: 深入理解后补充之)

UnderscoreNamingStrategy
1、下划线命名策略:按照类名生成表名、属性名生成字段名的时候,两个单词之间用下划线连接。
2、implements net.sf.hibernate.cfg.NamingStrategy
3、包含一个表名前缀属性:tablePrefix
4、应用:
    1)core包:DefaultSessionFactoryBean。
2)user包:无
3)jert包:无

DefaultSessionFactoryBean
1、扩展org.springframework.orm.hibernate.LocalSessionFactoryBean。
2、一个公开方法:public void setTablePrefix(String tablePrefix)。通过该方法向UnderscoreNamingStrategy中设置表名前缀tablePrefix。
3、覆写LocalSessionFactoryBean的newConfiguration()方法:创建Configuration,并使用UnderscoreNamingStrategy。
4、应用:
    1)core包:无
2)user包:
       a) src\test\com\javaeye\user\application_context.xml中bean id 为sessionFactory的类是DefaultSessionFactoryBean。
          测试之用:HibernateTest测试程序使用该xml定义。
3)jert包:
  a) src\com\javaeye\jert\application_context.xml中bean id 为sessionFactory的类是DefaultSessionFactoryBean。
     <bean id="sessionFactory" class="com.javaeye.core.hibernate.DefaultSessionFactoryBean">
     (1) 通常sessionFactory使用Spring提供的org.springframework.orm.hibernate.LocalSessionFactoryBean。
     (2) 使用DefaultSessionFactoryBean定义的sessionFactory可以
<1> 直接使用UnderscoreNamingStrategy
<2> 在xml中外部定义表名前缀。定义如下:
[code:1]<bean id="sessionFactory" class="com.javaeye.core.hibernate.DefaultSessionFactoryBean">
……
<property name="tablePrefix"><value>jert_</value></property>
</bean>[/code:1]
  b) 还用一些xml的sessionFactory使用DefaultSessionFactoryBean,它们都是用于测试。
src\test\com\javaeye\jert\service\BookmarkServiceTest.xml
src\test\com\javaeye\jert\service\DatabaseServiceTest.xml
src\test\com\javaeye\jert\service\DrillDownColumnServiceTest.xml
src\test\com\javaeye\jert\service\ReportDefinitionServiceTest.xml
src\test\com\javaeye\jert\service\SecurityServiceTest.xml
src\test\com\javaeye\jert\service\UserPrefsServiceTest.xml
5、讨论:
1)直接使用Spring提供的org.springframework.orm.hibernate.LocalSessionFactoryBean,也可以定义NamingStrategy。定义如下:
[code:1] <bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
……
<property name="namingStrategy">
<bean class="net.sf.hibernate.cfg.ImprovedNamingStrategy"></bean>
</property>
……
</bean>[/code:1]
2)但是如果想在xml中外部定义表名前缀,则应使用前面关于DefaultSessionFactoryBean的定义方法。

6、Hibernate中NamingStrategy的作用
   1)NamingStrategy是个接口,允许你对数据库对象和schema元素指定"命名标准"。
   2)NamingStrategy中方法的用途如下:
方法 用途
public String classToTableName(String className); 如果hbm.xml中<class>标签中没有定义table属性,则使用该方法从全称类名中获取table名。
public String propertyToColumnName(String propertyName); 如果hbm.xml中<property>标签中没有定义column属性,则使用该方法从属性路径中获取column名。
public String tableName(String tableName); 如果hbm.xml中<class>标签中定义了table属性,则使用该方法从该table属性值中获取table名。
public String columnName(String columnName); 如果hbm.xml中<property>标签中定义了column属性,则使用该方法从该属性值中获取column名。
public String propertyToTableName(String className, String propertyName); 如果hbm.xml中定义集合的标签中没有指定table属性,则使用该方法从全称类名和属性名称中计算表名。

3)我的建议:最好不使用NamingStrategy中的classToTableName()、propertyToColumnName()和propertyToTableName()方法,因为:
   (1) 如果hbm.xml中不定义table, column,Hibernate只能使用classToTableName()这样的转换方法。
   (2) 对于给定的class, property,每次转换得结果都一样。
   (3) Hibernate使用classToTableName()这样的转换方法的频率是非常高的——几乎所有的Hibernate操作都会使用!
   (4) 即使是DefaultNamingStrategy,使用StringHelper.unqualify()进行转换的代价也是比较高的:
[code:1]public static String unqualify(String qualifiedName, String seperator) {
return qualifiedName.substring( qualifiedName.lastIndexOf(seperator) + 1 );
}[/code:1]
    而String.substring()的主体是
[code:1]return ((beginIndex == 0) && (endIndex == count)) ? this :
    new String(offset + beginIndex, endIndex - beginIndex, value);[/code:1]
    对于classToTableName(String className)来说,由于className是个fully-qualified class name,必然会new一个新的String。
(5) ImprovedNamingStrategy、UnderscoreNamingStrategy这样的NamingStrategy的转换代价更高。
4)我的做法:
   (1) 设计时为每个实体类编写文档。
   (2) 编写代码生成程序,读取设计文档,生成类定义和hbm.xml定义。
   (3) 代码生成程序调用UnderscoreNamingStrategy这样的NamingStrategy,一次性生成hbm.xml所需要的table, column属性值。
   (4) 编写NoMappingNamingStrategy,禁止使用classToTableName()、propertyToColumnName()和propertyToTableName()方法。方法如下:
[code:1]public String classToTableName(String className) {
throw new RuntimeException(“禁止使用classToTableName(String className)! className=” + className);
}[/code:1]
   (5) 用5.1的方法配制NamingStrategy
   
0 请登录后投票
最后更新时间:2006-09-12
package: core.service

PaginationSupport
1、分页支持类
2、使用者是AbstractService,但是jert中好像没有使用PaginationSupport。
3、TODO



AbstractService
1、extends HibernateDaoSupport
2、两个成员(实例)变量:
    private boolean cacheQueries = false;
private String queryCacheRegion;
   它们只有Setter,Spring通过这两个Setter为它们赋值。
3、initDao():当Spring通过工厂创建AbstractService的子类,并且在所有属性设置之后,调用initDao()方法。initDao()的任务是将cacheQueries和queryCacheRegion赋给HibernateTemplate中相应得属性。
4、封装一些常用的Hibernate操作,如create,delete,update,各种find等。
5、应用:
    1)core包:无
2)user包:无
3)jert包:AbstractService是下面服务类的基类
BookmarkFolderServiceImpl
BookmarkServiceImpl
DatabaseServiceDefaultImpl
DrillDownColumnServiceImpl
ReportDefinitionServiceDefaultImpl
SecurityServiceDefaultImpl
UserPrefsServiceDefaultImpl
6、讨论:
1)最初我开发Spring+Hibernate应用,学习的是wiring。系统架构是


  所谓的Service是在UI和Persistence之间的业务层,命名规则为:接口XxxService,实现类XxxServiceImpl。
      而Persistence层的命名规则为:接口XxxManager、或者XxxDao,实现类XxxManagerImpl、XxxDaoImpl,实现类extends HibernateDaoSupport。
2)Quake Wang的simpleoa也像wiring那样分三层。
3)重新检视Service代码,与Manager结构类同。该层是否有些多余,我不能确定?
4)如果为了实现分布式架构,应该加上这一层。但是将Web Server和Application Server从物理上分开,我还没有用过。倒是用过Web的集群。再有,如果是分布式结构,恐怕不能使用OpenSessionInView模式,麻烦!


GenericServiceException
1、extends RuntimeException:说明GenericServiceException是一个unchecked exception。
2、应用:
    1)core包:无
2)user包:无
3)jert包:
(1) 在DatabaseServiceDefaultImpl中抛出GenericServiceException:
    [code:1]private void checkName(Database db) {
        List sameNameDbs = findByNamedQuery("findSameNameDatabase", new Object[] { db.getName(), db.getId() });
        if (sameNameDbs != null && sameNameDbs.size() > 0)
            throw new GenericServiceException("error.same.name.db.exists");
}
(2)CreateDatabase、UpdateDatabase捕获GenericServiceException:
        try {
            …
        } catch (GenericServiceException e) {
            addFieldError("database.name", getText(e.getMessage()));
            return ERROR;
        }[/code:1]
(3) 在BaseDatabaseAction_zh_CN.properties中
[code:1]error.same.name.db.exists=\u4E00\u4E2A\u76F8\u540C\u540D\u5B57\u7684\u6570\u636E\u5E93\u5DF2\u7ECF\u5B58\u5728\uFF0C\u8BF7\u4FEE\u6539[/code:1]
           因此,getText(e.getMessage())的结果是“一个相同名字的数据库已经存在,请修改”。
       (4) 追踪getText(e.getMessage()):
           ActionSupport的getText(String aTextName)
TextProviderSupport的getText(String aTextName)
ResourceBundle.getBundle(aBundleName, locale, Thread.currentThread().getContextClassLoader())
XWork: Localization文档中提到:
引用
Any action can indicate that it supports localization by implementing com.opensymphony.xwork.TextProvider. To access a localized message, simply use one of the various getText() method calls.
The default implementation for this is com.opensymphony.xwork.TextProviderSupport, which in turn relies on com.opensymphony.xwork.util.LocalizedTextUtil. Any Action that extends com.opensymphony.xwork.ActionSupport will automatically gain localization support via TextProviderSupport.
In this implementation, when you attempt to look up a message, it attempts to do the following:
 Look for the message in the Action's class hierarchy.
 Look for the message in a resource bundle for the class
 If not found, look for the message in a resource bundle for any interface implemented by the class
 If not found, get the super-class and repeat from the first sub-step unless the super-class is Object

CreateDatabase和UpdateDatabase的基类都是BaseDatabaseAction,locale是Locale("zh", "CN"),
因此可以在BaseDatabaseAction_zh_CN.properties中得到信息。

3、一个bug
表现:
1、创建2个数据库,名称分别叫A和B,没有问题。
2、然后修改数据库B的名称为A,虽然提示“一个相同名字的数据库已经存在,请修改”,但名称(以及其它的改动)已经被提交。

发生bug的过程如下:
1、在updateDatabase.ftl视图中点击[提交]按钮,执行updateDatabase这个Action。
2、但是在执行updateDatabase之前:
1)updateDatabase.ftl中录入的值由WebWork传递给UpdateDatabase(实际上是UpdateDatabase的基类BaseDatabaseAction)的属性中。
2)由于参数databaseId是以“Id”结尾的,首先执行setDatabaseId(Long databaseId),其中this.database = databaseService.getDatabase(databaseId); 按照databaseId读取DB,实例化database。
3)由于databaseService.getDatabase()是只读的,没有transaction处理。(见application_context.xml中baseTxProxy中<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>)
4)随后,updateDatabase.ftl中其它录入的值按照属性名称依次修改刚刚实例化的database对象。
5)在执行updateDatabase这个Action之前,还要执行拦截器PermissionInterceptor,该拦截器执行Action的hasPermission()方法。
6)updateDatabase的hasPermission()如下(在updateDatabase的基类BaseAdminAction中)
[code:1]public boolean hasPermission() {
    return securityService.hasPermission(RemoteUser.get(), SecurityService.ADMIN);
}[/code:1]
7)然而,securityService.hasPermission()是一个transactional方法!方法结束后最终执行session.flush(); session.connection().commit();
8)这样,还没有执行updateDatabase中的execute(),修改后的database对象已经被提交到DB中。DatabaseServiceDefaultImpl中的checkName()成了马后炮!

改正:
修改application_context.xml中baseTxProxy定义
[code:1]<bean id="baseTxProxy" lazy-init="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
        <property name="transactionManager"><ref bean="transactionManager"/></property>
        <property name="transactionAttributes">
            <props>
<prop key="*">PROPAGATION_REQUIRED</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="hasPermission">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>[/code:1]

看来严格定义transactionAttributes非常有必要!
   
0 请登录后投票
最后更新时间:2006-09-12
package: core.web.filter

EncodingFilter

1、robbin的评论:
引用
EncodingFilter是用来设定HTTP GET/POST字符串的字符集的,实际上我认为这也是多余的,因为只要在webwork.properties里面指定:
webwork.i18n.encoding=UTF-8
的话,那么webwork的ServletDispatch就会做这个工作,参考ServletDispatch源代码第225行。

2、ServletDispatch相关源码:
[code:1]public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        try {
            if (encoding != null) {
                try {
                    request.setCharacterEncoding(encoding);
                } catch (Exception e) {
                }
            }
            if (locale != null) {
                response.setLocale(locale);
            }
            …
}

    public void init(ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);

        LocalizedTextUtil.addDefaultResourceBundle("com/opensymphony/webwork/webwork-messages");

        //check for configuration reloading
        if ("true".equalsIgnoreCase(Configuration.getString("webwork.configuration.xml.reload"))) {
            FileManager.setReloadingConfigs(true);
        }

        if (Configuration.isSet("webwork.i18n.encoding")) {
            encoding = Configuration.getString("webwork.i18n.encoding");
        }

        if (Configuration.isSet("webwork.locale")) {
            locale = localeFromString(Configuration.getString("webwork.locale"));
        }
     …
}[/code:1]

3、应用:
虽然可以不使用EncodingFilter,但是可以复习一下Filter用法
在web.xml中
[code:1] <filter>
    <filter-name>encoding</filter-name>
    <filter-class>com.javaeye.core.web.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>[/code:1]

package: core.web.listener

DefaultApplicationSetupListener
1、在contextInitialized()中,将Container的具体实现——SpringContainer赋值到Application的Container属性。而DefaultApplicationSetupListener作为一个listener定义到web.xml中,这样J2EE应用启动时,向Application加载Container的具体实例。
2、DefaultApplicationSetupListener依赖ContextLoaderListener,在web.xml中ContextLoaderListener放到DefaultApplicationSetupListener的前面:
[code:1] <listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<listener>
<listener-class>com.javaeye.core.web.listener.DefaultApplicationSetupListener</listener-class>
</listener>[/code:1]

package: core.web.servlet

DefaultFreemarkerDecoratorServlet
1、extends com.opensymphony.module.sitemesh.freemarker.FreemarkerDecoratorServlet
2、首先调用基类FreemarkerDecoratorServlet的preTemplateProcess()方法,其核心代码是:
[code:1] SimpleHash hash = (SimpleHash) templateModel;
。。。
hash.put("page",htmlPage);
。。。
hash.put("title",title);
hash.put("body",body);
hash.put("head",head);
hash.put("base",request.getContextPath());[/code:1]
3、然后执行DefaultFreemarkerDecoratorServlet的preTemplateProcess()方法,其核心代码是:
[code:1]        SimpleHash hash = (SimpleHash) templateModel;
。。。
        hash.put("req", request);
        hash.put("res", response);
。。。
        TaglibFactory factory = (TaglibFactory) hash.get(FreemarkerManager.KEY_JSP_TAGLIBS);
        hash.put(KEY_WEBWORK_TAG_LIB, factory.get("/WEB-INF/webwork.tld"));[/code:1]
4、因此,templateModel中包含如下scalars(标量):page,title,body,head,base,req,res,ww。页面布局文件(.dec)中可以使用这些标量,FreeMarker将.dec模板和templateModel合并后产生html页面。例如,main.dec
[code:1]<#include "/decorators/includes/header.ftl">
<#include "/decorators/includes/banner.ftl">
<#include "/decorators/includes/navigation.ftl">
<div id="bodycol" class="app">
    ${body}
</div>
<#include "/decorators/includes/footer.ftl">[/code:1]
   
0 请登录后投票
最后更新时间:2006-09-12
core.webwork.action

Protected
1、用于Action的权限管理。
2、源码
[code:1]public interface Protected {
    public boolean hasPermission();
}[/code:1]
3、Quake Wang在http://forum.javaeye.com/viewtopic.php?t=10400做了比较详细的解释。
4、应用
1)core包:
   PermissionInterceptor中检查权限
[code:1]public String intercept(ActionInvocation invocation) throws Exception {
        Action action = invocation.getAction();
        if(action instanceof Protected) {
            if(!((Protected) action).hasPermission()){
                return NOPERMISSION;
            }
        }
        return invocation.invoke();
    }[/code:1]

   
2)user包:无
3)jert包:下面的Action实现了Protected接口
BaseAdminAction、BaseUserAction、BaseReportAction
5、小建议:几乎所有Action都需要检查权限,加上一个应用系统的Action或许还有些共同的处理工作,不如实现一个JertActionSupport。
[code:1]public abstract class JertActionSupport extends ActionSupport implements Protected {
protected SecurityService securityService;

    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
}
}[/code:1]

package: core.webwork.converter

DateConverter和LocaleConverter
1、Quake Wang在http://forum.javaeye.com/viewtopic.php?t=10507有详细的解释。
2、应用:
1)core包:xwork-default-conversion.properties
[code:1]java.util.Date=com.javaeye.core.webwork.converter.DateConverter
java.util.Locale=com.javaeye.core.webwork.converter.LocaleConverter[/code:1]
2)user包:无
3)jert包:无。由于引用core,jert默认使用了xwork-default-conversion.properties定义的类型转化。

package: core.webwork.interceptor
1、定义了四个拦截器:
ComponentAutowireInterceptor
调用Container的autowireComponent(invocation.getAction())方法,这样可以按照Action中属性名称自动为Action装配协作对象(多半是一些在applicationContext中定义的Service, Manager等)。最终的效果是:在xwork.xml中定义的Action不再需要编写<external-ref>为Action指定外部的属性引用。

ExceptionInterceptor:异常拦截器
发生异常(Exception)时:
1)、向log中记录异常信息。
2)、记录StackTrace,准备在异常页上显示StackTrace。
ExceptionInterceptor中:记录StackTrace
  [code:1]invocation.getInvocationContext().put("_exception_string_", sw.toString());[/code:1]

exception.ftl中:显示StackTrace
[code:1]<pre>
${stack.context["_exception_string_"]}
</pre>[/code:1]

ParametersInterceptor:参数拦截器
    1)在Action执行之前,将参数放到ValueStack中。
2)将以“Id”结尾的参数放到ValueStack的前面,这样可以先执行Action中SetXxxId(Long xxxId)方法。(Xxx表示某个实体类)。
3)在Action的SetXxxId(Long xxxId)方法中,可以这样编码:
public void SetXxxId(Long xxxId) {
this.xxxId = xxxId;
this.xxx = xxxService.getXxx(xxxId);
}
这时候,相应的实体对象被实例化。以后的程序(如其它Xxx的属性的Getter and Setter)就可以使用这个对象了。
4)com.javaeye.core.webwork.interceptor.ParametersInterceptor取代com.opensymphony.xwork.interceptor.ParametersInterceptor,应跟踪xwork的ParametersInterceptor最新版本,core0.2版的ParametersInterceptor比xwork的旧,应更新一下。

PermissionInterceptor:权限拦截器。前面已经讨论过。


2、配置
在jert的webwork-default.xml(被xwork.xml引用)中
[code:1]<interceptors>
。。。
            <interceptor name="component-autowire" class="com.javaeye.core.webwork.interceptor.ComponentAutowireInterceptor"/>
            <interceptor name="params" class="com.javaeye.core.webwork.interceptor.ParametersInterceptor"/>
            <interceptor name="permission" class="com.javaeye.core.webwork.interceptor.PermissionInterceptor"/>
            <interceptor name="exception" class="com.javaeye.core.webwork.interceptor.ExceptionInterceptor"/>           
。。。
            <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="login"/>
                <interceptor-ref name="static-params"/>
                <interceptor-ref name="component-autowire"/>
<interceptor-ref name="user-prefs"/>
                <interceptor-ref name="params"/>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="permission"/>
            </interceptor-stack>
。。。
        </interceptors>

。。。

<global-results>
            <result name="login" type="redirect">/viewLogin.action</result>
            <result name="nopermission">/nopermission.ftl</result>
            <result name="exception">/exception.ftl</result>
        </global-results>[/code:1]
   
0 请登录后投票
最后更新时间:2006-09-12
package: core.webwork.validator

RegexpFieldValidator
1、用一个正则表达式校验字符串类型的字段。
2、extends FieldValidatorSupport

JavaScriptRegexpFieldValidator
1、在IE中用javascript对字符串类型的字段进行正则表达式校验。
2、extends RegexpFieldValidator

配置:
core包的validators.xml中
[code:1]<validators>
    <validator name="required" class="com.opensymphony.xwork.validator.validators.RequiredFieldValidator"/>
    <validator name="requiredstring" class="com.opensymphony.webwork.validators.JavaScriptRequiredStringValidator"/>
    <validator name="int" class="com.opensymphony.webwork.validators.JavaScriptIntRangeFieldValidator"/>
    <validator name="date" class="com.opensymphony.webwork.validators.JavaScriptDateRangeFieldValidator"/>
    <validator name="expression" class="com.opensymphony.xwork.validator.validators.ExpressionValidator"/>
    <validator name="fieldexpression" class="com.opensymphony.xwork.validator.validators.FieldExpressionValidator"/>
    <validator name="email" class="com.opensymphony.webwork.validators.JavaScriptEmailValidator"/>
    <validator name="url" class="com.opensymphony.webwork.validators.JavaScriptURLValidator"/>
    <validator name="visitor" class="com.opensymphony.webwork.validators.JavaScriptVisitorFieldValidator"/>
    <validator name="conversion" class="com.opensymphony.xwork.validator.validators.ConversionErrorFieldValidator"/>
    <validator name="stringlength" class="com.opensymphony.xwork.validator.validators.StringLengthFieldValidator"/>
    <validator name="regexp" class="com.javaeye.core.webwork.validator.RegexpFieldValidator"/>
</validators>[/code:1]

package: core.webwork.views.freemarker

FreemarkerResult
1、com.javaeye.core.webwork.views.freemarker.FreemarkerResult是jert的默认结果类型(result type)。见jert的webwork-default.xml
<result-type name="freemarker" class="com.javaeye.core.webwork.views.freemarker.FreemarkerResult" default="true"/>
2、extends com.opensymphony.webwork.views.freemarker.FreemarkerResult
3、查看com.opensymphony.webwork.views.freemarker.FreemarkerResult源码,其核心是createModel()创建的TemplateModel。查看
[code:1]ScopesHashModel model = FreemarkerManager.getInstance().buildScopesHashModel(servletContext, request, response, wrapper);

FreemarkerManager.getInstance().populateContext(model, invocation.getStack(), invocation.getAction(), request, response);[/code:1]
    可以推测TemplateModel的标量有:Application、JspTaglibs、Session、Request(HttpRequestHashModel)、req、res、stack、ognl、webwork、action
4、com.javaeye.core.webwork.views.freemarker.FreemarkerResult又添加了两个Scope(标量):base和ww。
5、示例:listDatabases。webwork-application.xml中
        [code:1]<action name="listDatabases" class="com.javaeye.jert.action.admin.database.BaseDatabaseAction">
            <result name="success">/admin/database/listDatabases.ftl</result>
        </action>[/code:1]

listDatabases.ftl
[code:1]<h3>${action.getText("database.list")}</h3>
<table width="100%">
<tr>
<th>${action.getText("database.name")}</th>
<th>${action.getText("database.driver")}</th>
<th>${action.getText("database.url")}</th>
<th>${action.getText("operations")}</th>
</tr>
<#list databaseService.databases as db>
<tr>
<td>${db.name}</td>
<td>${db.driver}</td>
<td>${db.url}</td>
<td><a href="viewUpdateDatabase.action?databaseId=${db.id}">${action.getText("update")}</a> | <a href="deleteDatabase.action?databaseId=${db.id}" onclick="if(confirm('${action.getText("confirm.delete.database")}')){return true;}else{return false;}">${action.getText("delete")}</a></td>
</tr>
</#list>
<tr>
<td colspan="4"><a href="viewCreateDatabase.action">${action.getText("create.new.database")}</a></td>
</tr>
</table>[/code:1]
简单解释一下:${action.getText(…)}
(1)ActionSupport的getText()方法,从properties文件中读取文本(前面有说明)。
(2)对于listDatabases,查找的properties文件是BaseDatabaseAction_zh_CN.properties
[code:1]database.sample.list=\u4F8B\u5B50\u5217\u8868
database.list=\u6570\u636E\u5E93\u5217\u8868
database.name=\u6570\u636E\u5E93\u540D\u79F0
database.driver=\u9A71\u52A8
database.url=\u8FDE\u63A5\u5B57\u7B26\u4E32
database.username=\u7528\u6237\u540D
database.password=\u5BC6\u7801

database.name.required=\u8BF7\u8F93\u5165\u6570\u636E\u5E93\u540D\u79F0
database.driver.required=\u8BF7\u8F93\u5165\u6570\u636E\u5E93\u7684\u9A71\u52A8
database.url.required=\u8BF7\u8F93\u5165\u8FDE\u63A5\u5B57\u7B26\u4E32
database.username.required=\u8BF7\u8F93\u5165\u7528\u6237\u540D

create.new.database=\u521B\u5EFA\u65B0\u6570\u636E\u5E93
update.database=\u66F4\u65B0\u6570\u636E\u5E93

confirm.delete.database=\u5220\u9664\u6570\u636E\u5E93\u7684\u540C\u65F6\u4E5F\u4F1A\u5220\u9664\u8FD9\u4E2A\u6570\u636E\u5E93\u4E0B\u6240\u6709\u7684\u62A5\u8868\uFF0C\u4F60\u786E\u5B9A\u5417\uFF1F

error.same.name.db.exists=\u4E00\u4E2A\u76F8\u540C\u540D\u5B57\u7684\u6570\u636E\u5E93\u5DF2\u7ECF\u5B58\u5728\uFF0C\u8BF7\u4FEE\u6539[/code:1]
   
0 请登录后投票
最后更新时间:2006-09-12
user篇
(user的版本是0.2)

package: user

User、Group、UserManager和它们的实现类
1、User和Group是两个接口,它们的实现类(是实体类/持久类)是双向多对多关联。
2、UserManager是User和Group持久管理接口,即CRUD。
3、user\impl\hibernate包中是User、Group、UserManager的hibernate实现。
4、user\impl\memory包中是User、Group、UserManager的memory实现,用Map模拟数据库。
   1)没有看到在哪里使用memory方式?
   2)是不是受OSWorkflow中MemoryWorkflowStore的影响?
5、jert的应用:
   1)jert中Java类只使用了User、Group、UserManager的接口,与它们的实现无关。
   2)在application_context.xml配置了UserManager的hibernate实现。

DuplicatePartyException
1)extends Exception,表明它是一个checked exception。
2)应用:保存User、Group时,在发生名称重复情况下抛出。

package: user.util
RemoteUser
1、将当前登录用户保存到一个ThreadLocal中,供应用程序使用。
2、应用:
   user包:LoginInterceptor中,在登录成功的情况下将当前用户保存到RemoteUser中。
   jert包:很多action都使用RemoteUser.get()获取当前用户。

package: user.webwork.action

Anonymous
1、是一个标记接口。
2、可以匿名的Action实现这个接口。
3、应用:
   user包:
1)LoginInterceptor中,只要拦截的Action实现了Anonymous接口,即使未登录的情况下也可以继续执行Action。
2)com.javaeye.user.webwork.action.BaseAction实现Anonymous接口。
3)LoginAction和LogoutAction继承BaseAction,表明这两个Action可以在匿名情况下使用。

   jert包:
1)com.javaeye.jert.action.setup.BaseSetupAction实现Anonymous接口。
2)CreateAdminAccount继承BaseSetupAction,表明匿名情况下,可以创建AdminAccount。

BaseAction
1、extends ActionSupport implements Anonymous
2、两个子类:LoginAction和LogoutAction。

LoginAction
1、extends BaseAction
2、主干逻辑:按照录入的username取User,如果找到并且密码正确,则将登录用户放到Session中。
3、originalURL功能:很别致!在LoginInterceptor中详细描述。

LogoutAction
1、extends BaseAction
2、清除Session。转到/logout.ftl

package: user.webwork.interceptor

LoginInterceptor
1、implements Interceptor
2、拦截器逻辑描述:
   1)如果登录成功:RemoteUser.set(user),然后执行被拦截的action。
   2)如果未登录:若Action是可以匿名执行的(实现Anonymous接口),可以继续执行被拦截的action。
   3)如果未登录,并且Action不是匿名的:必须先登录!但是为了实现登录成功后再次执行当前的Action,需要记录originalURL。具体如下:
(1)首先,产生originalURL,即buildOriginalURL():把参数变成 ?xx=xxx&xxx=xxxx这样的形式。
(2)然后,将originalURL记录到Session中。
(3)进入登录页面。这时由于在webwork-default.xml中:
[code:1] <global-results>
            <result name="login" type="redirect">/viewLogin.action</result>
。。。
        </global-results>[/code:1]
(4)登录成功后,继续执行原来的action。在webwork-default.xml中,login的action是这样定义的:
[code:1]<action name="login" class="com.javaeye.user.webwork.action.LoginAction">
<interceptor-ref name="validationStack"/>
<result name="success" type="redirect">${originalURL}</result>
<result name="input">/login.ftl</result>           
<result name="error">/login.ftl</result>
</action>[/code:1]
${originalURL}的值是什么?请看LoginAction中
[code:1]public String getOriginalURL() {
  return (String) ActionContext.getContext().getSession().remove(LoginInterceptor.ORIGINAL_URL);
}[/code:1]
${originalURL}是在LoginInterceptor中记录到Session中的originalURL,取出后将其清除。
   
0 请登录后投票
最后更新时间:2006-09-12
jert篇
(jert的版本是0.3)

对于jert包,我将采用按主题学习的办法。

主题1:用示例说明jert的请求处理流程

以listDatabases.action为例
启动jert,登录后,点击“管理员”的“数据库”链接,即执行http://localhost:8080/jert/admin/listDatabases.action

以下是以SiteMesh的角度阐述jert的请求处理流程:

1、当Servlet容器得到请求http://localhost:8080/jert/admin/listDatabases.action后,SiteMesh的PageFilter的doFilter()被调用。
    注:web.xml中设定了PageFilter是*.action的过滤器,因此其doFilter方法被调用。

2、PageFilter的doFilter()调用parsePage()方法,该方法捕捉chain.doFilter()的执行结果(即listDatabases这个action的结果),并将其解析为一个Page对象。
    注:chain.doFilter()的执行过程比较复杂,后面详细说明。对于这个示例来说,结果是以“/admin/database/listDatabases.ftl”为模板,FreeMarker产生的网页片段,即数据库列表的HTML表达形式(保存到Page对象的body属性中)。

3、由DecoratorMapper来确定哪一个Decorator将被执行。Decorator decorator = factory.getDecoratorMapper().getDecorator(request, page);
    注:该示例的decorator是decorators.xml中<decorator name="admin" page="admin.dec">。

4、执行相应的decorator。即applyDecorator()。

5、RequestDispatcher转发请求给decorator指定的页面。对于本示例,即请求/decorators/admin.dec。

6、DefaultFreemarkerDecoratorServlet的preTemplateProcess()被调用,Page中的属性以及相关的context等信息被放入TemplateModel中。这样decorator就可以按照admin.dec的布局要求产生带有Page对象数据的完整网页。
    注:
    1)在web.xml中*.dec的Servlet是DefaultFreemarkerDecoratorServlet,因此它被调用。
    2)解释一下admin.dec中${body}:
      (1)在DefaultFreemarkerDecoratorServlet的基类FreemarkerDecoratorServlet中执行hash.put("body",body);
         (2)Decorator调用FreeMarker解释${body}。回顾上面的描述可知,${body}就是listDatabases.ftl的结果。

WebWork的Action处理流程:
上面SiteMesh流程第2步,PageFilter的parsePage()方法中执行chain.doFilter(),就是本流程的开始。

2a、com.opensymphony.webwork.dispatcher.ServletDispatcher的service()方法被调用。
    注:在web.xml中*.action的Servlet是com.opensymphony.webwork.dispatcher.ServletDispatcher,因此它被调用。

2b、service()方法首先设置encoding和locale,然后调用serviceAction()。

2c、serviceAction()中根据Action的名称、namespace等创建该Action的ActionProxy。

2d、ActionProxy在执行Action之前,先调用该Action的拦截器,然后执行Action。

2e、在webwork-application.xml中是这样定义listDatabases这个Action的:
[code:1]<action name="listDatabases" class="com.javaeye.jert.action.admin.database.BaseDatabaseAction">
<result name="success">/admin/database/listDatabases.ftl</result>
</action>[/code:1]
它规定Action执行成功后,用/admin/database/listDatabases.ftl来展现结果。
结果的类型是com.javaeye.core.webwork.views.freemarker.FreemarkerResult。

2f、FreemarkerResult的doExecute()方法被调用,该方法完成FreeMarker的核心功能:Template + data model = output
  - 其中Template是/admin/database/listDatabases.ftl
  - data model是ValueStackModel,内含action和一些环境变量。
  - template.process(model, getWriter()); 处理模板,其中getWriter()就是output。

2g、listDatabases.ftl 中<#list databaseService.databases as db>调用了BaseDatabaseAction中databaseService的getDatabases()方法,它返回一个List。FreeMarker根据listDatabases.ftl模板结合getDatabases()结果,产生数据库列表的HTML片段。
   
0 请登录后投票
最后更新时间:2006-09-12
很是佩服dotnet的细心 认真的态度。
   
0 请登录后投票
最后更新时间:2006-09-12
我看了jert的源码及dotnet兄的学习笔记,但还有一个问题没有弄清。它是怎么向action中装配那些协作对象的呢。我看了dotnet兄的学习笔记中提到,是ComponentAutowireInterceptor调用Container的autowireComponent。但这个ComponentAutowireInterceptor是什么时候被执行的呢,另外,执行之后是怎么样装配那些协作对象的呢
   
0 请登录后投票
论坛首页 Java版

跳转论坛:
JavaEye推荐