`
jianfulove
  • 浏览: 119997 次
  • 性别: Icon_minigender_1
  • 来自: 湛江
社区版块
存档分类
最新评论

struts2的源代码分析及struts2的工作流程(一)

阅读更多

 本文通过分析祥细的分析strut2.31的源代码来加深对struts2的认识。使我们在使用struts2框架的时候更有把握更得心应手。(独到的分析,非常祥细,原创)
struts2的入口就是一个StrutsPrepareAndExecuteFilter 过滤器,网上的也有很多分析struts2的文章了,但在filter入口这里却一笔略过,
其实我觉得先让大家都深入了解filter的原理这后,再一条线索往下,也许这样更能使你的思路更清淅,
所以我借助tomcat中filter的源代码从源头上开始分析,正所谓技术是日新月异的,也许哪天他的实现方式改变了,但是原理这东西是沉甸下来的,原理就是原理。
请耐心地看,也许我有哪些地方分析得不是很好,但我期望指正,分享学习也是一种快乐,让我们一起前进。


     Servlet2.3中引入Filter其实它就是使用了职责链模式(Chain Of Responsibility)的抽象设计了。责链设计模式的意图就是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
     其实tomcat中管道和过滤器模式就是职责链模式的抽象,把它应用到软件体系架构中。

     用一个通俗例子来说,就好比警察抓犯人,在案件发生后经侦察已经确认此案由一团伙共5个犯人所作,数量已经确定了,打个比方这个犯罪团伙是这样组成的,这5个犯人中只有一个主犯 E,其余四个从犯中只有D这个人认识并能找到主犯,D找了个只有他才认识的C犯,C又找了只有他才认识的B犯,B也是找了只有他才认识的A犯, 所以他们组成了一个犯罪团伙了。正好警察是从抓到 A开始的,接着A招供了B,B招供了C,C招供了D,D最后招供了主犯E了。(A->B->C->D->>E),呵呵,可能比喻得有点勉强了,但你能清楚了解就好,其实为什么突出有个主犯E呢,就好类比tomcat中的Filter 和servlet的执行顺序。(A->B->C->D)都好比如是Filter.doFilter(),链式的执行后,而主犯E就是最后用户定义的Servlet.service()了。

     上面的都是比喻性地说明了一下,从下面开始就结合源代码来分析说明了。

    init(FilterConfig):
     当容器如tomcat启动的时候就会调用的filter初始化方法。在容器启动的时候。它加载应用程序中的配置描述符 web.xml 文件,解析过滤器配置信息。 并获取文件中指定的filter初始化参数。filter顺序是按<filter-mapping>的顺序依次排列保存下来,所以此时filter的数量 n 已经能被计算确定下来了。


在Tomcat处理请求的Servlet实例之前先要处理与之相关联的所有Filter的,在StandardWrapperValve类的#invoke()方法中调用filterChain.doFilter(request.getRequest(), response.getResponse()); 从这开始调用Filter了,

   以下是在ApplicationFilterChain类两个方法的源代码:

public void doFilter(ServletRequest request, ServletResponse response) throws IOException,  
            ServletException {  
      
        if (Globals.IS_SECURITY_ENABLED) {  
            final ServletRequest req = request;  
            final ServletResponse res = response;  
            try {  
                java.security.AccessController  
                        .doPrivileged(new java.security.PrivilegedExceptionAction() {  
                            public Object run() throws ServletException, IOException {  
                                internalDoFilter(req, res);  
                                return null;  
                            }  
                        });  
            } catch (PrivilegedActionException pe) {  
              ......
            }  
        } else {  
            internalDoFilter(request, response);  //这个方法无论怎么样都会被调用
        }  
    }  


        private void internalDoFilter(ServletRequest request, ServletResponse response)  
            throws IOException, ServletException {  
      

        if (pos < n) {  

              //n 就是web.xml定义的Filter的数量,init时已经计算出来,filter的排列顺序按<filter-mapping>的顺序已经定下来,
              而且保存在filterConfig中了。

            ApplicationFilterConfig filterConfig = filters[pos++];  
            Filter filter = null;  
            try {  
                // 得到当前Filter  
                filter = filterConfig.getFilter();  
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request,  
                        response);  
              
                if (Globals.IS_SECURITY_ENABLED) {  
                    final ServletRequest req = request;  
                    final ServletResponse res = response;  
                    Principal principal = ((HttpServletRequest) req).getUserPrincipal();  
      
                    Object[] args = new Object[] { req, res, this };  
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args);  
      
                    args = null;  
                } else {  

                    // 这里调用Filter的#doFilter()方法。  注意看这个filter.doFilter(request, response, this); 中
                    //的this引用指向的就是ApplicationFilterChain类的对象,所以传递给Filter的chain实例就是ApplicationFilterChain类的对象。
                    filter.doFilter(request, response, this);  
                                        //还记得前面比喻的例子吗, 犯人A记住B,B->C,C->D吗,
                                       //在Filter的#doFilter()方法里都会调用#chain.doFilter(request,response); 这个方法才能往下继续执行下去,
                                       //就好比只要有一犯人记不住下一个犯人,这条线索就中断的不会继续往下了。
                                      // 再来看这个chain的定义,它是ApplicationFilterChain类的的引用,而这个internalDoFilter(..)方法所在的类就是ApplicationFilterChain类中
                                     //于是程序执行到Filter 中的chain.doFilter(request,response)相当于程序又再次调用本方法,也可以说是一种变相的递归了。
                }  
      
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request,  
                        response);  
            } catch (Exception e) {  
               ...............
            } ........省略

            return;  //说明只要符合if(pos < n)这条件,这方法只执行到这了。
        }  
      
        try {  
            if (Globals.STRICT_SERVLET_COMPLIANCE) {  
                lastServicedRequest.set(request);  
                lastServicedResponse.set(response);  
            }  



            // 当Filter全部调用完毕后,而且在最后的一个Filter中有调用#chain.doFilter(request,response);这一行,
            //才会把请求真正的传递给Servlet了,调用它的#service()方法。  
 
            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT, servlet, request,  
                    response);  
            if ((request instanceof HttpServletRequest)  
                    && (response instanceof HttpServletResponse)) {  
      
                if (Globals.IS_SECURITY_ENABLED) {  
                    final ServletRequest req = request;  
                    final ServletResponse res = response;  
                    Principal principal = ((HttpServletRequest) req).getUserPrincipal();  
                    Object[] args = new Object[] { req, res };  
                    SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args,  
                            principal);  
                    args = null;  
                } else {  
                    servlet.service((HttpServletRequest) request, (HttpServletResponse) response);  
                }  
            } else {  
                servlet.service(request, response);  
            }  
            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, servlet, request,  
                            response);  
        } catch (Exception e) {  
          ............
        }............
        finally {  
            if (Globals.STRICT_SERVLET_COMPLIANCE) {  
                lastServicedRequest.set(null);  
                lastServicedResponse.set(null);  
            }  
        }  
      
    }  

 在Tomcat怎么处理Filter的流程说完了,接下来开始讨论Struts2的了,
Struts2就是由一个 StrutsPrepareAndExecuteFilter也就是一个filter作为控制器入口。所以它要在web.xml中配置。当浏览器发来的请求到达这个 StrutsPrepareAndExecuteFilter了,
就会调用 它的#doFilter()方法:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            prepare.setEncodingAndLocale(request, response);//设置编码和Locale
            prepare.createActionContext(request, response);
            prepare.assignDispatcherToThread();//获取Dispatcher的实例然后设置ThreadLocal<Dispatcher> instance中,可见Struts2框架为每一个线程都提供了一个Dispatcher对象,所以在编写Action的时候不需要考虑多线程的问题了。

               if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
                    chain.doFilter(request, response);
               } else {
                    /* (1) 对请求进行包装 */  
                    request = prepare.wrapRequest(request);

                     /* (2) 获得Action Mapping ,其中包含namespace处理*/  
                    ActionMapping mapping = prepare.findActionMapping(request, response, true);
                   /* (3)当Mapping为空时,检查是否访问的为静态资源 */
                    if (mapping == null) {
                         boolean handled = execute.executeStaticResourceRequest(request, response);
                         if (!handled) {
                              chain.doFilter(request, response);
                         }
                    } else {

                  /* (4) 如果请求的是action资源,这会执行此方法,在这方法中会调用很多相关的方法做了很多工作(例如execute)和拦截器执行等 */  
                         execute.executeAction(request, response, mapping);
                    //注意到这里了吗,没有chain.doFilter(request, response);也就是说如果执行的请求的资源是Action,就不会再往下执行到Servlet.service()了。
                    }
               }
        } finally {
            prepare.cleanupRequest(request);
        }
    }

 

 从上面(1)(2)(3)(4)点分别分开重点分明;
    (1) 对请求进行包装
    (2) 获得Action Mapping 以及怎么处理namespace的。
   (3)如何检查是否访问的为静态资源,还有如果是正常的请求如jsp,js等,则让它真接通过chain.doFilter(..)链下去。
   (4)调用被请求的Action的执行方法。

   下面形如依次分析说明了。
   (1)对请求进行包装:request = prepare.wrapRequest(request);
   public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
        HttpServletRequest request = oldRequest;
        try {
            request = dispatcher.wrapRequest(request, servletContext);
       }catch{..}
     }

     以下是Dispatcher中#wrapRequest()源代码:

 

 public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
        // don't wrap more than once
        if (request instanceof StrutsRequestWrapper) {// 判断request是否是StrutsRequestWrapper的对象,保证对request只包装一次。 
            return request;
        }

        String content_type = request.getContentType();

        //判断Content-Type是否是multipart/form-data,如果是的话返回一个MultiPartRequestWrapper的对象处理文件上传,
        //否则返回StrutsRequestWrapper的对象处理普通请求。 

        if (content_type != null && content_type.contains("multipart/form-data")) {  
            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;
    }

 

  (2) 获得Action Mapping 以及怎么处理namespace的。
    ActionMapping mapping = prepare.findActionMapping(request, response, true);
  //ActionMapping类,它内部封装了如下6个字段:

 

//ActionMapping类,它内部封装了如下6个字段: 
    public class ActionMapping {

    private String name;// Action名
    private String namespace;// Action所在的名称空间  
    private String method;// 执行方法 
    private String extension;//扩展名如(.action)
    private Map<String, Object> params;// 可以通过set方法设置的参数  
    private Result result;// 返回的结果类型
         }

    public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
        ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
        if (mapping == null || forceLookup) {
            try {
                mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
                if (mapping != null) {
                    request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
                }
            } catch (Exception ex) {
              .....
            }
        }

        return mapping;
    }

 实质调用的是DefaultActionMapper类的getMapping(..)

 public ActionMapping getMapping(HttpServletRequest request,
                                    ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
        String uri = getUri(request);//下面祥细介绍

        int indexOfSemicolon = uri.indexOf(";");
        uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;

        uri = dropExtension(uri, mapping);//下面祥细介绍
        if (uri == null) {
            return null;
        }

        parseNameAndNamespace(uri, mapping, configManager);//下面祥细介绍

        handleSpecialParameters(request, mapping);//此方法用于处理Struts框架定义的四种特殊的prefix:Method prefix,Action prefix,Redirect prefix,Redirect-action prefix  

        if (mapping.getName() == null) {
            return null;
        }

        parseActionName(mapping);

        return mapping;
    }



protected String getUri(HttpServletRequest request) {
        // handle http dispatcher includes.
        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();
        return uri.substring(request.getContextPath().length());
    }

 

getUri()这个方法首先判断请求是否来自于一个jsp的include,
如果是,那么请求request.getAttribute("javax.servlet.include.servlet_path")属性可以获得include的页面uri,
否则通过一般的方法获得请求的uri,最后返回去掉ContextPath的请求路径,
比如http://127.0.0.1:8080/struts2/text/index.jsp?param=1,返回的为/text/index.jsp。去掉了ContextPath和查询字符串等。


 uri = dropExtension(uri);
   负责去掉Action的"扩展名"(默认为"action")并设置在mapping里,源代码如下:

protected String dropExtension(String name, ActionMapping mapping) {
        if (extensions == null) {
            return name;
        }
        for (String ext : extensions) {
            if ("".equals(ext)) {
                int index = name.lastIndexOf('.');
                if (index == -1 || name.indexOf('/', index) >= 0) {
                    return name;
                }
            } else {
                String extension = "." + ext;
                if (name.endsWith(extension)) {
                    name = name.substring(0, name.length() - extension.length());
                    mapping.setExtension(ext);//设置扩展名
                    return name;
                }
            }
        }
        return null;
    }

 

此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。源代码如下:

protected void parseNameAndNamespace(String uri, ActionMapping mapping,
                                         ConfigurationManager configManager) {
        String namespace, name;
        int lastSlash = uri.lastIndexOf("/");
           //首先如果经过前面处理后的uri为空“” ,就如请求contextPath/后就没有“/”了,则设置 namespace = "";
        if (lastSlash == -1) {
            namespace = "";
            name = uri;
        } else if (lastSlash == 0) {
            //如果经过前面处理后的uri为/everything ,则设置 namespace = "/";
            namespace = "/";
            name = uri.substring(lastSlash + 1);
        } else if (alwaysSelectFullNamespace) {
            // alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符串作为名称空间。  
            namespace = uri.substring(0, lastSlash);// 获得字符串 namespace
            name = uri.substring(lastSlash + 1);// 获得字符串 name  
        } else {

          //尝试去找在.xml中已经定义好的namespace,默认为空""字符。
            Configuration config = configManager.getConfiguration();
            String prefix = uri.substring(0, lastSlash);
            namespace = "";
            boolean rootAvailable = false;
            // Find the longest matching namespace, defaulting to the default
            //按最长匹配原则,如果struts.xml中的配置的有几个package并且namespace分别为 (如namespace=/first 。namespace=/first/second)
            //请求的uri如果是/first/second/text.action 则先找到最长匹配namespace=/first/second的package
            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);

            //// allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false  
            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);
    }

 

其实当这次namespace设置在mapping中后,在execute.executeAction(request, response, mapping)处理流程中会去相应的package中查找请求的action,
如果根据这次的namespace中对应的package中没有找到请求的action,会再次在DefaultConfiguration#getActionConfig(String namespace, String name)
 中去找这个namespace的config,如果还是没有在这个namespace中找到对应name的Action则最终去默认为""空字符串的namespace中找。

 

 

 

 

 

 

public synchronized ActionConfig getActionConfig(String namespace, String name) {
            ActionConfig config = findActionConfigInNamespace(namespace, name);

            // try wildcarded namespaces
            if (config == null) {
                NamespaceMatch match = namespaceMatcher.match(namespace);
                if (match != null) {
                    config = findActionConfigInNamespace(match.getPattern(), name);

                    // If config found, place all the matches found in the namespace processing in the action's parameters
                    if (config != null) {
                        config = new ActionConfig.Builder(config)
                                .addParams(match.getVariables())
                                .build();
                    }
                }
            }

            
            //如果还是找不到在namespace的配置Actionconfig,则去namespace为""空的package中找Action为name为ActionConfig
            if ((config == null) && (namespace != null) && (!"".equals(namespace.trim()))) {
                config = findActionConfigInNamespace("", name);
            }


            return config;
        }


//解析ActionName,并通过是否存在动态方法调用如:"name!method"形式的action调用
protected ActionMapping parseActionName(ActionMapping mapping) {
        if (mapping.getName() == null) {
            return mapping;
        }
        if (allowDynamicMethodCalls) {//如果允许动态方法调用,则去判断ActionName中是否为name!method形式,
                                      //如果有!分隔则解析出name和method并保存在mapping;
            
            String name = mapping.getName();
            int exclamation = name.lastIndexOf("!");
            if (exclamation != -1) {
                mapping.setName(name.substring(0, exclamation));
                mapping.setMethod(name.substring(exclamation + 1));
            }
        }
        return mapping;//此时mapping的6个属性已经组装完成了,就返回;
    }

 


(3)如何检查是否访问的为静态资源,还有如果是正常的请求如jsp等,
则让它真接通过chain.doFilter(..)链下去直到它会调用Servlet.service(),注:jsp最终也被解析为一个Servlet。
一就如的doFilter()中的源代码:

if (mapping == null) {  //当前面获取mapping后还是null时,则检查是否访问的为静态资源
          boolean handled = execute.executeStaticResourceRequest(request, response);
          if (!handled) {
                              //请求如.jsp .html .js,让它通过则让给容器如tomcat处理。
            chain.doFilter(request, response);
          }
        } 


        public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
          
          //该请求不是一个Action请求时,先查找是不是访问的为静态资源
        String resourcePath = RequestUtils.getServletPath(request);

        if ("".equals(resourcePath) && null != request.getPathInfo()) {
            resourcePath = request.getPathInfo();
        }

        StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
        if (staticResourceLoader.canHandle(resourcePath)) {
            staticResourceLoader.findStaticResource(resourcePath, request, response);
            //如果访问的为静态资源则The framework did its job here
            return true;

        } else {
          //如果是一个正常的请求如.jsp .html .js,让它通过
            return false;
        }
    }

 

 (4)调用被请求的Action的执行方法。

 

-----由于太长了,所以分开在struts2的源代码分析及struts2的工作流程(二)

 

7
1
分享到:
评论
4 楼 freezingsky 2013-03-15  
用的人多,看教程的人也多,但能从源码的角度分析的人甚少。望博主,能尽力为之!
3 楼 jianfulove 2013-03-15  
bonait 写道
不错的,虽然不是很明白!!!

呵呵,如果已经懂得如何使用struts2再耐心的看的话,你一定会懂的。
2 楼 bonait 2013-03-15  
不错的,虽然不是很明白!!!
1 楼 bonait 2013-03-15  
不错,虽然不是很懂

相关推荐

    struts2 源码分析

    Struts2 是一个基于MVC 模式的Web 应用程序框架,它的源码分析可以帮助我们更好地理解框架的内部机制和工作流程。下面是Struts2 源码分析的相关知识点: 1. Struts2 架构图 Struts2 的架构图主要包括 Filter chain...

    struts2源代码分析

    Struts2的设计思路和工作流程与Struts1.x有很大的区别,这使得深入理解其源代码变得至关重要。 在分析Struts2的源代码之前,你需要首先获取Struts2的源代码,可以通过访问...

    struts2源代码 struts2源代码

    struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码

    struts2源码分析

    struts2源码详细解析51CTO下载-struts2源代码分析(个人觉得非常经典)

    Struts2源码分析

    在深入理解Struts2的工作原理时,源码分析是必不可少的步骤。Struts2的核心设计理念和设计模式相比Struts1.x有了显著的变化,这使得它成为一个独立且成熟的框架。 首先,Struts2的架构基于WebWork的核心,这意味着...

    struts2源码分析总结

    本文将深入探讨Struts2的源码分析,特别是关于StrutsPrepareAndExecuteFilter的初始化过程,这是Struts2的核心组件之一,负责处理HTTP请求。 首先,我们来看`StrutsPrepareAndExecuteFilter`的初始化。这个过滤器...

    struts2.3.4源代码

    尽管描述中提到不包含XWork源代码,但XWork是Struts2的基础,它处理Action的执行和异常管理。 在Struts2.3.4源代码中,我们可以深入理解以下关键知识点: 1. **FilterDispatcher**: 这是Struts2框架的入口点,负责...

    struts2部分源码分析

    本篇文章将深入探讨Struts2的运行原理,通过源码分析来揭示其内部工作机制。 首先,我们从核心组件开始。Struts2的核心组件包括Action、FilterDispatcher、Interceptor和Result。Action是业务逻辑的载体,它接收...

    Struts2框架源码

    在MyEclipse9中,你可以导入这些源码,通过调试和阅读代码,理解Struts2的工作原理。对于初学者来说,这是一条深入理解MVC框架的好途径。你可以逐步分析Action类如何被调用,拦截器如何影响Action的执行流程,以及...

    struts2 项目源代码

    通过分析这个"北大青鸟 struts2 项目源代码",你可以了解到Struts2框架的实战应用,包括请求处理流程、配置文件的编写、Action的设计和实现、视图的渲染以及拦截器的使用。这有助于你更深入地理解和掌握Struts2框架...

    Struts2源码阅读

    Struts2是一个流行的Java Web应用程序框架,用于构建MVC(模型-视图-控制器)架构的应用。源码阅读对于理解其工作原理至关重要。本文将深入探讨Struts2的核心概念、类和请求处理流程。 首先,我们来看Struts2的架构...

    struts2工作流程

    接下来,我们详细分析Struts2的工作流程: 1. **请求接收**: 当用户在浏览器中发送HTTP请求到服务器时,Struts2框架首先通过一个前端控制器(Front Controller),即`StrutsPrepareAndExecuteFilter`,拦截这个...

    Struts2源代码项目

    这个"Struts2源代码项目"显然包含了Struts2框架的源代码,这对于开发者深入理解其内部工作原理,以及进行定制化开发和调试是极其有价值的。 首先,我们来看看`.classpath`和`.project`这两个文件。它们是Eclipse ...

    struts2教程源代码

    "strut2课程源代码第一天及说明"可能包含了逐步的教程,指导你从零开始搭建和运行一个简单的Struts2应用。 标签"struts2例子代码"表明这些源代码包含了具体的操作示例,比如Action类的编写、配置文件的设置、拦截器...

    struts2框架源码分析及问题汇总

    本文将深入剖析Struts2的源码,揭示其工作原理,并汇总常见问题,帮助开发者更好地理解和使用这个框架。 一、Struts2框架基础 1. 框架结构:Struts2的核心组件包括Action、Result、Interceptor(拦截器)等。...

Global site tag (gtag.js) - Google Analytics