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

概述

由于之前一直使用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 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中,如果没有则会跳过,扫描包时会做限制扫描指定路径下的包,而不会全局扫描,防止因为项目太大,扫描时会比较慢,导致项目启动变慢,同时该方法也支持枚举内静态方法的调用。

  • Spring配置FreemarkerMap
    locations指定扫描包的路径:
    <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>
  • 配置FreeMarkerViewResolver
    attributeMap指定freemarkerMap的引用
  
    <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>
  • 页面读取方式
    页面直接使用类似于${(StaticUtil.get().name)!”}的读取方式来获取就行了,由于get()方法可以返回一个对象,freemarker可以直接.属性来获取对应属性的值,非常方便。

不足与改进

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

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

你可能感兴趣的:(java)