第一篇:Linux操作系统的权限代码分析
现在关于内核的书很少涉及到Linux内核的安全,内核安全大概包括了密码学实现(crypto)和访问控制(security)两个部分。安全系 统作为Linux内核的一个重要的子系统,已经为我们提供了很多的相关接口,这里我们就对安全访问控制做一个简要的分析和介绍。访问控制的原理注定要和虚拟文件系统和进程管理有着非常紧密的联系,因为作为用户主体的表现形式就是进程,而作为资源客体对象的表现形式就是文件,而访问 控制就是如何实现正确的用户可以访问正确的资源。Linux能够提供给我们许多可信的方式来处理这样的问题。
初始化工作
这个初始化工作在init/main.c中的start_kernel()中security_init()定义了,其具体的实现是在security/security.c中:
int __init security_init(void){ printk(KERN_INFO “Security Framework v” SECURITY_FRAMEWORK_VERSION “ initialized/n”);if(verify(&dummy_security_ops)){ printk(KERN_ERR “%s could not verify ” “dummy_security_ops structure./n”, __FUNCTION__);return-EIO;} security_ops = &dummy_security_ops;do_security_initcalls();return 0;} 这个函数首先用verify来验证所指定的访问控制策略(dummy_security_ops)是否为空,如果为空就按“保持默认”的方式进行分 配,这里的“保持沉默”就是对于任何的访问控制采取不管不问的方式处理了。然后就是把dummy_security_ops指定给系统全局安全策略 security_ops。
访问控制策略的相关接口
关于这些接口就是定义在了include/linux/security.h中的security_operations,包括如下一些操作:当父 进程trace子进程时进行的权限检查,对权能的获取、设置检查、设置、有效性检查,对进程做审计的检查,当某个操作使用一般系统接口表时需要的权限检 查,当使用内核消息环或改变登录终端时需要的权限检查,当改变系统时间需要的检查,当分配一个新的虚拟内存页需要的权限检查,当执行二进制程序时需要的各 种权限分配和检查,对文件系统操作时需要的各种访问控制操作,对inode索引节点操作时需要的各种访问控制操作,对文件操作时的各种访问控制操作,对进 程操作的需要的各种访问控制操作,对进程间通信信号灯的权限控制,对消息队列的控制,对进程间通信的共享内存区域的控制,对网络消息处理需要的各种控制,注册与撤销访问控制策略,对网络连接的控制,对套接字的各种控制,对IPSEC中xfrm用户自定义策略的分配,密钥管理的控制等等,几乎囊括了系统各种 行为的控制。
权限管理
虚拟文件系统为各种类型的文件系统提供统一的操作接口,同时这样的做法也可以简化文件权限的管理。那么Linux时如何巧妙地实现这种想法呢?Linux 采用的是基于列的ACL自主访问控制,即在每个文件里存储对本文件的访问权限信息,这里我们采用索引节点inode(定义在 include/linux/fs.h)作为切入点进行分析。在inode结构体中有i_uid和i_gid元素,还有一个i_mode元素。这个 i_mode是16位的无符号整数表示,由9位权限方式位、3位“粘滞”标志位和4位文件类型标志位,它们的具体的定义在 include/linux/stat.h中:
#define S_IFMT 00170000 /* 用于抽取i_mode域中类型部分的屏蔽位 */ #define S_IFSOCK 0140000 /* 套接字类型码 */ #define S_IFLNK 0120000 /* 符号连接类型码 */ #define S_IFREG 0100000 /* 普通文件类型码 */ #define S_IFBLK 0060000 /* 块特别文件类型码 */ #define S_IFDIR 0040000 /* 目录文件类型码 */ #define S_IFCHR 0020000 /* 字符特别文件类型码 */ #define S_IFIFO 0010000 /* 管道或FIFO类型码 */ #define S_ISUID 0004000 /* 用户粘滞位 */ #define S_ISGID 0002000 /* 用户组粘滞位 */ #define S_ISVTX 0001000 /* 粘滞位 */ #define S_IRWXU 00700 /* 用户读写执行 */ #define S_IRUSR 00400 /* 用户读 */ #define S_IWUSR 00200 /* 用户写 */ #define S_IXUSR 00100 /* 用户执行 */ #define S_IRWXG 00070 /* 用户组读写执行 */ #define S_IRGRP 00040 /* 用户组读 */ #define S_IWGRP 00020 /* 用户组写 */ #define S_IXGRP 00010 /* 用户组执行 */ #define S_IRWXO 00007 /* 其他用户读写执行 */ #define S_IROTH 00004 /* 其他用户读 */ #define S_IWOTH 00002 /* 其他用户写 */ #define S_IXOTH 00001 /* 其他用户执行 */ #define S_IRWXUGO(S_IRWXU|S_IRWXG|S_IRWXO)/* 全部用户读写执行 */ #define S_IALLUGO(S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)/* 全部用户全部权限 */ #define S_IRUGO(S_IRUSR|S_IRGRP|S_IROTH)/* 全部用户读 */ #define S_IWUGO(S_IWUSR|S_IWGRP|S_IWOTH)/* 全部用户写 */ #define S_IXUGO(S_IXUSR|S_IXGRP|S_IXOTH)/* 全部用户执行 */ 同时,每个进程的task_struct中也有对应的uid,euid,suid,fsuid,gid,egid,sgid,fsgid等元素,当用户登录系统就创建了一个shell进程,它从/etc/passwd中取得对应用户的uid和gid 来唯一标志这个用户,以后所有的进程就代代相传。当内核在执行用户进程访问文件的请求时就要对比进程的uid、gid与文件的访问模式位,由此决定该进程 是否有对文件的操作权限。uid为零的用户为超级用户,可以对任何资源进行管理,当然这也导致了系统安全的不完备性。
判定一个进程是否有对某个文件有某种访问的主要工作是由fs/namei.c中的permission函数决定的,具体的实现方式如下,其中的mask参数是所要求的访问方式位的标志位:
int permission(struct inode *inode, int mask, struct nameidata *nd){ umode_t mode = inode->i_mode;int retval, submask;if(mask & MAY_WRITE){ //假如加载的文件系统是只读的就不允许写,比如是磁盘设备
if(IS_RDONLY(inode)&&(S_ISREG(mode)|| S_ISDIR(mode)|| S_ISLNK(mode)))
return-EROFS;//假如加载的文件系统是不可变的就不允许写
if(IS_IMMUTABLE(inode))return-EACCES;} //是否满足可执行
if((mask & MAY_EXEC)&& S_ISREG(mode)&&(!(mode & S_IXUGO)||(nd && nd->mnt &&(nd->mnt->mnt_flags & MNT_NOEXEC))))return-EACCES;submask = mask & ~MAY_APPEND;//返回适应的权限位
if(inode->i_op && inode->i_op->permission)//交给了具体文件系统实现,比如说ext3文件系统
retval = inode->i_op->permission(inode, submask, nd);else //如果当前进程的fsuid与文件uid相同要比对文件属主的权限,否则比对用户组
retval = generic_permission(inode, submask, NULL);if(retval)return retval;//返回适应的访问控制策略的权限位,比如说selinux return security_inode_permission(inode, mask, nd);}
第二篇:机票操作系统代码
操作系统代码
1,查询航班:AVH/紧跟输入城市段、日期(数字)、月份(英文)后回车查看。如果查询指定航空公司月份后加“/”再加航空公司代号。
2,订座:SD后紧跟序号计划预定仓位跟人数后回车。(如果显示JET代表待定航班)
3.人名:NM1后紧跟客人姓名,如果多个个客人,人名雨人名之间用数字1隔开(国际航班必须输入英文,中国人姓在前后加/,外国人名在前)
4,联系方式:CT后输入联系电话
5,预留时间:TKTL/后跟几点/日期月份BJS…(代码)
6,封口:@IK(封口号码为5位数字)
7,提记录:RT后紧跟封口号码
8,取消订票:XEPNR
9,价格查询:FD:城市段(只使用于国内查询)PAT:A 查国内税和价格
10:查询那些航空公司飞:SKPEK紧跟目的地
11,查询指定日期直达航班:AV:城市段/日期月份
12,查询经停点:IT:航班号/日期月份
13,查询航班经停的城市起降时间和机型:FF:航班号/日期月份(没有经停的不显示)14,查税(价格):QTE:/承运人(航空公司)(必须输入完行程封口或达到上面第二步),如果出来很多仓位,在输入XSFSQ后跟代表仓位代码的序号。(共享的航班不能查税)15, 查询学生机票的税和价格QTE:SD/航空公司
16,查询移民机票价:QTE:EM/航空公司
17,查询青年机票价格:QTE:ZZ/航空公司
18,OPE票的预定指令:SN:承运人---舱位---出发地与目的地
19,查询SPA价格的指令:NFAD:城市段/CA(只能用于国航联运协议的航空公司。国际段的查询)
20,查汇率:XS(空格跟FSC后跟币种代码/人民币(可以互换)
21,查代码代表城市:CD:跟城市代码
22,用姓名查找记录:RT/旅客姓的拼音/航班号/日月年
23,SK:城市段/日期 查询在特定周期内所有航班的信息,所显示的航班信息时间为指定时间的前后三天一周的时间
24,查看是否出票:提记录后,输入PG1回车,有票号证明已经出票完毕。
25,查询国际段航班价格指令:XSFSD(空格)行程/日期/航空公司,如果后加X,最便宜的会显示在最前面。
26,如果没有舱位需要候补舱位:SD后跟序号在跟舱位/LL后跟人数
CP全清屏I清上次屏PN下翻PB上翻PF最前页PG重新显示当前页PL最后页。Q值的计算方法:Q值乘以兑换率。(如果使用系统里票面价格的时候不用单独计算Q值,因为系统里的报价已经包含全部费用,如果使用促销价即不使用系统里显示的价格的时候要计算Q值再加税)
学生票:LH的Q舱位UA的V舱位 大部分情况下代表学生票
外航(例如:AC,UA,NW等)大部分是Q票面,(国际段的价格票面应该以做境外段的票务公司报出的价格为准)国航的价格看系统或大本政策。去往北美洲国航联运的比较AC,UA等转机的价格略高。去往欧洲的国航相对法航的要便宜,HU飞日本韩国便宜 去往东南亚国家南航便宜,北京去往韩国MU,北京到香港CZ便宜
第三篇:操作系统课程设计题目及代码
题目一
模拟操作系统设计
设计一个模拟操作系统管理程序,实现下列管理功能: 1.内存管理功能 2.文件管理功能 3.磁盘管理功能
题目二
虚拟存储器各页面置换算法的实现与比较 内 容:设计一个虚拟存储区和内存工作区,通过产生一个随机数的方法得到一个页面序列,假设内存给定的页面数由键盘输入,分别计算使用下述各方法时的内存命中率:
先进先出算法(FIFO)、最近最少使用算法(LRU)、最佳淘汰算法(OPT)、最少访问页面算法(LFU)等。
参考资料
题目二
资料
虚拟存储器各页面置换算法的实现与比较
1.实验目的
存储管理的主要功能之一是合理的分配空间。请求页式管理是一种常用的虚拟存储管理技术。
本实验的目的是通过请求页式存储管理中页面置换算法模拟设计,了解虚拟存储技术的特点,掌握请求页式存储管理的页面置换算法。2.实验内容
(1)通过随机数产生一个指令序列,共320条指令。指令的地址按下述原则生成: 1)50%的指令是顺序执行的;
2)25%的指令是均匀分布在前地址部分; 3)25%的指令是均匀分布在后地址部分; 具体的实施方法是:
1)在[0,319]的指令地址之间随机选取一起点m; 2)顺序执行一条指令,即执行地址为m+1的指令;
3)在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m'; 4)顺序执行一条指令,其地址为m'+1;
5)在后地址[m'+2,319]中随机选取一条指令并执行; 6)重复上述步骤1)-5),直到执行320次指令。(2)将指令序列变换成为页地址流 设:1)页面大小为1k;
2)用户内存容量为4页到32页; 3)用户虚存容量为32k; 在用户虚存中,按每k存放10条指令排列虚存地址,即320条指令在虚存中的存放方式为: 第0条-第9条指令为第0页(对应虚存地址为[0,9]); 第10条-第19条指令为第1页(对应虚存地址为[10,19]);
...第310条-第319条指令为第31页(对应虚存地址为[310,319]);
按以上方式,用户指令可组成为32页。
(3)计算并输出下列各种算法在不同内存容量下的命中率。1)先进先出的算法(FIFO); 2)最近最少使用算法(LRR);3)最佳淘汰算法(OPT):先淘汰最不常用的页地址; 4)最少访问页面算法(LF.U); 5)最近最不经常使用算法(NUR)。其中3)和4)为选择内容。命中率=1-页面失效次数/页地址流长度
在本实验中,页地址流长度为320,页面失效次数为每次访问相应指令时,该指令所对应的页不在内存的次数。3.随机数产生办法
关于随机数产生办法,Linux或Unix系统提供函数srand()和rand(),分别进行初始化和产生随机数。例如: srand();
语句可初始化一个随机数; a[0]=10*rand()/32767*319+1;a[1]=10*rand()/32767*a[0];
..语句可用来产生a[0]与a[1]中的随机数。
提示:
首先用Srand()和rand()函数定义和产生指令序列,然后将指令序列变换成相应的页地址流,并针对不同的算法计算出相应的命中率。
命中率=1-页面失效次数/页地址流长度
1、数据结构
(1)页面类型 typedef struct{
int pn,pfn,counter,time;}pl-type;
其中pn为页号,pfn为页面号,count为一个周期内访问该页面的次数,time为访问时间。
(2)页面控制结构 pfc_struct{
int pn,pfn;
struct pfc_struct *next;
};typedef struct
pfc_struct pfc_type;pfc_type
pfc[total_vp],*freepf_head,*busypf_head;pfc_type *busypf_tail;其中,pfc[total_vp]定义用户进程虚页控制结构,*freepf_head为空页面头的指针,*busypf_head为忙页面头的指针,*busyf_tail为忙页面尾的指针。
2、函数定义
(1)Void initialize():初始化函数,给每个相关的页面赋值。(2)Void FIFO():计算使用FIFO算法时的命中率。(2)Void LRU():计算使用FIFO算法时的命中率。(4)VoidOPT():计算使用OPT算法时的命中率。(5)Void LFU():计算使用LFU算法时的命中率。(6)Void
NUR():计算使用NUR算法时的命中率。
3、变量定义
(1)int a[tatal_instruction] :指令流数据组。
(2)int page[total_instruction]:每条指令所属页号。
(3)int offset[total_instruction]:每页装入不敷出0条指令后取模运算页号偏移量。(4)int total_pf:用户进程的内存页面数。(5)int diseffect:页面失效次数。
程序清单
程序: 程序: #include “stdio.h” #include “process.h” #include “stdlib.h” #define TRUE 1 #define FALSE 0 #define INVALID-1 #define null 0 #define total_instruction 320 /*指令流长*/ #define total_vp 32 /*虚页长*/ #define clear_period 50 /*清0周期*/ typedef struct { int pn,pfn,counter,time;}pl_type;pl_type pl[total_vp];/*页面数据结构*/ struct pfc_struct{ /*页面控制结构*/ int pn,pfn;struct pfc_struct *next;};typedef struct pfc_struct pfc_type;pfc_type pfc[total_vp],*freepf_head,*busypf_head,*busypf_tail;int diseffect,a[total_instruction];int page[total_instruction],offset[total_instruction];void initialize();void FIFO();void LRU();void OPT();void LFU();void NUR();main(){ int S,i,j;srand(getpid()*10);/*由于每次运行时进程号不同,故可用来作为初始化随机数队
列的种子*/ S=(float)319*rand()/32767+1;for(i=0;i
busypf_head=busypf_tail=freepf_head;else {busypf_tail->next=freepf_head;busypf_tail=freepf_head;} freepf_head=p;} } printf(“FIFO:%6.4”,1-(float)diseffect/320);} void LRU(total_pf)/*LRU*/ int total_pf;{ int min,minj,i,j,present_time;initialize(total_pf);present_time=0;for(i=0;i 1、操作系统实验教程 张丽芬编著 清华大学出版社 2、操作系统原理实验教程(基于Linux)胡峰松编 清华大学出版社 1.Struts2架构图和请求处理流程 请求首先通过Filter chain,Filter主要包括ActionContextCleanUp,它主要清理当前线程的ActionContext和Dispatcher;FilterDispatcher主要通过AcionMapper来决定需要调用哪个Action。 ActionMapper取得了ActionMapping后,在Dispatcher的serviceAction方法里创建ActionProxy,ActionProxy创建ActionInvocation,然后ActionInvocation调用Interceptors,执行Action本身,创建Result并返回,当然,如果要在返回之前做些什么,可以实现PreResultListener。 2.Struts2部分类介绍 这部分从Struts2参考文档中翻译就可以了。 ActionMapper ActionMapper其实是HttpServletRequest和Action调用请求的一个映射,它屏蔽了Action对于Request等java Servlet类的依赖。Struts2中它的默认实现类是DefaultActionMapper,ActionMapper很大的用处可以根据自己的需要来设计url格式,它自己也有Restful的实现,具体可以参考文档的docs¥actionmapper.html。 ActionProxy&ActionInvocation Action的一个代理,由ActionProxyFactory创建,它本身不包括Action实例,默认实现DefaultActionProxy是由ActionInvocation持有Action实例。ActionProxy作用是如何取得Action,无论是本地还是远程。而ActionInvocation的作用是如何执行Action,拦截器的功能就是在ActionInvocation中实现的。 ConfigurationProvider&Configuration ConfigurationProvider就是Struts2中配置文件的解析器,Struts2中的配置文件主要是尤其实现类XmlConfigurationProvider及其子类StrutsXmlConfigurationProvider来解析。 3.Struts2请求流程 1、客户端发送请求 2、请求先通过ActionContextCleanUp-->FilterDispatcher 3、FilterDispatcher通过ActionMapper来决定这个Request需要调用哪个Action 4、如果ActionMapper决定调用某个Action,FilterDispatcher把请求的处理交给ActionProxy,这儿已经转到它的Delegate--Dispatcher来执行 5、ActionProxy根据ActionMapping和ConfigurationManager找到需要调用的Action类 6、ActionProxy创建一个ActionInvocation的实例 7、ActionInvocation调用真正的Action,当然这涉及到相关拦截器的调用 8、Action执行完毕,ActionInvocation创建Result并返回,当然,如果要在返回之前做些什么,可以实现PreResultListener。添加PreResultListener可以在Interceptor中实现。 首先强调一下struts2的线程程安全,在Struts2中大量采用ThreadLocal线程局部变量的方法来保证线程的安全,像Dispatcher等都是通过ThreadLocal来保存变量值,使得每个线程都有自己独立的实例变量,互不相干.接下来就从Dispatcher开始看起,先看其构造函数: //创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方 public Dispatcher(ServletContext servletContext, Map this.servletContext = servletContext; //配置在web.xml中的param参数 this.initParams = initParams; } //创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方 public Dispatcher(ServletContext servletContext, Map this.servletContext = servletContext; //配置在web.xml中的param参数 this.initParams = initParams; } 我们再看在FilterDispatcher创建Dispatcher的: protected Dispatcher createDispatcher(FilterConfig filterConfig){ Map for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){ String name =(String)e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } 都可以从FilterConfig中得到 return new Dispatcher(filterConfig.getServletContext(), params); } protected Dispatcher createDispatcher(FilterConfig filterConfig){ Map for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){ String name =(String)e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } 都可以从FilterConfig中得到 return new Dispatcher(filterConfig.getServletContext(), params); } 分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties() 创建Dispatcher之后,来看init()方法 init()方法是用来Load用户配置文件,资源文件以及默认的配置文件.主要分七步走,看下面注释 public void init(){ if(configurationManager == null){ //设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等 configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } //读取properties信息,默认的default.properties,init_DefaultProperties();// [1] //读取xml配置文件 init_TraditionalXmlConfigurations();// [2] //读取用户自定义的struts.properties init_LegacyStrutsProperties();// [3] //自定义的configProviders init_CustomConfigurationProviders();// [5] //载入FilterDispatcher传进来的initParams init_FilterInitParameters();// [6] //将配置文件中的bean与具体的类映射 init_AliasStandardObjects();// [7] //构建一个用于依赖注射的Container对象 //在这里面会循环调用上面七个ConfigurationProvider的register方法 //其中的重点就是DefaultConfiguration的#reload()方法 Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if(!dispatcherListeners.isEmpty()){ for(DispatcherListener l : dispatcherListeners){ l.dispatcherInitialized(this); } } } public void init(){ if(configurationManager == null){ //设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等 configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } //读取properties信息,默认的default.properties,init_DefaultProperties();// [1] //读取xml配置文件 init_TraditionalXmlConfigurations();// [2] //读取用户自定义的struts.properties init_LegacyStrutsProperties();// [3] //自定义的configProviders init_CustomConfigurationProviders();// [5] //载入FilterDispatcher传进来的initParams init_FilterInitParameters();// [6] //将配置文件中的bean与具体的类映射 init_AliasStandardObjects();// [7] //构建一个用于依赖注射的Container对象 //在这里面会循环调用上面七个ConfigurationProvider的register方法 //其中的重点就是DefaultConfiguration的#reload()方法 Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if(!dispatcherListeners.isEmpty()){ for(DispatcherListener l : dispatcherListeners){ l.dispatcherInitialized(this); } } } 分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties() private void init_DefaultProperties(){ configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); } 接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了register()方法 public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { Settings defaultSettings = null; try { defaultSettings = new PropertiesSettings(“org/apache/struts2/default”); } catch(Exception e){ throw } loadSettings(props, defaultSettings); } private void init_DefaultProperties(){ configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); } 接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了register()方法 public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { Settings defaultSettings = null; try { defaultSettings = new PropertiesSettings(“org/apache/struts2/default”); } catch(Exception e){ new ConfigurationException(“Could not find or error in org/apache/struts2/default.properties”, e); throw } new ConfigurationException(“Could not find or error in org/apache/struts2/default.properties”, e); loadSettings(props, defaultSettings); } //PropertiesSettings构造方法 //读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写 public PropertiesSettings(String name){ URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass()); if(settingsUrl == null){ LOG.debug(name + “.properties missing”); settings = new LocatableProperties(); return; } settings // Load settings InputStream in = null; try { in = settingsUrl.openStream(); settings.load(in); } catch(IOException e){ throw new StrutsException(“Could not load ” + name + “.properties:” + e, e); } finally { if(in!= null){ try { = new LocatableProperties(new LocationImpl(null, settingsUrl.toString())); in.close(); } catch(IOException io){ LOG.warn(“Unable to close input stream”, io); } } } } //loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props //这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){ // We are calling the impl methods to get around the single instance of Settings that is expected for(Iterator i = settings.listImpl();i.hasNext();){ String name =(String)i.next(); props.setProperty(name, settings.getLocationImpl(name)); } } //PropertiesSettings构造方法 //读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写 public PropertiesSettings(String name){ URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass()); if(settingsUrl == null){ LOG.debug(name + “.properties missing”); settings = new LocatableProperties(); return; } settings = new LocatableProperties(new LocationImpl(null, settingsUrl.toString())); settings.getImpl(name),// Load settings InputStream in = null; try { in = settingsUrl.openStream(); settings.load(in); } catch(IOException e){ throw new StrutsException(“Could not load ” + name + “.properties:” + e, e); } finally { if(in!= null){ try { in.close(); } catch(IOException io){ LOG.warn(“Unable to close input stream”, io); } } } } //loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props //这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){ // We are calling the impl methods to get around the single instance of Settings that is expected for(Iterator i = settings.listImpl();i.hasNext();){ String name =(String)i.next(); props.setProperty(name, settings.getLocationImpl(name)); } } 再来看第二步:init_TraditionalXmlConfigurations() private void init_TraditionalXmlConfigurations(){ settings.getImpl(name), //首先读取web.xml中的config初始参数值 //如果 没 有 配 置 就 使 用 默 认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了 //如果不想使用默认的名称,直接在web.xml中配置config初始参数即可 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)){ //XmlConfigurationProvider负责解析xwork.xml configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false)); } else { //其它xml都是由StrutsXmlConfigurationProvider来解析 configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException(“Invalid configuration file name”); } } } private void init_TraditionalXmlConfigurations(){ //首先读取web.xml中的config初始参数值 //如果 没 有 配 置 就 使 用 默 认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了 //如果不想使用默认的名称,直接在web.xml中配置config初始参数即可 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)){ //XmlConfigurationProvider负责解析xwork.xml configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false)); } else { //其它xml都是由StrutsXmlConfigurationProvider来解析 configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException(“Invalid configuration file name”); } } } 对于其它配置文件只用接口。 类XmlConfigurationProvider负责配置文件的读取和解析,首先通过init()中的loadDocuments(configFileName);利用DomHelper中的 public static Document parse(InputSource inputSource, Map addAction()方法负责读取 loadInterceptorStack()方法负责将 StrutsXmlConfigurationProvider,此类继承XmlConfigurationProvider,而XmlConfigurationProvider又实现ConfigurationProviderloadInterceptorStacks()方法负责将 而上面的方法最终会被addPackage()方法调用,addPackage又会被Provider的loadPackages()调用,将所读取到的数据汇集到PackageConfig对象中。 protected PackageConfig addPackage(Element packageElement) throws ConfigurationException { PackageConfig.Builder newPackage = buildPackageContext(packageElement); if(newPackage.isNeedsRefresh()){ return newPackage.build(); } // add result types(and default result)to this package addResultTypes(newPackage, packageElement); // load the interceptors and interceptor stacks for this package loadInterceptors(newPackage, packageElement); // load the default interceptor reference for this package loadDefaultInterceptorRef(newPackage, packageElement); // load the default class ref for this package loadDefaultClassRef(newPackage, packageElement); // load the global result list for this package loadGlobalResults(newPackage, packageElement); // load the global exception handler list for this package loadGobalExceptionMappings(newPackage, packageElement); // get actions NodeList actionList = packageElement.getElementsByTagName(“action”); for(int i = 0;i < actionList.getLength();i++){ Element actionElement =(Element)actionList.item(i); addAction(actionElement, newPackage); } // load the default action reference for this package loadDefaultActionRef(newPackage, packageElement); PackageConfig cfg = newPackage.build(); configuration.addPackageConfig(cfg.getName(), cfg); return cfg; } loadConfigurationFiles解析读取xml中的内容 private List loadConfigurationFiles(String fileName,Element includeElement){ ...//通过DomHelper调用SAX进行解析xml doc = DomHelper.parse(in, dtdMappings); ...Element rootElement = doc.getDocumentElement(); NodeList children = rootElement.getChildNodes(); int childSize = children.getLength(); for(int i = 0;i < childSize;i++){ Node childNode = children.item(i); if(childNode instanceof Element){ Element child =(Element)childNode; final String nodeName = child.getNodeName(); if(“include”.equals(nodeName)){ String includeFileName = child.getAttribute(“file”); //解析每个action配置是,对于include文件可以使用通配符*来进行配置 //如Struts.xml中可配置成 if(includeFileName.indexOf('*')!=-1){ ClassPathFinder wildcardFinder = new ClassPathFinder(); wildcardFinder.setPattern(includeFileName); Vector for(String match : wildcardMatches){ //递归Load子file中的 docs.addAll(loadConfigurationFiles(match, child)); } } else { docs.addAll(loadConfigurationFiles(includeFileName, child)); } } } } docs.add(doc); loadedFileUrls.add(url.toString()); ...return docs; } 首先强调一下struts2的线程程安全,在Struts2中大量采用ThreadLocal线程局部变量的方法来保证线程的安全,像Dispatcher等都是通过ThreadLocal来保存变量值,使得每个线程都有自己独立的实例变量,互不相干.接下来就从Dispatcher开始看起,先看其构造函数: //创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方 public Dispatcher(ServletContext servletContext, Map this.servletContext = servletContext; //配置在web.xml中的param参数 this.initParams = initParams; } //创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方 public Dispatcher(ServletContext servletContext, Map this.servletContext = servletContext; //配置在web.xml中的param参数 this.initParams = initParams; } 我们再看在FilterDispatcher创建Dispatcher的: protected Dispatcher createDispatcher(FilterConfig filterConfig){ Map for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){ String name =(String)e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } 都可以从FilterConfig中得到 return new Dispatcher(filterConfig.getServletContext(), params); } protected Dispatcher createDispatcher(FilterConfig filterConfig){ Map for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){ String name =(String)e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } 都可以从FilterConfig中得到 return new Dispatcher(filterConfig.getServletContext(), params); } 分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties() 创建Dispatcher之后,来看init()方法 init()方法是用来Load用户配置文件,资源文件以及默认的配置文件.主要分七步走,看下面注释 public void init(){ if(configurationManager == null){ //设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等 configurationManager = ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } //读取properties信息,默认的default.properties,init_DefaultProperties();// [1] //读取xml配置文件 init_TraditionalXmlConfigurations();// [2] //读取用户自定义的struts.properties init_LegacyStrutsProperties();// [3] //自定义的configProviders init_CustomConfigurationProviders();// [5] //载入FilterDispatcher传进来的initParams init_FilterInitParameters();// [6] //将配置文件中的bean与具体的类映射 init_AliasStandardObjects();// [7] //构建一个用于依赖注射的Container对象 //在这里面会循环调用上面七个ConfigurationProvider的register方法 //其中的重点就是DefaultConfiguration的#reload()方法 Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if(!dispatcherListeners.isEmpty()){ for(DispatcherListener l : dispatcherListeners){ l.dispatcherInitialized(this); } } new } public void init(){ if(configurationManager == null){ //设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等 configurationManager = ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } //读取properties信息,默认的default.properties,init_DefaultProperties();// [1] //读取xml配置文件 init_TraditionalXmlConfigurations();// [2] //读取用户自定义的struts.properties init_LegacyStrutsProperties();// [3] //自定义的configProviders init_CustomConfigurationProviders();// [5] //载入FilterDispatcher传进来的initParams init_FilterInitParameters();// [6] //将配置文件中的bean与具体的类映射 init_AliasStandardObjects();// [7] //构建一个用于依赖注射的Container对象 //在这里面会循环调用上面七个ConfigurationProvider的register方法 //其中的重点就是DefaultConfiguration的#reload()方法 Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if(!dispatcherListeners.isEmpty()){ for(DispatcherListener l : dispatcherListeners){ l.dispatcherInitialized(this); } } new } 分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties() private void init_DefaultProperties(){ configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); } 接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了register()方法 public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { Settings defaultSettings = null; try { defaultSettings = new PropertiesSettings(“org/apache/struts2/default”); } catch(Exception e){ throw } loadSettings(props, defaultSettings); } private void init_DefaultProperties(){ configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); } 接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了new ConfigurationException(“Could not find or error in org/apache/struts2/default.properties”, e); register()方法 public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { Settings defaultSettings = null; try { defaultSettings = new PropertiesSettings(“org/apache/struts2/default”); } catch(Exception e){ throw } loadSettings(props, defaultSettings); } //PropertiesSettings构造方法 //读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写 public PropertiesSettings(String name){ URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass()); if(settingsUrl == null){ LOG.debug(name + “.properties missing”); settings = new LocatableProperties(); return; } settings // Load settings InputStream in = null; try { = new LocatableProperties(new LocationImpl(null, settingsUrl.toString())); new ConfigurationException(“Could not find or error in org/apache/struts2/default.properties”, e); in = settingsUrl.openStream(); settings.load(in); } catch(IOException e){ throw new StrutsException(“Could not load ” + name + “.properties:” + e, e); } finally { if(in!= null){ try { in.close(); } catch(IOException io){ LOG.warn(“Unable to close input stream”, io); } } } } //loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props //这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){ // We are calling the impl methods to get around the single instance of Settings that is expected for(Iterator i = settings.listImpl();i.hasNext();){ String name =(String)i.next(); props.setProperty(name, settings.getLocationImpl(name)); } } //PropertiesSettings构造方法 //读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写 public PropertiesSettings(String name){ URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass()); settings.getImpl(name),if(settingsUrl == null){ LOG.debug(name + “.properties missing”); settings = new LocatableProperties(); return; } settings // Load settings InputStream in = null; try { in = settingsUrl.openStream(); settings.load(in); } catch(IOException e){ throw new StrutsException(“Could not load ” + name + “.properties:” + e, e); } finally { if(in!= null){ try { in.close(); } catch(IOException io){ LOG.warn(“Unable to close input stream”, io); } } } } //loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props //这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){ // We are calling the impl methods to get around the single instance of Settings that is expected for(Iterator i = settings.listImpl();i.hasNext();){ String name =(String)i.next(); = new LocatableProperties(new LocationImpl(null, settingsUrl.toString())); props.setProperty(name, settings.getLocationImpl(name)); } } 再来看第二步:init_TraditionalXmlConfigurations() private void init_TraditionalXmlConfigurations(){ //首先读取web.xml中的config初始参数值 //如果 没 有 配 置 就 使 settings.getImpl(name),用默认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了 //如果不想使用默认的名称,直接在web.xml中配置config初始参数即可 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)){ //XmlConfigurationProvider负责解析xwork.xml configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false)); } else { //其它xml都是由StrutsXmlConfigurationProvider来解析 configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException(“Invalid configuration file name”); } } } private void init_TraditionalXmlConfigurations(){ //首先读取web.xml中的config初始参数值 //如果 没 有 配 置 就 使 用 默 认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了 //如果不想使用默认的名称,直接在web.xml中配置config初始参数即可 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)){ //XmlConfigurationProvider负责解析xwork.xml configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false)); } else { //其它xml都是由StrutsXmlConfigurationProvider来解析 configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException(“Invalid configuration file name”); } } } 对于其它配置文件只用接口。 类XmlConfigurationProvider负责配置文件的读取和解析,首先通过init()中的loadDocuments(configFileName);利用DomHelper中的 public static Document parse(InputSource inputSource, Map addAction()方法负责读取 loadInterceptorStack()方法负责将 loadInterceptorStacks()方法负责将 而上面的方法最终会被addPackage()方法调用,addPackage又会被Provider的loadPackages()调用,将所读取到的数据汇集到PackageConfig对象中。 protected PackageConfig addPackage(Element packageElement) throws ConfigurationException { PackageConfig.Builder newPackage = buildPackageContext(packageElement); if(newPackage.isNeedsRefresh()){ return newPackage.build(); } // add result types(and default result)to this package addResultTypes(newPackage, packageElement); // load the interceptors and interceptor stacks for this package loadInterceptors(newPackage, packageElement); // load the default interceptor reference for this package loadDefaultInterceptorRef(newPackage, packageElement); // load the default class ref for this package loadDefaultClassRef(newPackage, packageElement); // load the global result list for this package loadGlobalResults(newPackage, packageElement); // load the global exception handler list for this package loadGobalExceptionMappings(newPackage, packageElement); // get actions NodeList actionList = packageElement.getElementsByTagName(“action”); for(int i = 0;i < actionList.getLength();i++){ Element actionElement =(Element)actionList.item(i); addAction(actionElement, newPackage); } // load the default action reference for this package loadDefaultActionRef(newPackage, packageElement); PackageConfig cfg = newPackage.build(); configuration.addPackageConfig(cfg.getName(), cfg); return cfg; } loadConfigurationFiles解析读取xml中的内容 private List loadConfigurationFiles(String fileName, includeElement){ ...//通过DomHelper调用SAX进行解析xml doc = DomHelper.parse(in, dtdMappings); ...Element rootElement = doc.getDocumentElement(); NodeList children = rootElement.getChildNodes(); int childSize = children.getLength(); for(int i = 0;i < childSize;i++){ Node childNode = children.item(i); if(childNode instanceof Element){ Element child =(Element)childNode; final String nodeName = child.getNodeName(); if(“include”.equals(nodeName)){ String includeFileName = child.getAttribute(“file”); //解析每个action配置是,对于include文件可以使用通配符*来进行配置 //如Struts.xml中可配置成 if(includeFileName.indexOf('*')!=-1){ ClassPathFinder wildcardFinder = new ClassPathFinder(); wildcardFinder.setPattern(includeFileName); Element Vector for(String match : wildcardMatches){ //递归Load子file中的 docs.addAll(loadConfigurationFiles(match, child)); } } else { docs.addAll(loadConfigurationFiles(includeFileName, child)); } } } } docs.add(doc); loadedFileUrls.add(url.toString()); ...return docs; } 接下来第三步:init_LegacyStrutsProperties()调用的是调用的是LegacyPropertiesConfigurationProvider 通过比较前 面 DefaultPropertiesProvider 与 调 用的是LegacyPropertiesConfigurationProvider.发现DefaultPropertiesProvider继承自后者,但重写了register()方法,主要是生成PropertiesSetting的不同,前者是根据org/apache/struts2/default.properties 后者是根据struts.properties 我们展开register()中的Settings.getInstance(),最后是调用getDefaultInstance() private static Settings getDefaultInstance(){ if(defaultImpl == null){ // Create bootstrap implementation //不带参数的DefaultSettings(),区别与DefaultPropertiesProvider中直接带default.properties参数 //不带参数就是默认为struts.propertes,并且加载struts.custom.properties所定义的properties文件 defaultImpl = new DefaultSettings(); // Create default implementation try { //STRUTS_CONFIGURATION为:struts.configuration //在struts.proterties中查找struts.configuration的值,这个值必须是org.apache.struts2.config.Configuration接口的实现类 //所以我有个困惑就是在下面的转换当中怎么将Configuration转换成Setting类型的...//这一点先放下了,有时间再研究 String className = get(StrutsConstants.STRUTS_CONFIGURATION); if(!className.equals(defaultImpl.getClass().getName())){ try { // singleton instances shouldn't be built accessing request or session-specific context data defaultImpl oader().loadClass(className), null); } catch(Exception e){ LOG.error(“Settings: } } } catch(IllegalArgumentException ex){ // ignore } private static Settings getDefaultInstance(){ if(defaultImpl == null){ // Create bootstrap implementation //不带参数的DefaultSettings(),区别与DefaultPropertiesProvider中直接带default.properties参数 //不带参数就是默认为struts.propertes,并且加载struts.custom.properties所定义的properties文件 defaultImpl = new DefaultSettings(); // Create default implementation try { //STRUTS_CONFIGURATION为:struts.configuration //在struts.proterties中查找struts.configuration的值,这个值必须是 Could not instantiate the struts.configuration object, substituting the default implementation.”, e); = (Settings)ObjectFactory.getObjectFactory().buildBean(Thread.currentThread().getContextClassLorg.apache.struts2.config.Configuration接口的实现类 //所以我有个困惑就是在下面的转换当中怎么将Configuration转换成Setting类型的...//这一点先放下了,有时间再研究 String className = get(StrutsConstants.STRUTS_CONFIGURATION); if(!className.equals(defaultImpl.getClass().getName())){ try { // singleton instances shouldn't be built accessing request or session-specific context data defaultImpl oader().loadClass(className), null); } catch(Exception e){ LOG.error(“Settings: } } } catch(IllegalArgumentException ex){ // ignore } 在2.1.6中去掉了第四步:init_ZeroConfiguration();第五步是自定义的configProviders private void init_CustomConfigurationProviders(){ //从这里可以看到可以将自定义的Provider定义在web.xml中FilterDispatcher的param中:configProviders 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)cls.newInstance(); configurationManager.addConfigurationProvider(prov); prov = Could not instantiate the struts.configuration object, substituting the default implementation.”, e); = (Settings)ObjectFactory.getObjectFactory().buildBean(Thread.currentThread().getContextClassL } ...} } } private void init_CustomConfigurationProviders(){ //从这里可以看到可以将自定义的Provider定义在web.xml中FilterDispatcher的param中:configProviders 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)cls.newInstance(); configurationManager.addConfigurationProvider(prov); } ...} } } 第六步:init_FilterInitParameters //从这里可以看出struts.properties中的属性不仅可以在struts.xml中以constant形式定义,而且可以在FilterDispatcher的param中定义 private void init_FilterInitParameters(){ configurationManager.addConfigurationProvider(new ConfigurationProvider(){ public void destroy(){} public void init(Configuration configuration) throws ConfigurationException {} public void loadPackages()throws ConfigurationException {} public boolean needsReload(){ return false;} prov = public void register(ContainerBuilder builder, LocatableProperties props)throws ConfigurationException { props.putAll(initParams);//在这里实现滴~ } }); } //从这里可以看出struts.properties中的属性不仅可以在struts.xml中以constant形式定义,而且可以在FilterDispatcher的param中定义 private void init_FilterInitParameters(){ configurationManager.addConfigurationProvider(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);//在这里实现滴~ } }); } 第七步:init_AliasStandardObjects,使用BeanSelectionProvider 这是将配置文件中定义的 接下来是看怎样调用这些ConfigurationProviders 展开init_PreloadConfiguration() private Container init_PreloadConfiguration(){ Configuration config = configurationManager.getConfiguration(); Container container = config.getContainer(); boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); LocalizedTextUtil.setReloadBundles(reloadi18n); return container; } //再看getConfiguration() public synchronized Configuration getConfiguration(){ if(configuration == null){ setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName)); try { //重点就是这个reloadContainer configuration.reloadContainer(getContainerProviders()); } catch(ConfigurationException e){ setConfiguration(null); throw new ConfigurationException(“Unable to load configuration.”, e); } } else { conditionalReload(); } return configuration; } private Container init_PreloadConfiguration(){ Configuration config = configurationManager.getConfiguration(); Container container = config.getContainer(); boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); LocalizedTextUtil.setReloadBundles(reloadi18n); return container; } //再看getConfiguration() public synchronized Configuration getConfiguration(){ if(configuration == null){ setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName)); try { //重点就是这个reloadContainer configuration.reloadContainer(getContainerProviders()); } catch(ConfigurationException e){ setConfiguration(null); throw new ConfigurationException(“Unable to load configuration.”, e); } } else { conditionalReload(); } return configuration; } 展开DefaultConfiguration中的reloadContainer public synchronized List reloadContainer(List packageContexts.clear(); loadedFileNames.clear(); List packageProviders = new ArrayList (); //Struts2(xwork2)用Container来完成依赖注入的功能 //首先初始化一个ContainerBuilder,再由builder来保存接口与实现类或工厂类的对应关系 //然后通过builder.create(boolean)方法产生container //由container.getInstance(Class);就可以得到接口的实现实例了 //这一部分比较复杂,后面研究完成了,会单独拿出来讲,这里先弄清楚Xwork依赖注入的实现步骤就可以了 ContainerProperties props = new ContainerProperties(); ContainerBuilder builder = new ContainerBuilder(); for(final ContainerProvider containerProvider : providers) { //循环调用ConfigurationProvider的init和register方法,明白了吧,在这里统一循环调用 containerProvider.init(this); containerProvider.register(builder, props); } props.setConstants(builder); //注入依赖关系,在这里并不产生实例 builder.factory(Configuration.class, new Factory 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); //create已经注入依赖关系的Container container = builder.create(false); setContext(container); objectFactory = container.getInstance(ObjectFactory.class); // Process the configuration providers first for(final ContainerProvider containerProvider : providers) { if(containerProvider instanceof PackageProvider){ container.inject(containerProvider); //调用PackageProvider的loadPackages()方法,这里主要是针对XmlConfigurationProvider和StrutsXmlConfigurationProvider ((PackageProvider)containerProvider).loadPackages(); packageProviders.add((PackageProvider)containerProvider); } } // Then process any package providers from the plugins Set packageProviderNames = container.getInstanceNames(PackageProvider.class); if(packageProviderNames!= null){ for(String name : packageProviderNames){ PackageProvider provider.init(this); provider.loadPackages(); packageProviders.add(provider); } } rebuildRuntimeConfiguration(); } finally { if(oldContext == null){ ActionContext.setContext(null); } } return packageProviders; } Dispatcher已经在之前讲过,这就好办了。FilterDispatcher是Struts2的核心控制器,首先看一下init()方法。 public void init(FilterConfig filterConfig)throws ServletException { try { this.filterConfig = filterConfig; initLogging(); //创建dispatcher,前面都已经讲过啰 dispatcher = createDispatcher(filterConfig); dispatcher.init(); //注入将FilterDispatcher中的变量通过container注入,如下面的staticResourceLoader dispatcher.getContainer().inject(this); //StaticContentLoader在BeanSelectionProvider中已经被注入了依赖关系:DefaultStaticContentLoader //可以在struts-default.xml中的 staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig)); } finally { provider = container.getInstance(PackageProvider.class, name); ActionContext.setContext(null); } } public void init(FilterConfig filterConfig)throws ServletException { try { this.filterConfig = filterConfig; initLogging(); //创建dispatcher,前面都已经讲过啰 dispatcher = createDispatcher(filterConfig); dispatcher.init(); //注入将FilterDispatcher中的变量通过container注入,如下面的staticResourceLoader dispatcher.getContainer().inject(this); //StaticContentLoader在BeanSelectionProvider中已经被注入了依赖关系:DefaultStaticContentLoader //可以在struts-default.xml中的 staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig)); } finally { ActionContext.setContext(null); } } //下面来看DefaultStaticContentLoader的setHostConfig public void setHostConfig(HostConfig filterConfig){ //读取初始参数 pakages,调用 parse(),解析成类似/org/apache/struts2/static,/template的数组 String param = filterConfig.getInitParameter(“packages”); //“org.apache.struts2.static org.apache.struts2.interceptor.debugging static” String packages = getAdditionalPackages(); if(param!= null){ packages = param + “ ” + packages; } this.pathPrefixes = parse(packages); initLogging(filterConfig); } template //下面来看DefaultStaticContentLoader的setHostConfig public void setHostConfig(HostConfig filterConfig){ //读取初始参数 pakages,调用 parse(),解析成类似/org/apache/struts2/static,/template的数组 String param = filterConfig.getInitParameter(“packages”); //“org.apache.struts2.static org.apache.struts2.interceptor.debugging static” String packages = getAdditionalPackages(); if(param!= null){ packages = param + “ ” + packages; } this.pathPrefixes = parse(packages); initLogging(filterConfig); } 现在回去doFilter的方法,每当有一个Request,都会调用这些Filters的doFilter方法 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException { HttpServletRequest request =(HttpServletRequest)req; HttpServletResponse response =(HttpServletResponse)res; ServletContext servletContext = getServletContext(); String timerKey = “FilterDispatcher_doFilter: ”; try { // FIXME: this should be refactored better to not duplicate work with the action invocation //先看看ValueStackFactory所注入的实现类OgnlValueStackFactory //new OgnlValueStack ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); ActionContext ctx = new ActionContext(stack.getContext()); ActionContext.setContext(ctx); template UtilTimerStack.push(timerKey); //如果是multipart/form-data就用MultiPartRequestWrapper进行包装 //MultiPartRequestWrapper 是 StrutsRequestWrapper的子类,两者都是HttpServletRequest实现 //此时在MultiPartRequestWrapper中就会把Files给解析出来,用于文件上传 //所有request都会StrutsRequestWrapper进行包装,StrutsRequestWrapper是可以访问ValueStack //下面是参见Dispatcher的wrapRequest // String content_type = request.getContentType(); //if(content_type!= null&&content_type.indexOf(“multipart/form-data”)!=-1){ //MultiPartRequest multi =getContainer().getInstance(MultiPartRequest.class); //request MultiPartRequestWrapper(multi,request,getSaveDir(servletContext)); //} else { // request = new StrutsRequestWrapper(request); // } request = prepareDispatcherAndWrapRequest(request, response); ActionMapping mapping; try { //根据url取得对应的Action的配置信息 //看一下注入的DefaultActionMapper的getMapping()方法.Action的配置信息存储在 ActionMapping对象中 mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); } catch(Exception ex){ log.error(“error getting ActionMapping”, ex); dispatcher.sendError(request,return; } //如果找不到对应的action配置,则直接返回。比如你输入***.jsp等等 //这儿有个例外,就是如果path是以“/struts”开头,则到初始参数packages配置的包路径去查找对应的静态资源并输出到页面流中,当然.class文件除外。如果再没有则跳转到 response,servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); =new 404 if(mapping == null){ // 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(); } if(staticResourceLoader.canHandle(resourcePath)){ // 在DefaultStaticContentLoader 中 :return serveStatic &&(resourcePath.startsWith(”/struts“)|| resourcePath.startsWith(”/static“)); staticResourceLoader.findStaticResource(resourcePath, response); } else { // this is a normal request, let it pass through chain.doFilter(request, response); } // The framework did its job here return; } //正式开始Action的方法 dispatcher.serviceAction(request, response, servletContext, mapping); } finally { try { ActionContextCleanUp.cleanUp(req); } finally { UtilTimerStack.pop(timerKey); } } } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException { HttpServletRequest request =(HttpServletRequest)req; request,HttpServletResponse response =(HttpServletResponse)res; ServletContext servletContext = getServletContext(); String timerKey = ”FilterDispatcher_doFilter: “; try { // FIXME: this should be refactored better to not duplicate work with the action invocation //先看看ValueStackFactory所注入的实现类OgnlValueStackFactory //new OgnlValueStack ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); ActionContext ctx = new ActionContext(stack.getContext()); ActionContext.setContext(ctx); UtilTimerStack.push(timerKey); //如果是multipart/form-data就用MultiPartRequestWrapper进行包装 //MultiPartRequestWrapperHttpServletRequest实现 //此时在MultiPartRequestWrapper中就会把Files给解析出来,用于文件上传 //所有request都会StrutsRequestWrapper进行包装,StrutsRequestWrapper是可以访问ValueStack //下面是参见Dispatcher的wrapRequest // String content_type = request.getContentType(); //if(content_type!= null&&content_type.indexOf(”multipart/form-data“)!=-1){ //MultiPartRequest multi =getContainer().getInstance(MultiPartRequest.class); //request MultiPartRequestWrapper(multi,request,getSaveDir(servletContext)); //} else { // request = new StrutsRequestWrapper(request); // } request = prepareDispatcherAndWrapRequest(request, response); ActionMapping mapping; try { =new 是 StrutsRequestWrapper的子类,两者都是 //根据url取得对应的Action的配置信息 //看一下注入的DefaultActionMapper的getMapping()方法.Action的配置信息存储在 ActionMapping对象中 mapping } catch(Exception ex){ log.error(”error getting ActionMapping“, ex); dispatcher.sendError(request,return; } //如果找不到对应的action配置,则直接返回。比如你输入***.jsp等等 //这儿有个例外,就是如果path是以“/struts”开头,则到初始参数packages配置的包路径去查找对应的静态资源并输出到页面流中,当然.class文件除外。如果再没有则跳转到404 if(mapping == null){ // 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(); } if(staticResourceLoader.canHandle(resourcePath)){ // 在DefaultStaticContentLoader 中 :return serveStatic &&(resourcePath.startsWith(”/struts“)|| resourcePath.startsWith(”/static“)); staticResourceLoader.findStaticResource(resourcePath, response); } else { // this is a normal request, let it pass through chain.doFilter(request, response); } // The framework did its job here return; } request,response,servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); //正式开始Action的方法 dispatcher.serviceAction(request, response, servletContext, mapping); } finally { try { ActionContextCleanUp.cleanUp(req); } finally { UtilTimerStack.pop(timerKey); } } } //下面是ActionMapper接口的实现类 DefaultActionMapper的getMapping()方法的源代码: public ActionMapping getMapping(HttpServletRequest request,ConfigurationManager configManager){ ActionMapping mapping = new ActionMapping(); String uri = getUri(request);//得到请求路径的URI,如:testAtcion.action或testAction.do int indexOfSemicolon = uri.indexOf(”;“);//修正url的带;jsessionid 时找不到而且的bug uri =(indexOfSemicolon >-1)? uri.substring(0, indexOfSemicolon): uri; uri = dropExtension(uri, mapping);//删除扩展名,默认扩展名为action if(uri == null){ return null; } parseNameAndNamespace(uri, mapping, configManager);//匹配Action的name和namespace handleSpecialParameters(request, mapping);//去掉重复参数 //如果Action的name没有解析出来,直接返回 if(mapping.getName()== null){ returnnull; } //下面处理形如testAction!method格式的请求路径 if(allowDynamicMethodCalls){ // handle ”name!method“ convention.String name = mapping.getName(); int exclamation = name.lastIndexOf(”!“);//!是Action名称和方法名的分隔符 if(exclamation!=-1){ mapping.setName(name.substring(0, exclamation));//提取左边为name mapping.setMethod(name.substring(exclamation + 1));//提取右边的method } } return mapping; } //下面是ActionMapper接口的实现类 DefaultActionMapper的getMapping()方法的源代码: public ActionMapping getMapping(HttpServletRequest request,ConfigurationManager configManager){ ActionMapping mapping = new ActionMapping(); String uri = getUri(request);//得到请求路径的URI,如:testAtcion.action或testAction.do int indexOfSemicolon = uri.indexOf(”;“);//修正url的带;jsessionid 时找不到而且的bug uri =(indexOfSemicolon >-1)? uri.substring(0, indexOfSemicolon): uri; uri = dropExtension(uri, mapping);//删除扩展名,默认扩展名为action if(uri == null){ return null; } parseNameAndNamespace(uri, mapping, configManager);//匹配Action的name和namespace handleSpecialParameters(request, mapping);//去掉重复参数 //如果Action的name没有解析出来,直接返回 if(mapping.getName()== null){ returnnull; } //下面处理形如testAction!method格式的请求路径 if(allowDynamicMethodCalls){ // handle ”name!method“ convention.String name = mapping.getName(); int exclamation = name.lastIndexOf(”!“);//!是Action名称和方法名的分隔符 if(exclamation!=-1){ mapping.setName(name.substring(0, exclamation));//提取左边为name mapping.setMethod(name.substring(exclamation + 1));//提取右边的method } } return mapping; } 从代码中看出,getMapping()方法返回ActionMapping类型的对象,该对象包含三个参数:Action的name、namespace和要调用的方法method。 如果getMapping()方法返回ActionMapping对象为null,则FilterDispatcher认为用户请求不是Action,自然另当别论,FilterDispatcher会做一件非常有意思的事:如果请求以/struts开头,会自动查找在web.xml文件中配置的 packages初始化参数,就像下面这样: org.apache.struts2.dispatcher.FilterDispatcher packages com.lizanhong.action org.apache.struts2.dispatcher.FilterDispatcher packages com.lizanhong.action FilterDispatcher会将com.lizanhong.action包下的文件当作静态资源处理,即直接在页面上显示文件内容,不过会忽略扩展名为class的文件。比如在com.lizanhong.action包下有一个aaa.txt的文本文件,其内容为“中华人民共和国”,访问 http://localhost:8081/Struts2Demo/struts/aaa.txt时会输出txt中的内容 FilterDispatcher.findStaticResource()方法 protectedvoid findStaticResource(String name,HttpServletRequest request, HttpServletResponse response)throws IOException { if(!name.endsWith(”.class“)){//忽略class文件 //遍历packages参数 for(String pathPrefix : pathPrefixes){ InputStream is = findInputStream(name, pathPrefix);//读取请求文件流 if(is!= null){ ...// set the content-type header String contentType = getContentType(name);//读取内容类型 if(contentType!= null){ response.setContentType(contentType);//重新设置内容类型 } ...try { //将读取到的文件流以每次复制4096个字节的方式循环输出 copy(is, response.getOutputStream()); } finally { is.close(); } return; } } } } protectedvoid findStaticResource(String name,HttpServletRequest request, HttpServletResponse response)throws IOException { if(!name.endsWith(”.class“)){//忽略class文件 //遍历packages参数 for(String pathPrefix : pathPrefixes){ InputStream is = findInputStream(name, pathPrefix);//读取请求文件流 if(is!= null){ ...// set the content-type header String contentType = getContentType(name);//读取内容类型 if(contentType!= null){ response.setContentType(contentType);//重新设置内容类型 } ...try { //将读取到的文件流以每次复制4096个字节的方式循环输出 copy(is, response.getOutputStream()); } finally { is.close(); } return; } } } } 如果用户请求的资源不是以/struts开头——可能是.jsp文件,也可能是.html文件,则通过过滤器链继续往下传送,直到到达请求的资源为止。 如果getMapping()方法返回有效的ActionMapping对象,则被认为正在请求某个Action,将调用 Dispatcher.serviceAction(request, response, servletContext, mapping)方法,该方法是处理Action的关键所在。 下面就来看serviceAction,这又回到全局变量dispatcher中了 //Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,ActionMapping mapping)throws ServletException { //createContextMap方法主要把Application、Session、Request的key value值拷贝到Map中 Map // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action ValueStack stack = (ValueStack)request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if(nullStack){ ActionContext ctx = ActionContext.getContext(); if(ctx!= null){ stack = ctx.getValueStack(); } } if(stack!= null){ extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = ”Handling request from Dispatcher“; try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); Configuration config = configurationManager.getConfiguration(); //创建一个Action的代理对象,ActionProxyFactory是创建ActionProxy的工厂 //参考实现类:DefaultActionProxy和DefaultActionProxyFactory ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! //如果是Result,则直接转向,关于Result,ActionProxy,ActionInvocation下一讲中再分析 if(mapping.getResult()!= null){ Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { //执行Action proxy.execute(); } // If there was a previous value stack then set it back onto the request if(!nullStack){ request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch(ConfigurationException e){ // WW-2874 Only log error if in devMode if(devMode){ LOG.error(”Could not find action or result“, e); } else { LOG.warn(”Could not find action or result“, e); } sendError(request, HttpServletResponse.SC_NOT_FOUND, e); } catch(Exception e){ sendError(request,} finally { UtilTimerStack.pop(timerKey); } } 下面开始讲一下主菜ActionProxy了.在这之前最好先去了解一下动态Proxy的基本知识.ActionProxy是Action的一个代理类,也就是说Action的调用是通过ActionProxy实现的,其实就是调用了ActionProxy.execute()方法,而该方法又调用了ActionInvocation.invoke()方法。归根到底,最后调用的是DefaultActionInvocation.invokeAction()方法。DefaultActionInvocation()->init()->createAction()。 最后 通 过 调 用ActionProxy.exute()-->ActionInvocation.invoke()-->Intercepter.intercept()-->ActionInvocation.invokeActionOnly()-->invokeAction()这里的步骤是先由ActionProxyFactory创建ActionInvocation和ActionProxy.public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map ActionInvocation inv = new DefaultActionInvocation(extraContext, true); container.inject(inv); return } public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map createActionProxy(inv,namespace,actionName,methodName, executeResult, cleanupContext); response,context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); response,context,ActionInvocation inv = new DefaultActionInvocation(extraContext, true); container.inject(inv); return } 下面先看DefaultActionInvocation的init方法 public void init(ActionProxy proxy){ this.proxy = proxy; Map // Setting this so that other classes, like object factories, can use the ActionProxy and other // contextual information to operate ActionContext actionContext = ActionContext.getContext(); if(actionContext!= null){ actionContext.setActionInvocation(this); } //创建Action,struts2中每一个Request都会创建一个新的Action createAction(contextMap); if(pushAction){ stack.push(action); contextMap.put(”action“, action); } invocationContext = new ActionContext(contextMap); invocationContext.setName(proxy.getActionName()); // get a new List so we don't get problems with the iterator if someone changes the list List interceptorList = new ArrayList interceptors = interceptorList.iterator(); createActionProxy(inv,namespace,actionName,methodName, executeResult, cleanupContext); } protected void createAction(Map // load action String timerKey = ”actionCreate: “ + proxy.getActionName(); try { UtilTimerStack.push(timerKey); //默认为SpringObjectFactory:struts.objectFactory=spring.这里非常巧妙,在struts.properties中可以重写这个属性 //在前面BeanSelectionProvider中通过配置文件为ObjectFactory设置实现类 //这里以Spring为例,这里会调到SpringObjectFactory的buildBean方法,可以通过ApplicationContext的getBean()方法得到Spring的Bean action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); } catch(InstantiationException e){ throw new XWorkException(”Unable to intantiate Action!“,e, proxy.getConfig()); } catch(IllegalAccessException e){ throw new XWorkException(”Illegal access to constructor, is it public?", e, proxy.getConfig()); } catch(Exception e){ ...} finally { UtilTimerStack.pop(timerKey); } if(actionEventListener!= null){ action = actionEventListener.prepare(action, stack); } } //SpringObjectFactory public Object buildBean(String beanName, Map Object o = null; try { //SpringObjectFactory 会 通 过 web.xml 中的 data fit;input X1 X2 X3 X4 X5 Y1 Y2 Y3;cards;14651000 3446 98.8 2094.51 104.2 2555.14 2637.67 179.76 13985000 3339 113.8 2305.2233 133.8 2462.45 2670.99 161.74 15162900 3093 108.9 2494.6668 93.8 2831.87 3015.04 186 14275800 3084 99.6 2770.48 99.8 2957.2 2259.86 210.3 13966000 3040 101.6 3224.05 142.4 2767.25 2169.47 206.16 13947000 2978 112.4086 3690.34 123.8 2935 2307 218.36 14632000 2952 102.5 3980.44 79.1 3119.91 2332.38 232.33 14123200 2761 106.2 4543.41 97.9 3230.04 2344.04 241.53 14299300 2703 107.3469 5231.33 143.2 3195.12 2411.98 239.79 14849000 2644 111.3 6007.5498 90.8 3342.09 2466.6 252.5 15218000 2604 103.8 6790.899 97.2 3456.7 2471.53 261.34 15344000 2567 99 7565 95.7 3518 2360.31 266.29 run;ods rtf file='F:结果.doc';proccancorr data=fit all vprefix=YING vname='yingxiang' wprefix=CHAN wname='shengchan';var X1 X2 X3 X4 X5;with Y1 Y2 Y3;run;ods rtf close;第四篇:struts2代码分析
第五篇:典型相关分析SAS代码