- 背景:
从一个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>
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; } }
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); } }
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;60%;" cellpadding="10" cellspacing="0" border="1"> <tr style=""> <td style="10%;">id</td> <td style="40%;">name</td> <td style="20%;">age</td> <td style="20%;">gender</td> <td style="10%;">view</td> <td style="10%;">edit</td> <td style="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>
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>
- 通过断点调试了解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 }
- 通过新增用户、修改用户、查看用户及删除用户功能来学习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>
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>
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>