之前为了解一个问题,把struts2的源码又大致过了一下。上次看源码还是几年前了,这次把它记录下来。
这篇文章先说一下struts2是怎么进行配置的。分两部分,一部分是日志,第二部分是其余配置。
一、日志配置
日志的配置主要流程如下图
filterHostConfig通过filterConfig获得相应参数。可以看一下所有配置项类的情况如下:
init方法部分源码如下
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
省略
}
省略
}
在initLogging方法中,通过filterConfig去获得一个名字为loggerFactory的参数。该参数的值为一个类名,loggerFactory的类名。
比如:
<filter>
<filter-name>strut2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
<init-param>
<param-name>loggerFactory</param-name>
<param-value>
com.opensymphony.xwork2.util.logging.commons.CommonsLoggerFactory
</param-value>
</init-param>
</filter>
获得了该类名以后,通过反射实例化loggerFactory,并把它作为整个struts2的loggerFactory。源码如下:
public void initLogging( HostConfig filterConfig ) {
String factoryName = filterConfig.getInitParameter("loggerFactory");
if (factoryName != null) {
try {
Class cls = ClassLoaderUtil.loadClass(factoryName, this.getClass());
LoggerFactory fac = (LoggerFactory) cls.newInstance();
LoggerFactory.setLoggerFactory(fac);
} catch ( InstantiationException e ) {
System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default");
e.printStackTrace();
} catch ( IllegalAccessException e ) {
System.err.println("Unable to access logger factory: " + factoryName + ", using default");
e.printStackTrace();
} catch ( ClassNotFoundException e ) {
System.err.println("Unable to locate logger factory class: " + factoryName + ", using default");
e.printStackTrace();
}
}
}
那么还有一个问题,当factoryName==null的时候,以上代码没有做任何处理,那么loggerFactory是什么呢。答案在LoggerFactory的
getLoggerFactory方法中。我们看一下源码
protected static LoggerFactory getLoggerFactory() {
lock.readLock().lock();
try {
if (factory != null) {
return factory;
}
} finally {
lock.readLock().unlock();
}
lock.writeLock().lock();
try {
if (factory == null) {
try {
Class.forName("org.apache.commons.logging.LogFactory");
factory = new com.opensymphony.xwork2.util.logging.commons.CommonsLoggerFactory();
} catch (ClassNotFoundException ex) {
// commons logging not found, falling back to jdk logging
factory = new JdkLoggerFactory();
}
}
return factory;
}
finally {
lock.writeLock().unlock();
}
}
如果我们没有配置loggerFactory,那么我们在代码里面调用LoggerFactory.getLoggerFactory的时候,会先去寻找是否有commonLogger的jar包,找不到直接用jdk提供的LoggerFactory。因此,也看到struts2原生没有提供对log4j的支持。
二、其余配置
我们还是先看一下整体的流程图吧
我们看到struts2的核心类Dispatcher在InitOperation中创建。Dispatcher包含了filterConfig。源码如下:
public Dispatcher initDispatcher( HostConfig filterConfig ) {
Dispatcher dispatcher = createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
private Dispatcher createDispatcher( HostConfig filterConfig ) {
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方法,大部分的配置都是在该方法中完成的。但是在讲解之前,需要先了解一下struts2的配置管理设计。
struts2的配置主要分为三部分
1 、ConfigurationProvider:获取各种配置,比如从properties和xml文件中获取配置,也可以自定义ConfigurationProvider接口,只要实现相应接口即可。
2、ConfigurationManager:管理各种ConfigurationProvider,可以看做ConfigurationProvider的集合。
3、Configuration:配置类
看一下比较重要的类图,我没有把所有的ConfigurationProvider画出来,只画了几个关键的,旁边标注了它们的作用。自定义ConfigurationProvider只需要实现ConfigurationProvider接口即可。另外Configuration的实现类分为普通的实现和Mock测试的实现。
现在,我们来分析dispatcher.init方法,该方法其实就是创建了一个ConfigurationManager,然后往ConfigurationManager里面添加各种ConfigurationProvider。我们看一下源码吧
public void init() {
if (configurationManager == null) {
configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
}
try {
init_DefaultProperties(); // [1]
init_TraditionalXmlConfigurations(); // [2]
init_LegacyStrutsProperties(); // [3]
init_CustomConfigurationProviders(); // [5]
init_FilterInitParameters() ; // [6]
init_AliasStandardObjects() ; // [7]
Container container = init_PreloadConfiguration();
container.inject(this);
init_CheckConfigurationReloading(container);
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);
}
}
这里面的各种init_*方法就是往ConfigurationManager中添加ConfigurationProvider。我截取几个init_*方法的源码来看看
private void init_DefaultProperties() {
configurationManager.addContainerProvider(new DefaultPropertiesProvider());
}
private void init_LegacyStrutsProperties() {
configurationManager.addContainerProvider(new LegacyPropertiesConfigurationProvider());
}
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");
}
}
}
protected XmlConfigurationProvider createXmlConfigurationProvider(String filename, boolean errorIfMissing) {
return new XmlConfigurationProvider(filename, errorIfMissing);
}
protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx);
}
private void init_CustomConfigurationProviders() {
String configProvs = initParams.get("configProviders");
if (configProvs != null) {
String[] classes = configProvs.split("\\s*[,]\\s*");
for (String cname : classes) {
try {
Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
configurationManager.addContainerProvider(prov);
} catch (InstantiationException e) {
throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Unable to access provider: "+cname, e);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Unable to locate provider class: "+cname, e);
}
}
}
}
private void init_FilterInitParameters() {
configurationManager.addContainerProvider(new ConfigurationProvider() {
public void destroy() {
}
public void init(Configuration configuration) throws ConfigurationException {
}
public void loadPackages() throws ConfigurationException {
}
public boolean needsReload() {
return false;
}
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
props.putAll(initParams);
}
});
}
其中,initParams就是filterConfig。在init_TraditionalXmlConfigurations中,DEFAULT_CONFIGURATION_PATHS为struts-
default.xml,struts-plugin.xml,struts.xml。也就是说在web.xml中,如果不对StrutsPrepareFilter设置参数config的话,默认xml为struts-
default.xml,struts-plugin.xml,struts.xml。
在init_CustomConfigurationProviders中,我们知道在web.xml中对StrutsPrepareFilter设置参数configProviders位自定义的ConfigurationProvider即可。它会通过类加载器加载配置的类名,然后实例化,并交由ConfigurationManager管理。
最后,还有一个配置,哪些后缀不纳入struts2的filter过滤。这个web.xml中的配置项通过InitOperations的buildExcludedPatternsList来获取,源码如下:
public void init(FilterConfig filterConfig) throws ServletException {
省略
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
省略
}
public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) {
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;
}
}
其中StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN为struts.action.excludePattern
返回的 List 保存在this.excludedPatterns之中,将会在 doFilter 中进行判断,如果 request 请求的是排除的后缀,那么 struts2 就不包装 request ,也不获取 action ,则是普通的 request 调用,源码如下:
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, new Object());
} else {
request = prepare.wrapRequest(request);
prepare.findActionMapping(request, response);
}
chain.doFilter(request, response);