Struts(九):值栈(OGNL)
- 引言
在我们开发过程中,往往会使用一个对像传递到一个具体的action中,之后到跳转页面中访问对应对象的具体的参数。
比如:我们搭建一个struts2项目:
回顾下如何搭建strut2:
1、下载的struts2开发包(struts-2.3.31-all.zip);
2、解压struts-2.3.31-all.zip,并把\apps\struts2-blank.war解压;
3、取出\struts2-blank\WEB-INF\lib下的所有jar包到Struts_01\WebContent\WEB-INF\lib下;
4、\struts2-blank\WEB-INF\src\java下的struts.xml、log4j2.xml、velocity.properties拷贝到Struts_01\WebContent\src;
5、拷贝\struts2-blank\WEB-INF\web.xml到Struts_01\WebContent\WEB-INF\web.xml下;
6、创建包com.dx.struts2.valuestack,并在包下创建Product.java。
package com.dx.struts2.valuestack; public class Product { private Integer productId; private String productName; private String productDesc; private Double productPrice; public Integer getProductId() { return productId; } public void setProductId(Integer productId) { this.productId = productId; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public String getProductDesc() { return productDesc; } public void setProductDesc(String productDesc) { this.productDesc = productDesc; } public Double getProductPrice() { return productPrice; } public void setProductPrice(Double productPrice) { this.productPrice = productPrice; } public String save(){ System.out.println("save"); return "success"; } }
7、修改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 01</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>
8、修改struts.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <!-- <constant name="struts.action.extension" value="action" /> --> <constant name="struts.enable.DynamicMethodInvocation" value="false" /> <constant name="struts.devMode" value="false" /> <package name="default" namespace="/" extends="struts-default"> <action name="product-save" class="com.dx.struts2.valuestack.Product" method="save"> <result>/details.jsp</result> </action> </package> <!-- Add packages here --> </struts>
9、添加页面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> <s:form action="product-save"> product name: <input name="productName" /> <br /> product description: <input name="productDesc" /> <br /> product price: <input name="productPrice" /> <br /> <s:submit name="method:save.do" value="提交"></s:submit> </s:form> </body> </html>
10、添加details.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!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> ${productName}<br/> request.getAttribute("productName"):<%=request.getAttribute("productName") %><br/> ${productDesc}<br/> ${productPrice}<br/> <%=request %> </body> </html>
运行发现request对象不是HttpRequestWrapper。
- 跟踪调试:
从“引言”的代码运行结果中发现request是org.apache.struts2.dispatcher.StrutsRequestWrapper类型。那就说明一个问题,这里边的${productName}和<%=request.getAttribute("productName")%>都是通过调用StrutsRequestWrapper(Ctrl+T会弹出窗口,输入StrutsRequestWrapper会自动索引到源代码)的getAttribute(String key)函数:
/** * Gets the object, looking in the value stack if not found * * @param key The attribute key */ public Object getAttribute(String key) { if (key == null) { throw new NullPointerException("You must specify a key value"); } if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) { // don't bother with the standard javax.servlet attributes, we can short-circuit this // see WW-953 and the forums post linked in that issue for more info return super.getAttribute(key); } ActionContext ctx = ActionContext.getContext(); Object attribute = super.getAttribute(key); //supper->HttpRequestWrapper if (ctx != null && attribute == null) { boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE)); // note: we don't let # come through or else a request for // #attr.foo or #request.foo could cause an endless loop if (!alreadyIn && !key.contains("#")) { try { // If not found, then try the ValueStack ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE); ValueStack stack = ctx.getValueStack(); if (stack != null) { attribute = stack.findValue(key); } } finally { ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE); } } } return attribute; }
如果debug的话,可以从代码跟踪中找到上边的Product对象的参数是存储到哪里。
付截图:
从上边截图中我们发现Product对象是存储到ValueStack(值栈)中,而并不是在请求域中真的存在这样的一个值。
查看ActionContext源码,发现里边存储了Application/Seesion/Parameter等。
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.inject.Container; 19 import com.opensymphony.xwork2.util.ValueStack; 20 21 import java.io.Serializable; 22 import java.util.HashMap; 23 import java.util.Locale; 24 import java.util.Map; 25 26 27 /** 28 * The ActionContext is the context in which an {@link Action} is executed. Each context is basically a 29 * container of objects an action needs for execution like the session, parameters, locale, etc. <p> 30 * <p/> 31 * The ActionContext is thread local which means that values stored in the ActionContext are 32 * unique per thread. See the {@link ThreadLocal} class for more information. The benefit of 33 * this is you don't need to worry about a user specific action context, you just get it: 34 * <p/> 35 * <ul><code>ActionContext context = ActionContext.getContext();</code></ul> 36 * <p/> 37 * Finally, because of the thread local usage you don't need to worry about making your actions thread safe. 38 * 39 * @author Patrick Lightbody 40 * @author Bill Lynch (docs) 41 */ 42 public class ActionContext implements Serializable { 43 44 static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>(); 45 46 /** 47 * Constant for the name of the action being executed. 48 */ 49 public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name"; 50 51 /** 52 * Constant for the {@link com.opensymphony.xwork2.util.ValueStack OGNL value stack}. 53 */ 54 public static final String VALUE_STACK = ValueStack.VALUE_STACK; 55 56 /** 57 * Constant for the action's session. 58 */ 59 public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session"; 60 61 /** 62 * Constant for the action's application context. 63 */ 64 public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application"; 65 66 /** 67 * Constant for the action's parameters. 68 */ 69 public static final String PARAMETERS = "com.opensymphony.xwork2.ActionContext.parameters"; 70 71 /** 72 * Constant for the action's locale. 73 */ 74 public static final String LOCALE = "com.opensymphony.xwork2.ActionContext.locale"; 75 76 /** 77 * Constant for the action's type converter. 78 */ 79 public static final String TYPE_CONVERTER = "com.opensymphony.xwork2.ActionContext.typeConverter"; 80 81 /** 82 * Constant for the action's {@link com.opensymphony.xwork2.ActionInvocation invocation} context. 83 */ 84 public static final String ACTION_INVOCATION = "com.opensymphony.xwork2.ActionContext.actionInvocation"; 85 86 /** 87 * Constant for the map of type conversion errors. 88 */ 89 public static final String CONVERSION_ERRORS = "com.opensymphony.xwork2.ActionContext.conversionErrors"; 90 91 92 /** 93 * Constant for the container 94 */ 95 public static final String CONTAINER = "com.opensymphony.xwork2.ActionContext.container"; 96 97 private Map<String, Object> context; 98 99 /** 100 * Creates a new ActionContext initialized with another context. 101 * 102 * @param context a context map. 103 */ 104 public ActionContext(Map<String, Object> context) { 105 this.context = context; 106 } 107 108 109 /** 110 * Sets the action invocation (the execution state). 111 * 112 * @param actionInvocation the action execution state. 113 */ 114 public void setActionInvocation(ActionInvocation actionInvocation) { 115 put(ACTION_INVOCATION, actionInvocation); 116 } 117 118 /** 119 * Gets the action invocation (the execution state). 120 * 121 * @return the action invocation (the execution state). 122 */ 123 public ActionInvocation getActionInvocation() { 124 return (ActionInvocation) get(ACTION_INVOCATION); 125 } 126 127 /** 128 * Sets the action's application context. 129 * 130 * @param application the action's application context. 131 */ 132 public void setApplication(Map<String, Object> application) { 133 put(APPLICATION, application); 134 } 135 136 /** 137 * Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise. 138 * 139 * @return a Map of ServletContext or generic application level Map 140 */ 141 public Map<String, Object> getApplication() { 142 return (Map<String, Object>) get(APPLICATION); 143 } 144 145 /** 146 * Sets the action context for the current thread. 147 * 148 * @param context the action context. 149 */ 150 public static void setContext(ActionContext context) { 151 actionContext.set(context); 152 } 153 154 /** 155 * Returns the ActionContext specific to the current thread. 156 * 157 * @return the ActionContext for the current thread, is never <tt>null</tt>. 158 */ 159 public static ActionContext getContext() { 160 return actionContext.get(); 161 } 162 163 /** 164 * Sets the action's context map. 165 * 166 * @param contextMap the context map. 167 */ 168 public void setContextMap(Map<String, Object> contextMap) { 169 getContext().context = contextMap; 170 } 171 172 /** 173 * Gets the context map. 174 * 175 * @return the context map. 176 */ 177 public Map<String, Object> getContextMap() { 178 return context; 179 } 180 181 /** 182 * Sets conversion errors which occurred when executing the action. 183 * 184 * @param conversionErrors a Map of errors which occurred when executing the action. 185 */ 186 public void setConversionErrors(Map<String, Object> conversionErrors) { 187 put(CONVERSION_ERRORS, conversionErrors); 188 } 189 190 /** 191 * Gets the map of conversion errors which occurred when executing the action. 192 * 193 * @return the map of conversion errors which occurred when executing the action or an empty map if 194 * there were no errors. 195 */ 196 public Map<String, Object> getConversionErrors() { 197 Map<String, Object> errors = (Map) get(CONVERSION_ERRORS); 198 199 if (errors == null) { 200 errors = new HashMap<String, Object>(); 201 setConversionErrors(errors); 202 } 203 204 return errors; 205 } 206 207 /** 208 * Sets the Locale for the current action. 209 * 210 * @param locale the Locale for the current action. 211 */ 212 public void setLocale(Locale locale) { 213 put(LOCALE, locale); 214 } 215 216 /** 217 * Gets the Locale of the current action. If no locale was ever specified the platform's 218 * {@link java.util.Locale#getDefault() default locale} is used. 219 * 220 * @return the Locale of the current action. 221 */ 222 public Locale getLocale() { 223 Locale locale = (Locale) get(LOCALE); 224 225 if (locale == null) { 226 locale = Locale.getDefault(); 227 setLocale(locale); 228 } 229 230 return locale; 231 } 232 233 /** 234 * Sets the name of the current Action in the ActionContext. 235 * 236 * @param name the name of the current action. 237 */ 238 public void setName(String name) { 239 put(ACTION_NAME, name); 240 } 241 242 /** 243 * Gets the name of the current Action. 244 * 245 * @return the name of the current action. 246 */ 247 public String getName() { 248 return (String) get(ACTION_NAME); 249 } 250 251 /** 252 * Sets the action parameters. 253 * 254 * @param parameters the parameters for the current action. 255 */ 256 public void setParameters(Map<String, Object> parameters) { 257 put(PARAMETERS, parameters); 258 } 259 260 /** 261 * Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of 262 * parameters otherwise. 263 * 264 * @return a Map of HttpServletRequest parameters or a multipart map when in a servlet environment, or a 265 * generic Map of parameters otherwise. 266 */ 267 public Map<String, Object> getParameters() { 268 return (Map<String, Object>) get(PARAMETERS); 269 } 270 271 /** 272 * Sets a map of action session values. 273 * 274 * @param session the session values. 275 */ 276 public void setSession(Map<String, Object> session) { 277 put(SESSION, session); 278 } 279 280 /** 281 * Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise. 282 * 283 * @return the Map of HttpSession values when in a servlet environment or a generic session map otherwise. 284 */ 285 public Map<String, Object> getSession() { 286 return (Map<String, Object>) get(SESSION); 287 } 288 289 /** 290 * Sets the OGNL value stack. 291 * 292 * @param stack the OGNL value stack. 293 */ 294 public void setValueStack(ValueStack stack) { 295 put(VALUE_STACK, stack); 296 } 297 298 /** 299 * Gets the OGNL value stack. 300 * 301 * @return the OGNL value stack. 302 */ 303 public ValueStack getValueStack() { 304 return (ValueStack) get(VALUE_STACK); 305 } 306 307 /** 308 * Gets the container for this request 309 * 310 * @param cont The container 311 */ 312 public void setContainer(Container cont) { 313 put(CONTAINER, cont); 314 } 315 316 /** 317 * Sets the container for this request 318 * 319 * @return The container 320 */ 321 public Container getContainer() { 322 return (Container) get(CONTAINER); 323 } 324 325 public <T> T getInstance(Class<T> type) { 326 Container cont = getContainer(); 327 if (cont != null) { 328 return cont.getInstance(type); 329 } else { 330 throw new XWorkException("Cannot find an initialized container for this request."); 331 } 332 } 333 334 /** 335 * Returns a value that is stored in the current ActionContext by doing a lookup using the value's key. 336 * 337 * @param key the key used to find the value. 338 * @return the value that was found using the key or <tt>null</tt> if the key was not found. 339 */ 340 public Object get(String key) { 341 return context.get(key); 342 } 343 344 /** 345 * Stores a value in the current ActionContext. The value can be looked up using the key. 346 * 347 * @param key the key of the value. 348 * @param value the value to be stored. 349 */ 350 public void put(String key, Object value) { 351 context.put(key, value); 352 } 353 }
继续调试,我来看下是ValueStack是怎么存储数据的。
我们发现ValueStack是:com.opensymphony.xwork2.ognl.OgnlValueStack,
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.ognl; 17 18 import com.opensymphony.xwork2.ActionContext; 19 import com.opensymphony.xwork2.TextProvider; 20 import com.opensymphony.xwork2.XWorkConstants; 21 import com.opensymphony.xwork2.XWorkException; 22 import com.opensymphony.xwork2.conversion.impl.XWorkConverter; 23 import com.opensymphony.xwork2.inject.Container; 24 import com.opensymphony.xwork2.inject.Inject; 25 import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; 26 import com.opensymphony.xwork2.util.ClearableValueStack; 27 import com.opensymphony.xwork2.util.CompoundRoot; 28 import com.opensymphony.xwork2.util.MemberAccessValueStack; 29 import com.opensymphony.xwork2.util.ValueStack; 30 import com.opensymphony.xwork2.util.logging.Logger; 31 import com.opensymphony.xwork2.util.logging.LoggerFactory; 32 import com.opensymphony.xwork2.util.logging.LoggerUtils; 33 import com.opensymphony.xwork2.util.reflection.ReflectionContextState; 34 import ognl.*; 35 36 import java.io.Serializable; 37 import java.util.HashMap; 38 import java.util.Map; 39 import java.util.Set; 40 import java.util.regex.Pattern; 41 42 /** 43 * Ognl implementation of a value stack that allows for dynamic Ognl expressions to be evaluated against it. When evaluating an expression, 44 * the stack will be searched down the stack, from the latest objects pushed in to the earliest, looking for a bean with a getter or setter 45 * for the given property or a method of the given name (depending on the expression being evaluated). 46 * 47 * @author Patrick Lightbody 48 * @author tm_jee 49 * @version $Date$ $Id$ 50 */ 51 public class OgnlValueStack implements Serializable, ValueStack, ClearableValueStack, MemberAccessValueStack { 52 53 public static final String THROW_EXCEPTION_ON_FAILURE = OgnlValueStack.class.getName() + ".throwExceptionOnFailure"; 54 55 private static final long serialVersionUID = 370737852934925530L; 56 57 private static final String MAP_IDENTIFIER_KEY = "com.opensymphony.xwork2.util.OgnlValueStack.MAP_IDENTIFIER_KEY"; 58 private static final Logger LOG = LoggerFactory.getLogger(OgnlValueStack.class); 59 60 CompoundRoot root; 61 transient Map<String, Object> context; 62 Class defaultType; 63 Map<Object, Object> overrides; 64 transient OgnlUtil ognlUtil; 65 transient SecurityMemberAccess securityMemberAccess; 66 private transient XWorkConverter converter; 67 68 private boolean devMode; 69 private boolean logMissingProperties; 70 71 protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) { 72 setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess); 73 push(prov); 74 } 75 76 protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticAccess) { 77 setRoot(xworkConverter, accessor, new CompoundRoot(vs.getRoot()), allowStaticAccess); 78 } 79 80 @Inject 81 public void setOgnlUtil(OgnlUtil ognlUtil) { 82 this.ognlUtil = ognlUtil; 83 securityMemberAccess.setExcludedClasses(ognlUtil.getExcludedClasses()); 84 securityMemberAccess.setExcludedPackageNamePatterns(ognlUtil.getExcludedPackageNamePatterns()); 85 securityMemberAccess.setExcludedPackageNames(ognlUtil.getExcludedPackageNames()); 86 } 87 88 protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot, 89 boolean allowStaticMethodAccess) { 90 this.root = compoundRoot; 91 this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess); 92 this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), securityMemberAccess); 93 context.put(VALUE_STACK, this); 94 Ognl.setClassResolver(context, accessor); 95 ((OgnlContext) context).setTraceEvaluations(false); 96 ((OgnlContext) context).setKeepLastEvaluation(false); 97 } 98 99 @Inject(XWorkConstants.DEV_MODE) 100 public void setDevMode(String mode) { 101 devMode = "true".equalsIgnoreCase(mode); 102 } 103 104 @Inject(value = "logMissingProperties", required = false) 105 public void setLogMissingProperties(String logMissingProperties) { 106 this.logMissingProperties = "true".equalsIgnoreCase(logMissingProperties); 107 } 108 109 /** 110 * @see com.opensymphony.xwork2.util.ValueStack#getContext() 111 */ 112 public Map<String, Object> getContext() { 113 return context; 114 } 115 116 /** 117 * @see com.opensymphony.xwork2.util.ValueStack#setDefaultType(java.lang.Class) 118 */ 119 public void setDefaultType(Class defaultType) { 120 this.defaultType = defaultType; 121 } 122 123 /** 124 * @see com.opensymphony.xwork2.util.ValueStack#setExprOverrides(java.util.Map) 125 */ 126 public void setExprOverrides(Map<Object, Object> overrides) { 127 if (this.overrides == null) { 128 this.overrides = overrides; 129 } else { 130 this.overrides.putAll(overrides); 131 } 132 } 133 134 /** 135 * @see com.opensymphony.xwork2.util.ValueStack#getExprOverrides() 136 */ 137 public Map<Object, Object> getExprOverrides() { 138 return this.overrides; 139 } 140 141 /** 142 * @see com.opensymphony.xwork2.util.ValueStack#getRoot() 143 */ 144 public CompoundRoot getRoot() { 145 return root; 146 } 147 148 /** 149 * @see com.opensymphony.xwork2.util.ValueStack#setParameter(String, Object) 150 */ 151 public void setParameter(String expr, Object value) { 152 setValue(expr, value, devMode); 153 } 154 155 /** 156 157 /** 158 * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object) 159 */ 160 public void setValue(String expr, Object value) { 161 setValue(expr, value, devMode); 162 } 163 164 /** 165 * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object, boolean) 166 */ 167 public void setValue(String expr, Object value, boolean throwExceptionOnFailure) { 168 Map<String, Object> context = getContext(); 169 try { 170 trySetValue(expr, value, throwExceptionOnFailure, context); 171 } catch (OgnlException e) { 172 handleOgnlException(expr, value, throwExceptionOnFailure, e); 173 } catch (RuntimeException re) { //XW-281 174 handleRuntimeException(expr, value, throwExceptionOnFailure, re); 175 } finally { 176 cleanUpContext(context); 177 } 178 } 179 180 private void trySetValue(String expr, Object value, boolean throwExceptionOnFailure, Map<String, Object> context) throws OgnlException { 181 context.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, expr); 182 context.put(REPORT_ERRORS_ON_NO_PROP, (throwExceptionOnFailure) ? Boolean.TRUE : Boolean.FALSE); 183 ognlUtil.setValue(expr, context, root, value); 184 } 185 186 private void cleanUpContext(Map<String, Object> context) { 187 ReflectionContextState.clear(context); 188 context.remove(XWorkConverter.CONVERSION_PROPERTY_FULLNAME); 189 context.remove(REPORT_ERRORS_ON_NO_PROP); 190 } 191 192 private void handleRuntimeException(String expr, Object value, boolean throwExceptionOnFailure, RuntimeException re) { 193 if (throwExceptionOnFailure) { 194 String message = ErrorMessageBuilder.create() 195 .errorSettingExpressionWithValue(expr, value) 196 .build(); 197 throw new XWorkException(message, re); 198 } else { 199 if (LOG.isWarnEnabled()) { 200 LOG.warn("Error setting value [#0] with expression [#1]", re, value.toString(), expr); 201 } 202 } 203 } 204 205 private void handleOgnlException(String expr, Object value, boolean throwExceptionOnFailure, OgnlException e) { 206 boolean shouldLog = shouldLogMissingPropertyWarning(e); 207 String msg = null; 208 if (throwExceptionOnFailure || shouldLog) { 209 msg = ErrorMessageBuilder.create() 210 .errorSettingExpressionWithValue(expr, value) 211 .build(); 212 } 213 if (shouldLog) { 214 LOG.warn(msg, e); 215 } 216 217 if (throwExceptionOnFailure) { 218 throw new XWorkException(msg, e); 219 } 220 } 221 222 /** 223 * @see com.opensymphony.xwork2.util.ValueStack#findString(java.lang.String) 224 */ 225 public String findString(String expr) { 226 return (String) findValue(expr, String.class); 227 } 228 229 public String findString(String expr, boolean throwExceptionOnFailure) { 230 return (String) findValue(expr, String.class, throwExceptionOnFailure); 231 } 232 233 /** 234 * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String) 235 */ 236 public Object findValue(String expr, boolean throwExceptionOnFailure) { 237 try { 238 setupExceptionOnFailure(throwExceptionOnFailure); 239 return tryFindValueWhenExpressionIsNotNull(expr); 240 } catch (OgnlException e) { 241 return handleOgnlException(expr, throwExceptionOnFailure, e); 242 } catch (Exception e) { 243 return handleOtherException(expr, throwExceptionOnFailure, e); 244 } finally { 245 ReflectionContextState.clear(context); 246 } 247 } 248 249 private void setupExceptionOnFailure(boolean throwExceptionOnFailure) { 250 if (throwExceptionOnFailure) { 251 context.put(THROW_EXCEPTION_ON_FAILURE, true); 252 } 253 } 254 255 private Object tryFindValueWhenExpressionIsNotNull(String expr) throws OgnlException { 256 if (expr == null) { 257 return null; 258 } 259 return tryFindValue(expr); 260 } 261 262 private Object handleOtherException(String expr, boolean throwExceptionOnFailure, Exception e) { 263 logLookupFailure(expr, e); 264 265 if (throwExceptionOnFailure) 266 throw new XWorkException(e); 267 268 return findInContext(expr); 269 } 270 271 private Object tryFindValue(String expr) throws OgnlException { 272 Object value; 273 expr = lookupForOverrides(expr); 274 if (defaultType != null) { 275 value = findValue(expr, defaultType); 276 } else { 277 value = getValueUsingOgnl(expr); 278 if (value == null) { 279 value = findInContext(expr); 280 } 281 } 282 return value; 283 } 284 285 private String lookupForOverrides(String expr) { 286 if ((overrides != null) && overrides.containsKey(expr)) { 287 expr = (String) overrides.get(expr); 288 } 289 return expr; 290 } 291 292 private Object getValueUsingOgnl(String expr) throws OgnlException { 293 try { 294 return ognlUtil.getValue(expr, context, root); 295 } finally { 296 context.remove(THROW_EXCEPTION_ON_FAILURE); 297 } 298 } 299 300 public Object findValue(String expr) { 301 return findValue(expr, false); 302 } 303 304 /** 305 * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String, java.lang.Class) 306 */ 307 public Object findValue(String expr, Class asType, boolean throwExceptionOnFailure) { 308 try { 309 setupExceptionOnFailure(throwExceptionOnFailure); 310 return tryFindValueWhenExpressionIsNotNull(expr, asType); 311 } catch (OgnlException e) { 312 final Object value = handleOgnlException(expr, throwExceptionOnFailure, e); 313 return converter.convertValue(getContext(), value, asType); 314 } catch (Exception e) { 315 final Object value = handleOtherException(expr, throwExceptionOnFailure, e); 316 return converter.convertValue(getContext(), value, asType); 317 } finally { 318 ReflectionContextState.clear(context); 319 } 320 } 321 322 private Object tryFindValueWhenExpressionIsNotNull(String expr, Class asType) throws OgnlException { 323 if (expr == null) { 324 return null; 325 } 326 return tryFindValue(expr, asType); 327 } 328 329 private Object handleOgnlException(String expr, boolean throwExceptionOnFailure, OgnlException e) { 330 Object ret = findInContext(expr); 331 if (ret == null) { 332 if (shouldLogMissingPropertyWarning(e)) { 333 LOG.warn("Could not find property [#0]!", e, expr); 334 } 335 if (throwExceptionOnFailure) { 336 throw new XWorkException(e); 337 } 338 } 339 return ret; 340 } 341 342 private boolean shouldLogMissingPropertyWarning(OgnlException e) { 343 return (e instanceof NoSuchPropertyException || e instanceof MethodFailedException) 344 && devMode && logMissingProperties; 345 } 346 347 private Object tryFindValue(String expr, Class asType) throws OgnlException { 348 Object value = null; 349 try { 350 expr = lookupForOverrides(expr); 351 value = getValue(expr, asType); 352 if (value == null) { 353 value = findInContext(expr); 354 return converter.convertValue(getContext(), value, asType); 355 } 356 } finally { 357 context.remove(THROW_EXCEPTION_ON_FAILURE); 358 } 359 return value; 360 } 361 362 private Object getValue(String expr, Class asType) throws OgnlException { 363 return ognlUtil.getValue(expr, context, root, asType); 364 } 365 366 private Object findInContext(String name) { 367 return getContext().get(name); 368 } 369 370 public Object findValue(String expr, Class asType) { 371 return findValue(expr, asType, false); 372 } 373 374 /** 375 * Log a failed lookup, being more verbose when devMode=true. 376 * 377 * @param expr The failed expression 378 * @param e The thrown exception. 379 */ 380 private void logLookupFailure(String expr, Exception e) { 381 String msg = LoggerUtils.format("Caught an exception while evaluating expression '#0' against value stack", expr); 382 if (devMode && LOG.isWarnEnabled()) { 383 LOG.warn(msg, e); 384 LOG.warn("NOTE: Previous warning message was issued due to devMode set to true."); 385 } else if (LOG.isDebugEnabled()) { 386 LOG.debug(msg, e); 387 } 388 } 389 390 /** 391 * @see com.opensymphony.xwork2.util.ValueStack#peek() 392 */ 393 public Object peek() { 394 return root.peek(); 395 } 396 397 /** 398 * @see com.opensymphony.xwork2.util.ValueStack#pop() 399 */ 400 public Object pop() { 401 return root.pop(); 402 } 403 404 /** 405 * @see com.opensymphony.xwork2.util.ValueStack#push(java.lang.Object) 406 */ 407 public void push(Object o) { 408 root.push(o); 409 } 410 411 /** 412 * @see com.opensymphony.xwork2.util.ValueStack#set(java.lang.String, java.lang.Object) 413 */ 414 public void set(String key, Object o) { 415 //set basically is backed by a Map pushed on the stack with a key being put on the map and the Object being the value 416 Map setMap = retrieveSetMap(); 417 setMap.put(key, o); 418 } 419 420 private Map retrieveSetMap() { 421 Map setMap; 422 Object topObj = peek(); 423 if (shouldUseOldMap(topObj)) { 424 setMap = (Map) topObj; 425 } else { 426 setMap = new HashMap(); 427 setMap.put(MAP_IDENTIFIER_KEY, ""); 428 push(setMap); 429 } 430 return setMap; 431 } 432 433 /** 434 * check if this is a Map put on the stack for setting if so just use the old map (reduces waste) 435 */ 436 private boolean shouldUseOldMap(Object topObj) { 437 return topObj instanceof Map && ((Map) topObj).get(MAP_IDENTIFIER_KEY) != null; 438 } 439 440 /** 441 * @see com.opensymphony.xwork2.util.ValueStack#size() 442 */ 443 public int size() { 444 return root.size(); 445 } 446 447 private Object readResolve() { 448 // TODO: this should be done better 449 ActionContext ac = ActionContext.getContext(); 450 Container cont = ac.getContainer(); 451 XWorkConverter xworkConverter = cont.getInstance(XWorkConverter.class); 452 CompoundRootAccessor accessor = (CompoundRootAccessor) cont.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()); 453 TextProvider prov = cont.getInstance(TextProvider.class, "system"); 454 boolean allow = "true".equals(cont.getInstance(String.class, XWorkConstants.ALLOW_STATIC_METHOD_ACCESS)); 455 OgnlValueStack aStack = new OgnlValueStack(xworkConverter, accessor, prov, allow); 456 aStack.setOgnlUtil(cont.getInstance(OgnlUtil.class)); 457 aStack.setRoot(xworkConverter, accessor, this.root, allow); 458 459 return aStack; 460 } 461 462 463 public void clearContextValues() { 464 //this is an OGNL ValueStack so the context will be an OgnlContext 465 //it would be better to make context of type OgnlContext 466 ((OgnlContext) context).getValues().clear(); 467 } 468 469 public void setAcceptProperties(Set<Pattern> acceptedProperties) { 470 securityMemberAccess.setAcceptProperties(acceptedProperties); 471 } 472 473 public void setExcludeProperties(Set<Pattern> excludeProperties) { 474 securityMemberAccess.setExcludeProperties(excludeProperties); 475 } 476 477 @Inject 478 public void setXWorkConverter(final XWorkConverter converter) { 479 this.converter = converter; 480 } 481 }
继续调试查看ValueStack属性,context是ognl.OgnlContext也是一个Map类型,Root类型是一个ValueStack类型。
这里我们看以看出Root是一个CompoundRoot类型
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.util; 17 18 import java.util.ArrayList; 19 import java.util.List; 20 import java.util.concurrent.CopyOnWriteArrayList; 21 22 23 /** 24 * A Stack that is implemented using a List. 25 * 26 * @author plightbo 27 * @version $Revision$ 28 */ 29 public class CompoundRoot extends CopyOnWriteArrayList<Object> { 30 31 private static final long serialVersionUID = 8563229069192473995L; 32 33 public CompoundRoot() { 34 } 35 36 public CompoundRoot(List<?> list) { 37 super(list); 38 } 39 40 41 public CompoundRoot cutStack(int index) { 42 return new CompoundRoot(subList(index, size())); 43 } 44 45 public Object peek() { 46 return get(0); 47 } 48 49 public Object pop() { 50 return remove(0); 51 } 52 53 public void push(Object o) { 54 add(0, o); 55 } 56 }
从源代码中我们可以看出CompoundRoot是一个通过List实现的栈,而结合测试我们可以知道Product对象就是存储到root对象中。
在数据查找时,如果都存储在值栈中的话,会先从值栈中第一个元素查找,如果第一个元素的属性不包含要找的对象,就从第二元素找。
- 总结:
ValueStack(值栈):贯穿整个Action的生命周期(每个Action类的对象实例都拥有一个ValueStack对象),相当于一个数据的中转站,在其中保存当前Action对象和其他相关对象。
Struts框架把ValueStack对象保存在名为"struts.valueStack"的请求属性中。
在值栈对象的内部有两个逻辑部分:
1、ContextMap:Struts把各种各样的映射关系(一些Map类型的对象)压入ContextMap中。实际上就是对ActionContext的引用。Struts会把下面这些映射压入ContextMap对中:
parameters:该Map中包含当请求的请求参数
request:该Map中包含当前request对象中的所有属性
session:该Map中包含当前session对象中所有属性
application:该Map中包含当前application对象中的所有属性
attr:该Map中按如下顺序来检索某个属性:request,session,application
2、ObjectStack:Struts把Action和相关对象压入ObjectStack中。
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。