struts2源码初读(二)预处理
下面开始浏览struts2请求处理部分源码,最核心的方法doFilter
/** * Dispatcher */ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { /** * 实例化HttpServletRequest和HttpServletResponse */ HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { /** * 处理编码与本地化 */ prepare.setEncodingAndLocale(request, response); /** * 创建action上下文 */ prepare.createActionContext(request, response); /** * 关联当前的dispatcher与当前线程 * */ prepare.assignDispatcherToThread(); /** * 判断请求的URL模式是不是规定的模式 */ if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { /** * 封装request对象 */ request = prepare.wrapRequest(request); /** * xwork的ConfigurationManager读取struts.xml * 返回值来自Dispatcher的Container */ ActionMapping mapping = prepare.findActionMapping(request, response, true); /** * 根据请求的URL,如果ActionMapping找不到,执行静态资源请求 * 如果找到就转发给action处理 */ if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { /** * 在执行这个方法之前对http请求做预处理,为业务处理准备数据和运行环境 * 执行这个方法后把控制权交给xwork * struts核心设计就是解耦合,消除核心程序对外部运行环境的依赖 * Web容器和MVC分离 */ execute.executeAction(request, response, mapping); } } } finally { /** * 清理本次请求相关内容 */ prepare.cleanupRequest(request); } }
每一个请求过来就会调用一次doFilter方法,下面来看一下doFilter方法中都做了哪些操作。在实例化HttpServletRequest和HttpServletResponse后,设置请求的编码和国际化
/** * PrepareOperations */ public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) { dispatcher.prepare(request, response); } /** * Dispacher */ public void prepare(HttpServletRequest request, HttpServletResponse response) { String encoding = null; if (defaultEncoding != null) { encoding = defaultEncoding; } Locale locale = null; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } if (encoding != null) { try { request.setCharacterEncoding(encoding); } catch (Exception e) { LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e); } } if (locale != null) { response.setLocale(locale); } if (paramsWorkaroundEnabled) { request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request } }
struts2用注解的方式在初始化的时候对属性赋值,类似与spring的依赖注入
在StrutsConstants中
public static final String STRUTS_LOCALE = "struts.locale"; public static final String STRUTS_I18N_ENCODING = "struts.i18n.encoding";
在default.properties中
# struts.locale=en_US struts.i18n.encoding=UTF-8
@Inject(StrutsConstants.STRUTS_I18N_ENCODING) public void setDefaultEncoding(String val) { defaultEncoding = val; } @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false) public void setDefaultLocale(String val) { defaultLocale = val; }
所以在初始化的时候defaultEncoding赋值为UTF-8,stauts.locale被注掉了,同时注解设置了required=false,对不存在的属性,初始化注入的时候被忽略了,故defaultLocale为null
下面来看一下是如何创建action上下文,又做了哪些操作。我们知道每一个请求都会创建一个action上下文,不同的action不会共享action上下文,这是通过本地线程变量实现的。
public static ActionContext getContext() { return (ActionContext) actionContext.get(); } static ThreadLocal actionContext = new ThreadLocal();
下面来看看ActionContext中有哪些属性,除了一些静态常量外就只有Map<String,Object>context一个属性。
/** * PrepareOperations */ public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; 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.setContext(ctx); return ctx; }
createActionContext中最重要的是dispatcher.createContextMap(request,response,null,servletContext),该方法就是将容器对象封装成普通java对象.这个方法是struts2核心设计,前面说过struts的核心设计就是解耦合,消除核心程序对外部运行环境的依赖,即Web容器和MVC分离,在创建action上下文的时候把web容器相关的数据Request,Session,
Applicatioin...封装成普通的java对象Map使得xwork不依赖与web容器,从而解耦和。
/** * Dispatcher */ public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping, ServletContext context) { // request map wrapping the http request objects Map requestMap = new RequestMap(request); // parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately Map params = new HashMap(request.getParameterMap()); // session map wrapping the http session Map session = new SessionMap(request); // application map wrapping the ServletContext Map application = new ApplicationMap(context); Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context); if (mapping != null) { extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); } return extraContext; }
createContextMap代码很清晰,先把容器对象封装成Map,在把这些Map作为value添加到contextMap中,创建之后再通过ActionContext.setContext(ctx)把context加到ThreadLocal中。
包装request的方法wrapRequest,在wrapRequest方法中又调用
/** * Dispatcher */ public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { /** * 判断有没有做过封装,确保只做一次封装 */ if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); /** * 封装Request对象,判断content_type是不是multipart/form-data * 如果是返回MultiPartRequestWrapper对象处理文件上传 * 如果不是返回StrutsRequestWrapper对象处理普通请求 */ 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 = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext)); } else { request = new StrutsRequestWrapper(request); } return request; }
接下来看一看prepare.findActionMapping(request,response,true)方法,在PrepareOperations的findActionMapping方法中又调用了dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request,dispatcher.getConfigurationManager())
这里是调用ActionMapper的实现类DefaultActionMapper的getMapping方法,分析getMapping之前先看一下ActionMapping类的属性
/** * action名 */ private String name; /** * action的命名空间 */ private String namespace; /** * action的执行方法 */ private String method; /** * url后缀名 */ private String extension; /** * 参数 */ private Map<String, Object> params; /** * 返回的结果 */ private Result result;
/** * DefaultActionMapper */ public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); /** * 获取请求中的url */ String uri = getUri(request); /** * 去掉url中的参数 * eg:test/test.cation;id=1-->test/test.cation */ int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; /** * 去掉url中的后缀名test/test.cation-->test/test */ uri = dropExtension(uri, mapping); if (uri == null) { return null; } /** * 解析Action的名称和命名空间 */ parseNameAndNamespace(uri, mapping, configManager); /** * 去掉请求中的重复项 */ handleSpecialParameters(request, mapping); if (mapping.getName() == null) { return null; } /** * 处理test!mehtod格式的请求 */ parseActionName(mapping); return mapping; }
/** * DefaultActionMapper */ protected String getUri(HttpServletRequest request) { /** * 判断请求是否来自于一个jsp的include * 如果通过属性"javax.servlet.include.servlet_path"取得url */ String uri = (String) request .getAttribute("javax.servlet.include.servlet_path"); if (uri != null) { return uri; } uri = RequestUtils.getServletPath(request); if (uri != null && !"".equals(uri)) { return uri; } uri = request.getRequestURI(); /** * 去掉contextPath的路径 */ return uri.substring(request.getContextPath().length()); }
下面看一下去除url后缀名的方法,先说一下extensions的赋值
在StrutsConstants中
String STRUTS_ACTION_EXTENSION = "struts.action.extension";
在default.properties中
struts.action.extension=action,,
@Inject(StrutsConstants.STRUTS_ACTION_EXTENSION) public void setExtensions(String extensions) { if (extensions != null && !"".equals(extensions)) { List<String> list = new ArrayList<String>(); String[] tokens = extensions.split(","); for (String token : tokens) { list.add(token); } if (extensions.endsWith(",")) { list.add(""); } this.extensions = Collections.unmodifiableList(list); } else { this.extensions = null; } }
通过注解在初始化的时候赋值,所以extensions值为[action,]
/** * DefaultActionMapper */ protected String dropExtension(String name, ActionMapping mapping) { if (extensions == null) { return name; } for (String ext : extensions) { if ("".equals(ext)) { /** * 如果name中不包含. * 或name中最后一个点后还有/直接返回name */ int index = name.lastIndexOf('.'); if (index == -1 || name.indexOf('/', index) >= 0) { return name; } } else { String extension = "." + ext; /** * 如果name结尾匹配定义后缀,则去掉name的匹配部分 */ if (name.endsWith(extension)) { name = name.substring(0, name.length() - extension.length()); mapping.setExtension(ext); return name; } } } return null; }
parseNameAndNamespace解析action的名称和命名空间
/** * DefaultActionMapper */ protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; int lastSlash = uri.lastIndexOf("/"); /** * 如果处理过的url中不包含/或/在url的开头,那么namespace为"" */ if (lastSlash == -1) { namespace = ""; name = uri; } else if (lastSlash == 0) { namespace = "/"; name = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) { /** * alwaysSelectFullNamespace默认是false * 判断是否把最后一个/前的字符全作为命名空间 */ namespace = uri.substring(0, lastSlash); name = uri.substring(lastSlash + 1); } else { // Try to find the namespace in those defined, defaulting to "" Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); namespace = ""; boolean rootAvailable = false; /** * 匹配最长的命名空间 * eg:url test1/test2/test * 如果配置文件有两个namespace test1/test2和test1 * 会匹配最长的那个test1/test2即贪婪匹配 */ for (Object cfg : config.getPackageConfigs().values()) { String ns = ((PackageConfig) cfg).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } if ("/".equals(ns)) { rootAvailable = true; } } name = uri.substring(namespace.length() + 1); // Still none found, use root namespace if found if (rootAvailable && "".equals(namespace)) { namespace = "/"; } } if (!allowSlashesInActionNames && name != null) { int pos = name.lastIndexOf('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } mapping.setNamespace(namespace); mapping.setName(name); }
根据请求的URL,如果ActionMapping找不到,执行静态资源请求,如果找到就转发给action处理,struts的预处理到此就结束了。