`

JSTL/EL如何方便高效的访问Constants和CodeTable(存储于DB的应用系统变量)

阅读更多
之前只是简单的使用JSTL/EL进行输出,一般的思路很简单:retrieve data -> put to Request -> JSTL/EL


一直没太注意两个问题:

1、JSTL/EL官方上无法方便、直接的访问静态变量。


比如,我们定义了一个Constants类:
public class Constants implements Serializable {
	public static final String CONSTANT_A= "ABC";	
	...
}

我们并不能直接这样使用:
<c:out value="${Constants.CONSTANT_A}"/>


原因很简单:
1)这个Constants必须出现在某个scope,比如requestScope;
2)这个CONSTANT_A必须有一个getter方法,EL支持bean和map的规范

怎么办?


2、JSTL/EL如何才能简单的使用CodeTable(存储于DB的应用系统变量)?
应用系统变量几乎是无可避免的,好处大家都懂的。
一般人的思维肯定是:
1)提供一个service,拥有若干方法,比如getCodesByType(String codeType);
2)用的时候get出来,然后put到request上面,最后在JSP中用JSTL/EL来取出

最后的用法,以Spring MVC的tag为例,一般是:
<form:select path="gender">
  <form:option value="-" label="--Please Select"/>
  <form:options items="${CodeTable.Gender}" itemValue="codeValue" itemLabel="codeName"/>
</form:select>

此时表示要获取Gender的CodeTable,并以codeValue为值,codeName为Label

是否存在更简单有效的方法?


我目前正在整合一些信息并加以模式化,试图提供一个简洁有效的办法来达成目标,也希望大家参与讨论,提供“一站式”的解决方案

分享到:
评论
25 楼 itstarting 2011-04-28  
haitaohehe 写道
楼主  看下你的代码bulidCodeTables()中map为空啊  并没有放值啊..



你说的对,这就是我认为比较有意思的设计之一:这里存放的只是KEY及其引用,以确保值是refreshable的。

这样的话,是否缓存及其缓存策略,都交给原来的service/dao来做(比如我所涉及的项目就是用了简单的iBatis LRU缓存,24小时自动flush),这里既然是轻量级的做法,就不要管这些了。

当然了,你如果愿意,也可以在这里考虑缓存,但此时需要考虑缓存的生命周期管理了
24 楼 fuyaner 2011-04-28  
itstarting 写道
之前只是简单的使用JSTL/EL进行输出,一般的思路很简单:retrieve data -> put to Request -> JSTL/EL


一直没太注意两个问题:

1、JSTL/EL官方上无法方便、直接的访问静态变量。


比如,我们定义了一个Constants类:
public class Constants implements Serializable {
	public static final String CONSTANT_A= "ABC";	
	...
}

我们并不能直接这样使用:
<c:out value="${Constants.CONSTANT_A}"/>


原因很简单:
1)这个Constants必须出现在某个scope,比如requestScope;
2)这个CONSTANT_A必须有一个getter方法,EL支持bean和map的规范

怎么办?


2、JSTL/EL如何才能简单的使用CodeTable(存储于DB的应用系统变量)?
应用系统变量几乎是无可避免的,好处大家都懂的。
一般人的思维肯定是:
1)提供一个service,拥有若干方法,比如getCodesByType(String codeType);
2)用的时候get出来,然后put到request上面,最后在JSP中用JSTL/EL来取出

最后的用法,以Spring MVC的tag为例,一般是:
<form:select path="gender">
  <form:option value="-" label="--Please Select"/>
  <form:options items="${CodeTable.Gender}" itemValue="codeValue" itemLabel="codeName"/>
</form:select>

此时表示要获取Gender的CodeTable,并以codeValue为值,codeName为Label

是否存在更简单有效的方法?


我目前正在整合一些信息并加以模式化,试图提供一个简洁有效的办法来达成目标,也希望大家参与讨论,提供“一站式”的解决方案


我们几年前是通过自定义tag,还有freemarker宏实现这种鸟功能。每个人只要放一个key进去,就能取出各种值:单值,列表,map之类的。
23 楼 haitaohehe 2011-04-28  
楼主  看下你的代码bulidCodeTables()中map为空啊  并没有放值啊..
22 楼 itstarting 2011-04-18  
downpour 写道

极不情愿地最后一次回你的贴。

看问题要看清问题的本质。你能说出“自己再维护一个ValueStack”这种话来,只能证明你对StrutsRequestWrapper的工作原理根本还不理解,也不知道Struts2为什么要这么做。

其实你的这个问题早在2004年就在Javaeye中讨论过,不过当时侧重点转向了Tag实现,直到Webwork2.2的发布,大家才发现RequestWrapper是最简单有效的方案。时代在进步,7年过去了,难道讨论还始终停留在7年前的水平么?



哈哈,终于把高手惹毛了。

我感觉我是理解RequestWrapper的用意的,既然在servlet API标准中就考虑到了这种设计,自然鼓励servlet容器或者大型的基础框架中适度采用装饰模式加以扩展,自然是不错的实践。

按照我的理解,采用RequestWrapper无非两点:
1、可以扩展自己的数据交换堆栈,即ValueStack或类似实现,这样自然可以有自己的存放、转换等更自由的扩展、实现;
2、根据某种约定来获取和交换数据


但是……对于我这种场景,各位大牛们确定我的实现是04年的水平而严重out了之举?

这样吧,如果还有大牛愿意浪费笔墨来点评,请:
1.直接了当说明我这种实现最烂最out的地方,以便一棍子打到04年乃至更早去;
2.也直接了当说明RequestWrapper的实现能确保跟上范冰冰2011年的春装,即便我不喜欢她。

另外说一下,我不用Struts2,我用Spring MVC 3

21 楼 xieke 2011-04-18  
itstarting 写道
标签?不行吧,有了custom的标签,如何用EL?

自己封装fn,我也觉得不好,或者说你在这方面有啥更具体的建议?


custom的标签完全可以和EL并存啊,甚至EL表达式可以放到自定义标签中作为一个参数。

个人认为为了扩展Jstl去wrapper request 的确有些傻
20 楼 downpour 2011-04-18  
itstarting 写道

极不情愿的下载了struts2,看了StrutsRequestWrapper的源码,两点感觉:
1.自己再维护一个ValueStack,不愿意也不会这么干;
2.看到了这个类的Comment,不爽:
All Struts requests are wrapped with this class, which provides simple JSTL accessibility. This is because JSTL works with request attributes, so this class delegates to the value stack except for a few cases where required to prevent infinite loops.
你们自己看看这个because

如果为了这个JSTL还要自己扩展request wrapper,感觉太委屈了自己——没必要这么找罪受吧我想


抱歉downpour,咱的境界也许没到你那个层次,咱只会干些投机取巧的事情(好像有这种感觉),总是觉得简单明了的解决方案就是最好的


极不情愿地最后一次回你的贴。

看问题要看清问题的本质。你能说出“自己再维护一个ValueStack”这种话来,只能证明你对StrutsRequestWrapper的工作原理根本还不理解,也不知道Struts2为什么要这么做。

其实你的这个问题早在2004年就在Javaeye中讨论过,不过当时侧重点转向了Tag实现,直到Webwork2.2的发布,大家才发现RequestWrapper是最简单有效的方案。时代在进步,7年过去了,难道讨论还始终停留在7年前的水平么?
19 楼 itstarting 2011-04-17  
downpour 写道


你对HttpServletRequestWrapper的作用还不理解,所以我才让你去看StrutsRequestWrapper的源码。

你的需求是试图通过JSTL或者EL来获取Java对象中的值。途径只有两种:第一,把你要取的值放到request/session/application中。第二,不要从request/session/application中去取值。

之前所有讨论的思路都是第一种途径,这种途径其实没有从本质上解决你的问题,尤其是reloadable的需求。第二种途径是推荐的做法,既干净又有效。

我之所以推荐第二种方式,是因为你的问题其实和Struts2与JSTL整合的问题是极其类似的。JSTL和EL想要获取Struts2中Action内部的属性的值,通过什么方法呢?Action内部的值,完全不存在request/session/application的作用域中。Struts2给出的方案就是采用StrutsRequestWrapper。你可以思考一下,为什么一个框架级别的解决方案,也采用了这种方法呢?因为它是一种最佳实践。



极不情愿的下载了struts2,看了StrutsRequestWrapper的源码,两点感觉:
1.自己再维护一个ValueStack,不愿意也不会这么干;
2.看到了这个类的Comment,不爽:
All Struts requests are wrapped with this class, which provides simple JSTL accessibility. This is because JSTL works with request attributes, so this class delegates to the value stack except for a few cases where required to prevent infinite loops.
你们自己看看这个because

如果为了这个JSTL还要自己扩展request wrapper,感觉太委屈了自己——没必要这么找罪受吧我想


抱歉downpour,咱的境界也许没到你那个层次,咱只会干些投机取巧的事情(好像有这种感觉),总是觉得简单明了的解决方案就是最好的
18 楼 downpour 2011-04-17  
itstarting 写道
downpour 写道
最好的方案应该是使用包装模式,把HttpServletRequest包装起来,并在包装类中定义特殊的表达式形式和值获取方式来返回结果。

请参照Struts2中的StrutsRequestWrapper是怎么处理JSTL标签对ValueStack的取值方式的。

这种做法也太笨重了吧?

难道其他引用request的地方也要考虑使用自己的request wrapper以求得一劳永逸?
我见过重新封装request,session,application的,但需求场景应该严重不一样。

还请多加指点


你对HttpServletRequestWrapper的作用还不理解,所以我才让你去看StrutsRequestWrapper的源码。

你的需求是试图通过JSTL或者EL来获取Java对象中的值。途径只有两种:第一,把你要取的值放到request/session/application中。第二,不要从request/session/application中去取值。

之前所有讨论的思路都是第一种途径,这种途径其实没有从本质上解决你的问题,尤其是reloadable的需求。第二种途径是推荐的做法,既干净又有效。

我之所以推荐第二种方式,是因为你的问题其实和Struts2与JSTL整合的问题是极其类似的。JSTL和EL想要获取Struts2中Action内部的属性的值,通过什么方法呢?Action内部的值,完全不存在request/session/application的作用域中。Struts2给出的方案就是采用StrutsRequestWrapper。你可以思考一下,为什么一个框架级别的解决方案,也采用了这种方法呢?因为它是一种最佳实践。
17 楼 itstarting 2011-04-16  
downpour 写道
最好的方案应该是使用包装模式,把HttpServletRequest包装起来,并在包装类中定义特殊的表达式形式和值获取方式来返回结果。

请参照Struts2中的StrutsRequestWrapper是怎么处理JSTL标签对ValueStack的取值方式的。

这种做法也太笨重了吧?

难道其他引用request的地方也要考虑使用自己的request wrapper以求得一劳永逸?
我见过重新封装request,session,application的,但需求场景应该严重不一样。

还请多加指点
16 楼 downpour 2011-04-15  
最好的方案应该是使用包装模式,把HttpServletRequest包装起来,并在包装类中定义特殊的表达式形式和值获取方式来返回结果。

请参照Struts2中的StrutsRequestWrapper是怎么处理JSTL标签对ValueStack的取值方式的。
15 楼 itstarting 2011-04-15  
haitaohehe 写道
希望楼主多分享类似于这样有价值的文章。。。 实用

咱是实战派的,呵呵

不过这个帖子响应率超级低,可见大家应该有其他更好的方案
14 楼 haitaohehe 2011-04-14  
希望楼主多分享类似于这样有价值的文章。。。 实用
13 楼 itstarting 2011-01-05  
kongzhizhen 写道
为啥不在启动的时候就加载存放在Application Scope呢?
或者以内存表的方式加载并存储。



不是这个问题,或者说这不是问题,我们要解决和讨论的是其他问题:)
12 楼 kidneyball 2011-01-05  
itstarting 写道

当然,这两种方法的思路其实都一样:put到application scope里。
这里外加一点小技巧——对于code table,提供的是引用而非真实数据,确保reloadable.


用自定义ELResolver的好处是,它允许你在求值的瞬间,对求值过程拥有完全的支配权。在ELResolver的getValue方法中你可以拿到EL表达式被求值瞬间的实时上下文,当然包括访问数据库或拿到spring bean。坏处是实现起来相对麻烦,要实现好几个接口方法。

在系统启动期把值put到application scope的好处是实现简单,事实上是借用了求值过程不可控的ScopedAttributeELResolver。问题是put进去与实际求值两个行为之间有个时间差。而reload,就是在还可控的代码中不断地调整application scope中的东西,使得在不可控的求值过程中能获取到尽可能最新的上下文。

当然,我个人觉得还是实用至上,能满足当前需求的方案就是好方案。自定义ELResolver可以作为一种候选方案,等有一天确实application scope方案满足不了再考虑也不迟。
11 楼 kongzhizhen 2011-01-05  
为啥不在启动的时候就加载存放在Application Scope呢?
或者以内存表的方式加载并存储。
10 楼 itstarting 2011-01-05  
kidneyball 写道
既然是增强EL表达式的解释能力,添加一个ELResolver会更加自然。

大致思路是,在应用启动时(例如你上面的 initApplicationContext()方法中,或者一个load-on-startup的servlet的init方法里)用
JspFactory.getJspApplicationContext(javax.servlet.ServletContext).addELResolver(ELResorver resolver)

方法加入一个ELResolver的实现。(这个方法不能在应用已经处理过请求之后使用,否则会抛IllegalStateException)

这个ELResolver会被加入到JSP规范的默认的ImplicitObjectELResolver之后,其他规范ELResolver之前(例如BeanELResolver与ScopedAttributeELResolver)。也就是说,优先级低于隐含对象,高于scope属性。

在ELResolver中,是知道了EL表达式的实际内容(例如"Constants"字符串),再去求值。这时你可以用forName或者ClassLoader拿到Constants类(拿不到就不处理,留给后续的ELResolver处理,如果后续的Resolver也无法处理,容器就报错),这就没有reload的需要了。

详情可参考 http://download.oracle.com/docs/cd/E17802_01/products/products/jsp/2.1/docs/jsp-2_1-pfd2/javax/servlet/jsp/JspApplicationContext.html



谢谢指点

你说的这种方式看来是最“正统”的办法了——而我所提供的方法是更Spring-ly的

当然,这两种方法的思路其实都一样:put到application scope里。
这里外加一点小技巧——对于code table,提供的是引用而非真实数据,确保reloadable.
9 楼 kidneyball 2011-01-04  
既然是增强EL表达式的解释能力,添加一个ELResolver会更加自然。

大致思路是,在应用启动时(例如你上面的 initApplicationContext()方法中,或者一个load-on-startup的servlet的init方法里)用
JspFactory.getJspApplicationContext(javax.servlet.ServletContext).addELResolver(ELResorver resolver)

方法加入一个ELResolver的实现。(这个方法不能在应用已经处理过请求之后使用,否则会抛IllegalStateException)

这个ELResolver会被加入到JSP规范的默认的ImplicitObjectELResolver之后,其他规范ELResolver之前(例如BeanELResolver与ScopedAttributeELResolver)。也就是说,优先级低于隐含对象,高于scope属性。

在ELResolver中,是知道了EL表达式的实际内容(例如"Constants"字符串),再去求值。这时你可以用forName或者ClassLoader拿到Constants类(拿不到就不处理,留给后续的ELResolver处理,如果后续的Resolver也无法处理,容器就报错),这就没有reload的需要了。

详情可参考 http://download.oracle.com/docs/cd/E17802_01/products/products/jsp/2.1/docs/jsp-2_1-pfd2/javax/servlet/jsp/JspApplicationContext.html
8 楼 itstarting 2010-12-28  
xieke 写道
itstarting 写道
我目前的思路是:
1、对于Constants,我将自动在启动时导出为applicationScope的map;
2、对于Code Table,我将自动在启动时加载到一系列的map,以CodeType为key

那之后就可以用“标准”的EL语法透明的访问Constants和CodeTable了。


但在这里有一个问题,那就是对于Code Table而言,一旦“自动在启动时加载到一系列的map”,会不会导致这些Code Table是non-reloadable的——我想这是难以让人接受的——总不能改一下系统变量就要reload一次application吧?


该系统变量时 单独对 Map 里某个 key 作更新就好了,为何要reload.



你这种思路是:更改后马上更新,即时生效。

也是不错的思路

但需要remove掉原先application scope中,当前在改的key的map,然后再reload进来,感觉有点麻烦——会不会对原先的code table管理部分造成不必要的困扰?
7 楼 itstarting 2010-12-28  
itstarting 写道
而针对code table的解决方案,其实有不少技巧:
1、暴露到application scope的不是简单的数据,而是引用[color=red][/color]。这一点非常重要,也确保了我们的code table是reloadable的——而reload的策略,完全取决于业务的需要,比如我们用ibatis,可以简单的配置cache-model就行了,此时在DAO层cache,如要在service层,要专门考虑采用cache框架的API;
2、单独定义一个需要真正业务实现的接口,这样就变得更具通用性了。

public class ApplicationScopeLoader extends WebApplicationObjectSupport {
...
	
  /** code table service */
  private final CodeTableLoaderService codeTableLoaderService = ApplicationContextManager
		.getBean("codeTableLoaderService",CodeTableLoaderService.class);
	
  private final static String CODE_TABLE_CONTEXT_NAME = "CodeTable";

  protected void initApplicationContext() {
    try {
      //build constants and put it to the application scope
      this.buildConstants();
			
      //build code table and put it to the application scope
      this.buildCodeTables();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
	
  /**
   * build code tables
   * each code table will be group by the type
   * for example, if there is a code table type 'gender'
   * then will be put to 
   */
  private void buildCodeTables(){
    String[] codeTypes = codeTableLoaderService.loadAllCodeTypes();
	for(String codeType: codeTypes){
		this.buildOneCodeTableByCodeType(codeType);
	}
  }

  /**
   * extract given codes to a sub array of codes by code type
   * @param codes
   * @return
   */
  private void buildOneCodeTableByCodeType(String codeType){
	Map<Object,Object> codeMap = new HashMap<Object,Object>() {
            private static final long serialVersionUID = -1759893921358235848L;
            
            /**
             * get from the service, not raw data for avoiding non-refresh issue
             */
            public Object get(Object key) {
                return codeTableLoaderService.getCodesByType(String.valueOf(key));
            }

            public boolean containsKey(Object key) {
                return true;
            }
        };
		
	logger.debug("put code table of [{}] to application scope.", codeType);
	this.getServletContext().setAttribute(CODE_TABLE_CONTEXT_NAME, codeMap);
  }
}


接口定义:
/**
 * It's a code table loading service 
 * for exporting code tables to application scope
 * 
 * but we don't put the dummy data to application scope directly
 * for avoiding non-refreshable issue
 * 
 * @author bright_zheng
 *
 */
public interface CodeTableLoaderService {

	/**
	 * load all code types from db/cache
	 * 
	 * @return an array including all code types
	 */
	public String[] loadAllCodeTypes();
	
	/**
	 * get all code objects/beans filter by code type
	 * 
	 * @param codeType
	 * @return responding code objects/beans
	 */
	public Object[] getCodesByType(String codeType);
}



为此,解决方案提供完毕。

当然了,关于Constants和Code table是整合考虑的方案,我分开了描述,只是为了阐述更有针对性而已
大家整合两部分的代码即可

欢迎讨论



补充用法:
1、在HTLM元素中使用:
--这里用了Spring的TAG,当然谁的TAG并不是关键
<form:select path="hotel.country">
 <form:option value="-" label="--Please Select"/>
 <form:options items="${CodeTable.Gender}" itemValue="codeValue" itemLabel="codeName"/>
</form:select>

这里表示要以Code Type为Gender的作为下拉框元素

2、用以打印:
<c:forEach var="ct" items="${CodeTable.Gender}">
  -->codeName: ${ct.codeName},  
  -->codeValue: ${ct.codeValue},  
  -->codeDesc: ${ct.codeDesc}<br> 
</c:forEach> 



对了,我这里为了方便,封装了个简单的CodeTable POJO,一并贴出来:
public class CodeTable implements Serializable {
  private String codeType;
  private String codeName;
  private String codeValue;
  private String codeDesc;
  
  //getter/setter omitted
  ...
}
6 楼 xieke 2010-12-28  
itstarting 写道
我目前的思路是:
1、对于Constants,我将自动在启动时导出为applicationScope的map;
2、对于Code Table,我将自动在启动时加载到一系列的map,以CodeType为key

那之后就可以用“标准”的EL语法透明的访问Constants和CodeTable了。


但在这里有一个问题,那就是对于Code Table而言,一旦“自动在启动时加载到一系列的map”,会不会导致这些Code Table是non-reloadable的——我想这是难以让人接受的——总不能改一下系统变量就要reload一次application吧?


该系统变量时 单独对 Map 里某个 key 作更新就好了,为何要reload.

相关推荐

Global site tag (gtag.js) - Google Analytics