11月08, 2017

SpringBoot下配置FreeMarker配置远程模版

需求产生原因

  • 要求在同一个接口中,根据不同的参数,返回不同的视图结果
  • 所有的视图中的数据基本一致
  • 要求页面能静态化,优化SEO

例如:A接口返回客户的信息

  1. 客户A在调用接口时,返回其个性化定制的页面A
  2. 客户B在调用这个接口时,返回其个性化主页B

实现方式 freemaker 的 TemplateLoader

freemaker的配置类freemarker.template.Configuration中提供了一个配置模版加载器的方法setTemplateLoader,需求是要求同时能加载本地和远程的模版,但是只提供了一个模版加载器的set方法,查询文档后官方给出了建议 //远程模版加载 RemoteTemplateLoader remoteTemplateLoader = new RemoteTemplateLoader(remotePath); //本地模版加载 ClassTemplateLoader classTemplateLoader = new ClassTemplateLoader(getClass(), "/WEB-INF/pages/"); MultiTemplateLoader templateLoader = new MultiTemplateLoader(new TemplateLoader[] {classTemplateLoader,remoteTemplateLoader});

  • SpringBoot配置
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.TemplateDirectiveModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.Set;

/**
 * Freemarker配置
 * @author wpy
 * @create 2017/11/8 14:51
 * @project_name jcstore
 */
@Configuration
public class FreemarkerConfig {

    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    @Autowired
    private WebApplicationContext applicationContext;

//    @Value("")
    private String remotePath = "http://localhost:8080/static";

    @PostConstruct
    public void freeMarkerConfigurer() {
        freemarker.template.Configuration configuration = freeMarkerConfigurer.getConfiguration();

        //注册所有自定义标签
        Map<String, TemplateDirectiveModel> tagsMap = applicationContext.getBeansOfType(TemplateDirectiveModel.class);
        Set<Map.Entry<String, TemplateDirectiveModel>> entries = tagsMap.entrySet();
        entries.forEach(entry ->
            configuration.setSharedVariable(entry.getKey(), entry.getValue())
        );

        //远程模版加载
        RemoteTemplateLoader remoteTemplateLoader = new RemoteTemplateLoader(remotePath);
        //本地模版加载
        ClassTemplateLoader classTemplateLoader = new ClassTemplateLoader(getClass(), "/WEB-INF/pages/");
        MultiTemplateLoader templateLoader = new MultiTemplateLoader(new TemplateLoader[] {classTemplateLoader,remoteTemplateLoader});

        configuration.setTemplateLoader(templateLoader);
    }

}
  • RemoteTemplateLoader 实现
import freemarker.cache.URLTemplateLoader;

import java.net.URL;
import java.net.URLConnection;

/**
 * 自定义远程模板加载器,用来加载文件服务
 *
 * @author Administrator
 */
public class RemoteTemplateLoader extends URLTemplateLoader {
    // 远程模板文件的存储路径(目录)
    private String remotePath;

    public RemoteTemplateLoader(String remotePath) {
        if (remotePath == null) {
            throw new IllegalArgumentException("remotePath is null");
        }
        this.remotePath = canonicalizePrefix(remotePath);
        if (this.remotePath.indexOf('/') == 0) {
            this.remotePath = this.remotePath.substring(this.remotePath
                    .indexOf('/') + 1);
        }
    }

    @Override
    protected URL getURL(String name) {
        String fullPath = this.remotePath + name;
        if ((this.remotePath.equals("/")) && (!isSchemeless(fullPath))) {
            return null;
        }
        if (name.contains("WEB-INF/template/")) {
            fullPath = fullPath.replace("WEB-INF/template/", "");
        }
        URL url = null;
        try {
            url = new URL(fullPath);
            URLConnection con = url.openConnection();
            long lastModified = con.getLastModified();
            if (lastModified == 0) {
                url = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            url = null;
        }
        return url;
    }

    private static boolean isSchemeless(String fullPath) {
        int i = 0;
        int ln = fullPath.length();

        if ((i < ln) && (fullPath.charAt(i) == '/'))
            i++;

        while (i < ln) {
            char c = fullPath.charAt(i);
            if (c == '/')
                return true;
            if (c == ':')
                return false;
            i++;
        }
        return true;
    }
}
  • 自定义标签实现
/**
 * 自定义标签示例
 *
 * <table style="width: 633px; height: 217px;" border="0">
 <tbody>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;"><strong>类型</strong></span></td>
 <td style="text-align: center;"><span style="font-size: 13px;"><strong>FreeMarker接口</strong></span></td>
 <td style="text-align: center;"><span style="font-size: 13px;"><strong>FreeMarker实现</strong></span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">字符串</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateScalarModel</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">SimpleScalar</span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">数值</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateNumberModel</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">SimpleNumber</span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">日期</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateDateModel</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">SimpleDate</span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">布尔</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateBooleanModel</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateBooleanModel.TRUE</span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">哈希</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateHashModel</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">SimpleHash</span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">序列</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateSequenceModel</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">SimpleSequence</span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">集合</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">TemplateCollectionModel</span></td>
 <td style="text-align: left;"><span style="font-size: 13px;">SimpleCollection</span></td>
 </tr>
 <tr>
 <td style="text-align: center;"><span style="font-size: 13px;">节点</span></td>
 <td><span style="font-size: 13px;">TemplateNodeModel</span></td>
 <td><span style="font-size: 13px;">NodeModel</span></td>
 </tr>
 </tbody>
 </table>
 * @author wpy
 * @create 2017/11/8 14:34
 * @project_name jcstore
 */
@Component
public class ExampleTag implements TemplateDirectiveModel {

    /**
     * 标签中的参数 name
     */
    private static final String NAME = "name";
    /**
     *
     */
    private static final String AGE = "age";
    /**
     *
     */
    private static final String SEX = "sex";

    private DefaultObjectWrapper objectWrapper;
    {
        Version version = new Version("2.3.21");
        DefaultObjectWrapperBuilder defaultObjectWrapperBuilder = new DefaultObjectWrapperBuilder(version);
        objectWrapper = defaultObjectWrapperBuilder.build();
    }

    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {

        TemplateScalarModel name = (TemplateScalarModel) params.get("name");
        TemplateNumberModel age = (TemplateNumberModel) params.get("age");
        TemplateScalarModel sex = (TemplateScalarModel) params.get("sex");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name",name.getAsString() + 1);
        jsonObject.put("age",age.getAsNumber().intValue() + 1);
        jsonObject.put("sex",sex.getAsString().equals("男") ? "女":"男");

        TemplateModel wrap = objectWrapper.wrap(jsonObject);
        if(loopVars.length > 0){
            loopVars[0] = wrap;
        }
        body.render(env.getOut());
    }
}
  • 模版 exampleTag.ftl
<html>
    <head></head>
    <body>
        <h1> 自定义标签测试(我是远程模版)</h1>
        <p>
            <@exampleTag name = "张三" age = 18 sex = "男"; loopv>
                <h1>${loopv.name}</h1>
                <h1>${loopv.age}</h1>
                <h1>${loopv.sex}</h1>
            </@exampleTag>
         </p>
    </body>

</html>
  • CustomTagController
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author wpy
 * @create 2017/11/8 15:02
 * @project_name jcstore
 */
@Controller
public class CustomTagController {

    @RequestMapping("/testTags")
    public String testTags(){
        return "/common/exampleTag";
    }

    @RequestMapping("/testTagsRemote")
    public String testTagsRemote(){
        return "/exampleTag";
    }
}

本文链接:https://www.qiangshuidiyu.xin/post/2017-11-08.html

-- EOF --

Comments