如何将Struts约定与Tiles集成,以保持约定的好处

时间:2013-04-20 03:13:35

标签: struts2 tiles tiles-3

如何在保持约定优势的同时将Struts约定与Tile集成?

问题是约定会自动将url-to-action-to链接到结果,并且可以很好地为jsp,velocity和freemarker结果做到这一点。它不期望处理瓷砖结果。

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

如何实现这一目标?

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

1 个答案:

答案 0 :(得分: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;
    }
}

如何使用以下形式的“location”字符串的示例: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结果仍会被渲染。

你有没有更多注释的约定+ tile(适用于正常情况)。

注意

  • 这个答案肯定并不完美,但它确实提供了一个可以应用于其他视图技术的策略的工作示例(sitemesh,其他)。
  • 目前,你可以看到“.jsp”被附加在tile中,而不是在tile定义中,这是不灵活的。应在tile中指定特定扩展名,即定义中的body属性应附加特定视图类型(.jsp,.fml,.vm),因为您当时应该知道最佳。
  • 请务必注意,定义是按照给定的顺序尝试的,因此您可以通过在REGEXP:(.*)#(.*)default定义之间放置定义来覆盖正常情况REGEXP:(.*)#(.*)。例如,可以在这两个定义之间放置一个名为authenticated\(.*)的定义。毕竟,如果你不能这样做,所有页面必须平铺,我们真的不会使用瓷砖!
  • 只是让你知道在使用tiles3(struts2 tiles3插件)时,你可以使用所有三种类型的视图技术(jsp,freemarker,velocity)来组成一个tile。有用。您可能会始终如一地使用一种视图技术,但很高兴知道它是可行的。