Struts(十六):通过CURD来学习Struts流程及ModelDriven的用法

  • 背景:

从一个Member的增删改查,来了解Struts2的运行原理及学习ModelDriven拦截器、Preparable拦截器。

  • 新建项目实现列表的展示及删除功能:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Struts 02</display-name>

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- Restricts access to pure JSP files - access available only via Struts 
        action <security-constraint> <display-name>No direct JSP access</display-name> 
        <web-resource-collection> <web-resource-name>No-JSP</web-resource-name> <url-pattern>*.jsp</url-pattern> 
        </web-resource-collection> <auth-constraint> <role-name>no-users</role-name> 
        </auth-constraint> </security-constraint> <security-role> <description>Don't 
        assign users to this role</description> <role-name>no-users</role-name> </security-role> -->
</web-app>
View Code

struts.xml

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE struts PUBLIC
 3     "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
 4     "http://struts.apache.org/dtds/struts-2.3.dtd">
 5 
 6 <struts>   
 7     <constant name="struts.ognl.allowStaticMethodAccess" value="true" />
 8     <constant name="struts.devMode" value="false" />
 9 
10     <package name="default" namespace="/" extends="struts-default"> 
11         <action name="member-*" class="com.dx.struts.actions.MemberAction" method="{1}">
12             <result name="{1}">/member-{1}.jsp</result>       
13             <result name="delete" type="redirectAction">member-list</result>
14         </action>
15     </package>
16 </struts>

 

Member.java

/**
 * @author Administrator
 *
 */
package com.dx.struts.entity;

public class Member{
    private Long id;
    private String name;
    private Integer age;
    private String gender;    

    public Member() {        
    }
    
    public Member(Long id, String name, Integer age, String gender) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    
}
View Code

 MemberAction.java(暂时实现删除、列表功能)

 1 package com.dx.struts.actions;
 2 
 3 import java.util.Date;
 4 import java.util.List;
 5 import java.util.Map;
 6 
 7 import org.apache.struts2.interceptor.RequestAware;
 8 
 9 import com.dx.struts.dao.MemberDao;
10 import com.dx.struts.entity.Member;
11 
12 public class MemberAction implements RequestAware {
13     private MemberDao memberDao = new MemberDao();
14     
15     private Long id;
16 
17     public void setId(Long id) {
18         this.id = id;
19     }
20 
21     public String list() {
22         List<Member> members = memberDao.getMembers();
23         request.put("members", members);
24         return "list";
25     }
26 
27     public String delete() {
28         memberDao.remove(this.id);
29         // 返回结果的类型应该为:redirectAction
30         // 也可以是chain:实际上chain是没有必要的,因为不需要在下一个action中保留啊当前action的状态
31         // 若使用chain,则到达目标页面后,地址栏显示的依然是删除的那个链接,则刷新时会重复提交。
32         return "delete";
33     }
34 
35 
36     private Map<String, Object> request;
37 
38     @Override
39     public void setRequest(Map<String, Object> request) {
40         this.request = request;
41     }
42     
43 }

注意:这里边根据id删除Member接收id参数是通过:在MemberAction类中添加了id属性,之后实现了id的set方法才实现了参数接收。

MemberDao.java

package com.dx.struts.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.dx.struts.entity.Member;

public class MemberDao {
    private static HashMap<Long, Member> members = new HashMap<Long, Member>();

    static {
        members.put(1001L, new Member(1001L, "member1", 20, "male"));
        members.put(1002L, new Member(1002L, "member2", 20, "female"));
        members.put(1003L, new Member(1003L, "member3", 20, "male"));
        members.put(1004L, new Member(1004L, "member4", 20, "male"));
    }

    public List<Member> getMembers() {
        return new ArrayList<Member>(members.values());
    }

    public Member get(Long id) {
        return members.get(id);
    }

    public void add(Member member) {
        members.put(member.getId(), member);
    }
    
    public void remove(Long id){
        members.remove(id);
    }
}
View Code

member-list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <s:a href="/Struts_02/member-add.jsp" >add</s:a><br>
    <table style="margin:0 auto;width:60%;" cellpadding="10" cellspacing="0" border="1">
        <tr style="">
            <td style="width:10%;">id</td>
            <td style="width:40%;">name</td>
            <td style="width:20%;">age</td>
            <td style="width:20%;">gender</td>
            <td style="width:10%;">view</td>
            <td style="width:10%;">edit</td>            
            <td style="width:10%;">delete</td>
        </tr>
        <s:iterator value="#request.members">
        <tr>
            <td>${id}</td>
            <td>${name}</td>
            <td>${age}</td>
            <td>${gender}</td>
            <td><s:a href="member-view.action?id=%{id}" >view</s:a></td>
            <td><s:a href="member-edit.action?id=%{id}" >edit</s:a></td>            
            <td><s:a href="member-delete.action?id=%{id}" >delete</s:a></td>
        </tr>
        </s:iterator>
    </table>
</body>
</html>
View Code

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="member-list.action">index</a>
</body>
</html>
View Code
  • 通过断点调试了解Action的运行原理:

把断点设置到delete方法内,断点调试代码的调用栈跟踪如下:

Daemon Thread [http-bio-8080-exec-7] (Suspended (breakpoint at line 52 in MemberAction))    
owns: Method  (id=91)    
owns: SocketWrapper<E>  (id=86)    
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]    
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62    
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43    
Method.invoke(Object, Object...) line: 498    
OgnlRuntime.invokeMethod(Object, Method, Object[]) line: 871    
OgnlRuntime.callAppropriateMethod(OgnlContext, Object, Object, String, String, List, Object[]) line: 1294    
XWorkMethodAccessor(ObjectMethodAccessor).callMethod(Map, Object, String, Object[]) line: 68    
XWorkMethodAccessor.callMethodWithDebugInfo(Map, Object, String, Object[]) line: 117    
XWorkMethodAccessor.callMethod(Map, Object, String, Object[]) line: 108    
OgnlRuntime.callMethod(OgnlContext, Object, String, Object[]) line: 1370    
ASTMethod.getValueBody(OgnlContext, Object) line: 91    
ASTMethod(SimpleNode).evaluateGetValueBody(OgnlContext, Object) line: 212    
ASTMethod(SimpleNode).getValue(OgnlContext, Object) line: 258    
Ognl.getValue(Object, Map, Object, Class) line: 467    
Ognl.getValue(Object, Map, Object) line: 431    
OgnlUtil$3.execute(Object) line: 352    
OgnlUtil.compileAndExecuteMethod(String, Map<String,Object>, OgnlTask<T>) line: 404    
OgnlUtil.callMethod(String, Map<String,Object>, Object) line: 350    
DefaultActionInvocation.invokeAction(Object, ActionConfig) line: 430    
DefaultActionInvocation.invokeActionOnly() line: 290    
DefaultActionInvocation.invoke() line: 251    
DeprecationInterceptor.intercept(ActionInvocation) line: 41    
DefaultActionInvocation.invoke() line: 245    
DebuggingInterceptor.intercept(ActionInvocation) line: 256    
DefaultActionInvocation.invoke() line: 245    
DefaultWorkflowInterceptor.doIntercept(ActionInvocation) line: 168    
DefaultWorkflowInterceptor(MethodFilterInterceptor).intercept(ActionInvocation) line: 98    
DefaultActionInvocation.invoke() line: 245    
AnnotationValidationInterceptor(ValidationInterceptor).doIntercept(ActionInvocation) line: 265    
AnnotationValidationInterceptor.doIntercept(ActionInvocation) line: 76    
AnnotationValidationInterceptor(MethodFilterInterceptor).intercept(ActionInvocation) line: 98    
DefaultActionInvocation.invoke() line: 245    
StrutsConversionErrorInterceptor(ConversionErrorInterceptor).intercept(ActionInvocation) line: 138    
DefaultActionInvocation.invoke() line: 245    
ParametersInterceptor.doIntercept(ActionInvocation) line: 229    
ParametersInterceptor(MethodFilterInterceptor).intercept(ActionInvocation) line: 98    
DefaultActionInvocation.invoke() line: 245    
ActionMappingParametersInteceptor(ParametersInterceptor).doIntercept(ActionInvocation) line: 229    
ActionMappingParametersInteceptor(MethodFilterInterceptor).intercept(ActionInvocation) line: 98    
DefaultActionInvocation.invoke() line: 245    
StaticParametersInterceptor.intercept(ActionInvocation) line: 191    
DefaultActionInvocation.invoke() line: 245    
MultiselectInterceptor.intercept(ActionInvocation) line: 73    
DefaultActionInvocation.invoke() line: 245    
DateTextFieldInterceptor.intercept(ActionInvocation) line: 125    
DefaultActionInvocation.invoke() line: 245    
CheckboxInterceptor.intercept(ActionInvocation) line: 91    
DefaultActionInvocation.invoke() line: 245    
FileUploadInterceptor.intercept(ActionInvocation) line: 253    
DefaultActionInvocation.invoke() line: 245    
ModelDrivenInterceptor.intercept(ActionInvocation) line: 100    
DefaultActionInvocation.invoke() line: 245    
ScopedModelDrivenInterceptor.intercept(ActionInvocation) line: 141    
DefaultActionInvocation.invoke() line: 245    
ChainingInterceptor.intercept(ActionInvocation) line: 145    
DefaultActionInvocation.invoke() line: 245    
PrepareInterceptor.doIntercept(ActionInvocation) line: 171    
PrepareInterceptor(MethodFilterInterceptor).intercept(ActionInvocation) line: 98    
DefaultActionInvocation.invoke() line: 245    
I18nInterceptor.intercept(ActionInvocation) line: 140    
DefaultActionInvocation.invoke() line: 245    
ServletConfigInterceptor.intercept(ActionInvocation) line: 164    
DefaultActionInvocation.invoke() line: 245    
AliasInterceptor.intercept(ActionInvocation) line: 193    
DefaultActionInvocation.invoke() line: 245    
ExceptionMappingInterceptor.intercept(ActionInvocation) line: 189    
DefaultActionInvocation.invoke() line: 245    
StrutsActionProxy.execute() line: 54    
Dispatcher.serviceAction(HttpServletRequest, HttpServletResponse, ActionMapping) line: 575    
ExecuteOperations.executeAction(HttpServletRequest, HttpServletResponse, ActionMapping) line: 81    
StrutsPrepareAndExecuteFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 99    
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 241    
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 208    
StandardWrapperValve.invoke(Request, Response) line: 218    
StandardContextValve.invoke(Request, Response) line: 110    
NonLoginAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 506    
StandardHostValve.invoke(Request, Response) line: 169    
ErrorReportValve.invoke(Request, Response) line: 103    
AccessLogValve.invoke(Request, Response) line: 962    
StandardEngineValve.invoke(Request, Response) line: 116    
CoyoteAdapter.service(Request, Response) line: 452    
Http11Processor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1087    
Http11Protocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 637    
JIoEndpoint$SocketProcessor.run() line: 318    
ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: 1142    
ThreadPoolExecutor$Worker.run() line: 617    

 

在这里介绍下上边UML是使用的http://plantuml.com/在线UML来实现的:

http://plantuml.com/

@startuml
"浏览器" -> StrutsPrepareAndExecuteFilter : dofilter() StrutsPrepareAndExecuteFilter -> StrutsActionProxy :execute() StrutsActionProxy -> DefaultActionInvocation :invoke() DefaultActionInvocation -> ExceptionMappingInterceptor : interceptor() ExceptionMappingInterceptor -> DefaultActionInvocation : invoke() DefaultActionInvocation -> XxxxInterceptor : interceptor() XxxxInterceptor -> DefaultActionInvocation : invoke() DefaultActionInvocation -> DebuggingInterceptor : interceptor() DebuggingInterceptor -> DefaultActionInvocation : invoke() DefaultActionInvocation -> DefaultActionInvocation : invokeAction() DefaultActionInvocation -> MemberAction : delete() @enduml

StrutsActionProxy
ActionProxy是Action的一个代理类,也就是说Action的调用是通过ActionProxy实现的,
其实就是调用了ActionProxy.execute()方法,而该方法又调用了ActionInvocation.invoke()方法,而该方法又调用了ActionInvocation

DefaultActionInvocation
ActionInvocation就是一个Action的调用者。
ActionInvocation在Action的执行过程中,负责Interceptor/Action/Result等一系列元素的调度。

具体代码请参考DefaultActionInvocation:

  1 /*
  2  * Copyright 2002-2006,2009 The Apache Software Foundation.
  3  * 
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  * 
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  * 
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 package com.opensymphony.xwork2;
 17 
 18 import com.opensymphony.xwork2.config.ConfigurationException;
 19 import com.opensymphony.xwork2.config.entities.ActionConfig;
 20 import com.opensymphony.xwork2.config.entities.InterceptorMapping;
 21 import com.opensymphony.xwork2.config.entities.ResultConfig;
 22 import com.opensymphony.xwork2.inject.Container;
 23 import com.opensymphony.xwork2.inject.Inject;
 24 import com.opensymphony.xwork2.interceptor.PreResultListener;
 25 import com.opensymphony.xwork2.ognl.OgnlUtil;
 26 import com.opensymphony.xwork2.util.ValueStack;
 27 import com.opensymphony.xwork2.util.ValueStackFactory;
 28 import com.opensymphony.xwork2.util.logging.Logger;
 29 import com.opensymphony.xwork2.util.logging.LoggerFactory;
 30 import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
 31 import ognl.MethodFailedException;
 32 import ognl.NoSuchPropertyException;
 33 import ognl.OgnlException;
 34 
 35 import java.util.ArrayList;
 36 import java.util.Iterator;
 37 import java.util.List;
 38 import java.util.Map;
 39 
 40 
 41 /**
 42  * The Default ActionInvocation implementation
 43  *
 44  * @author Rainer Hermanns
 45  * @author tmjee
 46  * @version $Date$ $Id$
 47  * @see com.opensymphony.xwork2.DefaultActionProxy
 48  */
 49 public class DefaultActionInvocation implements ActionInvocation {
 50 
 51     private static final Logger LOG = LoggerFactory.getLogger(DefaultActionInvocation.class);
 52 
 53     protected Object action;
 54     protected ActionProxy proxy;
 55     protected List<PreResultListener> preResultListeners;
 56     protected Map<String, Object> extraContext;
 57     protected ActionContext invocationContext;
 58     protected Iterator<InterceptorMapping> interceptors;
 59     protected ValueStack stack;
 60     protected Result result;
 61     protected Result explicitResult;
 62     protected String resultCode;
 63     protected boolean executed = false;
 64     protected boolean pushAction = true;
 65     protected ObjectFactory objectFactory;
 66     protected ActionEventListener actionEventListener;
 67     protected ValueStackFactory valueStackFactory;
 68     protected Container container;
 69     protected UnknownHandlerManager unknownHandlerManager;
 70     protected OgnlUtil ognlUtil;
 71 
 72     public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) {
 73         this.extraContext = extraContext;
 74         this.pushAction = pushAction;
 75     }
 76 
 77     @Inject
 78     public void setUnknownHandlerManager(UnknownHandlerManager unknownHandlerManager) {
 79         this.unknownHandlerManager = unknownHandlerManager;
 80     }
 81 
 82     @Inject
 83     public void setValueStackFactory(ValueStackFactory fac) {
 84         this.valueStackFactory = fac;
 85     }
 86 
 87     @Inject
 88     public void setObjectFactory(ObjectFactory fac) {
 89         this.objectFactory = fac;
 90     }
 91 
 92     @Inject
 93     public void setContainer(Container cont) {
 94         this.container = cont;
 95     }
 96 
 97     @Inject(required=false)
 98     public void setActionEventListener(ActionEventListener listener) {
 99         this.actionEventListener = listener;
100     }
101 
102     @Inject
103     public void setOgnlUtil(OgnlUtil ognlUtil) {
104         this.ognlUtil = ognlUtil;
105     }
106 
107     public Object getAction() {
108         return action;
109     }
110 
111     public boolean isExecuted() {
112         return executed;
113     }
114 
115     public ActionContext getInvocationContext() {
116         return invocationContext;
117     }
118 
119     public ActionProxy getProxy() {
120         return proxy;
121     }
122 
123     /**
124      * If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method
125      * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
126      * DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with
127      * the result params.
128      *
129      * @return a Result instance
130      * @throws Exception
131      */
132     public Result getResult() throws Exception {
133         Result returnResult = result;
134 
135         // If we've chained to other Actions, we need to find the last result
136         while (returnResult instanceof ActionChainResult) {
137             ActionProxy aProxy = ((ActionChainResult) returnResult).getProxy();
138 
139             if (aProxy != null) {
140                 Result proxyResult = aProxy.getInvocation().getResult();
141 
142                 if ((proxyResult != null) && (aProxy.getExecuteResult())) {
143                     returnResult = proxyResult;
144                 } else {
145                     break;
146                 }
147             } else {
148                 break;
149             }
150         }
151 
152         return returnResult;
153     }
154 
155     public String getResultCode() {
156         return resultCode;
157     }
158 
159     public void setResultCode(String resultCode) {
160         if (isExecuted())
161             throw new IllegalStateException("Result has already been executed.");
162 
163         this.resultCode = resultCode;
164     }
165 
166 
167     public ValueStack getStack() {
168         return stack;
169     }
170 
171     /**
172      * Register a com.opensymphony.xwork2.interceptor.PreResultListener to be notified after the Action is executed and before the
173      * Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order
174      * in which they are registered. Listener registration and execution does not need to be thread-safe.
175      *
176      * @param listener to register
177      */
178     public void addPreResultListener(PreResultListener listener) {
179         if (preResultListeners == null) {
180             preResultListeners = new ArrayList<PreResultListener>(1);
181         }
182 
183         preResultListeners.add(listener);
184     }
185 
186     public Result createResult() throws Exception {
187         LOG.trace("Creating result related to resultCode [#0]", resultCode);
188 
189         if (explicitResult != null) {
190             Result ret = explicitResult;
191             explicitResult = null;
192 
193             return ret;
194         }
195         ActionConfig config = proxy.getConfig();
196         Map<String, ResultConfig> results = config.getResults();
197 
198         ResultConfig resultConfig = null;
199 
200         try {
201             resultConfig = results.get(resultCode);
202         } catch (NullPointerException e) {
203             if (LOG.isDebugEnabled()) {
204                 LOG.debug("Got NPE trying to read result configuration for resultCode [#0]", resultCode);
205             }
206         }
207         
208         if (resultConfig == null) {
209             // If no result is found for the given resultCode, try to get a wildcard '*' match.
210             resultConfig = results.get("*");
211         }
212 
213         if (resultConfig != null) {
214             try {
215                 return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
216             } catch (Exception e) {
217                 if (LOG.isErrorEnabled()) {
218                     LOG.error("There was an exception while instantiating the result of type #0", e, resultConfig.getClassName());
219                 }
220                 throw new XWorkException(e, resultConfig);
221             }
222         } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
223             return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
224         }
225         return null;
226     }
227 
228     /**
229      * @throws ConfigurationException If no result can be found with the returned code
230      */
231     public String invoke() throws Exception {
232         String profileKey = "invoke: ";
233         try {
234             UtilTimerStack.push(profileKey);
235 
236             if (executed) {
237                 throw new IllegalStateException("Action has already executed");
238             }
239 
240             if (interceptors.hasNext()) {
241                 final InterceptorMapping interceptor = interceptors.next();
242                 String interceptorMsg = "interceptor: " + interceptor.getName();
243                 UtilTimerStack.push(interceptorMsg);
244                 try {
245                                 resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
246                             }
247                 finally {
248                     UtilTimerStack.pop(interceptorMsg);
249                 }
250             } else {
251                 resultCode = invokeActionOnly();
252             }
253 
254             // this is needed because the result will be executed, then control will return to the Interceptor, which will
255             // return above and flow through again
256             if (!executed) {
257                 if (preResultListeners != null) {
258                     LOG.trace("Executing PreResultListeners for result [#0]", result);
259 
260                     for (Object preResultListener : preResultListeners) {
261                         PreResultListener listener = (PreResultListener) preResultListener;
262 
263                         String _profileKey = "preResultListener: ";
264                         try {
265                             UtilTimerStack.push(_profileKey);
266                             listener.beforeResult(this, resultCode);
267                         }
268                         finally {
269                             UtilTimerStack.pop(_profileKey);
270                         }
271                     }
272                 }
273 
274                 // now execute the result, if we're supposed to
275                 if (proxy.getExecuteResult()) {
276                     executeResult();
277                 }
278 
279                 executed = true;
280             }
281 
282             return resultCode;
283         }
284         finally {
285             UtilTimerStack.pop(profileKey);
286         }
287     }
288 
289     public String invokeActionOnly() throws Exception {
290         return invokeAction(getAction(), proxy.getConfig());
291     }
292 
293     protected void createAction(Map<String, Object> contextMap) {
294         // load action
295         String timerKey = "actionCreate: " + proxy.getActionName();
296         try {
297             UtilTimerStack.push(timerKey);
298             action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
299         } catch (InstantiationException e) {
300             throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
301         } catch (IllegalAccessException e) {
302             throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
303         } catch (Exception e) {
304             String gripe;
305 
306             if (proxy == null) {
307                 gripe = "Whoa!  No ActionProxy instance found in current ActionInvocation.  This is bad ... very bad";
308             } else if (proxy.getConfig() == null) {
309                 gripe = "Sheesh.  Where'd that ActionProxy get to?  I can't find it in the current ActionInvocation!?";
310             } else if (proxy.getConfig().getClassName() == null) {
311                 gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
312             } else {
313                 gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ",  defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
314             }
315 
316             gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
317             throw new XWorkException(gripe, e, proxy.getConfig());
318         } finally {
319             UtilTimerStack.pop(timerKey);
320         }
321 
322         if (actionEventListener != null) {
323             action = actionEventListener.prepare(action, stack);
324         }
325     }
326 
327     protected Map<String, Object> createContextMap() {
328         Map<String, Object> contextMap;
329 
330         if ((extraContext != null) && (extraContext.containsKey(ActionContext.VALUE_STACK))) {
331             // In case the ValueStack was passed in
332             stack = (ValueStack) extraContext.get(ActionContext.VALUE_STACK);
333 
334             if (stack == null) {
335                 throw new IllegalStateException("There was a null Stack set into the extra params.");
336             }
337 
338             contextMap = stack.getContext();
339         } else {
340             // create the value stack
341             // this also adds the ValueStack to its context
342             stack = valueStackFactory.createValueStack();
343 
344             // create the action context
345             contextMap = stack.getContext();
346         }
347 
348         // put extraContext in
349         if (extraContext != null) {
350             contextMap.putAll(extraContext);
351         }
352 
353         //put this DefaultActionInvocation into the context map
354         contextMap.put(ActionContext.ACTION_INVOCATION, this);
355         contextMap.put(ActionContext.CONTAINER, container);
356 
357         return contextMap;
358     }
359 
360     /**
361      * Uses getResult to get the final Result and executes it
362      *
363      * @throws ConfigurationException If not result can be found with the returned code
364      */
365     private void executeResult() throws Exception {
366         result = createResult();
367 
368         String timerKey = "executeResult: " + getResultCode();
369         try {
370             UtilTimerStack.push(timerKey);
371             if (result != null) {
372                 result.execute(this);
373             } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
374                 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
375                         + " and result " + getResultCode(), proxy.getConfig());
376             } else {
377                 if (LOG.isDebugEnabled()) {
378                     LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation());
379                 }
380             }
381         } finally {
382             UtilTimerStack.pop(timerKey);
383         }
384     }
385 
386     public void init(ActionProxy proxy) {
387         this.proxy = proxy;
388         Map<String, Object> contextMap = createContextMap();
389 
390         // Setting this so that other classes, like object factories, can use the ActionProxy and other
391         // contextual information to operate
392         ActionContext actionContext = ActionContext.getContext();
393 
394         if (actionContext != null) {
395             actionContext.setActionInvocation(this);
396         }
397 
398         createAction(contextMap);
399 
400         if (pushAction) {
401             stack.push(action);
402             contextMap.put("action", action);
403         }
404 
405         invocationContext = new ActionContext(contextMap);
406         invocationContext.setName(proxy.getActionName());
407 
408         createInterceptors(proxy);
409     }
410 
411     protected void createInterceptors(ActionProxy proxy) {
412         // get a new List so we don't get problems with the iterator if someone changes the list
413         List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
414         interceptors = interceptorList.iterator();
415     }
416 
417     protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
418         String methodName = proxy.getMethod();
419 
420         if (LOG.isDebugEnabled()) {
421             LOG.debug("Executing action method = #0", methodName);
422         }
423 
424         String timerKey = "invokeAction: " + proxy.getActionName();
425         try {
426             UtilTimerStack.push(timerKey);
427 
428             Object methodResult;
429             try {
430                 methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
431             } catch (MethodFailedException e) {
432                 // if reason is missing method, try find version with "do" prefix
433                 if (e.getReason() instanceof NoSuchMethodException) {
434                     try {
435                         String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()";
436                         methodResult = ognlUtil.callMethod(altMethodName, getStack().getContext(), action);
437                     } catch (MethodFailedException e1) {
438                         // if still method doesn't exist, try checking UnknownHandlers
439                         if (e1.getReason() instanceof NoSuchMethodException) {
440                             if (unknownHandlerManager.hasUnknownHandlers()) {
441                                 try {
442                                     methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
443                                 } catch (NoSuchMethodException e2) {
444                                     // throw the original one
445                                     throw e;
446                                 }
447                             } else {
448                                 // throw the original one
449                                 throw e;
450                             }
451                             // throw the original exception as UnknownHandlers weren't able to handle invocation as well
452                             if (methodResult == null) {
453                                 throw e;
454                             }
455                         } else {
456                             // exception isn't related to missing action method, throw it
457                             throw e1;
458                         }
459                     }
460                 } else {
461                     // exception isn't related to missing action method, throw it
462                     throw e;
463                 }
464             }
465             return saveResult(actionConfig, methodResult);
466         } catch (NoSuchPropertyException e) {
467             throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
468         } catch (MethodFailedException e) {
469             // We try to return the source exception.
470             Throwable t = e.getCause();
471 
472             if (actionEventListener != null) {
473                 String result = actionEventListener.handleException(t, getStack());
474                 if (result != null) {
475                     return result;
476                 }
477             }
478             if (t instanceof Exception) {
479                 throw (Exception) t;
480             } else {
481                 throw e;
482             }
483         } finally {
484             UtilTimerStack.pop(timerKey);
485         }
486     }
487 
488     /**
489      * Save the result to be used later.
490      * @param actionConfig current ActionConfig
491      * @param methodResult the result of the action.
492      * @return the result code to process.
493      */
494     protected String saveResult(ActionConfig actionConfig, Object methodResult) {
495         if (methodResult instanceof Result) {
496             this.explicitResult = (Result) methodResult;
497 
498             // Wire the result automatically
499             container.inject(explicitResult);
500             return null;
501         } else {
502             return (String) methodResult;
503         }
504     }
505 
506     /**
507      * Version ready to be serialize
508      *
509      * @return instance without reference to {@link Container}
510      */
511     public ActionInvocation serialize() {
512         DefaultActionInvocation that = this;
513         that.container = null;
514         return that;
515     }
516 
517     /**
518      * Restoring Container
519      *
520      * @param actionContext current {@link ActionContext}
521      * @return instance which can be used to invoke action
522      */
523     public ActionInvocation deserialize(ActionContext actionContext) {
524         DefaultActionInvocation that = this;
525         that.container = actionContext.getContainer();
526         return that;
527     }
528 
529 }
View Code

 

  • 通过新增用户、修改用户、查看用户及删除用户功能来学习ModelDriven拦截器

 从上边实现删除代码中我们知道,我们可以通过在MemberAction类中添加一个id属性,并实现set方法,就可以实现在删除用户功能中接收传递到后台的id参数。不过,现在我们尝试学习一种新的方式:通过ModelDriven

修改struts.xml

        <action name="member-*" class="com.dx.struts.actions.MemberAction" method="{1}">
            <result name="{1}">/member-{1}.jsp</result>            
            <result name="delete" type="redirectAction">member-list</result>            
            <result name="modify" type="redirectAction">member-list</result>            
            <result name="create" type="redirectAction">member-list</result>
        </action>

 修改MemberAction.java

package com.dx.struts.actions;

import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.struts2.interceptor.RequestAware;
import com.opensymphony.xwork2.ModelDriven;
import com.dx.struts.dao.MemberDao;
import com.dx.struts.entity.Member;

public class MemberAction implements RequestAware, ModelDriven<Member> {
    private MemberDao memberDao = new MemberDao();
    private Member member;

    public String list() {
        List<Member> members = memberDao.getMembers();
        request.put("members", members);
        return "list";
    }

    public String view() {
        Member member_ = memberDao.get(this.member.getId());
        this.member.setAge(member_.getAge());
        this.member.setName(member_.getName());
        this.member.setGender(member_.getGender());

        return "view";
    }

    public String delete() {
        memberDao.remove(this.member.getId());
        // 返回结果的类型应该为:redirectAction
        // 也可以是chain:实际上chain是没有必要的,因为不需要在下一个action中保留啊当前action的状态
        // 若使用chain,则到达目标页面后,地址栏显示的依然是删除的那个链接,则刷新时会重复提交。
        return "delete";
    }

    public String edit() {
        Member member_ = memberDao.get(this.member.getId());
        this.member.setAge(member_.getAge());
        this.member.setName(member_.getName());
        this.member.setGender(member_.getGender());

        return "edit";
    }

    public String modify() {
        Member member_ = memberDao.get(this.member.getId());
        member_.setAge(this.member.getAge());
        member_.setName(this.member.getName());
        member_.setGender(this.member.getGender());

        return "modify";
    }

    public String create() {
        member.setId(new Date().getTime());
        memberDao.add(member);

        return "create";
    }

    private Map<String, Object> request;

    @Override
    public void setRequest(Map<String, Object> request) {
        this.request = request;
    }

    @Override
    public Member getModel() {
        this.member = new Member();
        return this.member;
    }
}

注意:

这里是如果把member的所有属性都copy一份到MemberAction其实也是可以实现接收表单提交的参数的;

不过,实现了ModelDriven<Member>接口之后,不光是可以实现form表单提交的参数,也可以接收url提交的参数等。

member-view.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <s:debug></s:debug>
    <s:form>
        <s:textfield name="id" label="ID"></s:textfield>
        <s:textfield name="name" label="Name"></s:textfield>
        <s:textfield name="age" label="Age"></s:textfield>
        <s:radio list="#{'male':'male','female':'female' }" name="gender" label="Gender"></s:radio>
    </s:form>
</body>
</html>
View Code

member-add.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:debug></s:debug>
    <s:form action="member-create.action">
        <s:textfield name="name" label="Name"></s:textfield>
        <s:textfield name="age" label="Age"></s:textfield>
        <s:radio list="#{'male':'male','female':'female' }" name="gender" label="Gender"></s:radio>
        <s:submit name="submit" label="提交"></s:submit>
    </s:form>
</body>
</html>
View Code

member-edit.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <s:debug></s:debug>
    <s:form action="member-modify.action">
        <s:textfield name="id" label="ID"></s:textfield>
        <s:textfield name="name" label="Name"></s:textfield>
        <s:textfield name="age" label="Age"></s:textfield>
        <s:radio list="#{'male':'male','female':'female' }" name="gender" label="Gender"></s:radio>
        <s:submit name="submit" label="提交"></s:submit>
    </s:form>
</body>
</html>
View Code

 

posted @ 2017-03-25 22:52  cctext  阅读(415)  评论(0编辑  收藏  举报