struct2源码解读之解析配置文件
上篇博文讲到struct2在初始化时,在创建Dispacher之后,会Dispacher.init()中会对配置文件进行解析,下面就让我们一起探讨下struct2是如何解析配置文件的。
public Dispatcher initDispatcher( HostConfig filterConfig ) { //创建Dispacher实例 Dispatcher dispatcher = createDispatcher(filterConfig); //解析配置文件,搭建struct2运行环境 dispatcher.init(); return dispatcher; }
这个init()方法,struct2到底做了些什么呢?我们一起进到init()方法中看看。
public void init() { if (configurationManager == null) { configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } try { //初始化解析各个配置文件的对象 init_DefaultProperties(); //解析default.properties的对象 init_TraditionalXmlConfigurations(); // 解析struct*.xml配置文件的对象 init_LegacyStrutsProperties(); // init_CustomConfigurationProviders(); // 自定义解析对象 init_FilterInitParameters() ; // web.xml的初始化参数 init_AliasStandardObjects() ; // 解析bean的对象 //解析配置文件,初始化一个IOC容器 Container container = init_PreloadConfiguration(); //把dispacher依赖注入到容器中 container.inject(this); //设置配置信息 init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); //用于扩展 if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } } //异常信息,省略 }
从上面可以看到,dispacher.init()主要工作是是解析配置文件,把每个配置信息封装到不同的对象,然后将这些对象交由Container管理。Container俗称容器(本质是接口),什么是容器?就是装水的杯子,我们可以往杯子里面在装水,也可以往杯子里面取水。struct2通过inject()方法,把对象放进container,然后通过getInstance()取出里面的对象,这里过程很复杂,但结果很简单,怎么实现,struct2帮我们实现了,我们只要通过container这个接口的方法,就可以往这个容器取放对象了。这个和spring的ioc容器ActionContext有点类似,具体原理我们以后再分析,这里主要分析流程。
一:初始化解析各个配置文件的对象
(1)struct2解析配置文件的接口
struct2解析不同配置文件由不同的类解析。
刚开始解析配置文件的方法统一封装到ContainerProvider这个接口的中的
public interface ContainerProvider { public void destroy(); //处理配置文件的方法 public void init(Configuration configuration) throws ConfigurationException; public boolean needsReload(); //解析配置文件的方法 public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException; }
但后来因为配置文件中有很多的标签属性,如解析contant,解析package的,为了分类管理,又把解析package标签的方法单独封装成一个接口-PackageProvider
public interface PackageProvider { //处理配置文件的方法 public void init(Configuration configuration) throws ConfigurationException; public boolean needsReload(); //解析package标签的方法 public void loadPackages() throws ConfigurationException; }
这个接口专门解析package标签。
因为ContainerProvider和PackageProvider接口都是为了解析配置文件,为了方便管理,又设计了一个接口ConfigurationProvider 继承了这两个接口,解析配置文件的实现类,只要实现这个类就行了。
public interface ConfigurationProvider extends ContainerProvider, PackageProvider { }
(2)struct2解析配置文件的实现类
针对不同的配置文件,struct2都提供了不同的实现类,这些实现类都继承了ConfigurationProvider 这个接口,从名字来看,ConfigurationProvider 是配置提供者的意思,所以解析property的类为DefaultPropertiesProvider,解析xml配置文件的为XmlConfigurationProvider,当然,struct2还会对每个类进行细分,如XmlConfigurationProvider的又分StrutsXmlConfigurationProvider和其他什么的。这里大家对每个类有个概念就行了,毕竟是别人设计的。这些对象都交由一个对象-ConfigurationManager管理,从名字看,这个就是配置管理者。所谓的管理,实际是把每个实现类,添加到ConfigurationManager一个属性(list集合中),如:
private List<ContainerProvider> containerProviders = new CopyOnWriteArrayList<ContainerProvider>(); private void init_DefaultProperties() { configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); }
(3)初始化解析各个配置文件的解析对象
//初始化解析各个配置文件的对象 init_DefaultProperties(); //解析default.properties的对象 init_TraditionalXmlConfigurations(); // 解析struct*.xml配置文件的对象 init_LegacyStrutsProperties(); // init_CustomConfigurationProviders(); // 自定义解析对象 init_FilterInitParameters() ; // web.xml的初始化参数 init_AliasStandardObjects() ; // 解析bean的对象
这里用了6个方法实例化了6个对象,这些对象都是针对不同的配置文件进行解析的。下面我们选一二个例子来说明下就可以了,这里主要是实例化几个对象,没有什么实质性的工作,所以没什么好纠结的。
①实例化解析default.properties的对象
private void init_DefaultProperties() { configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); }
这里new了一个DefaultPropertiesProvider对象,然后把这个对象添加到名为configurationManager的list集合中。
②实例化解析struct*.xml的对象
private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml"; private void init_TraditionalXmlConfigurations() { //获得web.xml中配置的*.xml路径,当协同开发时,我们可以把每个成员的xml配置在这里 String configPaths = initParams.get("config"); if (configPaths == null) { //如果没有配置自定义的,则默认解析三个*.xml文件 configPaths = DEFAULT_CONFIGURATION_PATHS; } //把上面定义的字符串,以每个xml文件为单位,分割到一个字符数组中 String[] files = configPaths.split("\\s*[,]\\s*"); //循环遍历,找出每个配置文件的解析器 for (String file : files) { //这个只解析xml文件 if (file.endsWith(".xml")) { if ("xwork.xml".equals(file)) { //如果是xwork.xml文件,则用XmlConfigurationProvider解析 configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false)); } else { //其他xml文件,则用StrutsXmlConfigurationProvider解析 configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException("Invalid configuration file name"); } } }
这里是实例化解析xml文件的对象,默认是解析struts-default.xml,struts-plugin.xml,struts.xml这三个,struts-default.xml这个是struct2默认的,一定要有,struts-plugin.xml这个是和spring集成的时候添加进去的,struts.xml这个是自定义的,当协同工作时,每个人都会有自己的xml文件,这时可以配置到web.xml,或者是放到/web-info/class目录下,然后用include方法放到struts.xml中。struct2解析xml文件,用的是StrutsXmlConfigurationProvider对象,StrutsXmlConfigurationProvider这个继承了XmlConfigurationProvider。
其他的,如解析自定义解析器的,我们在自定义解析器时,会以configProviders为key值配置到web.xml中,然后在这里实例化。具体的就不详细解析了。
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 = ClassLoaderUtils.loadClass(cname, this.getClass()); //实例化一个ConfigurationProvider ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance(); configurationManager.addConfigurationProvider(prov); } //异常信息 } } }
二、解析配置文件,初始化IOC容器
我们在init_PreloadConfiguration()这个办法循环调用各个解析的regist()或者是loadpack()方法对各个配置进行解析,然后返回一个Container对象
private Container init_PreloadConfiguration() { //解析配置文件 Configuration config = configurationManager.getConfiguration(); //返回一个ioc容器 Container container = config.getContainer(); //i18n国际化信息 boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); LocalizedTextUtil.setReloadBundles(reloadi18n); return container; }
(1)解析配置文件
public synchronized Configuration getConfiguration() { if (configuration == null) { //初始化一个对象来保存配置信息 setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName)); try { //解析配置文件,getContainerProviders()获取上面包含了所有解析器的集合 configuration.reloadContainer(getContainerProviders()); } //异常信息 } else { //重新加载 conditionalReload(); } return configuration; }
①初始化一个对象来保存配置信息
Configuration是一个接口,DefaultConfiguration是其实现类,这个类封装了解析出来的配置信息。如解析每个package 标签会把配置信息封装到packageConfig对象,因为有很多个package,也就是有很多个packageConfig对象,因此这里,把这些packageConfig对象放到一个map中,这个map就交给Configuration管理,通过这个类的get()方法,就可以获取package的配置信息了
protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>(); protected RuntimeConfiguration runtimeConfiguration; protected Container container; protected String defaultFrameworkBeanName; protected Set<String> loadedFileNames = new TreeSet<String>(); protected List<UnknownHandlerConfig> unknownHandlerStack;
②解析配置文件
这里就是解析配置文件的地方了,很复杂,我下篇博文再详细解析。
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException { packageContexts.clear(); loadedFileNames.clear(); List<PackageProvider> packageProviders = new ArrayList<PackageProvider>(); ContainerProperties props = new ContainerProperties(); ContainerBuilder builder = new ContainerBuilder(); //循环遍历各个解析器的register方法解析配置文件 for (final ContainerProvider containerProvider : providers) { containerProvider.init(this); containerProvider.register(builder, props); } props.setConstants(builder); builder.factory(Configuration.class, new Factory<Configuration>() { public Configuration create(Context context) throws Exception { return DefaultConfiguration.this; } }); ActionContext oldContext = ActionContext.getContext(); try { // Set the bootstrap container for the purposes of factory creation Container bootstrap = createBootstrapContainer(); setContext(bootstrap); //实例化一个container container = builder.create(false); setContext(container); objectFactory = container.getInstance(ObjectFactory.class); // 解析package标签 for (final ContainerProvider containerProvider : providers) { if (containerProvider instanceof PackageProvider) { container.inject(containerProvider); ((PackageProvider)containerProvider).loadPackages(); packageProviders.add((PackageProvider)containerProvider); } } // 解析插件的package标签 Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class); if (packageProviderNames != null) { for (String name : packageProviderNames) { PackageProvider provider = container.getInstance(PackageProvider.class, name); provider.init(this); provider.loadPackages(); packageProviders.add(provider); } } //整理配置信息 rebuildRuntimeConfiguration(); } finally { if (oldContext == null) { ActionContext.setContext(null); } } return packageProviders; }
(2)获取IOC容器
因为解析配置文件时,把配置的对象都放到了Container,然后把这个Container交给Configuration管理,通过这个类的get()方法,就可以获取package的配置信息了。
//返回一个ioc容器 Container container = config.getContainer(); public Container getContainer() { return container; }
(3)设置配置信息
//设置ReloadingConfigs属性 private void init_CheckConfigurationReloading(Container container) { FileManager.setReloadingConfigs("true".equals(container.getInstance(String.class, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD))); } //设置paramsWorkaroundEnabled 属性 private void init_CheckWebLogicWorkaround(Container container) { // test whether param-access workaround needs to be enabled if (servletContext != null && servletContext.getServerInfo() != null && servletContext.getServerInfo().indexOf("WebLogic") >= 0) { paramsWorkaroundEnabled = true; } else { paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class, StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)); } }
设置的这个属性没多大重要的,这里就不详谈了,有兴趣的可以去了解下。
(4)设置DispatcherListener接口
if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } }
有了DispatcherListener接口,相当于我们拥有了Dispatcher初始化过程中进行自由扩展的一个接口。这也是struct2在初始化主线中为我们提供的一个重要扩展点。
四、总结
从这里我们就了解到了struct2是如何解析配置文件和搭建struct2所需运行的环境的。在初始化核心对象Dispacher时,会先初始化解析各种配置文件的对象,这些对象都实现了ConfigurationProvider 接口,然后循环遍历这些对象的init()和regist()方法,解析各种配置文件,如default.properties和struct*.xml文件,把解析出来的属性封装到不同的对象中,如package标签的属性封装到packageConfig对象,这些对象又汇集到configuration对象中统一管理,configuration对象又交给configurationManager对象,configurationManager对象又交给Diapcher,一层一层封装,从而就搭起了struct2的运行环境。至于是如何解析的,下篇博文我们继续来探讨。