struts2执行流程

写在前面:struts2在web应用层面属于表示层框架,在MVC开发模式中属于C(Controller控制器),负责对M(Model模型)和V(View视图)进行解耦。struts2是在struts1和webwork的技术基础上进行了合并的全新的框架。struts2虽然和struts1在名字上很相似,但是却不是后者的升级版。struts2其实是以另一个表示层框架webwork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以struts2也可以理解为webwork的更新产品。从它们的处理请求的执行流程就可以看出相似点。

直观感受一下:

webwork:
这里写图片描述

struts2:

这里写图片描述

struts2和webwork都是通过一个FilterDispatcher过滤器来匹配客户端发送的所有请求(当然,现在struts2的过滤器名是StrutsPreparedAndExecuteFilter),不同于struts1是通过一个servlet来匹配所有请求,好了,这里先简单的了解一下struts2和struts1的区别, 在文章末尾会详细的介绍struts2和struts1的区别,开始进入主题。

struts2的执行流程(结合流程图分析):

  1. 客户端发送一个HTTP请求

  2. 该请求被struts2的核心过滤器StrutsPreparedAndExecuteFilter匹配(只要是在过滤器的url-pattern中配置了/*,那么任何请求都会进入该过滤器,无论该请求是否需要struts2来处理),当然,在进入这个过滤器之前会依次进入在web.xml中配置的位置在struts2过滤器之前的其他Filter或Servlet

  3. struts2的过滤器会询问(形象一点的说法,其实就是调用方法)ActionMapper该请求是否有与之对应的业务控制类,如果没有,则放行,如果有,进入下一步执行流程

  4. struts2通过ActionProxy实例化ActionInvocation,当然在这之前ActionProxy还会通过ConfigurationManager按序加载struts2的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml…(先加载struts默认的,然后才是自己定义的),正是因为加载了这些配置文件所以struts才能找到相应的拦截器以及业务控制类。

  5. ActionProxy初始化一个ActionInvocation并通过它的invoke来正式执行一系列的拦截器以及Action,在执行完Action之后会根据使用的模板(jsp, velocity, freemarker…)组装结果集Result,渲染页面

  6. 返回给客户端响应

接下来详细的分析一下:

1. 客户端发送一个HTTP请求

可能是一个登陆请求,也可能是查询某个功能列表的请求…

2. StrutsPreparedAndExecuteFilter过滤器拦截该请求

过滤器拦截到该请求的时候会调用doFilter方法,如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    // 1.将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    try {
        // 2.对不由struts2处理的请求放行,这个excludedPatterns是一个List<Pattern>集合,里面存储了不被struts2的过滤器匹配的url
        if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
            chain.doFilter(request, response);
        } else {
            // 3.设置请求和相应的编码以及国际化的相关信息
            prepare.setEncodingAndLocale(request, response);
            // 4.创建一个Action的上下文,并初始化一个本地线程
            // 源码注释:Creates the action context and initializes the thread local
            prepare.createActionContext(request, response);
            // 5.把dispatcher指派给本地线程
            // 源码注释:Assigns the dispatcher to the dispatcher thread local
            prepare.assignDispatcherToThread();
            // 6.包装一下request防止它是一个multipart/form-data类型的请求
            // 源码注释:Wrap request first, just in case it is multipart/form-data
            request = prepare.wrapRequest(request);
            // 7.查找ActionMapping信息(包括name,namespace,method,extention,params,result)
            ActionMapping mapping = prepare.findActionMapping(request, response, true);
            // 8.没有找到请求对应的业务控制类
            if (mapping == null) {
                boolean handled = execute.executeStaticResourceRequest(request, response);
                if (!handled) {
                    chain.doFilter(request, response);
                }
            } else {
            // 9.找到了对应的业务控制类那就去执行该Action
                execute.executeAction(request, response, mapping);
            }
        }
    } finally {
        // 10.释放掉这个Request所占用的一些内存空间
        prepare.cleanupRequest(request);
    }
}

2.1. 首先将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象

2.2. 判断是否设置了不被struts2过滤器拦截的请求

if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
    chain.doFilter(request, response);
}

这个excludedPatterns是一个List< Pattern >集合,里面包含了不被struts2过滤器拦截的url。看这一句:prepare.isUrlExcluded(request, excludedPatterns),判断这个请求里面是否包含这样的url,跟进源码查看一具体的实现:

public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) {
    if (excludedPatterns != null) {
        // 1.获取当前请求中的uri
        String uri = RequestUtils.getUri(request);
        // 2.查看集合中是否有与之匹配的,有就返回true
        for ( Pattern pattern : excludedPatterns ) {
            if (pattern.matcher(uri).matches()) {
                return true;
            }
        }
    }
    return false;
}

知道了拦截器是通过excludedPatterns来判断哪个url不被拦截,那么这个excludedPatterns的值是从哪里来的呢?初步猜测是在StrutsPreparedAndExecuteFilter初始化(init)的时候设置的…果不其然,看源码:

public void init(FilterConfig filterConfig) throws ServletException {
    InitOperations init = new InitOperations();
    Dispatcher dispatcher = null;
    try {
        FilterHostConfig config = new FilterHostConfig(filterConfig);
        init.initLogging(config);
        dispatcher = init.initDispatcher(config);
        init.initStaticContentLoader(config, dispatcher);

        prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
        execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
        // 就是这里,创建了不匹配的url列表
        this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

        postInit(dispatcher, filterConfig);
    } finally {
        if (dispatcher != null) {
            dispatcher.cleanUpAfterInit();
        }
        init.cleanup();
    }
}

跟进buildExcludedPatternsList方法:

public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) {
    // 由此可知是struts2是读取了STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配
    return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_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.add(Pattern.compile(token.trim()));
        }
        return Collections.unmodifiableList(list);
    } else {
        return null;
    }
}

struts2是根据STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配,所以我们如果想要设置某些请求不被struts2匹配就可以设置这个常量struts.action.excludePattern,多个pattern之间用逗号隔开(pattern的写法和web.xml中配置的类似)

2.3 设置请求和响应的编码以及国际化的相关信息

prepare.setEncodingAndLocale(request, response),是由Dispatcher在准备的过程中完成的,源码如下:

/**
 * Sets the request encoding and locale on the response
 * 设置请求和响应的国际化编码
 */
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
    dispatcher.prepare(request, response);
}

跟进prepare方法一看究竟:

/**
 * Prepare a request, including setting the encoding and locale.
 *
 * @param request The request
 * @param response The response
 */
public void prepare(HttpServletRequest request, HttpServletResponse response) {
    String encoding = null;
    if (defaultEncoding != null) {
        encoding = defaultEncoding;
    }
    // check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
    if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
        encoding = "UTF-8";
    }

    Locale locale = null;
    if (defaultLocale != null) {
        locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
    }

    if (encoding != null) {
        applyEncoding(request, encoding);
    }

    if (locale != null) {
        response.setLocale(locale);
    }

    if (paramsWorkaroundEnabled) {
        request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
    }
}

设置编码的具体实现。嗯,代码写的蛮好…

2.4 创建一个Action的上下文,并初始化一个本地线程

/**
 * Creates the action context and initializes the thread local
 */
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
    ActionContext ctx;
    // 计数器,作用:记录这个Action被访问的次数,与后续释放ActionContext的内存空间有关,初始化为1
    Integer counter = 1;
    Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
    // 如果request域里面已经有一个计数器,说明这个Action已经被实例化调用过了,那么就将这个计数器的值加1
    if (oldCounter != null) {
        counter = oldCounter + 1;
    }
    // 和上面的计数器类似,尝试从request获取这个Action的上下文对象,如果存在就直接使用这个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 {
        // 不存在就创建一个值栈(存储了与这个Action相关的一些属性)和一个ActionContext
        ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
        stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
        ctx = new ActionContext(stack.getContext());
    }
    // 把这个计数器放到request域里面
    request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
    // 跟进这个方法后有这么个注释:Sets the action context for the current thread(把这个Action上下文对象放入当前线程)
    ActionContext.setContext(ctx);
    return ctx;
}
2.4.1 创建Action计数器

这个计数器可是有点用处:它用来记录一个Action被调用的次数。那么为什么要记录它被调用的次数呢?这里先提前看一下doFilter方法的最后一步:prepare.cleanupRequest(request);这一步是用来清理掉该次请求所占用的内存,跟进源码:

/**
 * Cleans up a request of thread locals
 */
public void cleanupRequest(HttpServletRequest request) {
    // 获取request域中计数器
    Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
    if (counterVal != null) {
        // 这个计数器的值不为空就把它减一
        counterVal -= 1;
        // 重新放入request域,用CLEARUP_RECURSION_COUNTER这个常量保存
        request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);
        // 关键:如果这个计数器在减一之后的值仍然大于0,那么就不释放它所占用的内存,记录一条日志就直接返回了
        if (counterVal > 0 ) {
            if (log.isDebugEnabled()) {
                log.debug("skipping cleanup counter="+counterVal);
            }
            return;
        }
    }
    // 否则就clearUp掉
    // always clean up the thread request, even if an action hasn't been executed
    try {
        dispatcher.cleanUpRequest(request);
    } finally {
        ActionContext.setContext(null);
        Dispatcher.setInstance(null);
    }
}

相信看到这里,大家大致已经明白了这个计数器存在的意义:记录Action被请求的次数,如果请求的次数非常频繁,说明这个Action被调用的次数非常多,那么就暂时不释放掉它所占用的内存,反之,如果只请求了一次或者是几次,那么在这个Action执行完毕后就会释放掉它所占用的内存。

2.4.2 创建ActionContext

跟进源码看一下具体的实现:

public class ActionContext implements Serializable {

    static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();

    // 处理请求的Action的name
    public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name";

    // 与这个Action相关的值栈
    public static final String VALUE_STACK = ValueStack.VALUE_STACK;

    // session相关
    public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session";

    // application域
    public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application";
    ...

这个ActionContext存储了一些与当前Action相关的信息。

2.5 把dispatcher指派给本地线程-prepare.assignDispatcherToThread()

其实从上一步我们就不难看出struts2为每一个Action创建一个线程,这也体现了struts2相比于struts1的优势:线程安全,因为每一个Action都由一个单独的线程来负责,不存在共享数据,所以安全。我们进入PrepareOperations类的assignDispatcherToThread()方法的源码看一下:

public void assignDispatcherToThread() {
    // 将当前的这个dispatcher实例化
    Dispatcher.setInstance(dispatcher);
}

再进入Dispatcher的setInstance方法中看一下:

public static void setInstance(Dispatcher instance) {
    // 调用了这个Dispatcher的instance属性的set方法
    // ps:这个instance是一个ThreadLocal对象
    Dispatcher.instance.set(instance);
}

再进入set方法中看一下:

public void set(T value) {
Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

终于找到根源了,获取到当前的线程,把dispatcher放入当前线程中。那么这个Dispatcher有什么作用呢?看一下源码,这个源码有点多,就摘抄一个重要的方法来看下吧:

/**
 * 初始化一系列的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml...文件,并且是按顺序加载
 */
public void init() {

    if (configurationManager == null) {
        configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
    }

    try {
        init_FileManager();
        init_DefaultProperties(); // [1]default.properties
        init_TraditionalXmlConfigurations(); // [2]struts-default.xml...
        init_LegacyStrutsProperties(); // [3]struts.properties
        init_CustomConfigurationProviders(); // [5]struts.xml
        init_FilterInitParameters() ; // [6]初始化过滤器配置的参数
        init_AliasStandardObjects() ; // [7]别名什么的..这里就不深究了

        Container container = init_PreloadConfiguration();
        container.inject(this);
        init_CheckWebLogicWorkaround(container);

        if (!dispatcherListeners.isEmpty()) {
            for (DispatcherListener l : dispatcherListeners) {
                l.dispatcherInitialized(this);
            }
        }
    } catch (Exception ex) {
        if (LOG.isErrorEnabled())
            LOG.error("Dispatcher initialization failed", ex);
        throw new StrutsException(ex);
    }
}

Dispatcher可以用来初始化一系列的配置文件,并且是按序加载。

2.6 包装一下request

request = prepare.wrapRequest(request);

wrapRequest方法的源码如下:

public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
    HttpServletRequest request = oldRequest;
    try {
        // Wrap request first, just in case it is multipart/form-data
        // 为了防止这个请求是一个multipart/form-data(上传文件)类型的请求
        // parameters might not be accessible through before encoding (ww-1278)
        // 因为这种类型请求的参数如果不经过处理可能获取不到
        request = dispatcher.wrapRequest(request, servletContext);
    } catch (IOException e) {
        throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
    }
    return request;
}

为了防止这个请求是一个multipart/form-data(上传文件)类型的请求,将它包装一下。因为这种类型请求的参数如果不经过处理可能获取不到。继续进入dispatcher.wrapRequest的源码中:

public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
    // don't wrap more than once
    // 只包装一次
    if (request instanceof StrutsRequestWrapper) {
        return request;
    }

    String content_type = request.getContentType();
    // 如果请求类型不为空并且是multipart/form-data类型的请求,那么就包装一下
    if (content_type != null && content_type.contains("multipart/form-data")) {
        MultiPartRequest mpr = getMultiPartRequest();
        LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
        request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider);
    } else {
        request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
    }

    return request;
}

2.7 查找该请求相关的信息actionMapping

prepare.findActionMapping(request, response, true),源码如下:

public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
    ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
    if (mapping == null || forceLookup) {
        try {
            // 这里就开始询问ActionMaper是否存在与该请求对应的业务控制类,详见下面的第三大步
            mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
            if (mapping != null) {
                request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
            }
        } catch (Exception ex) {
            dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
        }
    }

    return mapping;
}

发现这个ActionMapping其实是通过ActionMapper的getMapping方法来获得的

ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager);

发现这个ActionMapper是一个接口,这里我启动我的项目debug发现是调用了它的实现类:DefaultActionMapper的getMapping方法,实现细节如下:

tips:在eclipse中,按Ctrl+T可以查看当前类的子类或者实现类

public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
    ActionMapping mapping = new ActionMapping();
    // 通过request工具类获取当前请求的uri
    String uri = RequestUtils.getUri(request);

    int indexOfSemicolon = uri.indexOf(";");
    uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
    // 去掉后缀.action...
    uri = dropExtension(uri, mapping);
    if (uri == null) {
        return null;
    }
    // 解析出configManager里面设置的actionName和namespace并放入ActionMapping
    parseNameAndNamespace(uri, mapping, configManager);
    handleSpecialParameters(request, mapping);
    return parseActionName(mapping);
}

在getMaping方法中,获得当前请求中的uri以及配置文件中配置的action的name和namespace,看一下这个parseNameAndNamespace方法的实现细节:

protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {
    String namespace, name;
    int lastSlash = uri.lastIndexOf("/");
    ...
    // 将解析出来的name和namespace放入mapping
    mapping.setNamespace(namespace);
    mapping.setName(cleanupActionName(name));
}

从倒数两行代码可以看出了该方法的最终目的:将解析出来的name和namespace放入mapping,交给调用者来根据这个actionMapping判断请求是否有对应的业务控制类

2.8 没有找到请求对应的业务控制类所进行的操作

说明并没有为这个请求配置相应的业务控制类Action,就说明这个请求可能是一个静态的资源请求,于是就有了如下代码:

if (mapping == null) {
   boolean handled = execute.executeStaticResourceRequest(request, response);
   // 这里会根据上一步的返回值来确定是否执行
    if (!handled) {
        chain.doFilter(request, response);
    }
}

查看executeStaticResourceRequest方法的具体实现:

public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    // there is no action in this request, should we look for a static resource?
    // 老外还挺逗:这个请求没有对应的action来处理,我们应不应该看看它是不是请求一个静态资源?
    String resourcePath = RequestUtils.getServletPath(request);

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

    StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
    // 如果这个请求请求的资源在这个项目的资源路径下,返回true
    if (staticResourceLoader.canHandle(resourcePath)) {
        staticResourceLoader.findStaticResource(resourcePath, request, response);
        // The framework did its job here
        // 属于struts2管辖范围的静态资源,由struts2来处理
        return true;

    } else {
        // this is a normal request, let it pass through
        // 一个普通的请求,放行
        return false;
    }
}

简单点说就是:这个静态资源在项目中存在就返回,不存在就放行,交给其他的过滤器处理。

2.9 如果找到了这个请求对应的业务控制类Action

那就调用ExecuteOperations的executeAction方法去执行这个Action,源码如下:

public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
  dispatcher.serviceAction(request, response, servletContext, mapping);
}

它又调用了Dispatcher的serviceAction方法,源码如下:

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
                      ActionMapping mapping) throws ServletException {

    Map<String, Object> extraContext = createContextMap(request, response, mapping, context);

    // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
    ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
    boolean nullStack = stack == null;
    if (nullStack) {
        ActionContext ctx = ActionContext.getContext();
        if (ctx != null) {
            stack = ctx.getValueStack();
        }
    }
    if (stack != null) {
        extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
    }

    String timerKey = "Handling request from Dispatcher";
    try {
        UtilTimerStack.push(timerKey);
        // 获取ActionMapping中的namespace,name,method
        String namespace = mapping.getNamespace();
        String name = mapping.getName();
        String method = mapping.getMethod();

        Configuration config = configurationManager.getConfiguration();
        // 创建ActionProxy代理
        ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                namespace, name, method, extraContext, true, false);

        request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

        // if the ActionMapping says to go straight to a result, do it!
        // 如果mapping中有结果集,那么就去执行结果集
        if (mapping.getResult() != null) {
            Result result = mapping.getResult();
            result.execute(proxy.getInvocation());
        } else {
            // 代理开始执行
            proxy.execute();
        }

        // If there was a previous value stack then set it back onto the request
        // 之前已经有值栈了,就把它放入当前request域里面
        if (!nullStack) {
            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
        }
    } catch (ConfigurationException e) {
        logConfigurationException(request, e);
        sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
    } catch (Exception e) {
        if (handleException || devMode) {
            sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
        } else {
            throw new ServletException(e);
        }
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}

这一步主要做了三件事:创建Action的代理,封装结果集Result,设置值栈。

实际上是通过调用DefaultActionProxyFactory的createActionProxy方法来创建的Action的代理

public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
    // 创建ActionInvocation负责迭代拦截器和执行Action
    ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
    // 放入容器中
    container.inject(inv);
    // 调用这个重载的createActionProxy方法
    return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}

在方法返回的时候调用重载的createActionProxy方法,进入源码看一下:

public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {

    DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
    container.inject(proxy);
    // 这里,proxy开始准备
    proxy.prepare();
    return proxy;
}

看这里:proxy.prepare();,proxy开始准备,继续跟源码:

protected void prepare() {
    String profileKey = "create DefaultActionProxy: ";
    try {
        UtilTimerStack.push(profileKey);
        config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);

        if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
            config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
        }
        if (config == null) {
            throw new ConfigurationException(getErrorMessage());
        }

        resolveMethod();

        if (!config.isAllowedMethod(method)) {
            throw new ConfigurationException("Invalid method: " + method + " for action " + actionName);
        }
        // 关键:开始初始化invocation
        invocation.init(this);

    } finally {
        UtilTimerStack.pop(profileKey);
    }
}

准备的关键代码就是初始化invocation,点进去一看又是一个接口,Ctrl+T,选中DefaultActionInvocation这个实现类,查看它的init方法:

public void init(ActionProxy proxy) {
    this.proxy = proxy;
    Map<String, Object> contextMap = createContextMap();

    // Setting this so that other classes, like object factories, can use the ActionProxy and other
    // contextual information to operate
    ActionContext actionContext = ActionContext.getContext();

    if (actionContext != null) {
        actionContext.setActionInvocation(this);
    }
    // 创建Action(关键)
    createAction(contextMap);

    if (pushAction) {
        stack.push(action);
        contextMap.put("action", action);
    }

    invocationContext = new ActionContext(contextMap);
    invocationContext.setName(proxy.getActionName());

    // get a new List so we don't get problems with the iterator if someone changes the list
    // 看到了吧,在这里获得了与action相关的拦截器,共19个
    List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
    // 迭代(就是执行所有的拦截器)
    interceptors = interceptorList.iterator();
}

终于找到了创建Action的代码了,继续跟进源码:

protected void createAction(Map<String, Object> contextMap) {
    // load action
    String timerKey = "actionCreate: " + proxy.getActionName();
    try {
        UtilTimerStack.push(timerKey);
        // 可以看到在这里通过工厂类来创建Action
        action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
    } catch (InstantiationException e) {
        ...
}

经过这一步,终于通过了DefaultActionInvocation创建了Action。

然后我们回到上一步,成功创建Action之后就获取与之相关的拦截器列表,并用一个list集合装起来,依次迭代它们。

// get a new List so we don't get problems with the iterator if someone changes the list
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();

拦截器执行完毕后再执行Action,再封装结果集,再出拦截器,给客户端响应。

2.10 最终Action执行完毕一定要clear掉

防止内存泄漏(内存泄漏是指分配出去的内存不再使用,但是无法回收),当然在clear的时候还是要根据前面提到的计数器来判断是否清除。

struts1和struts2的区别

  1. struts1的业务控制类必须继承ActionSupport,struts2可以不用继承
  2. struts1是单例的,存在线程安全问题,struts2是多例的,不存在线程安全问题
  3. struts1的业务控制类需要依赖servletAPI,struts2不需要
  4. struts1对于页面请求的参数是通过一个ActionForm表单来收集的,struts2直接通过拦截器注入
  5. struts1的业务流程是固定的(可以参考我的另一篇博客struts1原理),struts2可以通过拦截器改变这个流程
  6. struts1是通过servlet来匹配所有的请求,struts2是通过filter来匹配所有的请求

猜你喜欢

转载自blog.csdn.net/a909301740/article/details/79322046