struts源码简单解读

一、问题现象和猜测

  1. 问题情况:

    在工作遇到一个404问题,此问题的现象是:单独的一个web模块在本地启动时,通过浏览器访问没有出现404,是可以正常运行的,但是当各个子web模块集成时,却出现了404错误。
    
  2. 问题的原因:

    子模块中的struts.xml配置文件中的package元素的name和namespace属性出现重复
    
  3. 问题解决方法:

    修改name和namespace
    
  4. 问题重现:

    下面将这个问题简单重现下(这个问题不会报404,但是定义了相同的name和namespace的package只有其中一个有效),具体的java代
    

    码就不列出了,比较简单,下面给出struts的配置文件:

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    主要定义了3个struts.xml文件,struts-2.xml和struts-3.xml文件中存在着相同的package元素。
    导致的现象就是:其中一个package失效了,通过浏览器访问:只会访问到struts-3.xml中的package元素。
    
    下面提出几个问题:
    1、为什么struts-2.xml文件中的package元素会失效尼?
    2、为什么不是struts-3.xml文件中的package元素失效尼?
    
    首先我们猜测下,导致这个问题的根本原因(**注意现在只是猜测**):
    
    struts框架在将配置文件映射成java对象时,通过使用一个以name或namespace属性值为
    key的Map容器,来存储各个配置文件中的package元素,所以导致相同name或namespace的package会覆盖?
    

二、简单介绍struts2框架的大概情况

struts框架的核心控制器(也可以叫做前置控制器)是
StrutsPreparedAndExecutedFilter,查看类的继承结构,可以发现它是一个Filter(过滤器),一个Filter接口中定义的了三个方法

这里写图片描述

我们这里只讲解init和doFilter方法,我们知道init和doFilter方法
被调用的时机不同,init方法是在servlet容器启动的时候,doFilter是在用户产生Http请求时被调用。
struts2正好利用Filter这2个方法的特性,将struts2的整个框架分为2条主线:

1、初始化主线
   该主线的任务:将struts2中各种不同格式的配置文件映射成类型丰富的java对象。在struts2中主要有2中格式的配置文件:
   (1)、properties文件
       主要用来配置struts2中运行时的一些常量
       比如:struts.devMode(定义为开发模式,如果程序出现错误,struts2会更加友好的向你展示这些错误)、struts.enable.DynamicMethodInvocation(允许动态方法调用)
       想查看更多这些运行时常量,可以查看struts-core中的default.properties:

这里写图片描述

   (2)、xml文件
     主要用来定义struts2程序运行时需要使用的对象(这部分由bean元素定义)、Http请求响应相关的对象
    比如:PackageConfig,ActionConfig等等(从类名就可以直接看出这2个类分别对应着配置文件中的Package元素和Action元素)
    想查看更多,可以查看struts-core中的struts-default.xml

下面列出所有struts2涉及到的配置文件(这张图来自:struts2技术内幕这本书里):

这里写图片描述

2、对Http请求进行处理的主线
    该主线任务主要是对Http请求的处理,按照struts2技术内幕这本书里所述,又可以细分为2部分:
    (1)对Http请求的预处理(与web容器耦合)    
        将于web容器相关对象转换成普通 java对象,
        比如:Dispatcher中的createContextMap方法,

这里写图片描述

     这里并不是按照我以前所想的那样,将request、session、servletContext中的所有parameter和attribute都复制到相应的Map结构中去,个人理解,struts2在这里使用适配器模型,通过适配器(RequestMap,SessionMap,ApplicaitonMap)将这些web对象包裹在内部,

这里写图片描述

    从上图可以看到,RequestMap继续了AbstractMap,并重写了它的方法,简单来看下entrySet()方法

这里写图片描述
(2)Xwork事件处理(与web容器解耦)
这部分的处理流程都被封装在ActionProxy对象中,这部分的执行主要在ActionProxy的prepare()方法和execute()方法中,这部分会在后面简单描述

三、通过源代码查找问题:

按照我的思路:
首先我们应该知道在对Http请求时,struts2框架,是怎么通过url查找到对应的Action
1对Http请求进行处理的主线
按照前面所述,StrutsPreparedAndExecutedFilter中的doFilter()方法是我们的切入点
这里写图片描述

我们主要讲4、5步骤(1,2,3步骤应该一目了然)
4、通过ActionMapper将当前Http请求的url解析成ActionMapping

这里写图片描述

这里写图片描述

5、真正进行处理的地方
在对Http请求进行简单处理处理后,我们即将进入真正进行处理Http请求的地方

这里写图片描述

可见真正的Http请求的处理是委托给dispatcher来进行处理的,下面是dispatcher的serviceAction方法的部分代码

这里写图片描述

首先我们进入ActionProxyFactory的createActonProxy(),去看看ActionProxy.prepare()方法

这里写图片描述

这里写图片描述

这里写图片描述

得到相应的ActionConfig对象后,我们将进入ActionProxy.execute()方法,从这里开始,struts框架开始将Http请求的处理控制权交给了xwork框架。
这里写图片描述

下面就开始进入ActionInvocation的invoke方法(你可以先去看看设计模式中的责任链模式)

这里写图片描述

好了,struts2的第二条主线基本讲完,具体的细节(比如:在ActionInvocation.init方法中会将当前Action压入ValueStack中的根对象CompoundRoot中)

从官网找了个图,总结下整个struts2 Http请求处理主线的流程
这里写图片描述

*2、初始化主线*

这条主线的驱动力,就是我们要找到Http请求处理主线中的RuntimeConfiguration,并且如何将配置文件映射成java对象,存储在RuntimeConfiguration中,这里我们只讲应用级别的struts.xml的映射,其他文件和框架级别的可以以此内推。

首先进入StrutsPreparedAndExecutedFilter中的init方法

这里写图片描述

整个初始化主线的核心其实就在InitOperation的initDispatcher()方法中,

这里写图片描述

这里写图片描述

这里写图片描述

对于这里将struts的配置文件映射成ConfigurationProvider对象没什么好说的,我们来看看ConfigurationProvider的继承体系比较有看头

这里写图片描述

这里写图片描述

这里写图片描述

可以看到ConfigurationProvider继承了ContainerProvider和PackageProvider接口,为什么要使ConfigurationProvider继承这2个接口尼?
个人理解:是为了在创建容器对象Container时便于统一化操作

从代码注释可以看到ContainerProvider,主要是为容器提供bean和constants常量的,
这里的bean对于xml配置文件中bean元素,constants对于properties配置文件中的常量和xml配置文件中的constant元素。而PackageProvider就是对应于xml配置文件中的Package元素了,
在struts2技术内幕这本书中,将这2中配置元素划分为:1、容器对象元素 2、事件配置元素

下面我们进入真正解析配置元素的init_PreloadConfiguration方法中

这里写图片描述

这里写图片描述

这里写图片描述



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();
        Container bootstrap = createBootstrapContainer(providers);
        for (final ContainerProvider containerProvider : providers)
        {//这个循环主要是将xml配置文件中的Bean和constant元素以及Properties文件中的常量,注册到Container容器中
            bootstrap.inject(containerProvider);
            containerProvider.init(this);//对于xml文件的Provider,会将xml解析成一个Document
            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

            setContext(bootstrap);
            container = builder.create(false);
            setContext(container);
            objectFactory = container.getInstance(ObjectFactory.class);

            // Process the configuration providers first
            for (final ContainerProvider containerProvider : providers)
            {//这里是解析Package元素
                if (containerProvider instanceof PackageProvider) {
                    container.inject(containerProvider);
                    ((PackageProvider)containerProvider).loadPackages();
                    packageProviders.add((PackageProvider)containerProvider);
                }
            }
//这里我有个不懂的地方,就是为什么要解析PackageProvider2次
            // Then process any package providers from the plugins
            Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
            for (String name : packageProviderNames) {
                PackageProvider provider = container.getInstance(PackageProvider.class, name);
                provider.init(this);
                //这里是解析Package元素,这里还是比较复杂的,但是因为时间原因就不讲了
                provider.loadPackages();
                packageProviders.add(provider);
            }
//这个方法就是我们要找的RuntimeConfuguration,下面会对这个方法讲解
            rebuildRuntimeConfiguration();
        } finally {
            if (oldContext == null) {
                ActionContext.setContext(null);
            }
        }
        return packageProviders;
    }

这里写图片描述

这里写图片描述

非常粗糙的讲完了初始化主线,赶紧洗澡睡觉

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值