以下是所需的步骤:
- 创建自定义切片结果,该结果动态构建一个“位置”字符串(位置字符串是传递给切片的值),其中考虑了命名空间 actionName。
- 创建一个使用此结果的包(名为“tiles”)并约定将其用作父包
- 实现并注册一个“com.opensymphony.xwork2.UnknownHandler”,这一步是最关键的,因为当结果无法解析时会调用这个处理程序
- 使用从第一步传入的“位置”的瓷砖定义
以上步骤需要struts.xml中的以下内容
<struts>
<constant name="struts.convention.default.parent.package" value="tiles-package"/>
<bean type="com.opensymphony.xwork2.UnknownHandler" name="tilesUnknownHandler" class="com.kenmcwilliams.tiles.result.TilesUnknownHandler"/>
<package name="tiles-package" extends="convention-default">
<result-types>
<result-type default="true" name="tiles" class="com.kenmcwilliams.tiles.result.TilesResult"/>
</result-types>
</package>
</struts>
自定义结果类型实现:
package com.kenmcwilliams.tiles.result;
import com.opensymphony.xwork2.ActionInvocation;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.ServletDispatcherResult;
import org.apache.tiles.TilesContainer;
import org.apache.tiles.access.TilesAccess;
import org.apache.tiles.request.ApplicationContext;
import org.apache.tiles.request.servlet.ServletRequest;
import org.apache.tiles.request.servlet.ServletUtil;
public class TilesResult extends ServletDispatcherResult {
private static final Logger log = Logger.getLogger(TilesResult.class.getName());
public TilesResult() {
super();
}
public TilesResult(String location) {
super(location);
}
@Override
public void doExecute(String location, ActionInvocation invocation) throws Exception {
//location = "test.definition"; //for test
log.log(Level.INFO, "TilesResult doExecute() location: {0}", location);
//Start simple conventions
//
if (/** tiles && **/location == null) {
String namespace = invocation.getProxy().getNamespace();
String actionName = invocation.getProxy().getActionName();
location = namespace + "#" + actionName + ".jsp"; //Warning forcing extension
log.log(Level.INFO, "TilesResult namespace: {0}", namespace);
log.log(Level.INFO, "TilesResult actionName: {0}", actionName);
log.log(Level.INFO, "TilesResult location: {0}", location);
}
//End simple conventions
setLocation(location);
ServletContext context = ServletActionContext.getServletContext();
ApplicationContext applicationContext = ServletUtil.getApplicationContext(context);
TilesContainer container = TilesAccess.getContainer(applicationContext);
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
ServletRequest servletRequest = new ServletRequest(applicationContext, request, response);
container.render(location, servletRequest);
}
}
TilesUnknownHandler 实现:
package com.kenmcwilliams.tiles.result;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.XWorkException;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig.Builder;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import flexjson.JSONSerializer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import org.apache.commons.lang.StringUtils;
import org.apache.struts2.convention.ConventionUnknownHandler;
public class TilesUnknownHandler extends ConventionUnknownHandler {
private static final Logger log = Logger.getLogger(TilesUnknownHandler.class.getName());
private static final String conventionBase = "/WEB-INF/content";
@Inject
public TilesUnknownHandler(Configuration configuration, ObjectFactory objectFactory,
ServletContext servletContext, Container container,
@Inject("struts.convention.default.parent.package") String defaultParentPackageName,
@Inject("struts.convention.redirect.to.slash") String redirectToSlash,
@Inject("struts.convention.action.name.separator") String nameSeparator) {
super(configuration, objectFactory, servletContext, container, defaultParentPackageName,
redirectToSlash, nameSeparator);
log.info("Constructed TilesUnknownHandler");
}
@Override
public ActionConfig handleUnknownAction(String namespace, String actionName)
throws XWorkException {
ActionConfig actionConfig;
log.info("TilesUnknownHandler: before handleUnknownAction");
ActionConfig handleUnknownAction = super.handleUnknownAction(namespace, actionName);
log.info("TilesUnknownHandler: after handleUnknownAction, returning with:");
log.log(Level.INFO, "...ActionConfig value: {0}", (new JSONSerializer().serialize(handleUnknownAction)));
log.log(Level.INFO, "Modifying handleUnknowAction result handler");
Map<String, ResultConfig> results = handleUnknownAction.getResults();
ResultConfig resultConfig = results.get("success");
Builder builder = new ResultConfig.Builder("com.opensymphony.xwork2.config.entities.ResultConfig", "com.kenmcwilliams.tiles.result.TilesResult");
Map<String, String> params = resultConfig.getParams();
String tilesResultString = null;
String location = params.get("location");
if (location != null && !location.isEmpty()) {
int length = conventionBase.length();
if(StringUtils.startsWith(location, conventionBase)){
String subString = location.substring(length); //chop off "/WEB-INF/content"
int count = StringUtils.countMatches(subString, "/");//TODO: maybe check for "//", although I don't know why it would be in the string
if (count == 1){//empty namespace
tilesResultString = subString.replaceFirst("/", "#"); //TODO: because I am doing a straight replacement of the last element the else can probably be removed
}else{ //replace the last slash between the namespace and the file with "#"
int lastIndex = subString.lastIndexOf("/");
//subString.substring(lastIndex, lastIndex);
String nameSpace = subString.substring(0, lastIndex);
String file = subString.substring(lastIndex + 1);
tilesResultString = nameSpace + "#" + file;
}
}
}
Map<String, String> myParams = new LinkedHashMap<String, String>();
myParams.put("location", tilesResultString);
builder.addParams(myParams);
ResultConfig build = builder.build();
Map<String, ResultConfig> myMap = new LinkedHashMap<String, ResultConfig>();
myMap.put("success", build);
log.log(Level.INFO, "\n\n...results: {0}\n\n", (new JSONSerializer().serialize(results)));
actionConfig = new ActionConfig.Builder(handleUnknownAction).addResultConfigs(myMap).build();
//className("com.kenmcwilliams.tiles.result.TilesResult")
return actionConfig;
}
@Override
public Result handleUnknownResult(ActionContext actionContext, String actionName,
ActionConfig actionConfig, String resultCode) throws XWorkException {
log.info("TilesUnknownHandler: before handleUnknownResult");
Result handleUnknownResult = super.handleUnknownResult(actionContext, actionName, actionConfig, resultCode);
log.info("TilesUnknownHandler: after handleUnknownResult, returning with:");
log.log(Level.INFO, "...Result value: {0}", (new JSONSerializer().serialize(handleUnknownResult)));
return handleUnknownResult;
}
}
如何使用我们的“位置”字符串的示例,其形式为:NameSpace + "#" + ActionName + ".jsp",请注意以下定义<definition name="REGEXP:(.*)#(.*)" extends="default">
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="default" template="/WEB-INF/template/template.jsp">
<put-list-attribute name="cssList" cascade="true">
<add-attribute value="/style/cssreset-min.css" />
<add-attribute value="/style/cssfonts-min.css" />
<add-attribute value="/style/cssbase-min.css" />
<add-attribute value="/style/grids-min.css" />
<add-attribute value="/script/jquery-ui-1.8.24.custom/css/ui-lightness/jquery-ui-1.8.24.custom.css" />
<add-attribute value="/style/style.css" />
</put-list-attribute>
<put-list-attribute name="jsList" cascade="true">
<add-attribute value="/script/jquery/1.8.1/jquery.min.js" />
<add-attribute value="/script/jquery-ui-1.8.24.custom/js/jquery-ui-1.8.24.custom.min.js" />
<add-attribute value="/script/jquery.sort.js" />
<add-attribute value="/script/custom/jquery-serialize.js" />
</put-list-attribute>
<put-attribute name="title" value="defaults-name" cascade="true" type="string"/>
<put-attribute name="head" value="/WEB-INF/template/head.jsp"/>
<put-attribute name="header" value="/WEB-INF/template/header.jsp"/>
<put-attribute name="body" value="/WEB-INF/template/body.jsp"/>
<put-attribute name="footer" value="/WEB-INF/template/footer.jsp"/>
</definition>
<definition name="REGEXP:(.*)#(.*)" extends="default">
<put-attribute name="title" cascade="true" expression="OGNL:@com.opensymphony.xwork2.ActionContext@getContext().name"/>
<put-attribute name="body" value="/WEB-INF/content{1}/{2}"/>
</definition>
</tiles-definitions>
有了这个,您可以在 /WEB-INF/content/someplace/my-action.jsp 下创建 JSP
就像您使用约定和瓷砖一样,如果您创建一个com.myapp.action.someplace.MyAction
没有任何结果类型调用的动作类,该代码将执行并且/WEB-INF/content/someplace/my-action.jsp
仍将呈现结果。
那里有约定+没有更多注释的图块(对于正常情况来说很好)。
注意事项:
- 这个答案当然并不完美,但它确实提供了一个可以应用于其他视图技术(sitemesh、其他)的策略的工作示例。
- 目前,您可以看到“.jsp”被附加在图块结果中,而不是在图块定义中,这是不灵活的。具体的扩展名应该在tiles中指定,即定义中的body属性应该附加具体的视图类型(.jsp、.fml、.vm),因为那时你应该最清楚。
- 重要的是要注意定义是按照给出的顺序尝试的,因此您可以通过在和
REGEXP:(.*)#(.*)
定义之间放置定义来覆盖正常情况。例如,可以在这两个定义之间放置一个名为的定义。毕竟,如果您不能这样做并且所有页面都必须平铺相同,我们真的不会使用平铺!default
REGEXP:(.*)#(.*)
authenticated\(.*)
- Just so you know when using tiles3 (the struts2 tiles3 plugin) you can use all three types of view technologies (jsp, freemarker, velocity) to compose one tile. It works. You are probably going to use one view technology consistently but it's nice to know it is possible.