blog5开发:页面数据和模板片段

现在页面已经抛弃了以前的页面挂件的方式,所谓挂件说白了就是一个可动态渲染的片段(可能还需要有一点数据)而已,现在将数据和片段分开了,但整体渲染流程还是一样的。
比如需要渲染首页,而首页模板为:
<!DOCTYPE html>
<html>
<head>
<title th:text="${space == null ? '主页' : space.name}"></title>
</head>
<body>
    <nav th:replace="base/nav"></nav>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-md-8  col-sm-8 text">
                <fragement name="文章列表" data-sort-lab="false" />
            </div>

            <div class="col-lg-4 col-md-4 col-sm-4 text">
                <fragement name="文章日期归档" />
                <fragement name="文章空间归档" />
                <fragement name="最近评论" />
            </div>
        </div>
    </div>

    <div th:replace="base/foot">&nbsp;</div>
    <div th:replace="base/foot_source">&nbsp;</div>

    <data name="文章列表" />
    <data name="文章日期归档" />
    <data name="文章空间归档" />
    <data name="最近评论" />
</body>
</html>
首先会去查询整个页面的模板,然后解析其中的data和fragement标签,根据不同的data标签查询不同的DataTagProcessor,如果存在,则查询数据。
public abstract class DataTagProcessor<T> {

    /**
     * 是否忽略逻辑异常
     */
    private static final String IGNORE_LOGIC_EXCEPTION = "ignoreLogicException";
    private static final String DATA_NAME = "dataName";

    private String name;// 数据名,全局唯一
    private String dataName;// 默认数据绑定名,页面唯一

    public DataTagProcessor(String name, String dataName) {
        this.name = name;
        this.dataName = dataName;
    }

    public final DataBind<T> getData(Space space, Params params, Map<String, String> attributes) throws LogicException {
        if (attributes == null)
            attributes = new HashMap<>();
        T result = null;
        try {
            result = query(space, params, attributes);
        } catch (LogicException e) {
            if (!ignoreLogicException(attributes)) {
                throw e;
            }
        }
        DataBind<T> bind = new DataBind<>();
        bind.setData(result);
        String dataNameAttV = attributes.get(DATA_NAME);
        if (!Validators.isEmptyOrNull(dataNameAttV, true)) {
            bind.setDataName(dataNameAttV);
        } else {
            bind.setDataName(dataName);
        }
        return bind;
    }

    private boolean ignoreLogicException(Map<String, String> attributes) {
        String v = attributes.get(IGNORE_LOGIC_EXCEPTION);
        if (v != null)
            try {
                return Boolean.parseBoolean(v);
            } catch (Exception e) {
            }
        return false;
    }

    /**
     * 构造预览用数据
     * 
     * @param attributes
     * @return
     */
    public final DataBind<T> previewData(Map<String, String> attributes) {
        if (attributes == null)
            attributes = new HashMap<>();
        T result = buildPreviewData(attributes);
        DataBind<T> bind = new DataBind<>();
        bind.setData(result);
        bind.setDataName(dataName);
        return bind;
    }

    /**
     * 获取测试数据
     * 
     * @return
     */
    protected abstract T buildPreviewData(Map<String, String> attributes);

    protected abstract T query(Space space,Params params, Map<String, String> attributes) throws LogicException;

    public String getName() {
        return name;
    }

    public String getDataName() {
        return dataName;
    }

}
其中,Params是从页面获取的数据,attributes是data标签所包含的属性,space为空间。
fragement标签用来放置片段,由thymeleaf负责解析和渲染:
public class FragementTagProcessor extends AbstractElementTagProcessor {

    private static final String TAG_NAME = "fragement";
    private static final String ATTRIBUTES = "attributes";
    private static final int PRECEDENCE = 1000;
    private static final String NAME = "name";

    public FragementTagProcessor(String dialectPrefix) {
        super(TemplateMode.HTML, // This processor will apply only to HTML mode
                dialectPrefix, // Prefix to be applied to name for matching
                TAG_NAME, // Tag name: match specifically this tag
                false, // Apply dialect prefix to tag name
                null, // No attribute name: will match by tag name
                false, // No prefix to be applied to attribute name
                PRECEDENCE); // Precedence (inside dialect's own precedence)
    }

    @Override
    protected final void doProcess(ITemplateContext context, IProcessableElementTag tag,
            IElementTagStructureHandler structureHandler) {
        RenderedPage page = UIContext.get();
        if (page != null) {
            IAttribute nameAtt = tag.getAttribute(NAME);
            if (nameAtt != null) {
                String name = nameAtt.getValue();
                Fragement fragement = page.getFragementMap().get(name);
                if (fragement != null) {
                    Attributes attributes = handleAttributes(tag);
                    if (attributes == null) {
                        attributes = new Attributes();
                    }
                    IEngineContext _context = (IEngineContext) context;
                    _context.setVariable(ATTRIBUTES, attributes);
                    try {
                        IModel model = context.getModelFactory().parse(context.getTemplateData(), fragement.getTpl());
                        Writer writer = new FastStringWriter(200);
                        context.getConfiguration().getTemplateManager().process((TemplateModel) model, _context,
                                writer);
                        structureHandler.replaceWith(writer.toString(), false);
                    } catch (Throwable e) {
                        throw new FragementTagParseException(e, name);
                    } finally {
                        _context.removeVariable(ATTRIBUTES);
                    }
                    return;
                }
            }
        }
        structureHandler.removeElement();
    }

    public static final class FragementTagParseException extends RuntimeException {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        private Throwable originalThrowable;
        private String fragement;

        public FragementTagParseException(Throwable originalThrowable, String fragement) {
            this.originalThrowable = originalThrowable;
            this.fragement = fragement;
        }

        public Throwable getOriginalThrowable() {
            return originalThrowable;
        }

        public String getFragement() {
            return fragement;
        }

    }

    protected Attributes handleAttributes(IProcessableElementTag tag) {
        Attributes attributes = new Attributes();
        IAttribute[] attributArray = tag.getAllAttributes();
        for (IAttribute attribute : attributArray) {
            String v = attribute.getValue();
            if (v != null) {
                attributes.put(attribute.getAttributeCompleteName(), v);
            }
        }
        return attributes;
    }

    protected class Attributes {
        private Map<String, String> attributes = new HashMap<>();

        public String get(String key) {
            return attributes.get(key);
        }

        public double getDouble(String key, double _default) {
            String v = get(key);
            if (v != null) {
                return Double.parseDouble(v);
            }
            return _default;
        }

        public long getLong(String key, long _default) {
            String v = get(key);
            if (v != null) {
                return Long.parseLong(v);
            }
            return _default;
        }

        public int getInt(String key, int _default) {
            String v = get(key);
            if (v != null) {
                return Integer.parseInt(v);
            }
            return _default;
        }

        public boolean getBoolean(String key, boolean _default) {
            String v = get(key);
            if (v != null) {
                return Boolean.parseBoolean(v);
            }
            return _default;
        }

        public Attributes() {
        }

        public void put(String key, String v) {
            attributes.put(key, v);
        }
    }
}
这样解析渲染之后才会输出最终的页面模板。
相比较以前挂件的方式,这更加的灵活,可以为data标签设置各种查询属性,这样即便在没有页面参数传入的时候也能指定数据渲染,更重要的是它支持ajax的查询,比如:www.qyh.me/data/文章列表?query=内存。而原来的挂件方式由于数据和模板结合的过于紧密而无能为力。

至于为什么要采用这种动态渲染的方式,因为它可以给我带来最大的灵活度,我只要改变某个fragement,就可以改变某些数据的渲染,如果选择覆盖fragement(只需要创建一个同名的全局fragement即可),便可以改变整个站点的数据渲染,除此之外,通过更改页面我可以随时更改css、js文件,如果把这些文件放在fragement中,那我更多的时候只需要改变fragement即可。
当然灵活的同时也带来了很多问题,比如学习成本,性能等等,当然最让人头疼的还是异常处理(由于动态渲染的特点,除了记录错误日志之外我实在想不出有什么好的办法),抛开这些问题,我还是非常乐于尝试这种做法。