三、webx中veloctiy的使用
3.1 webx集成veloctiy配置入口
在webx的webx-component.xml里面有一个配置如下:
<services:template
xmlns="http://www.alibaba.com/schema/services/template/engines"
searchExtensions="true">
<velocity-engine templateEncoding="UTF-8"
strictReference="false" path="/${component}/templates">
<plugins>
<vm-plugins:escape-support defaultEscape="html">
<vm-plugins:noescape>
<vm-plugins:if-matches pattern="^control\." />
<vm-plugins:if-matches pattern="^screen_placeholder" />
<vm-plugins:if-matches pattern="^stringEscapeUtil\.escape" />
<vm-plugins:if-matches pattern="^csrfToken\.(get)?(\w*)hiddenField" />
</vm-plugins:noescape>
</vm-plugins:escape-support>
</plugins>
</velocity-engine>
<freemarker-engine templateEncoding="UTF-8"
path="/${component}/templates" />
<jsp-engine path="/${component}/templates" />
</services:template>
而解析该配置的解析器为:TemplateServiceDefinitionParser
首先说下,该配置目的是创建一个beanname=templateService的TemplateServiceImpl类,并实例化三种渲染引擎。下面看下解析代码:
public class TemplateServiceDefinitionParser extends AbstractNamedBeanDefinitionParser<TemplateServiceImpl> implements
ContributionAware { private ConfigurationPoint templateEnginesConfigurationPoint; //(1) 解析插件等配置
public void setContribution(Contribution contrib) { this.templateEnginesConfigurationPoint = getSiblingConfigurationPoint("services/template/engines", contrib);
} @Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
parseBeanDefinitionAttributes(element, parserContext, builder);
Map<Object, Object> engines = createManagedMap(element, parserContext);
Map<Object, Object> mappings = createManagedMap(element, parserContext);
ElementSelector engineSelector = ns(templateEnginesConfigurationPoint.getNamespaceUri());
ElementSelector mappingSelector = and(sameNs(element), name("template-mapping")); for (Element subElement : subElements(element)) { //(2) engine,解析并创建三个渲染引擎的bean定义
if (engineSelector.accept(subElement)) {
BeanDefinitionHolder engine = parseConfigurationPointBean(subElement,
templateEnginesConfigurationPoint, parserContext, builder);
engines.put(engine.getBeanName(), engine);
} // mapping
else if (mappingSelector.accept(subElement)) {
String ext = normalizeExtension(subElement.getAttribute("extension"));
String engineName = assertNotNull(trimToNull(subElement.getAttribute("engine")), "engine");
assertNotNull(ext, "extension");
assertTrue(!mappings.containsKey(ext), "duplicated extension: %s", ext);
mappings.put(ext, engineName);
}
} //设置TemplateServiceImpl的engines和engineNameMappings属性
builder.addPropertyValue("engines", engines);
builder.addPropertyValue("engineNameMappings", mappings);
attributesToProperties(element, builder, "defaultExtension", "searchExtensions", "searchLocalizedTemplates", "cacheEnabled");
} @Override
protected String getDefaultName() { return "templateService";
}
}
其中(2)创建了velocity的渲染引擎实例VelocityEngineImpl,解析器为VelocityEngineDefinitionParser为:
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
attributesToProperties(element, "configuration.", builder, "path", "cacheEnabled", "modificationCheckInterval", "strictReference", "templateEncoding");
ElementSelector globalMacros = and(sameNs(element), name("global-macros"));
ElementSelector plugins = and(sameNs(element), name("plugins"));
ElementSelector advancedProperties = and(sameNs(element), name("advanced-properties")); for (Element subElement : subElements(element)) { if (globalMacros.accept(subElement)) { //设置VelocityEngineImpl的成员变量configuration的属性值macros
parseGlobalMacros(subElement, parserContext, builder);
} else if (plugins.accept(subElement)) { //设置VelocityEngineImpl的成员变量configuration的属性值properties
parsePlugins(subElement, parserContext, builder);
} else if (advancedProperties.accept(subElement)) {
parseAdvancedProperties(subElement, parserContext, builder);
}
}
}
3.2 velocity初始化
上节讲了集成velocity需要的一些bean的定义,下面看下如何实例并初始化:
SpringIOC首先创建TemplateServiceImpl对象,但是要首先创建成员变量engines里面的VelocityEngineImpl,所以首先创建了它,然后调用init初始化,初始化过程首先获取webx-compoment.xml里面配置的插件等用户自定义属性,然后初始化默认的velocity.properties,然后用户属性覆盖默认属性,这也说明webx框架还是留有口子,让用户自定义veloctiy行为。然后回到
TemplateServiceImpl的创建,调用init方法设置映射,也就是后缀名与模板引擎的映射如下:
ftl=com.alibaba.citrus.service.freemarker.impl.FreeMarkerEngineImpl#684bd25b:FreeMarkerEngine, jsp=JspEngine[/common/templates/],
jspx=JspEngine[/common/templates/], vm=com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#5c6c2c1c:VelocityEngine
}
webx是基于模块的,并且所有模块都import了webx-component.xml ,所以每个模块都有自己的veloctiyengine,例如我的webx例子里面有auth,comm,business,rock模块,所以如下创建了四个引擎对象,对应不同的模板文件路径:
{com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#3fdeabbc=com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#3fdeabbc:VelocityEngine, [/rock/templates/]}
{com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#5783395d=com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#5783395d:VelocityEngine, [/comm/templates/]}
{com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#2d4f8a40=com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#2d4f8a40:VelocityEngine, [/business/templates/]}
{com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#2ee2af0=com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl#2ee2af0:VelocityEngine, [/auth/templates/]}
3.3一次渲染过程
首先通过pipeline里面的PerformScreenValve调用screen对应的Java类设置变量,然后invoke RenderTemplateValve委托给veloctiy引擎VelocityEngineImpl执行模板获取与渲染。
四、SpringMVC中veloctiy的使用
4.1 SpringMVC集成veloctiy配置入口
在DispatcherServlet的配置文件里面添加如下:
<!--1、对模型视图名称的解析,即在模型视图名称添加前后缀 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
<property name="allowSessionOverride" value="true" />
<property name="exposeSessionAttributes" value="true" />
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="layoutUrl" value="layout.vm" />
<property name="suffix" value=".vm" />
<property name="contentType">
<value>text/html;charset=UTF-8</value>
</property>
</bean>
<!--2、velocity的一些设置 -->
<bean id="velocityConfig"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath">
<value>/WEB-INF/templates/</value>
</property>
<property name="velocityProperties">
<props>
<prop key="input.encoding">UTF-8</prop>
<prop key="output.encoding">UTF-8</prop>
<prop key="contentType">text/html;charset=UTF-8</prop>
</props>
</property>
</bean>
4.2 veloctiy初始化
先上一个时序图:
如图DispatcherServlet的init方法中创建了一个spring的context,去解析springDispatcher-servlet.xml里面配置的bean,首先实例并初始化了VelocityLayoutViewResolver,然后实例初始化了VelocityConfigurer,在它的afterPropertiesSet方法创建了veloctiy引擎并初始化。
4.3 一次渲染过程
先看下时序图
首先找到对应的controller返回包含参数的mv, 然后通过VelocityLayoutViewResolver去创建view:
public View resolveViewName(String viewName, Locale locale) throws Exception { //是否开启缓存,没开启则创建
if (!isCache()) { return createView(viewName, locale);
} else { //开启缓存,首先看缓存
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey); if (view == null) { synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey); if (view == null) { // Ask the subclass to create the View object.
view = createView(viewName, locale); if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
} if (view != null) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
} return (view != UNRESOLVED_VIEW ? view : null);
}
}
由于配置时候cache=true所以打开了cache,这里有两级缓存,定义如下:
/** 快速从Cache中获取view, 不加全局锁 */
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT); /** 创建view时候加全局锁 */
@SuppressWarnings("serial") private final Map<Object, View> viewCreationCache = new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) { @Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) { if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey()); return true;
} else { return false;
}
}
};
为了性能,对已经创建的view直接从viewAccessCache中就可以快速获取,这避免了在创建view期间其他线程等待的现象,只有两个线程都要创建view时候才会等待。也就说当一个线程进入同步块创建view时候,其他线程获取已经存在的view不需要等待。
获取velocityview后,会委托给velocityengine获取模板,然后调用velocityview的render方法调用template.merge实际对AST进行渲染