SpringMVC结合Freemarker在页面调用静态方法优化总结

来源:互联网 发布:极限挑战电影 知乎 编辑:程序博客网 时间:2024/05/08 08:38

概述

由于之前一直使用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里面进行封装,而不去进行手动的配置,这样大大减少了手动配置的麻烦,同时也简化了开发,当然也会有一些问题,下面也会进行讨论。

具体实现

  • 编写FreemarkerStaticModels类
    该类是对上面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<String> locations;    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";    private static Logger log = Logger.getLogger(FreemarkerMap.class);    public List<String> getLocations() {        return locations;    }    public void setLocations(List<String> 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<String> 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中,如果没有则会跳过,扫描包时会做限制扫描指定路径下的包,而不会全局扫描,防止因为项目太大,扫描时会比较慢,导致项目启动变慢,同时该方法也支持枚举内静态方法的调用。

  • Spring配置FreemarkerMap
    locations指定扫描包的路径:
    <bean id="freemarkerMap" class="com.test.freemarker.FreemarkerMap" factory-method="getInstance">        <property name="locations">            <list>                <value>com.test.common.*.b</value>                <value>com.test.util.*.a</value>            </list>        </property>    </bean>
  • 配置FreeMarkerViewResolver
    attributeMap指定freemarkerMap的引用
<!-- 要求视图使用FreeMarker模板,指定controller层返回的页面在webapp目录下进行访问,且为html页面-->      <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">          <property name="prefix">              <value>/</value>          </property>          <property name="suffix">              <value>.html</value>        </property>          <!-- 此处需要声明为utf-8编码,否则即使页面是utf-8编码,中文还是不能正常显示 -->          <property name="contentType" value="text/html;charset=UTF-8"></property>         <property name="attributesMap" ref="freemarkerMap"/>       </bean>
  • 页面读取方式
    页面直接使用类似于${(StaticUtil.get().name)!”}的读取方式来获取就行了,由于get()方法可以返回一个对象,freemarker可以直接.属性来获取对应属性的值,非常方便。

不足与改进

  • 不足
    其实这种实现方案自然不是最优的,肯定也会存在一些不足之处:
    1、如果一个包下只有一个静态类,要扫描整个包感觉会很笨;
    2、如果项目很庞大,包扫描时很有可能会有遗漏,除非有非常明确的包及分层结构;
    3、如果项目很庞大,包扫描可能也会导致项目启动变得很慢。

  • 优化
    可以考虑将原始的版本和目前版本进行整合,如果有的包下只有一个静态类,那么我把这个静态类放到配置文件中进行配置,如果明确某一个包下大部的类都是静态的,比如枚举包,那么可以直接将这个包通过包扫描的方式进行加载,整合之后也可以有效的解决两种方案上的不足。