论坛首页 Java企业应用论坛

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

浏览 6827 次
精华帖 (1) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-12-20   最后修改:2010-12-20
之前只是简单的使用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

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


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

   发表时间:2010-12-20  
自己封一个标签,或者封一个fn,这是最简单的办法。
0 请登录后投票
   发表时间:2010-12-20  
标签?不行吧,有了custom的标签,如何用EL?

自己封装fn,我也觉得不好,或者说你在这方面有啥更具体的建议?
0 请登录后投票
   发表时间:2010-12-20  
我目前的思路是:
1、对于Constants,我将自动在启动时导出为applicationScope的map;
2、对于Code Table,我将自动在启动时加载到一系列的map,以CodeType为key

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


但在这里有一个问题,那就是对于Code Table而言,一旦“自动在启动时加载到一系列的map”,会不会导致这些Code Table是non-reloadable的——我想这是难以让人接受的——总不能改一下系统变量就要reload一次application吧?
0 请登录后投票
   发表时间:2010-12-28  
基于上述思路,针对Constants的解决方案如下:
...
public class ApplicationScopeLoader extends WebApplicationObjectSupport {

	private static final Logger logger = LoggerFactory.getLogger(ApplicationScopeLoader.class);
	
	/** constant classes which can be injected by spring */
	private Map<String, String> constantClass;

	protected void initApplicationContext() {
		try {
			//build constants and put it to the application scope
			this.buildConstants();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * build constants map
	 * 
	 * @return constants map
	 * @throws ClassNotFoundException 
	 */
	private void buildConstants() throws ClassNotFoundException{
		Map<String, Object> result = new HashMap<String, Object>();
		for(Map.Entry<String, String> map: constantClass.entrySet()){
			//the constant group name
			String k = map.getKey();
			//the class
			String v = map.getValue();
			Class<?> clazz = this.getClass().getClassLoader().loadClass(v);
			Field[] fields = clazz.getDeclaredFields();
			for (int i = 0; i < fields.length; i++) {
				Field field = fields[i];
				int modifier = field.getModifiers();
				//must be final & public
				if (Modifier.isFinal(modifier) && !Modifier.isPrivate(modifier))
					try {
						String name = field.getName();
						Object value = field.get(this);
						logger.debug("building constants{}: name={},value={}", 
								new Object[]{k, name,value});
						result.put(name, value);
					} catch (IllegalAccessException e) {
					}
			}

			logger.info("put constant [{}] to application scope.", k);
			this.getServletContext().setAttribute(k, result);
		}
	}

	public Map<String, String> getConstantClass() {
		return constantClass;
	}

	public void setConstantClass(Map<String, String> constantClass) {
		this.constantClass = constantClass;
	}

}


配置如下,可以配置多个需要EL引用访问的类:
<bean name="applicationScopeLoader" class="xxx.utils.ApplicationScopeLoader" lazy-init="false">
  <property name="constantClass">
    <map>
      <entry key="Constants" value="xxx.framework.Constants"/>
      <!-- 
      <entry key ="Constants2" value="another.constants.class"/>
      -->
    </map>
  </property>
</bean>


用法如下:
${Constants.PAGE_SIZE_DEFAULT}


可以看到,此时可以简单的用标准的EL实现静态变量的访问。
0 请登录后投票
   发表时间:2010-12-28  
而针对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是整合考虑的方案,我分开了描述,只是为了阐述更有针对性而已
大家整合两部分的代码即可

欢迎讨论
0 请登录后投票
   发表时间: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.
0 请登录后投票
   发表时间: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
  ...
}
0 请登录后投票
   发表时间: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管理部分造成不必要的困扰?
0 请登录后投票
   发表时间:2011-01-04   最后修改: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
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics