Struts2-初始化流程
aCoder2013 opened this issue · 0 comments
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,设置编码等
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);
}
总结
- 在Servlet容器(Jetty,Tomcat...)初始化的时候,加载web.xml初始化Filter
- 初始化StrutsPrepareAndExecuteFilter,调用init()方法
(1) 封装FilterConfig->FilterHostConfig
(2) 初始化日志操作
(3) 初始化Dispatcher
(4) 初始化PrepareOperations和ExecuteOperations