struts2原理及请求处理核心代码
StrutsXMLWeb
1. struts-default.xml
2. struts-plugin.xml
3. struts.xml
4. struts.properties
5. web.xml
多个文件定义了同一个常量值,则后一个会覆盖前面的定义!
每一个请求过来就会调用一次doFilter方法,下面来看一下doFilter方法中都做了哪些操作。在实例化HttpServletRequest和HttpServletResponse后,设置请求的编码和国际化
struts2用注解的方式在初始化的时候对属性赋值,类似与spring的依赖注入
在StrutsConstants中
在default.properties中
所以在初始化的时候defaultEncoding赋值为UTF-8,stauts.locale被注掉了,同时注解设置了required=false,对不存在的属性,初始化注入的时候被忽略了,故defaultLocale为null
下面来看一下是如何创建action上下文,又做了哪些操作。我们知道每一个请求都会创建一个action上下文,不同的action不会共享action上下文,这是通过本地线程变量实现的。
下面来看看ActionContext中有哪些属性,除了一些静态常量外就只有Map<String, Object> context一个属性。
createActionContext中最重要的是dispatcher.createContextMap(request, response, null, servletContext),该方法就是将容器对象封装成普通java对象.这个方法是struts2核心设计,前面说过struts的核心设计就是解耦合,消除核心程序对外部运行环境的依赖,即Web容器和MVC分离,在创建action上下文的时候把web容器相关的数据Request,Session,
Applicatioin...封装成普通的java对象Map使得xwork不依赖与web容器,从而解耦和。
createContextMap代码很清晰,先把容器对象封装成Map,在把这些Map作为value添加到contextMap中,创建之后再通过ActionContext.setContext(ctx)把context加到ThreadLocal中。
包装request的方法wrapRequest,在wrapRequest方法中又调用
接下来看一看prepare.findActionMapping(request, response, true)方法,在PrepareOperations的findActionMapping方法中又调用了dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager())
这里是调用ActionMapper的实现类DefaultActionMapper的getMapping方法,分析getMapping之前先看一下ActionMapping类的属性
下面看一下去除url后缀名的方法,先说一下extensions的赋值
在StrutsConstants中
在default.properties中
通过注解在初始化的时候赋值,所以extensions值为[action, ]
parseNameAndNamespace解析action的名称和命名空间
根据请求的URL,如果ActionMapping找不到,执行静态资源请求,如果找到就转发给action处理,struts的预处理到此就结束了
1. struts-default.xml
2. struts-plugin.xml
3. struts.xml
4. struts.properties
5. web.xml
多个文件定义了同一个常量值,则后一个会覆盖前面的定义!
上图来源于Struts2官方站点,是Struts 2 的整体结构。
Struts2框架由3个部分组成:核心控制器FilterDispatcher、业务控制器和用户实现的业务逻辑组件。在这3个部分里,Struts 2框架提供了核心控制器FilterDispatcher,而用户需要实现业务控制器和业务逻辑组件。
(1)核心控制器:FilterDispatcher
FilterDispatcher是Struts2框架的核心控制器,该控制器作为一个Filter运行在Web应用中,它负责拦截所有的用户请求,当用户请求到达时,该Filter会过滤用户请求。如果用户请求以action结尾,该请求将被转入Struts2框架处理。
Struts2框架获得了*.action请求后,将根据*.action请求的前面部分决定调用哪个业务逻辑组件,例如,对于login.action请求,Struts2调用名为login的Action来处理该请求。
Struts2应用中的Action都被定义在struts.xml文件中,在该文件中定义Action时,定义了该Action的name属性和class属性,其中name属性决定了该Action处理哪个用户请求,而class属性决定了该Action的实现类。
Struts2用于处理用户请求的Action实例,并不是用户实现的业务控制器,而是Action代理——因为用户实现的业务控制器并没有与Servlet API耦合,显然无法处理用户请求。而Struts2框架提供了系列拦截器,该系列拦截器负责将HttpServletRequest请求中的请求参数解析出来,传入到Action中,并回调Action 的execute方法来处理用户请求。
(2)一个请求在Struts2框架中的处理大概分为以下几个步骤
1 .客户端初始化一个指向Servlet容器(例如Tomcat)的请求 ,即HttpServletRequest请求。
2 .这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
3. 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
4 .如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
5 .ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类
6 .ActionProxy创建一个ActionInvocation的实例。
7 .ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8 .一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper
在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。
(1)核心控制器:FilterDispatcher
FilterDispatcher是Struts2框架的核心控制器,该控制器作为一个Filter运行在Web应用中,它负责拦截所有的用户请求,当用户请求到达时,该Filter会过滤用户请求。如果用户请求以action结尾,该请求将被转入Struts2框架处理。
Struts2框架获得了*.action请求后,将根据*.action请求的前面部分决定调用哪个业务逻辑组件,例如,对于login.action请求,Struts2调用名为login的Action来处理该请求。
Struts2应用中的Action都被定义在struts.xml文件中,在该文件中定义Action时,定义了该Action的name属性和class属性,其中name属性决定了该Action处理哪个用户请求,而class属性决定了该Action的实现类。
Struts2用于处理用户请求的Action实例,并不是用户实现的业务控制器,而是Action代理——因为用户实现的业务控制器并没有与Servlet API耦合,显然无法处理用户请求。而Struts2框架提供了系列拦截器,该系列拦截器负责将HttpServletRequest请求中的请求参数解析出来,传入到Action中,并回调Action 的execute方法来处理用户请求。
(2)一个请求在Struts2框架中的处理大概分为以下几个步骤
1 .客户端初始化一个指向Servlet容器(例如Tomcat)的请求 ,即HttpServletRequest请求。
2 .这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
3. 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
4 .如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
5 .ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类
6 .ActionProxy创建一个ActionInvocation的实例。
7 .ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8 .一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper
在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。
下面开始浏览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的预处理到此就结束了