struct2源码解读之处理Action请求
我们前面讨论过了struct2的初始化,我们先来回顾下
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); try { FilterHostConfig config = new FilterHostConfig(filterConfig); init.initLogging(config); //解析配置文件,并返回一个封装了封装配置信息的Dispacher对象 Dispatcher dispatcher = init.initDispatcher(config); init.initStaticContentLoader(config, dispatcher); //返回一个处理action请求的对象 prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); //返回一个执行action请求的对象 execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); //黑名单列表 this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); postInit(dispatcher, filterConfig); } finally { init.cleanup(); } }
从上面可以看出,struct2的初始化其实就是封装配置文件信息到Disacher对象,然后根据这个对象实例化两个对象:一个是用来处理action请求的PrepareOperations对象;一个是用来执行action请求的
ExecuteOperations对象,当有request请求的时候,就会调用这两个对象的方法来处理和执行请求。我们知道,当客户端放送请求的时候,会执行过滤器的doFilter()方法。我们来看看,StrutsPrepareAndExecuteFilter的doFilter()方法。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //1.处理aciton请求 try { //设置编码和本地化信息 prepare.setEncodingAndLocale(request, response); //创建actionContext上下文 prepare.createActionContext(request, response); //把dispacher添加到线程 prepare.assignDispatcherToThread(); //判断是否在黑名单内 if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { //在黑名单,跳过,转到下一个过滤器 chain.doFilter(request, response); } else { //处理request请求 request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); //如果找不到这个action if (mapping == null) { //执行静态文件 boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { //如果找不到这个静态文件,跳过,转到下一个过滤器 chain.doFilter(request, response); } } else { //2.找到action就执行action请求 execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }
初始化为我们准备了PrepareOperations对象和ExecuteOperations对象还有Dispacher,我们下面来看看struct2是如何利用这些对象处理action请求的。这里分两步,一步是处理aciton请求,包括解析url和找到action对应的方法;另一步是执行这个action的方法。
一、处理aciton请求(PrepareOperations对象)
处理aciton请求,都是调用PrepareOperations对象的方法来进行处理。
1.1.设置编码和国际化信息
prepare.setEncodingAndLocale(request, response);
在PrepareOperations.setEncodingAndLocale()方法中调用了dispatcher.prepare()方法,在这个方法中设置了编码信息和国际化信息。
public void prepare(HttpServletRequest request, HttpServletResponse response) { String encoding = null; /** * @Inject(StrutsConstants.STRUTS_I18N_ENCODING) * public void setDefaultEncoding(String val) { * defaultEncoding = val; * } *编码为xml中STRUTS_I18N_ENCODING中设置的值,默认为utf-8 */ if (defaultEncoding != null) { encoding = defaultEncoding; } Locale locale = null; /** * @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false) * public void setDefaultLocale(String val) { * defaultLocale = val; * } *国际化信息为STRUTS_LOCALE中设置的值,默认不设置 */ if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } if (encoding != null) { try { //设置编码 request.setCharacterEncoding(encoding); } //异常信息 } if (locale != null) { //设置国际化信息 response.setLocale(locale); } if (paramsWorkaroundEnabled) { request.getParameter("foo"); } }
在初始化的时候,通过container.inject(this)对dispacher进行了依赖注入,defaultEncoding和defaultLocale都标有@inject注解,这两个值就在这个时候设置好了。前面解析配置文件的时候,把这些值以key-value封装到了propertyContainer对象中,因此依赖注入的时候,通过value=key找到这些值并进行赋值。
1.2.创建actionContext上下文
prepare.createActionContext(request, response);
actionContext,顾名思义,指的是action请求的环境
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; //计数器,初始值为1,一次请求+1,请求执行完后保存到request的CLEANUP_RECURSION_COUNTER变量中 Integer counter = 1; Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (oldCounter != null) { counter = oldCounter + 1; } //从线程变量ThreadLocal中取出ActionContext ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap())); } else { //实例化一个值栈对象 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext)); ctx = new ActionContext(stack.getContext()); } //保存计数器的值 request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); //把actionContext保存到线程变量ThreadLocal中 ActionContext.setContext(ctx); return ctx; }
actionContext是一个Theadloacl类型,因此每个请求都是线程安全的。通过get()和set()对这个变量进行存取值。在创建actionContext的过程中,实例化了一个valueStack值栈对象。通过 dispatcher.getContainer()获得Container,然后通过Container.getInstance()取出容器中的对象,这个ValueStackFactory在strcut2-default.xml中就已配置好了。
<bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="struts" class="com.opensymphony.xwork2.ognl.OgnlValueStackFactory" />
查看OgnlValueStackFactory对象的createValueStack()方法
public ValueStack createValueStack() { //实例化一个ValueStack对象 ValueStack stack = new OgnlValueStack(xworkConverter, compoundRootAccessor, textProvider, allowStaticMethodAccess); //对valuestack依赖注入 container.inject(stack); //把container以一个map的形式保存到valuestack的context 属性中 stack.getContext().put(ActionContext.CONTAINER, container); return stack; }
值栈的context其实就是一个map,除了把container放进这个map中
dispatcher.createContextMap(request, response, null, servletContext)
这句话也把request,response,servletContext,application,session,params也放进了context这个map中.由此看出,struct2把所有值都放进了值栈valueStack.context中,而context是actionContext的一个属性,因此这个actionContext就涵盖了struct2运行所需的值,也就搭起了struct2的运行环境。
1.3.添加到线程
prepare.assignDispatcherToThread();
这句话,其实是把dispacher设置到一个线程变量ThreadLoacl<dispacher>中
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>(); public static void setInstance(Dispatcher instance) { Dispatcher.instance.set(instance); }
1.4.判断是否是黑名单
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { //如果设置了黑名单而且在请求在黑名单内,跳过,执行下一个过滤器 chain.doFilter(request, response); } else { //如果没设置黑名单或者是不在黑名单内,处理aciton请求 }
这个excludedPatterns是一个list<Pattern>集合,是一个正则表达式经编译后的表现模式.在struct2初始化的时候,我们对这个值进行设定
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
而这个黑名单,来源于我们在strcuts.xml文件中对struts.action.excludePattern的配置
public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) { //获得struts.action.excludePattern的值 return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN)); }
dispatcher.getContainer()获得Container,然后通过Container.getInstance()取出容器中的struts.action.excludePattern,这个在我们解析*.xml的时候已封装好。然后把这个值进一步封装到list<Pattern> 中
private List<Pattern> buildExcludedPatternsList( String patterns ) { if (null != patterns && patterns.trim().length() != 0) { List<Pattern> list = new ArrayList<Pattern>(); //以逗号分隔 String[] tokens = patterns.split(","); //循环遍历 for ( String token : tokens ) { //编译一个正规表达式,并把编译结果添加到一个list集合中 list.add(Pattern.compile(token.trim())); } return Collections.unmodifiableList(list); } else { return null; } }
如果设置了黑名单,把黑名单填加到一个list集合中后,PrepareOperations提供了一个isUrlExcluded()方法来对黑名单进行筛选
public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) { //如果设置了黑名单 if (excludedPatterns != null) { //获得请求url String uri = getUri(request); //循环遍历 for ( Pattern pattern : excludedPatterns ) { //pattern.matcher(uri)生成一个给定命名的Matcher对象,调用该对象的matches()方法进行匹配检测,只有整个目标字符串完全匹配时才返回真值 if (pattern.matcher(uri).matches()) { return true; } } } return false; }
1.5.处理request请求
如果通过了黑名单的匹配检测,则处理request请求
request = prepare.wrapRequest(request);
我们知道,普通的request请求和文件上传时的request请求时的request请求类型是不一样的,这一步就是对不同类型的request请求,进行封装
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { // 默认为StrutsRequestWrapper类型 if (request instanceof StrutsRequestWrapper) { return request; } //获得request请求的类型 String content_type = request.getContentType(); //如果request类型不为空且为multipart/form-data类型 if (content_type != null && content_type.indexOf("multipart/form-data") != -1) { MultiPartRequest mpr = null; //check for alternate implementations of MultiPartRequest Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class); if (multiNames != null) { for (String multiName : multiNames) { if (multiName.equals(multipartHandlerName)) { mpr = getContainer().getInstance(MultiPartRequest.class, multiName); } } } if (mpr == null ) { mpr = getContainer().getInstance(MultiPartRequest.class); } //把request封装成MultiPartRequestWrapper对象 request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext)); } else { //其它的封装成StrutsRequestWrapper对象 request = new StrutsRequestWrapper(request); } return request; }
1.6.处理action请求
ActionMapping mapping = prepare.findActionMapping(request, response, true);
当前面设置好编码和黑名单都检测通过后,就开始要处理action请求了,处理action请求,其实就是
把url信息封装到一个actionMapping对象。
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { //因为封装后的ActionMapping对象,最后会保存到request的STRUTS_ACTION_MAPPING_KEY变量中,因此在封装前要先判断 ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mapping == null || forceLookup) { try { //获得ActionMapper对象,调用其getMapping()方法封装url信息 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { //mapping值不为空时,设置到request的STRUTS_ACTION_MAPPING_KEY变量中 request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } //异常信息 } } return mapping; }
特别注意,ActionMapper是一个操作者,而ActionMapping是一个保存信息的对象,通过ActionMapper.getMapping()方法,获得一个ActionMapping对象
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); //获得uri,如url="http:\\localhost:8080\playwell\back\login.action",则uri为"playwell\back\login.action?id=1!method" String uri = getUri(request); //以分号分割 int indexOfSemicolon = uri.indexOf(";"); //如果有分号,全第一个分号前面的,否则的话,取原值 uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; //删除后缀,extension默认为aciton和"",如果为空,找最后一个“.”,找不到这个点才返回原值,如果是action,判断uri是否endwith action,是的话,取“.”前面的值,然后mapping.setExtension(); uri = dropExtension(uri, mapping); if (uri == null) { return null; } /* * *设置命名空间和action名。这里对url作逻辑判断,分离出命名空间和action名 * mapping.setNamespace(namespace); * mapping.setName(name); * */ parseNameAndNamespace(uri, mapping, configManager); //处理params handleSpecialParameters(request, mapping); if (mapping.getName() == null) { return null; } //mapping.setMethod(),处理!method parseActionName(mapping); return mapping; }
这一步主要是对url进行解析工作,把url的每一个字段分割出来,并封装到actionMapping对象中,大多都是字符串操作,在这就不过多叙述了,下面附上actionMapping的部分代码以加深大家对这一步操作的理解
public class ActionMapping { private String name; //记录action名 private String namespace; //记录命名空间 private String method; //记录!method private String extension; //记录后缀 private Map<String, Object> params; private Result result; }
二、执行action请求
解析完url,把url封装到actionMapping对象中后,就开始执行action请求。这里又为分actionMapping为空和actionMapping不为空两种情况。什么情况下actionMapping为空呢?我们看回actionMapping的封装过程
if (uri == null) { return null; } if (mapping.getName() == null) { return null; }
当uri地址为空或者是actionname为空时返回一个空的actionMapping对象
2.1.找不到action
当返回一个空的actionMapping对象时,struct2会寻找静态资源,如果连静态文件都找不到,则会调过当前过滤器,去到下一个过滤器
if (mapping == null) { //执行静态资源 boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { //跳到下一个过滤器 chain.doFilter(request, response); } }
在这个过程中,struct2会加载哪些静态资源呢?
public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String resourcePath = RequestUtils.getServletPath(request); if ("".equals(resourcePath) && null != request.getPathInfo()) { resourcePath = request.getPathInfo(); } //取得StaticContentLoader对象 StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class); if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); //加载静态资源,否则的话抛出404错误。response.sendError(HttpServletResponse.SC_NOT_FOUND); return true; } else { // 给跳到下一个过滤器设置标志 return false; } }
canHandle里面表明,以/structs或者是/static开头的url的静态资源才会被加载。
public boolean canHandle(String resourcePath) { return serveStatic && (resourcePath.startsWith("/struts") || resourcePath.startsWith("/static")); }
2.2.找到action
找到action后就开始正常执行action请求。这个在下篇博文继续探讨。
三、总结
本篇博文探讨了strcut2是如何处理action请求的:利用PrepareOperations对象的方法,设置了编码信息和本地化信息,设置了action的上下文actionContext,并处理了黑名单信息,最后封装了url信息到 actionMapping对象,利用actionMapping对象的去执行相应的请求,如actionMapping为空时就尝试去加载静态资源,不为空就取执行相应的操作。