Struts2-初始化流程

- 11 mins

Apache Struts is a free, open-source, MVC framework for creating elegant, modern Java web applications. It favors convention over configuration, is extensible using a plugin architecture, and ships with plugins to support REST, AJAX and JSON.

运行主线

入口程序

StrutsPrepareAndExecuteFilter是Struts2的入口点,实现了Filter和StrutsStatics接口,其中StrutsStatics定义了一些常量

public interface StrutsStatics {

    /**
     * Constant for the HTTP request object.
     */
    public static final String HTTP_REQUEST = "com.opensymphony.xwork2.dispatcher.HttpServletRequest";

   	...
   	...
    /**
     * Set as an attribute in the request to let other parts of the framework know that the invocation is happening inside an
     * action tag
     */
    public static final String STRUTS_ACTION_TAG_INVOCATION= "struts.actiontag.invocation";
}

而实现了Filter接口,让Struts2能够过滤请求,如静态资源、Servlet等,在doFilter()方法中实现过滤逻辑,而init()方法会在且只在Filter被初始化的时候被调用一次,让我们来看看StrutsPrepareAndExecuteFilter的init()方法

protected PrepareOperations prepare; 
protected ExecuteOperations execute;	
protected List<Pattern> excludedPatterns = null;

public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new InitOperations();//类似一个工具类,包含了一些初始化操作
        Dispatcher dispatcher = null;//Dispatcher:Struts2的核心分发器
        try {
            /**
             * 封装filterConfig,提供了一个便利的方法
             * getInitParameterNames(),将枚举类型的参数转换成Iterator(EnumerationIterator)
             */
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            init.initLogging(config);//初始化日志
            //初始化Dispatcher
            dispatcher = init.initDispatcher(config);
            init.initStaticContentLoader(config, dispatcher);//初始化静态文件加载器

            prepare = new PrepareOperations(dispatcher);//初始化HTTP预处理的操作类
            execute = new ExecuteOperations(dispatcher);//初始化进行HTTP请求处理的逻辑执行操作类
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

            postInit(dispatcher, filterConfig);//回调方法,留作用户拓展
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
    }

初始化核心分发器:Dispatcher

init()方法主要是对Dispatcher,PrepareOperations,ExecuteOperations三个类进行初始化,其中Dispatcher在Struts2中占有很重要的地位,无论是初始化Struts2还是对HTTP请求的处理,同时也架起了Struts2和XWork之间的一道桥梁,因此我们先深入 dispatcher = init.initDispatcher(config); 这段代码看一看

    public Dispatcher initDispatcher( HostConfig filterConfig ) {
        Dispatcher dispatcher = createDispatcher(filterConfig);
        dispatcher.init();//初始化方法
        return dispatcher;
    }

    private Dispatcher createDispatcher( HostConfig filterConfig ) {
        //将filterConfig中的参数名值对封装到Map中
        Map<String, String> params = new HashMap<String, String>();
        for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
            String name = (String) e.next();
            String value = filterConfig.getInitParameter(name);
            params.put(name, value);
        }
        return new Dispatcher(filterConfig.getServletContext(), params);
    }
    

上面的没什么, dispatcher.init(); 才是重头戏,继续深入

public void init() {
        //初始化配置文件管理器
    	if (configurationManager == null) {
            //根据name进行对象寻址
            //DEFAULT_BEAN_NAME = "struts"
            //<bean type="org.apache.struts2.dispatcher.DispatcherErrorHandler" name="struts".../>
            //<bean class="com.opensymphony.xwork2.ObjectFactory" name="struts"/>
            configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
    	}

        try {
            init_FileManager(); //初始化文件管理器

            // 初始化Struct2的默认配置加载器:
            // org/apache/struts2/default.properties,
            // 如果项目中需要覆盖,可以在classpath里的struts.properties里覆写
            init_DefaultProperties(); // [1]
            //初始化Xml配置加载器:
            // 如struts-default.xml,struts-plugin.xml,struts.xml
            init_TraditionalXmlConfigurations(); // [2]
            //初始化Properties配置加载器
            init_LegacyStrutsProperties(); // [3]
            //初始化用户自定义的配置加载器
            init_CustomConfigurationProviders(); // [5]
            //初始化由web.xml传入的参数
            init_FilterInitParameters() ; // [6]
            //初始化容器内置的对象
            //eg:ObjectFactory,FreemarkerManager....
            init_AliasStandardObjects() ; // [7]
            //创建容器, 初始化并预加载配置
            Container container = init_PreloadConfiguration();
            //对容器进行依赖注入
            container.inject(this);
            //检查对WebLogic的特殊支持
            init_CheckWebLogicWorkaround(container);
            //初始化所有的DispatcherListener
            if (!dispatcherListeners.isEmpty()) {
                for (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(this);
                }
            }
            //初始化错误处理器
            errorHandler.init(servletContext);

        } catch (Exception ex) {
            if (LOG.isErrorEnabled())
                LOG.error("Dispatcher initialization failed", ex);
            throw new StrutsException(ex);
        }
    }

    private void init_DefaultProperties() {
        configurationManager.addContainerProvider(new DefaultPropertiesProvider());
    }
    
    private void init_LegacyStrutsProperties() {
        configurationManager.addContainerProvider(new PropertiesConfigurationProvider());
    }

    private void init_TraditionalXmlConfigurations() {
        String configPaths = initParams.get("config");
        if (configPaths == null) {
            configPaths = DEFAULT_CONFIGURATION_PATHS;
        }
        String[] files = configPaths.split("\\s*[,]\\s*");
        for (String file : files) {
            if (file.endsWith(".xml")) {
                if ("xwork.xml".equals(file)) {
                    configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false));
                } else {
                    configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
                }
            } else {
                throw new IllegalArgumentException("Invalid configuration file name");
            }
        }
    }
    //...其他的省略

ConfigurationProvider

所有的初始化方法都是以init_开头,其核心只不过是调用configurationManager#addContainerProvider()方法,那到底什么是配置元素加载器(ContainerProvider)呢,比如init_DefaultProperties(),看一下DefaultPropertiesProvider的继承关系

public class DefaultPropertiesProvider extends PropertiesConfigurationProvider
	->public class PropertiesConfigurationProvider implements ConfigurationProvider
		 ->public interface ConfigurationProvider extends ContainerProvider, PackageProvider

其他的ContainerProvider也都实现了ConfigurationProvider这个接口,我们知道Struts2的配置文件形式有很多种,比如.xml,.properties等,所以Struts2就定义了ConfigurationProvider这个统一的接口,让框架支持处理所有的配置形式,而每一个ContainerProvider的实现类都可以根据不同的配置文件的特点进行设计。 同时ConfigurationProvider继承了ContainerProvider和PackageProvider两个接口,ContainerProvider的子类有:FileManagerFactoryProvider,StubConfigurationProvider, XmlConfigurationProvider, BeanSelectionProvider等,它的用途大概就是处理诸如XML,Properties等格式的配置文件。而PackageProvider的操作对象是PackageConfig,从源码可以看出,PackageConfig对应了XML配置文件中的package节点,这样PackageProvider的作用也不言而喻

public class PackageConfig extends Located implements Comparable, Serializable, InterceptorLocator {

    private static final Logger LOG = LoggerFactory.getLogger(PackageConfig.class);

    protected Map<String, ActionConfig> actionConfigs;
    protected Map<String, ResultConfig> globalResultConfigs;
    protected Map<String, Object> interceptorConfigs;
    protected Map<String, ResultTypeConfig> resultTypeConfigs;
    protected List<ExceptionMappingConfig> globalExceptionMappingConfigs;
    protected List<PackageConfig> parents;
    protected String defaultInterceptorRef;
    protected String defaultActionRef;
    protected String defaultResultType;
    protected String defaultClassRef;
    protected String name;
    protected String namespace = "";
    protected boolean isAbstract = false;
    protected boolean needsRefresh;
    ...
}

初始化容器

Struts2中的所有内置对象都会交给Container去管理,比如XML中的Bean,Constant节点以及Properties文件中的参数,Container的实现类会扫描 @Inject 注解,进行依赖注入,下面我们看看Container container = init_PreloadConfiguration();这行代码做了什么事情

    private Container init_PreloadConfiguration() {
        Container container = getContainer();

        boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
        LocalizedTextUtil.setReloadBundles(reloadi18n);

        boolean devMode = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_DEVMODE));
        LocalizedTextUtil.setDevMode(devMode);

        return container;
    }
public Container getContainer() {
        if (ContainerHolder.get() != null) {
            return ContainerHolder.get();
        }
        //ConfigurationManager类对所有的配置管理中心
        ConfigurationManager mgr = getConfigurationManager();
        if (mgr == null) {
            throw new IllegalStateException("The configuration manager shouldn't be null");
        } else {
            Configuration config = mgr.getConfiguration();
            if (config == null) {
                throw new IllegalStateException("Unable to load configuration");
            } else {
                Container container = config.getContainer();
                ContainerHolder.store(container);
                return container;
            }
        }
    }

我们再看看Container的实现类ContainerImpl,它的内部缓存了两个实例factories和factoryNamesByType,而factories根据Key缓存了不同对象的制造工厂,Key中有两个变量:type,name,factoryNamesByType则在factories基础之上根据名称进行寻址。 getInstance()方法的每次调用,都会根据传进来的type,class构造一个Key对象,然后到factories中查找到对应的工厂类,调用Factory的create()方法,创建对象

class ContainerImpl implements Container {

	final Map<Key<?>, InternalFactory<?>> factories;
	final Map<Class<?>, Set<String>> factoryNamesByType;

	@SuppressWarnings("unchecked")
	<T> T getInstance( Class<T> type, String name, InternalContext context ) {
		ExternalContext<?> previous = context.getExternalContext();
		Key<T> key = Key.newInstance(type, name);
		context.setExternalContext(ExternalContext.newInstance(null, key, this));
		try {
			InternalFactory o = getFactory(key);
			if (o != null) {
				return getFactory(key).create(context);
			} else {
				return null;
			}
		} finally {
			context.setExternalContext(previous);
		}
	}

	<T> T getInstance( Class<T> type, InternalContext context ) {
		return getInstance(type, DEFAULT_NAME, context);
	}
	/..
}
class Key<T> {

  final Class<T> type;
  final String name;
  final int hashCode;
  ..
}
interface InternalFactory<T> extends Serializable {

  /**
   * Creates an object to be injected.
   *
   * @param context of this injection
   * @return instance to be injected
   */
  T create(InternalContext context);
}

    <bean type="com.opensymphony.xwork2.factory.ActionFactory" name="struts" class="com.opensymphony.xwork2.factory.DefaultActionFactory" />
    <bean type="com.opensymphony.xwork2.factory.ConverterFactory" name="struts" class="com.opensymphony.xwork2.factory.DefaultConverterFactory" />
    <bean type="com.opensymphony.xwork2.factory.InterceptorFactory" name="struts" class="com.opensymphony.xwork2.factory.DefaultInterceptorFactory" />
    <bean type="com.opensymphony.xwork2.factory.ValidatorFactory" name="struts" class="com.opensymphony.xwork2.factory.DefaultValidatorFactory" />
    <bean type="com.opensymphony.xwork2.factory.UnknownHandlerFactory" name="struts" class="com.opensymphony.xwork2.factory.DefaultUnknownHandlerFactory" />

PrepareOperations和ExecuteOperations分析

从源码中可以看出,PrepareOperations负责创建ActionContext,清理Request,设置编码等

pre_method

ExecuteOperations则只有两个方法,负责真正的执行操作,executeStaticResourceRequest()负责静态资源,executeAction()是一个代理方法,将真正的执行交给Dispatcher.serviceAction()方法

 public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        // there is no action in this request, should we look for a static resource?
        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 {
            // this is a normal request, let it pass through
            return false;
        }
    }

    /**
     * Executes an action
     * @throws ServletException
     */
    public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
        dispatcher.serviceAction(request, response, mapping);
    }

总结

  1. 在Servlet容器(Jetty,Tomcat…)初始化的时候,加载web.xml初始化Filter
  2. 初始化StrutsPrepareAndExecuteFilter,调用init()方法 (1) 封装FilterConfig->FilterHostConfig (2) 初始化日志操作 (3) 初始化Dispatcher (4) 初始化PrepareOperations和ExecuteOperations
comments powered by Disqus
rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora