4

如何在保持约定好处的同时将 Struts 约定与 Tiles 集成?

问题是约定会自动将 url-to-action-to-result 链接到 jsp、velocity 和 freemarker 结果。它不希望处理瓷砖结果。

当使用瓦片时,我们通常希望我们的所有 UI 操作(与 json/xml 服务操作相反)都使用瓦片,但这样做我们失去了结果组件的约定并需要使用注释。注释允许我们偏离预期,但在大型应用程序中,当期望使用瓷砖时,这是一个烦恼。进一步的约定允许我们仅通过指定视图来创建操作。我们也希望在使用瓷砖时保留这种好处。为了纠正这个问题,我们需要建立一个约定,将切片结果传递给我们,这样我们就不需要使用注释来将动作绑定到切片结果,并且我们可以继续创建没有动作类的 JSP,这将获得好处约定(无 xml)和瓦片的好处(所有样板都被分解为瓦片)。

如何做到这一点?

这是一个自我回答,以帮助希望解决此问题的其他人

4

1 回答 1

3

以下是所需的步骤:

  • 创建自定义切片结果,该结果动态构建一个“位置”字符串(位置字符串是传递给切片的值),其中考虑了命名空间 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:(.*)#(.*)定义之间放置定义来覆盖正常情况。例如,可以在这两个定义之间放置一个名为的定义。毕竟,如果您不能这样做并且所有页面都必须平铺相同,我们真的不会使用平铺!defaultREGEXP:(.*)#(.*)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.
于 2013-04-20T03:57:36.157 回答