由于之前一直使用Struts2+Spring开发项目,整合Freemarker时页面如果想直接调用静态方法,可以使用<#assign a= stack.findValue(‘@com.test.package.class@method’)>的方式获取静态方法调用的返回值,现在使用SpringMVC+Freemarker来开发项目时,由于惯性思维的缘故,也想在页面上直接调用静态方法,来获取静态方法的返回值。
现有方案
在网上找了一部分的相关资料,只找到类似的方案:http://www.cnblogs.com/yqweber/p/3992513.html,仔细阅读这个方案,大致的思路是会在项目resources下新建一个staticClass.properties文件,在文件内配置所有需要使用的静态类,然后新建一个FreemarkerStaticModels类继承HashMap,用于将所有的静态类配置封装成<静态类名,TemplateHashModel >的形式,然后在Spring配置文件中的对FreeMarkerViewResolver的attributeMap配置成这个FreemarkerStaticModels对象,完成静态方法的配置,页面上可以直接使用静态类名+静态方法名的方法来访问静态方法了。
该方法的明显不足
1、如果每次我需要新建一个静态类,就需要在配置文件中新增一个配置,感觉特别麻烦;
2、如果我在配置文件中配置的静态类包名不正确,会导致该静态方法加载异常;
3、如果静态类特别多,这个文件会添加很多配置,这样看起来也不是特别舒服,查找起来也不方便。
所以就在原有的基础上进行了修改,大致思路是考虑扫描相关的静态类所处的包,然后将所有的静态类在FreemarkerStaticModels里面进行封装,而不去进行手动的配置,这样大大减少了手动配置的麻烦,同时也简化了开发,当然也会有一些问题,下面也会进行讨论。
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.ui.ModelMap;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;
import freemarker.ext.beans.BeansWrapper;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModelException;
public class FreemarkerMap extends ModelMap implements BeanFactoryPostProcessor{
private static final long serialVersionUID = -4675940717727748450L;
private List locations;
private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
private static Logger log = Logger.getLogger(FreemarkerMap.class);
public List getLocations() {
return locations;
}
public void setLocations(List locations) {
this.locations = locations;
}
private FreemarkerMap(){}
private static volatile FreemarkerMap instance;
/**
* 懒加载模式
*/
public static FreemarkerMap getInstance(){
if(instance == null){
synchronized (FreemarkerMap.class) {
if(instance == null){
instance = new FreemarkerMap();
}
}
}
return instance;
}
/**
* 后置处理器重新postProcessBeanFactory方法,加载静态类的配置
*/
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
loadCheckClassMethods(locations);
}
/**
* 根据扫描包的配置
* 加载需要检查的方法
*/
private static void loadCheckClassMethods(List scanPackages) {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (String basePackage : scanPackages) {
if (StringUtils.isBlank(basePackage)) {
continue;
}
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN ;
try {
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
loadClassMethod(metadataReaderFactory, resource);
}
} catch (Exception e) {
log.error("初始化SensitiveWordInterceptor失败", e);
}
}
}
/**
* 加载资源,判断里面的方法
*
* @param metadataReaderFactory spring中用来读取resource为class的工具
* @param resource 这里的资源就是一个Class
* @throws IOException
*/
private static void loadClassMethod(MetadataReaderFactory metadataReaderFactory, Resource resource) throws IOException {
try {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (metadataReader != null) {
String className = metadataReader.getClassMetadata().getClassName();
try {
tryCacheMethod(className);
} catch (ClassNotFoundException e) {
log.error("检查" + className + "是否含有需要信息失败", e);
}
}
}
} catch (Exception e) {
log.error("判断类中的方法实现需要检测xxx失败", e);
}
}
/**
* 把action下面的所有method遍历一次,标记他们是否需要进行xxx验证
* 如果需要,放入cache中
*
* @param fullClassName
* @throws TemplateModelException
*/
private static void tryCacheMethod(String fullClassName) throws ClassNotFoundException, TemplateModelException {
Class> clz = Class.forName(fullClassName);
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
int mod = method.getModifiers();
if (Modifier.isStatic(mod)&&Modifier.isPublic(mod)) {
BeansWrapper beansWrapper = BeansWrapper.getDefaultInstance();
TemplateHashModel model = beansWrapper.getStaticModels();
log.debug("已加载"+clz.getName());
instance.put(clz.getSimpleName(), (TemplateHashModel)model.get(clz.getName()));
break;
}
}
}
}
该方法会扫描locations指定路径包及子包下的所有类,同时也支持模糊包匹配,如果该类下面有静态方法,则会将该类加入FreemarkerMap中,如果没有则会跳过,扫描包时会做限制扫描指定路径下的包,而不会全局扫描,防止因为项目太大,扫描时会比较慢,导致项目启动变慢,同时该方法也支持枚举内静态方法的调用。
<bean id="freemarkerMap" class="com.test.freemarker.FreemarkerMap" factory-method="getInstance">
<property name="locations">
<list>
<value>com.test.common.*.bvalue>
<value>com.test.util.*.avalue>
list>
property>
bean>
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="prefix">
<value>/value>
property>
<property name="suffix">
<value>.htmlvalue>
property>
<property name="contentType" value="text/html;charset=UTF-8">property>
<property name="attributesMap" ref="freemarkerMap"/>
bean>
不足
其实这种实现方案自然不是最优的,肯定也会存在一些不足之处:
1、如果一个包下只有一个静态类,要扫描整个包感觉会很笨;
2、如果项目很庞大,包扫描时很有可能会有遗漏,除非有非常明确的包及分层结构;
3、如果项目很庞大,包扫描可能也会导致项目启动变得很慢。
优化
可以考虑将原始的版本和目前版本进行整合,如果有的包下只有一个静态类,那么我把这个静态类放到配置文件中进行配置,如果明确某一个包下大部的类都是静态的,比如枚举包,那么可以直接将这个包通过包扫描的方式进行加载,整合之后也可以有效的解决两种方案上的不足。