Struts2源码分析-StrutsPrepareAndExecuteFilter入口类

前言:

2019年上半年,那时还在上一家公司,工作比较闲很少加班,便利用起下班和周末时间,读完了Shiro、Spring、SpringMVC、MyBatis四个框架的源码,虽然过程异常枯燥但最后还是坚持了下来,读完之后,有了醍醐灌顶般的收获,之后在项目中对于每一个注解每一项配置,几乎都能清楚其内部的实现逻辑,这种感觉实在是太棒了。
在学习源码的过程中,除了看书也查阅了大量的博客,其中不乏有许多优秀的解析系列文章,受到启发便有了将所做的笔记整理发表成博客的想法,但一时不知如何下手,后面又忙于跳槽,便将此事搁置了。8月份跳槽到现在的公司,首次接触到真正意义上的大型商业项目,学习了很多技术和框架,但都仅仅停留在使用的层面上,并不知其实现原理,因为之前收获过阅读源码的好处,便打算研究项目中所用到框架源码,首先便是web层框架Struts2了。在经历了繁忙的几个月后到了12月底,终于有时间静下心来研究。本系列文章就是在学习研究完Struts2源码后整理而来,如果能帮助到恰好正在研究Struts2框架源码的朋友,本人将不胜荣幸。


本文所写的仅仅是站在本人的角度以及本人所能理解到的层次而来,因水平有限,如果与实际有偏差的地方,还请各位多多指正,不吝赐教。


正文

研究一个框架首先要找到研究的入口,用过Struts2的朋友应该知道在web.xml有如下配置

	<filter>
		<filter-name>StrutsFilter</filter-name>
		<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>StrutsFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

StrutsPrepareAndExecuteFilter就是Struts2的入口类,该类实现了Filter接口,遵循servlet规范,根据Filter的生命周期方法将Struts2分成了两条主线:一条初始化线,一条执行线,这一点和SpringMVC的DispatcherServlet类似,虽然其内部实现千差万别。先看init()方法,这个方法会在Servlet容器启动时触发。
StrutsPrepareAndExecuteFilter.java

public void init(FilterConfig filterConfig) throws ServletException {
	    //初始化操作类
        InitOperations init = createInitOperations(); 
        Dispatcher dispatcher = null;
        try {
       		//对filterConfig进行一层简单的封装
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            //初始化配置在Filter参数上的loggerFactory
            init.initLogging(config);
            //初始化核心分发器Dispatcher ,后面主要分析的地方
            dispatcher = init.initDispatcher(config);
            
            //初始化静态资源加载器,
            //默认访问org.apache.struts2.static、template、static包下的资源,会被框架判断为静态资源
            //也可以自定义Filter的init-param中自定义
            init.initStaticContentLoader(config, dispatcher);
			//初始化进行Http预处理的操作类
            prepare = createPrepareOperations(dispatcher);
            //初始化进行Http请求的操作类
            execute = createExecuteOperations(dispatcher);
            //初始化struts.action.excludePattern参数,用于不处理自定义请求
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
			//钩子函数,用于第三方扩展
            postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
 }

阅读任何框架的源码,我的建议是一开始不要深究每一处细节,先利用debug模式将整个流程跑几遍,不懂的地方暂时跳过,当对整个流程有了一个大概的了解后再深入研究每个环节。
init()方法内部的每个方法都做了简单的注释,其内部逻辑也很简单,虽然刚开始可能无法理解每个方法的意义,先不管,重点看InitOperations类的initDispatcher()方法,该方法返回返回一个Dispatcher对象,名称翻译过来就是“调度者”,该对象在Struts2生命周期里扮演了“指挥官”的角色,主要作用有:

  • 负责系统初始化
  • 接收并预处理Http请求

废话不多说,跟进去
InitOperations.java

public Dispatcher initDispatcher( HostConfig filterConfig ) {
        Dispatcher dispatcher = createDispatcher(filterConfig);
        dispatcher.init();
        return dispatcher;
    }

跟进createDispatcher()方法
InitOperations.java

protected Dispatcher createDispatcher(HostConfig filterConfig) {
        Map<String, String> params = new HashMap<>();
        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);
    }

createDispatcher()方法很简单,遍历Filter配置的参数生成Map对象,和ServletContext上下文对象一起传入构造方法生成Dispatcher对象,至此Dispatcher对象创建完成,该内部持有filter自定义的参数和ServletContext对象,有了ServletContext对象,就可以获取全局参数和项目中的资源文件(例如配置文件)。继续跟进Dispatcher对象的init()方法。
Dispatcher.java

public void init() {
    	if (configurationManager == null) {
    		configurationManager = createConfigurationManager(Container.DEFAULT_NAME);
    	}
        try {
            init_FileManager();
            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_CheckWebLogicWorkaround(container);

            if (!dispatcherListeners.isEmpty()) {
                for (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(this);
                }
            }
            errorHandler.init(servletContext);
        } catch (Exception ex) {
            throw new StrutsException(ex);
        }
    }

方法较长,但并不复杂,首先new了个ConfigurationManager对象,按名称翻译就是配置管理器,该对象是Dispatcher的成员变量,该对象的作用就是管理配置对象,看看它的类结构:
ConfigurationManager.java

public class ConfigurationManager {
	protected Configuration configuration;
	private List<ContainerProvider> containerProviders = new CopyOnWriteArrayList<>();
    private List<PackageProvider> packageProviders = new CopyOnWriteArrayList<>();
}

ConfigurationManager内部维护有一个Configuration 对象,以及ContainerProvider和PackageProvider两个缓存集合,上面说过ConfigurationManager的作用是管理配置对象,而配置对象指的就是Configuration,
严格地说应该是配置元素的载体,这样说过于抽象,解释一下:说到配置元素,我们马上就能想到.xml文件,.properties文件,是的,这些文件里定义的<bean>、<constant>、<package>都是配置元素,其实除了这些还有框架通过硬编码方式添加的.class对象也属于配置元素,它们最终都会抽象成一个个对象被加载到容器中,而上面所说的配置元素载体又是什么呢?我们就把它理解成文件对象或者未实例化的.class对象更合适,ContainerProvider和PackageProvider记录的就是配置文件的名称,路径以及类的全限定名称等信息,至于这些文件什么时候被加载、.class对象什么时候实例化,就是由Configuration 对象后期调配了。
ContainerProvider和PackageProvider又有什么区别呢?暂时不做深究,留个印象,等后面对整个体系有了大概的了解后再回头看,一切都会明了。
回到Dispatcher类的init()方法,可以看到[1]-[7]就是生成各种.xml和.properties以及硬编码方式添加的.class对象对应的ContainerProvider并添加到ConfigurationManager的集合对象中,此时并未真正的加载。
添加完各类provider之后就是生成Container对象了,跟进
Dispatcher.java

private Container init_PreloadConfiguration() {
        return getContainer();
}
public Container getContainer() {
        if (ContainerHolder.get() != null) {
            return ContainerHolder.get();
        }
        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;
            }
        }
    }

代码逻辑很简单,上面说过ConfigurationManager是Dispatcher的成员变量,Configuration又是ConfigurationManager的成员变量,这个方法先对ConfigurationManager做一个非空判断,再通过ConfigurationManager对象获取Configuration对象,跟进去
ConfigurationManager.java

public synchronized Configuration getConfiguration() {
        if (configuration == null) {
            //直接new出DefaultConfiguration对象
            setConfiguration(createConfiguration(defaultFrameworkBeanName));
            try {
                //重点跟进
                configuration.reloadContainer(getContainerProviders());
            } catch (ConfigurationException e) {
                setConfiguration(null);
                throw new ConfigurationException("Unable to load configuration.", e);
            }
        } else {
            conditionalReload();
        }
        return configuration;
    }

整个逻辑也很简单,先new个Configuration的默认实现类DefaultConfiguration的对象,然后调用它的reloadContainer()方法,并将前面收集到的一系列ContainerProvider对象传给它,继续跟进
DefaultConfiguration.java

public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
        packageContexts.clear();
        loadedFileNames.clear();
        List<PackageProvider> packageProviders = new ArrayList<>();
		
		//ContainerProperties对象是操作.properties对象的操作类
        ContainerProperties props = new ContainerProperties();
        //前面ConfigurationManager收集的一系列ContainerProvider对象就由它加载
        //最终由它生成一个Container容器对象
        ContainerBuilder builder = new ContainerBuilder();
        
        //创建一个Container 对象,但该对象并不是最终的容器对象,只是中间的一个过渡
        Container bootstrap = createBootstrapContainer(providers);
        //前面说的ContainerProvider 是配置元素的载体,记录的只是配置元素的信息
        //而真正开始加载的地方就在在这里,加载后的配置元素此时由ContainerBuilder对象一一收集
        //这里留个印象,后面再一一分析
        for (final ContainerProvider containerProvider : providers)
        {
            bootstrap.inject(containerProvider);
            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;
            }

            @Override
            public Class<? extends Configuration> type() {
                return DefaultConfiguration.this.getClass();
            }
        });

        ActionContext oldContext = ActionContext.getContext();
        try {
            setContext(bootstrap);
            //前面ContainerBuilder对象收集完各种配置元素,在这里就可以创建出一个完整的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);
                }
            }

            Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
            for (String name : packageProviderNames) {
                PackageProvider provider = container.getInstance(PackageProvider.class, name);
                provider.init(this);
                provider.loadPackages();
                packageProviders.add(provider);
            }
            //根据配置的<package>的namespace属性进行分类,为后期http请求的url映射做准备
            rebuildRuntimeConfiguration();
        } finally {
            if (oldContext == null) {
                ActionContext.setContext(null);
            }
        }
        return packageProviders;
    }

方法很长,但是就一条主线:创建ContainerBuilder对象,将ConfigurationManager收集的一系列ContainerProvider配置元素对象由它进行解析,解析完成后创建出Container容器对象。此时虽然对加载配置文件的具体细节不清楚,但是可以看出,这个方法包括前面ConfigurationManager以及Configuration所做的一切,就是在为这一步做铺垫。
前面说Dispatcher对象是整个初始化过程的核心,而Container对象即使核心中的核心,那么这个对象到底是什么?有什么用?这些不是简单的三言两语能说清楚,后面新开篇章,万里长征才刚刚开始。


后记

这篇文章主要就是为了认识Struts2框架初始化过程的主线,当对这个主线有了一个整体的概念后,再回头去看,似乎不再那么的生涩难懂了。读源码就该这样,先通读,再细读,最后带着问题有选择的读。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值