前面已经分析jsp标签类与组件类的结合,现在我将这种结合引向模板,这里我以freemark模板技术进行分析,它是struts2的默认模板技术,在前面jsp标签进行分析的时候说,将组件从标签类中分离出去主要为了在其它视图技术中复用这些组件类,那么具体到freemark模板技术,它又是如何复用的呢?在freemark模板技术需要使用用户自定义指令工变量都进行包装或实现了TemplateMode方法才可以使用,具体到struts2中主要涉及到2个重要的类,其中一个就是TagModel这个类实现了TemplateTransformModel,它的源代码的一部分如下:
public abstract class TagModel implements TemplateTransformModel {
private static final Logger LOG = LoggerFactory.getLogger(TagModel.class);
protected ValueStack stack;
protected HttpServletRequest req;
protected HttpServletResponse res;
public TagModel(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
this.stack = stack;
this.req = req;
this.res = res;
}
public Writer getWriter(Writer writer, Map params)
throws TemplateModelException, IOException {
Component bean = getBean();
Container container = (Container) stack.getContext().get(ActionContext.CONTAINER);
container.inject(bean);
Map unwrappedParameters = unwrapParameters(params);
//主要是将参数拷贝到组件对象去,此方法是在component中定义的
bean.copyParams(unwrappedParameters);
//返回一个回调的Writer,这是一个TagModel与Component结合的神奇之处,这个回调
//将所有指令实现的业务委托给了一个组件一完成,真是一个完美的回调
return new CallbackWriter(bean, writer);
}
protected abstract Component getBean();
TemplateTransformModel是模型标记接口{@link TemplateModel}的子接口,此接口的实现 将作为用户自定义指令进行使用,此接口只规范了一个方法 {@link TemplateTransformModel#getWriter(Writer, Map)}, 此方法返回的输出字符流将会被模板引擎使用来输出模板的内容,当前的struts2.2.3使用的模板的版本为2.3.16,但在
freemark2.3.15源代码中可以看出,在将的freemark2.4版本中此接口很可能作为一个多余的接被弃用,转而使用接口{@link TemplateDirectiveModel}替代, 此接口是一个较原始的接口,基本上所有事情可能都需要自已来做,struts只所以选择此接来实现所有的类标签模型,我想最主要一个考虑可能能就是它足够原始,可以在这样一个接口上做很多流程上的规范,以便与已经写好了业务逻辑的组件comonent对接,以共享资源
你从上面的源代码接口方法#getWriter并不能看出什么关键性的控制流程,而它神奇之处在于此方法返回CallbackWriter对象,这就是第二个关键类,它的源代码其实很简单:
public class CallbackWriter extends Writer implements TransformControl {
private Component bean;
private Writer writer;
private StringWriter body;
private boolean afterBody = false;
public CallbackWriter(Component bean, Writer writer) {
this.bean = bean;
this.writer = writer;
if (bean.usesBody()) {
this.body = new StringWriter();
}
}
public void close() throws IOException {
if (bean.usesBody()) {
body.close();
}
}
public void flush() throws IOException {
writer.flush();
if (bean.usesBody()) {
body.flush();
}
}
public void write(char cbuf[], int off, int len) throws IOException {
if (bean.usesBody() && !afterBody) {
body.write(cbuf, off, len);
} else {
writer.write(cbuf, off, len);
}
}
public int onStart() throws TemplateModelException, IOException {
boolean result = bean.start(this);
if (result) {
return EVALUATE_BODY;
} else {
return SKIP_BODY;
}
}
public int afterBody() throws TemplateModelException, IOException {
afterBody = true;
boolean result = bean.end(this, bean.usesBody() ? body.toString() : "");
if (result) {
return REPEAT_EVALUATION;
} else {
return END_EVALUATION;
}
}
public void onError(Throwable throwable) throws Throwable {
throw throwable;
}
public Component getBean() {
return bean;
}
}
这个回调的Writer不仅是一个Writer它同时也是一个TransformControl,如果说 {@link TemplateTransformModel}是一个简陋原始的接口,那么如果在接口方法 {@link TemplateTransformModel#getWriter(Writer, java.util.Map)}中返回一个实现了TransformControl接口类型的Writer立刻就会化腐朽为神奇,因为一个TransformControl将会对象模板引擎行为进行暗示,进行各种流程上控制,就类似于jspTag体系中规范的一样.这样它就将模板引擎的流程制通过上面的方法委托给了component组件的三个流程方法#start,#end,#usesBody,几乎所有freemark指定都继承于这个TagModel它需要做的主要工作就是设置参数,创建component对象,这与TagSupport做得工作基本一样,下面是TagModel继承体系图:
分析完了各视图技术对组件的适配,接下来就要分析如何在相应的视图技术中利用这些组件的问题,要解决这个问题必须了解几重要类:其中一个就是模板引擎类,模板引擎管理器类;另一个模板管理器类.
先说一下模板引擎类,此类主要是对象各种视图的差异性抽象,因为虽然各种模板技术虽然相似,但不相同,它在生成结果的细节是不一样的,但它们的行为是相同的,需要在利用模板的时候将这种差异性屏蔽,因此需要抽象这样一个类,它的继承体系图如下:
在模板引擎接中规范了方法有两个,其中第一个是最主要的,其源代码如下:
public interface TemplateEngine {
/**
* Renders the template
* @param templateContext context for the given template.
* @throws Exception is thrown if there is a failure when rendering.
*/
void renderTemplate(TemplateRenderingContext templateContext) throws Exception;
/**
* Get's the properties for the given template.
*
* @param template the template.
* @return the properties as key value pairs.
*/
Map getThemeProps(Template template);
}
在模板引擎中规范就是渲染模板,这是所有模板技术都必须去做的事情,只是做法上有些不同,所以被引入到struts2中的模技术都应该实现此引擎接口,一个具体的引擎实现,通常都会完成整个渲染操作,但它通常都会得到一个类的协助,这个类通过被称为模板管理器比如freemark就称为freemarkManager,而Velocity就称为VelocityManager;但是这些类并属于同一个继承体系,但它们做的事情差不多,它们其中有一个重要的工作就是将上面的包装的组件引入到模板中去,以freemarkManager为例,freemarkManager是在xwork容器主被管理,它的两段重要的源代码与组件库的引入相关:
@Inject
public void setContainer(Container container) {
Map<String,TagLibrary> map = new HashMap<String,TagLibrary>();
Set<String> prefixes = container.getInstanceNames(TagLibrary.class);
for (String prefix : prefixes) {
map.put(prefix, container.getInstance(TagLibrary.class, prefix));
}
this.tagLibraries = Collections.unmodifiableMap(map);
}
public ScopesHashModel buildTemplateModel(ValueStack stack, Object action, ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper) {
ScopesHashModel model = buildScopesHashModel(servletContext, request, response, wrapper, stack);
populateContext(model, stack, action, request, response);
if (tagLibraries != null) {
for (String prefix : tagLibraries.keySet()) {
//此处是我们在模板页面上可以使用<@s.property.....<@s.checkbox ...>等自定义指令的关键
model.put(prefix, tagLibraries.get(prefix).getFreemarkerModels(stack, request, response));
}
}
//place the model in the request using the special parameter. This can be retrieved for freemarker and velocity.
request.setAttribute(ATTR_TEMPLATE_MODEL, model);
return model;
}
初始化时注入标符库,在需要的时候通过buildTemplateModel创建模型对象,而创建的模型对象中就会添加相应的组件库中的内容