[转载]SQL截取字符串(substring与patindex的使用) - 爱与决择 - 博客园

mikel阅读(1220)

[转载]SQL截取字符串(substring与patindex的使用) – 爱与决择 – 博客园.

首先学习两个函数

1.substring  返回字符、binary、text 或 image 表达式的一部分。
基本语法:SUBSTRING ( expression , start , length
expression:字符串、二进制字符串、text、image、列或包含列的表达式
start:整数,指定子串的开始位置      注:SQL中”1″表示字符串中的第一个字符,而.NET中”0″表示第一个字符
length:整数,指定子串的长度(要返回的字符数或字节数)

2.patindex  返回指定表达式中某模式第一次出现的起始位置;如果在全部有效的文本和字符数据类型中没有找到该模式,则返回零。
基本语法:PATINDEX ( ‘%pattern%‘ , expression )
pattern:字符串。可以使用通配符,但 pattern 之前和之后必须有 % 字符(搜索第一个和最后一个字符时除外)。pattern 是短字符数据类型类别的表达式
      expression:表达式,通常为要在其中搜索指定模式的列,expression 为字符串数据类型类别 

下面结合上面的两个函数,截取字符串中指定的字符

declare @a varchar(50)
set @a=2009年7月15日星期五
select substring(@a,1,4获取年份2009
declare @b int
set @b=patindex(%日%,@a获取’日’这个字符在字符串中的位置,即10
select substring(@a,6,@b5获取日期’7月15日’

 

[转载]C#对图片文件的压缩、裁剪操作初探 - xiaoyang_ - 博客园

mikel阅读(964)

[转载]C#对图片文件的压缩、裁剪操作初探 – xiaoyang_ – 博客园.

在做项目时,对图片的处理,以前都采用在上传时,限制其大小的方式,这样带来诸多不便。毕竟网站运维人员不一定会对图片做处理,经常超出大小限制,即使会使用图片处理软件的,也由于个人水平方面原因,处理效果差强人意。

于是采用C#为我们提供的图像编辑功能,实现一站式上传,通过程序生成所需大小、尺寸的目标图片。

进入正题–>

先说图片压缩

第一步:需要读取一个图片文件,读取方法:

// <param name="ImageFilePathAndName">图片文件的全路径名称</param>
public Image ResourceImage =Image.FromFile(ImageFilePathAndName);

说明:

Image类:引用自System.Drawing,为源自 BitmapMetafile 的类提供功能的抽象基类。

主要属性:Size->获取此图像的以像素为单位的宽度和高度。

       PhysicalDimension->获取此图像的宽度和高度(如果该图像是位图,以像素为单位返回宽度和高度。如果该图像是图元文件,则以   0.01 毫米为单位返回宽度和高度。)。

     PixelFormat->获取此 Image 的像素格式。

     Height Width->获取此 Image 的高度、宽度(以像素为单位)。

主要方法:FromFile(String)->从指定的文件创建 Image

     FromStream(Stream)->从指定的数据流创建 Image

Save(String fileName)->将该 Image 保存到指定的文件或流。

       Save(Stream, ImageFormat)->将此图像以指定的格式保存到指定的流中。

Save(String, ImageFormat)->将此 Image 以指定格式保存到指定文件。

更多属性和方法说明请点击

第二步,生成缩略图,并且将原图内容按指定大小绘制到目标图片。

复制代码
/// <summary>
        /// 生成缩略图重载方法1,返回缩略图的Image对象 /// </summary>
        /// <param name="Width">缩略图的宽度</param>
        /// <param name="Height">缩略图的高度</param>
        /// <returns>缩略图的Image对象</returns>
        public Image GetReducedImage(int Width, int Height) { try { //用指定的大小和格式初始化Bitmap类的新实例
                Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); //从指定的Image对象创建新Graphics对象
                Graphics graphics = Graphics.FromImage(bitmap); //清除整个绘图面并以透明背景色填充
 graphics.Clear(Color.Transparent); //在指定位置并且按指定大小绘制原图片对象
                graphics.DrawImage(ResourceImage, new Rectangle(0, 0, Width, Height)); return bitmap; } catch (Exception e) { ErrMessage = e.Message; return null; } }
复制代码

说明: 1、Bitmap类

引用自System.Drawing,封装 GDI+ 位图,此位图由图形图像及其特性的像素数据组成。Bitmap 是用于处理由像素数据定义的图像的对象。

(封装图像的对象),详细介绍请点此

2、Graphics类

引用自System.Drawing处理图像的对象),封装一个 GDI+ 绘图图面。

详情请点此

第三步,保存

第二步操作中返回的Image对象,暂时命名为:iImage:

iImage.Save(pathAndName, System.Drawing.Imaging.ImageFormat.Jpeg);

以上是压缩操作,做了下试验,101k的图片,经过压缩后是57k。这个应该和尺寸有关系。

以下是图片裁剪,其实原理和上面相似,无非也就是对图片进行重画操作。

复制代码
/// <summary>
        /// 截取图片方法 /// </summary>
        /// <param name="url">图片地址</param>
        /// <param name="beginX">开始位置-X</param>
        /// <param name="beginY">开始位置-Y</param>
        /// <param name="getX">截取宽度</param>
        /// <param name="getY">截取长度</param>
        /// <param name="fileName">文件名称</param>
        /// <param name="savePath">保存路径</param>
        /// <param name="fileExt">后缀名</param>
        public static string CutImage(string url, int beginX, int beginY, int getX, int getY, string fileName, string savePath, string fileExt) { if ((beginX < getX) && (beginY < getY)) { Bitmap bitmap = new Bitmap(url);//原图
if (((beginX + getX) <= bitmap.Width) && ((beginY + getY) <= bitmap.Height)) { Bitmap destBitmap = new Bitmap(getX, getY);//目标图
 Rectangle destRect = new Rectangle(0, 0, getX, getY);//矩形容器
                    Rectangle srcRect = new Rectangle(beginX, beginY, getX, getY); Graphics.FromImage(destBitmap);
            Graphics.DrawImage(bitmap, destRect, srcRect, GraphicsUnit.Pixel);  ImageFormat format = ImageFormat.Png; switch (fileExt.ToLower()) { case "png": format = ImageFormat.Png; break; case "bmp": format = ImageFormat.Bmp; break; case "gif": format = ImageFormat.Gif; break; } destBitmap.Save(savePath + "//" + fileName , format); return savePath + "\\" + "*" + fileName.Split('.')[0] + "." + fileExt; } else { return "截取范围超出图片范围"; } } else { return "请确认(beginX < getX)&&(beginY < getY)"; } }
复制代码

说明:
Rectangle类:矩形,详情请点此
以上是裁剪一个图片文件的示例代码。

本文所使用代码,是项目中真实代码,已经过测试。

希望和大家交流共同学习。

[转载]开源Word读写组件DocX,通过word模板,导出用户简历使用示例 - WIN8新人 - 博客园

mikel阅读(1283)

[转载][原创]开源Word读写组件DocX,通过word模板,导出用户简历使用示例 – WIN8新人 – 博客园.

   入门请看:

【原创翻译】开源Word读写组件DocX介绍与入门[资料已发送]

我也是通过看上面的入门的。

1.DocX通过word模板批量导出用户简历

   由于Docx有两种方法可以自定义属性:1.1通过word模板文件(在word模板中定义好自定义属性)  1.2 用代码创建word模板,并同时用代码创建自定义属性。

  1.1通过word模板文件(在word模板中定义好自定义属性),自己新建一个模板文件。

 

每个要替换的部分,都定义成自定义属性

 

域代码如下:TAge 就为自定义属性名称

 

代码如下:

复制代码
  private  void CreateInvoice() { DocX g_document; try {
 //导入模板 g_document = DocX.Load(Server.MapPath(@"moban\Translator.docx")); //查数据,遍历。
            DataTable dt = sqldb.GetDataTable("select * from test"); if (dt.Rows.Count > 0) { foreach (DataRow dr in dt.Rows) { //把需要填充的数据,替换模板中的信息,并保存
                    g_document = CreateInvoiceFromTemplate(DocX.Load(Server.MapPath(@"moban\Translator.docx")),dr); g_document.SaveAs(Server.MapPath(@"translatorTemp\" + dr["name"].ToString() + ".docx")); } } } catch (FileNotFoundException) { //如模板不存在时,先创建模板,再执行上班操作 //g_document = CreateInvoiceTemplate(); //g_document.Save(); //CreateInvoice();
 } }
复制代码

下面代码为填充数据方法

复制代码
   
    private DocX CreateInvoiceFromTemplate(DocX template,DataRow dr) { //为自定义属性赋值,CustomerProperty(name,values),name就是我们刚刚在word中定义的名称。values就是要填充进去的内容
        #region Set CustomProperty values

        template.AddCustomProperty(new CustomProperty("Translatorno", dr["translator_no"].ToString())); template.AddCustomProperty(new CustomProperty("TName", dr["name"].ToString())); template.AddCustomProperty(new CustomProperty("TAge", dr["age"].ToString())); template.AddCustomProperty(new CustomProperty("TSex", dr["sex"].ToString())); template.AddCustomProperty(new CustomProperty("TNationality", dr["nationality"].ToString())); #endregion

        return template; }
复制代码

1.2 用代码创建word模板,并同时用代码为word模板创建自定义属性。

复制代码
 
        private static DocX CreateInvoiceTemplate() { // 创建一个文档
            DocX document = DocX.Create(@"docs\InvoiceTemplate.docx"); //先创建了一个表格
            Table layout_table = document.InsertTable(2, 2); layout_table.Design = TableDesign.TableNormal; layout_table.AutoFit = AutoFit.Window; // 定义格式
            Formatting dark_formatting = new Formatting(); dark_formatting.Bold = true; dark_formatting.Size = 12; dark_formatting.FontColor = Color.FromArgb(31, 73, 125); // 定义格式
            Formatting light_formatting = new Formatting(); light_formatting.Italic = true; light_formatting.Size = 11; light_formatting.FontColor = Color.FromArgb(79, 129, 189); #region Company Name
            //取表格的第一行第一列的第一段落
            Paragraph upper_left_paragraph = layout_table.Rows[0].Cells[0].Paragraphs[0]; // 新建一个自定义属性。其对应word中的内容是Translatorno为自定义属性名称,translatorno,为我们自己的word里面的内容
            CustomProperty company_name = new CustomProperty("Translatorno", "translatorno"); // 加入自定义属性
            layout_table.Rows[0].Cells[0].Paragraphs[0].InsertDocProperty(company_name, f: dark_formatting);
 return document; }
复制代码

2.资源

开源网址:http://docx.codeplex.com/

写篇博客不容易,烦躁的心情都见鬼去吧。兄弟们,对你有帮助,不要吝啬鼠标哦。

可以去下载示例代码。

[转载]CSS浮动,你不知道的事 - 越己 - 博客园

mikel阅读(938)

[转载]CSS浮动,你不知道的事 – 越己 – 博客园.

浮动到底做了什么?

浮动如何影响元素的盒模型?

浮动的元素和行内元素有何不同?

调整浮动元素的位置是通过什么规则进行的?

clear属性如何工作,它的目的又是什么?
浮动甚至能绊倒有经验的开发者,理解浮动能帮助你解决很多CSS问题。即使你认为已经知道了浮动的所有知识,我们足够深入的分析也许也能让你学到一些新东西。
什么是浮动?

CSS中的一些元素是块级元素,表示它们会自动另起一行。

举个例子,如果你创建了两个段落,每个段落都只有一个单词。这两个单词不会靠在一起,而是会各自占据一行。
另一些元素是行内元素,表示它们和前面的内容位于相同的一行。

举个例子,<a>可以出现在另一个元素中,比如<p>,这不会产生多余的空格或者出现换行。
欺骗这种布局模型的一种方式是使用浮动,浮动可以让一个元素移到它所在行的某一边,使得其他内容沿着该元素的边缘向下流。
一个典型的例子是你想要一张图片和一个段落并排出现,而不是一上一下排列。首先我们先来创建HTML:

<p>Lorem ipsum...</p>

单独这段代码并不能实现我们想要的效果。<p>是一个块级元素,它会独占一行,所以图片和段落是一上一下展现的。


通过让图片向右浮动可以改变这种行为,如下:

img {
    float: right;
    margin: 20px;
}

这样,图片就跑到右边去了,而段落则沿着图片的左边向下流式布局。


现在发生了一件有趣的事,当这张图片浮动后,其他的内容就会想办法尽可能的包围它。如果我们resize容器或者浏览器窗口,让它更窄,这段文本就会发生重排(reflow),这样它就永远不可能接触到图片。


盒模型如何工作

也许你已经对上面所讲的知识有了深刻的理解。但是,为了完全掌握浮动,你需要更加深刻的理解两个元素如何互相作用。举个例子,如果我们在段落和图片之间加一个边距会发生什么?

p {margin: 20px;}

但是呢,这么写并不会在图片和段落之间产生额外的空间。实际上,我们需要给图片加margin:

img {margin: 20px;}

 

 

也许你会问为什么呢?为什么增加<p>的margin不会增加图片和段落的间距呢?

原因是我们没有理解<p>的盒模型。
如果现在你对布局产生了一些疑虑,可以试着加一个或者两个border,看看会发生什么。下面给<p>加border:

p {
    border: solid 1px black;
}


如你所见,图片实际上位于<p>盒模型的内部!这就可以解释刚才的margin问题。我们加到<p>上的margin其实是作用于图片的右侧,这就是为什么它不能增加图片和段落之间的距离!
如果我们想改变这种行为,使得段落不会包围图片,我们应该让段落向左浮动,并设置一个宽度(如果不设置,<p>的宽度默认是100%,这样就不会和图片紧挨着了,因为如果段落很长,它会跑到下一行)。

img {
    float: right;
    margin: 20px;
}
 
p {
    float: left;
    width: 220px;
    margin: 20px;
}

 


疯狂的浮动规则

现在你知道什么是浮动了,并且知道浮动如何影响相关元素的盒模型。接下来要说的也许很多人都不了解:如何调整浮动元素的位置
web开发中很多人会给<li>使用浮动。下面来看一个例子:

<ul>
    <li><img src="http://placehold.it/100x100&text=1"/></li>
    <li><img src="http://placehold.it/100x150&text=2"/></li>
    <li><img src="http://placehold.it/100x100&text=3"/></li>
    <li><img src="http://placehold.it/100x100&text=4"/></li>
    <li><img src="http://placehold.it/100x100&text=5"/></li>
    <li><img src="http://placehold.it/100x150&text=6"/></li>
    <li><img src="http://placehold.it/100x100&text=7"/></li>
</ul>

所有<li>默认应该是垂直方向上排列的,这就表示<li>是块级元素。即使图片是行内元素,它也会被它的父级块 级元素管理。为了解决这个问题,我们让<li>向左浮动。当一行内的多个<li>被浮动后,它们会产生类似行内元素的流式布局。 然而,正如你即将看到的,它们有一些关键的不同。

li {
    float: left;
    margin: 4px;
}

现在,如果所有图片的高度相同,就会产生下面的效果。


但是,我们的图片高度不是一样的,一些是100px,另一些是150px。这就引起了一些严重的问题!


当我第一次看到这个效果,我蛋疼了。为什么图片4跑到右边去了?它不是应该尽可能的向左浮动么?如果我们放弃浮动而使用display:inline,结果会大不一样。

li {
    display: inline;
}

 


这个例子中,图片默认是垂直居底(bottom)对齐。这和我们之前的例子不同,为了解决对齐问题,我们添加一行CSS。

img {  
    vertical-align: top;
}

由此可知,使用display:inline 可以更容易猜到<li>的排列结果。当水平方向没有多余的空间时,下个元素就会另起一行。


浮动为什么不能实现这种效果呢?
CSS规范对于浮动行为概括了9个规则。但问题是,只有规范的作者和那些无聊的人才能理解这些规则。下面摘录了其中一个规则:

“If the current box is left-floating, and there are any left-floating boxes generated by elements earlier in the source document, then for each such earlier box, either the left outer edge of the current box must be to the right of the right outer edge of the earlier box, or its top must be lower than the bottom of the earlier box. Analogous rules hold for right-floating boxes.”

对于这些规则,也许你比我理解的更深,但说实话,这些规则让我非常蛋疼。为了简化它,Josh Johnson给出了他的9条规则(注:我觉得这位仁兄的9条规则依然很啰嗦,我再给精简一下):

1. 浮动元素的活动区域

仅限于它的父容器元素,不会超出父容器

2. 浮动元素的位置

水平方向:尽可能居左或居右,如果它前面还有浮动元素,会跟在它后面,如果超出该行就会换行

垂直方向:尽可能的居顶

 

关于水平方向的位置,需要注意以下几点:

1) 向左浮动的元素不会出现在向右浮动的元素的右侧

 

关于垂直方向的位置,需要注意以下几点:

1) 浮动元素不会比容器的顶部还高

2) 浮动元素不会比前一个块级元素或浮动元素更高

3) 浮动元素不会比前一个行内元素更高

 

在布局时,垂直方向的规则比水平方向的优先级更高

总的来说就是,浮动元素会移到左侧或右侧。除非该元素前面还有一个浮动元素,这时它就会紧挨着前面的元素。
真正让人迷惑的是:浮动元素会尽可能的居顶,并且垂直定位规则比水平浮动规则的优先级更高。
在前面的例子中,图片2撑高了该行的高度,所以在放完图片3后,仍然有足够的垂直空间放置图片4。
记住,当你有一个浮动元素(不位于尾行)时,它后面的浮动元素占用的垂直空间必须大于或等于它才会触发换行


浮动顺序

举个例子,我们有6张图片的一个列表。

<ul>
    <li><img src="http://placehold.it/100x100&text=1"/></li>
    <li><img src="http://placehold.it/100x100&text=2"/></li>
    <li><img src="http://placehold.it/100x100&text=3"/></li>
    <li><img src="http://placehold.it/100x100&text=4"/></li>
    <li><img src="http://placehold.it/100x100&text=5"/></li>
    <li><img src="http://placehold.it/100x100&text=6"/></li>
</ul>

如果我们向左浮动图片,它们就会按照原来的顺序排列。但如果向右浮动呢?


可以发现,第一张图片占据了最右的位置。类似的,换行后,第四张图也占据了最右的位置。这就是为什么你很少看到导航栏的列表项会向右浮动的原因。
清除浮动

使用浮动可以方便的实现一些布局,比如创建n栏内容。但是呢,一旦使用浮动就会影响文档正常的流式布局。比如,刚才那个例子中,我们想在列表下方加上一个段落。


这个结果也许不是你想要的。这里的解决方法是使用 clear 属性,它的作用是清除该元素某侧的浮动。比如,我们对第二个列表项使用clear:left

ul li:nth-child(2) {   
    clear: left;
}

 

这段代码告诉浏览器第二项的顶部必须比它前面的浮动元素的底部更低。如果所有列表项是向右浮动的,则需要使用clear: right


加上段落文本再看


很明显这依然不是我们想要的效果,解决方法是给段落使用clear,这会使得段落出现在浮动元素的下方而不是与它们相邻。

p {
    clear: both;
}

其实这里我们只需要清除左侧的浮动即可,但是当一个开发者为了确保清除了所有浮动,clear:both是很常用的一种方法。

浮动的问题和clearfix

当一个元素只包含浮动元素时,该容器元素会出现高度重叠(和高度为0的效果一样,即高度的顶边和底边重叠)。为了演示这个现象,我们还是使用刚才的例子,只是给列表加一个背景色。

ul {
    background: gray;
}

如果列表项没有浮动,可以看到整个列表都是灰色的,而列表项则是从上到下排列。


现在我们浮动所有列表项,这时<ul>只包含浮动的元素,所以它的高度重叠了,新手肯定会好奇背景色到底哪去了.


解决这个问题有好几种方法,最简单的一种是直接给容器元素设置高度。

ul {
    height: 300px;
}

 

这样背景色又回来了。但是,这种方法常常不能让我们满意,因为当我们需要容器的高度和内容自适应时,这种方法完全无效。如果我们再加上三个列表项,这个高度又不够了。
召唤clearfix

现在该轮到clearfix登场了,它通过clear属性解决了高度重叠的问题。
我们常常会创建一个空元素(通常是div),它和浮动元素同级,然后给它设置class为“clearfix”。回到CSS,我们加上这样一行样式:

.clearfix {
    clear: both;
}

这样就解决了高度的问题。

说下原理:我们知道当元素只包含浮动元素时,高度发生重叠(效果和高度为0一样),这时该元素有了一个子元素,即使它是个空元素,但它没有浮动,而且还清除了所有浮动,所以这个空元素会出现在所有元素的下方,从而撑起了容器的高度,于是height:auto恢复正常了。
这种方法的缺点是,HTML中多了一个额外的元素,不符合语义化的思想。
新的解决方法是使用 overflow 属性,如果你设值为hidden 或 auto,也可以解决高度重叠的问题。

ul {
    overflow: auto;
}

 

这种方法更简单也更优雅,但是还有个问题要说下,如果容器元素必须设置为overflow: visible,你又该怎么办呢?
方法是首先使用 :before 和 :after 在元素内创建一些不浮动的东西,但实际上你并不希望出现任何多余的东西,所以我们设置一个空字符串,但是要设置display:table,这样就创建了 一个匿名单元格(是不是想起了<td></td>?),最后使用老办法,清除浮动。
为了兼容老版本的IE,使用它特有的zoom:1清除浮动。

/* For modern browsers */
.cf:before,
.cf:after {
    content:"";
    display:table;
}
 
.cf:after {
    clear:both;
}
 
/* For IE 6/7 (trigger hasLayout) */
.cf {
    zoom:1;
}

http://designshack.net/articles/css/everything-you-never-knew-about-css-floats/

[转载]一种效果还不错的图片排列策略 - ChaunceyHao - 博客园

mikel阅读(1129)

[转载]一种效果还不错的图片排列策略 – ChaunceyHao – 博客园.

QuQuBlog相册中的图片列表,之前是把图片定高,容器定宽来排的。这样实现起来简单粗暴,但在图片大小不一,尤其是横竖图片混排时,竖着的图片两侧很空,不好看,还占地方。

最近,用了一种新策略来改进图片排列,效果大约是这样:

在各种分辨率下都是这样的效果:

  • 列表左右两侧对齐;
  • 每个图片容器边距一致;
  • 图片定高,不压缩不拉伸,尽可能保证原本高宽比例。

点顶部导航中的“相册”或者点这里可以看到实际效果,想知道具体策略继续往下看。

每个图片容器都是绝对定位,hidden掉overflow;图片容器高度总等于图片高度,宽度和位置是在页面加载完和每次窗口resize时算出来的。计算的步骤如下:

  1. 首先得到图片列表容器宽度containerWidth;
  2. 让每个图片容器的宽度等于自己图片的宽度;
  3. 循环图片容器列表,把图片容器分成若干行。确保每行宽度rowWidth大于或等于containerWidth(大部分情况是少一张宽度不够,多一张宽度就超过了,所以rowWidth一般都大于containerWidth);
  4. 如果rowWidth大于containerWidth,根据图片容器原始宽度在rowWidth所占比例,把超过的部分平均分给每个容器。这样每个容器宽度都减少了一些,列表右侧就是对齐的;
  5. 经过上一步,大部分图片容器宽度都小于图片宽度,给图片负margin,保证居中显示;
  6. 根据图片容器所在行数和顺序设置容器top和left位置;
  7. 最后根据图片容器总行数设置列表容器高度containerHeight。

上面的策略用QWrap写出来也就几十行。由于图片容器都是绝对定位,可以方便地加上动画。最后,窗口resize时要delay一下再重新排,不然看起来有点奇怪,也影响性能。

图片等比例缩放

mikel阅读(963)

页面代码:

<div id="pic">
<img src="/dd/gg.gif" height="233"/>
</div>

js代码

$(function(){
     $("#pic img").each(function() {
         var image = $(this);
         if (image.width() > 435) {
             image.width(435);
             image.height(435 / image.width() * image.height());

         }

     });
});

[转载]从程序员到项目经理(12):如何管理自己的时间(上) - 西西吹雪 - 博客园

mikel阅读(863)

[转载]从程序员到项目经理(12):如何管理自己的时间(上) – 西西吹雪 – 博客园.

对很多项目经理而言,是没有什么所谓的“我的时间”的,因为他们不是在管项目,而是被工作的潮水带着跑而已,他们的时间被工作主宰了。项目经理必须要主动的管理自己的时间,合理安排自己的工作,才能真正“翻身”做自己时间主人。

1.谁动了我的时间

时 间对于每个人而言,都是最稀缺的资源,对于一个管理者更是如此,时间不够用成为几乎所有管理者共同的问题。如果要对项目经理常说的话做一个调查的话,想信 “我很忙”一定可以名列前茅。以我的经验,当要求项目经理按时提交项目材料,或者临时支援某件紧急事务的时候,经常会听到同样的回答:“我很忙”。

多 年以前,我就从经理那里听说,厉害的管理者都是很轻松的,因为他的工作全部交出去了,根本不用自己操心,所以他们出去度假十天半个月,一切工作都会如常进 行。从那时起,我就充满了对管理的神往,可是后来我才发现原来这只是个传说,现实中忙忙碌碌的经理比比皆是,而轻松自如的管理者则是众里难寻。

为什么管理者都这么忙呢?是谁动了他们的时间?实际上,这是一个综合性的问题,既有内部原因,也有外部原因,既有主观原因,也有客观原因。总的来说,让经理们不堪重负的因素有三:

(1)工作

对于一个程序员来说,他的工作是比较单纯的,基本上是单线程运作,只需要项目经理交待开发任务即可,可是当上了项目经理就不一样了。以前好比在游泳池中游泳,现在是在大海里冲浪,各种事情如潮水一般向你涌来,让你顾此失彼,手足无措。

(2)下属

下属也是一种资源,即人力资源,这种资源与时间一样,同样具有稀缺性。其实我们可以设想一下极端情况,如果你的下属人数足够,能力也很强的话,你完全可以像我的经理说的一样,把你的全部工作授权给你的下属,你自己也就不用整天焦头烂额了。

因为你的下属不给力,所以你总是要自己来制定计划、自己来做系统架构、自己来监控进度、自己来检查质量、自己来写文档、自己来汇报工作、自己来解决重要问题、甚至自己来编写代码,你整天忙忙碌碌,就是在忙这样的事情。

然而,千万不要怪你的下属,因为他们不给力正是老板雇佣你的原因,况且资源的稀缺性是永远存在的——从原始社会到将来的共产主义社会。要知道,老板做项目为了赚钱,而不是让管理者更轻松,如果每个项目都是精兵强将,你只要一声令下工作就会自动完成,你倒是轻松了,但老板还要你来做什么?

(3)自己

既 然资源受限是一定的,项目经理还是应该反求诸己,从自己身上找到解决之道。这就好比天下雨了,你怪老天是没有用的,只能怪你自己没有带雨伞。经常问一问自 己,我对工作安排合理吗?我抓住了主要问题吗?我在旁枝末节的事情上浪费时间了吗?我有充分发挥下属的能力吗?我自己工作拖拖拉拉吗?通过不断的自省,改善自己的管理方法和行为习惯,我们对时间利用也必然会变得越来越高效。

2.时间管理的本质是对工作的梳理

要 破解忙的难题,必须要有意识的对时间进行管理。其实时间本身是没法管理的,因为无论你怎样管理,时间既不会变多,也不会变少,既不会变快,也不会变慢。所 谓的时间管理,其实就是如何更有效的利用时间的问题,更加直白地说,其本质就是工作管理,即通过对工作的梳理,让我们在有限的时间内,使得工作更有条理、 更有成效。

必 须要主动、有目标地对工作进行梳理,这是对一个管理者的基本要求。工作梳理就好比整理房间,你不去整理它,杂物就会堆积得越来越多,你房子最终会变得不适 合人类居住。一个好的家庭主妇,必定善于将各位物品分门别类,并且适时扔掉一些用处不大的物品。一个好的项目经理也一样,同样需要对工作进行分类,对不同 类型工作采用不同的策略,有些工作要现在就做,有些可以晚点做,或者不做;有些工作一定要自己做,有些工作则可以请其他人来完成。

通常对工作梳理,可以采用5W1H法,即:

        Why——为什么干这件事?(目的);

     What——什么事情?(对象);

    Where——在什么地方执行?(地点);

    When——什么时间执行?什么时间完成?(时间);

                 Who——由谁执行?(人员);

    How——怎样执行?采取哪些有效措施?(方法)。

在一般的项目中,Whywhere往往不是什么问题,或者说对项目经理的时间管理影响较小,因此我们不妨将其简化为3W1H,也就是确定要做什么,不做什么;先做什么,后做什么;谁来做;怎样做才更有效。基于此,项目经理可以按以下三个步骤来梳理工作:

(1)分析要做什么、不做什么,以及先做什么、后做什么

解决WhatWhen的问题。事有轻重缓急,事情的重要程度和紧急程序直接决定其处理的优先级。虽然很多事情来势汹汹,但并不表示一定要当即处理,有些事情只是静静的躺在那儿,也并不意味着要“等有了时间再做”。

(2)分析由谁来做

解决Who的问题。虽然我们提倡项目经理要以身作则、亲力亲为,但并不是说每件事项目经理要亲自去做。对于下属可以胜任的事情,就把它分配出去。如果出现项目经理很忙、下属很闲的情况,那就说明项目经理你做得太多了,不要和你的下属抢事情做。

(3)  如何让工作更有成效

做不做、什么时候做以及谁来做的问题都解决了,剩下就要解决怎么做才能让工作更有成效的问题了。在这里我们不是要讨论编码或写文档的技巧,而是个人的习惯和认识,这对工作成效的影响更是本质上的。

3.做事要分轻重缓急

老外就是善于总结,中国有词语叫“轻重缓急”,可是到了国外摇身一变,变成了“时间管理四象限法”——自从美国总统艾森豪威尔提出以来,人人将其奉为圭臬,成为时间管理领域最重要的方法论。

所谓的“四象限法”,就是将工作按照重要程度和紧急程度两个维度进行分类。我们找一张白纸,以紧急程度为纵轴,以重要程序为横轴,在纸上划上一个十字,将纸面分为四个象限,然后将当前所有要做的工作放到这个四个象限中。

一个典型的项目经理四象限图如下所示:

       (1) 第一象限:重要紧急

这一类往往是火烧眉毛的事情,需要马上去处理,否则项目会受到重大影响,比如客户服务器崩溃。

(2) 第二象限:重要不紧急

这类事情一般是预防型的工作,例如制定项目计划、团队建设等,它们不需要你停下手上的工作马上去做,但如果没做好的话,可能就会导致产生项目危机。许多第一象限工作产生的原因,正是因为第二象限的工作没有去做。

(3)第三象限:不紧急也不重要

这类事情看上去最不需要做了,例如上网偷菜、看新闻、写博客等,但如果你在办公室走上一圈,就会发现很多人正在干着这些不需要干的事情。

(4) 第四象限:紧急不重要

这类事情虽然不重要,却需要马上去处理。一个典型的例子就是桌上的电话响了,你接还是不接?当然要接,因为你不知道是谁。接通后,发现是推销保险的,你又不好意思立即挂掉,只好被对方折磨一番了。

我们到底该怎样安排四个象限的工作呢?对于一个普通的管理者,其工作的优先级一般是这样的:第一象限>第四象限>第二角限>第三象限。可是,等做完了第一、四象限的工作,根本就没有时间来人做第二象限的工作,于是项目到了后期项目经理只好四处救火。

管理大师彼德.德鲁克十分推崇“时间管理四象限法”,并将其总结为“要事第一”的原则。根据这个原则,每个象限的工作处理策略是不一样的。

(1)重要紧急

优先级最高,需要尽快处理。很多人都玩过《植物大战僵尸》的游戏吧,那你一定知道“一大波僵尸正在逼近”的感觉,是的,你必须要马上打死它们,不然它们就会冲进你的房子,吃掉你的大脑!

(2)重要不紧急

这 类事情看上去可以暂缓,但考虑到其重要性,应当与第一象限的工作并行去做。如果不及时去做,它们就会转移到让你头疼的第一象限中去,或者在第一象限产生更 多新的“僵尸”。所以,要在僵尸还没有逼近的时候,就好防御工事,并尽快打死它们,如果等到它们冲了过来,你还能不能保住大脑,就要看你的运气了。

(3)紧急不重要

它 们就像是在你耳边“嗡嗡嗡”地叫着的苍蝇,你必须要花时间去赶走它们。这多少让人有些无奈,但这些事情确实层出不穷。有些公司在实施紧急项目时,经常采用 封闭式开发,这样做的一个重要原因就是要回避那些紧急不重要的事情。很多管理专家建议我们在必要的时候勇敢说“不”,其实就是针对这类事情。如果实在无法 说不,建议安排或委托其他人来做。

(4)不紧急也不重要

如果不是时间充裕的话,建议不要去做。如果碍于人情的话,建议安排或委托其他人来做。它们就像一群在几百米远处飞的苍蝇而已,你完全不必要放下手中的饭碗,举起苍蝇拍跑过去和它们决斗。

因此,对于一个卓有成效的管理者,其优先级应该是这样的:第一象限=第二象限>>第四角限。第三象限就像数学中的无穷小一样,被舍弃了。

写到这里,我想起了前不久一位项目经理的故事:

项目定于当天上线,项目组决定搬到客户现场办公,以应付可能出现在的突发事件。项目成员电脑已经全部打包好,都围在项目经理周围等待。原来项目经理正在理一大堆发票准备报销,于是发生了这下面这样的对话:

我:“大家都在等你,怎么还在填报销单呢?”

项目经理:“今天是公司的报销日,不填好单子,又得推后很久。”

我:“你的电脑打包了没有?”

项目经理:“没有”

我:“放行条开了没有?”

项目经理:“没有”

我:“申请用车了没有?”

项目经理:“没有”

我 不知道说什么好了。要知道公司的报销单粘贴和填写非常严格,经常被打回重新弄,那一堆发票,显然不是十几分钟可以搞定的事情。还有公司的用车也比较紧张, 不赶紧申请,说不定就没有了,到时就只能租车或打的,这无疑又会耽误更多的时间。更何况六七个同事都在等项目经理一个人,耽误的时间还得要乘以他们的人 数。万一系统上线,状况频出,客户火烧眉毛,项目组却仍然在路上,这样的后果是很严重的。

贴报销单看上去一件重要紧急的事情,实际上它既不重要也不紧急,因为今天不报销,以后还是可以报销,可是因此耽误的宝贵时间,却无法再要回来。如果项目经理更加理智一些,分清楚什么才是真正紧急重要的事,也就不会出现这样的状况了。

[转载]用C#开发了俺的第一个Android 浏览器APP

mikel阅读(1123)

[转载]我有罪啊,我用了一下午时间,终于无师自学,用C#开发了俺的第一个Android 浏览器APP,我对不起java 社区啊(附开发过程讲解) – chinacloudy – 博客园.

我有罪啊,我用了一下午时间,终于无师自学,用C#开发了俺的第一个Android 浏览器APP,我对不起java 社区啊(附开发过程讲解)

 

开发流程:

安装 VS2010、安装 Android SDK、配置 AVD(Android) 模拟器

 

调试的时候打开 ADV(android 模拟器) 在里面你自己添加一个机器的型号,设置好分辨率,内存大小,SD卡,启动之后,就可以调试了!

调试生成的代码是不能发布到真实的机器上的,因为 android 实际程序在真实机器中运行的时候会进行验证,这个在 netbeans 或 eclipse 里面叫 key.store

所以发布的时候,要用 netbeans 或 eclipse 里面一个叫 keytools的工具,生成一个 key.store的文件
使用格式如下:
D:\Progra~1\Java\jdk1.7.0_11\jre\bin\keytool -genkey -v -keystore xxx-release-key.keystore -alias xxxkeystore -keyalg RSA -keysize 2048 -validity 20000
您的名字与姓氏是什么?
[Unknown]:  huangzhi
您的组织单位名称是什么?
[Unknown]:  bwsyq co.ltd
您的组织名称是什么?
[Unknown]:  bwsyq java develop group
您所在的城市或区域名称是什么?
[Unknown]:  hang zhou
您所在的省/市/自治区名称是什么?
[Unknown]:  hang zhou
该单位的双字母国家/地区代码是什么?
[Unknown]:  86
CN=huangzhi, OU=bwsyq co.ltd, O=bwsyq java develop group, L=hang zhou, ST=hang z
hou, C=86是否正确?

这样之后或要求你输入密码,这时候就会产生一个  xxx-release-key.keystore 的验证密码文件 别名叫: xxxkeystore

不论是 vs2010 还是 netbeans 还是 eclipse 在release的时候都会要求你输入密码,密码验证通过之后就会生成真正的发布安装文件 xxx.APK ,这个就是可以在真实机器上安装运行的 APP了!

例如:http://pan.baidu.com/share/link?shareid=367259&uk=201606611

这时后还不要高兴,这样编译的程序还只能运行一般的 APP ,如果要使用google 地图API ,你还要从 key.store中取出 MD5 密码
keytool -list -v -alias xxxkeystore -keystore xxx-release-key.keystore
通过这个 keytools的命令去取
然后到 http://www.google.com/glm/mmap google 地图网站获取一个根据 MD5生成的 地图 key
才能在你发布的程序中使用 google 地图 API

当然了,还有很多,比如如何注册,使用 admob 如何在你的应用中增加google 移动广告赚钱,等等,有兴趣的可以加入我们的QQ群一起探讨交流!

开始开发:新建 android application

产生代码如下:Activity1.cs 代码如下:

using System;
 
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Webkit;
 
namespace hz.android.test
{
    [Activity (Label = "hz.android.test", MainLauncher = true)]
    public class Activity1 : Activity
    {
        int count = 1;
 
        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);
 
             SetContentView (Resource.Layout.Main);
            Button button = FindViewById<Button> (Resource.Id.myButton);
            WebView webView = FindViewById<WebView> (Resource.Id.webView1);
            button.Click += delegate {
                button.Text = string.Format ("{0} clicks!", count++);
                webView.LoadUrl("http://blog.csdn.net/tengyunjiawu_com/article/details/8565766");
            };
        }
    }
}

 

界面设计是可视化的,所见即所得的,比 netneans 和 eclipse 强多了,还有很多各种各样的控件!

netneans 和 eclipse  界面都要自己写 axml文件,这个能拖拉好之后自动生成:

layout/Main.axml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:id="@+id/myButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <WebView
        android:layout_width="fill_parent"
        android:layout_height="220.7dp"
        android:id="@+id/webView1"
        android:layout_marginBottom="50.0dp" />
    <EditText
        android:inputType="textEmailAddress"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText1"
        android:text="bwsyq@bwsyq.com QQ:99923309"
        android:linksClickable="true" />
    <DatePicker
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/datePicker1" />
    <CheckedTextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/checkedTextView1" />
</LinearLayout>

 

Resource.designer.cs 这个相当于 netbeans 或 java 开发 Android时自动生成的R.java 类代码:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.239
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace hz.android.test
{
     
     
    public partial class Resource
    {
         
        public partial class Attribute
        {
             
            private Attribute()
            {
            }
        }
         
        public partial class Drawable
        {
             
            // aapt resource value: 0x7f020000
            public const int Icon = 2130837504;
             
            // aapt resource value: 0x7f020001
            public const int lbs = 2130837505;
             
            private Drawable()
            {
            }
        }
         
        public partial class Id
        {
             
            // aapt resource value: 0x7f050004
            public const int checkedTextView1 = 2131034116;
             
            // aapt resource value: 0x7f050003
            public const int datePicker1 = 2131034115;
             
            // aapt resource value: 0x7f050002
            public const int editText1 = 2131034114;
             
            // aapt resource value: 0x7f050000
            public const int myButton = 2131034112;
             
            // aapt resource value: 0x7f050001
            public const int webView1 = 2131034113;
             
            private Id()
            {
            }
        }
         
        public partial class Layout
        {
             
            // aapt resource value: 0x7f030000
            public const int Main = 2130903040;
             
            private Layout()
            {
            }
        }
         
        public partial class String
        {
             
            // aapt resource value: 0x7f040001
            public const int app_name = 2130968577;
             
            // aapt resource value: 0x7f040000
            public const int hello = 2130968576;
             
            private String()
            {
            }
        }
    }
}

 

调试的时候打开 ADV(android 模拟器) 在里面你自己添加一个机器的型号,设置好分辨率,内存大小,SD卡,启动之后,就可以调试了!

调试生成的代码是不能发布到真实的机器上的,因为 android 实际程序在真实机器中运行的时候会进行验证,这个在 netbeans 或 eclipse 里面叫 key.store

所以发布的时候,要用 netbeans 或 eclipse 里面一个叫 keytools的工具,生成一个 key.store的文件
使用格式如下:
D:\Progra~1\Java\jdk1.7.0_11\jre\bin\keytool -genkey -v -keystore xxx-release-key.keystore -alias xxxkeystore -keyalg RSA -keysize 2048 -validity 20000
您的名字与姓氏是什么?
[Unknown]:  huangzhi
您的组织单位名称是什么?
[Unknown]:  bwsyq co.ltd
您的组织名称是什么?
[Unknown]:  bwsyq java develop group
您所在的城市或区域名称是什么?
[Unknown]:  hang zhou
您所在的省/市/自治区名称是什么?
[Unknown]:  hang zhou
该单位的双字母国家/地区代码是什么?
[Unknown]:  86
CN=huangzhi, OU=bwsyq co.ltd, O=bwsyq java develop group, L=hang zhou, ST=hang z
hou, C=86是否正确?

这样之后或要求你输入密码,这时候就会产生一个  xxx-release-key.keystore 的验证密码文件 别名叫: xxxkeystore

不论是 vs2010 还是 netbeans 还是 eclipse 在release的时候都会要求你输入密码,密码验证通过之后就会生成真正的发布安装文件 xxx.APK ,这个就是可以在真实机器上安装运行的 APP了!

例如:http://pan.baidu.com/share/link?shareid=367259&uk=201606611

真机(在三星、摩托罗拉、HTC 上均测试通过)效果如下:

上面按钮点击后效果:

这时后还不要高兴,这样编译的程序还只能运行一般的 APP ,如果要使用google 地图API ,你还要从 key.store中取出 MD5 密码
keytool -list -v -alias xxxkeystore -keystore xxx-release-key.keystore
通过这个 keytools的命令去取
然后到 http://www.google.com/glm/mmap google 地图网站获取一个根据 MD5生成的 地图 key
才能在你发布的程序中使用 google 地图 API

当然了,还有很多,比如如何注册,使用 admob 如何在你的应用中增加google 移动广告赚钱,等等,有兴趣的可以加入我们的QQ群一起探讨交流!

本人也是新手有任何建议、意见、需要代码的可与我本人联系 :QQ交流群:72133568, 27236303,74965947, 16592133, 204725117, 204724518

[转载]SpringMVC深度探险(四) —— SpringMVC核心配置文件详解 - downpour - ITeye技术网站

mikel阅读(1175)

[转载]SpringMVC深度探险(四) —— SpringMVC核心配置文件详解 – downpour – ITeye技术网站.

本文是专栏文章(SpringMVC深度探险)系列的文章之一,博客地址为:http://downpour.iteye.com/blog/1389285

在上一篇文章中,我们从DispatcherServlet谈起,最终为读者详细分析了SpringMVC的初始化主线的全部过程。整个初始化主 线的研究,其实始终围绕着DispatcherServlet、WebApplicationContext和组件这三大元素之间的关系展开。

在文章写完之后,也陆续收到了一些反馈,其中比较集中的问题,是有关WebApplicationContext对组件进行初始化的过程交代的不够清楚。所以,本文作为上一篇文章的续文,就试图来讲清楚这个话题。

SpringMVC的核心配置文件

SpringMVC的核心配置文件,我们从整个专栏的第一篇文章就开始接触。所以,我们在这里首先对SpringMVC的核心配置文件做一些概括性的回顾。

downpour 写道
结论 SpringMVC的核心配置文件是构成SpringMVC应用程序的必要元素之一。

这是我们在讲有关SpringMVC的构成要素时就曾经提到过的一个重要结论。当时我们所说的另外两大必要元素就是 DispatcherServlet和Controller。因而,SpringMVC的核心配置文件在整个应用程序中所起到的作用也是举足轻重的。这也 就是我们在这里需要补充对这个文件进行详细分析的原因。

downpour 写道
结论 SpringMVC的核心配置文件与传统的Spring Framework的配置文件是一脉相承的。

这个结论很容易理解。作为Spring Framework的一部分,我们可以认为SpringMVC是整个Spring Framework的一个组件。因而两者的配置体系和管理体系完全相同也属情理之中。实际上,SpringMVC所采取的策略,就是借用Spring Framework强大的容器(ApplicationContext)功能,而绝非自行实现。

downpour 写道
结论 SpringMVC的核心配置文件是架起DispatcherServlet与WebApplicationContext之间的桥梁。

我们在web.xml中指定SpringMVC的入口程序DispatcherServlet时,实际上蕴含了一个对核心配置文件的指定过程 ([servlet-name]-servlet.xml)。当然,我们也可以明确指定这个核心配置文件的位置。这些配置选项,我们已经在上一篇文章中详 细介绍过,这里不再重复。

而上面这一结论,除了说明两者之间的配置关系之外,还包含了一层运行关系:DispatcherServlet负责对WebApplicationContext进行初始化,而初始化的依据,就是这个SpringMVC的核心配置文件。所以,SpringMVC的核心配置文件的内容解读将揭开整个SpringMVC初始化主线的全部秘密。

如果我们把这个结论与上一个结论结合起来来看,也正因为SpringMVC的核心配置文件使用了与Spring Framework相同的格式,才使其成为DispatcherServlet驾驭Spring的窗口。

downpour 写道
结论 SpringMVC的核心配置文件是SpringMVC中所有组件的定义窗口,通过它我们可以指定整个SpringMVC的行为方式。

这个结论告诉了我们SpringMVC核心配置文件在整个框架中的作用。组件行为模式的多样化,决定了我们必须借助一个容器(WebApplicationContext)来进行统一的管理。而SpringMVC的核心配置文件,就是我们进行组件管理的窗口。

核心配置文件概览

说了那么多有关SpringMVC核心配置文件的结论,我们不妨来看一下这个配置文件的概况:

Xml代码  收藏代码
  1. <?xml version=“1.0” encoding=“UTF-8”?>  
  2. <beans xmlns=“http://www.springframework.org/schema/beans”  
  3.        xmlns:mvc=“http://www.springframework.org/schema/mvc”  
  4.        xmlns:context=“http://www.springframework.org/schema/context”  
  5.        xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”  
  6.        xsi:schemaLocation=”http://www.springframework.org/schema/beans  
  7.             http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
  8.             http://www.springframework.org/schema/context   
  9.             http://www.springframework.org/schema/context/spring-context-3.1.xsd  
  10.             http://www.springframework.org/schema/mvc  
  11.             http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd”>  
  12.   
  13.     <!– Enables the Spring MVC @Controller programming model –>  
  14.     <mvc:annotation-driven />  
  15.       
  16.     <context:component-scan base-package=“com.demo2do.sample.web.controller” />  
  17.       
  18.     <!– Handles HTTP GET requests for /static/** by efficiently serving up static resources in the ${webappRoot}/static/ directory –>  
  19.     <mvc:resources mapping=“/static/**” location=“/static/” />  
  20.   
  21.     <bean class=“org.springframework.web.servlet.view.InternalResourceViewResolver”>    
  22.         <property name=“prefix” value=“/” />    
  23.         <property name=“suffix” value=“.jsp” />    
  24.     </bean>    
  25.   
  26. </beans>  

这是一个非常典型的SpringMVC核心配置文件。虽然我们在这里几乎对每一段重要的配置都做了注释,不过可能对于毫无SpringMVC开发经验的读者来说,这段配置基本上还无法阅读。所以接下来,我们就试图对这个文件中的一些细节加以说明。

【头部声明】

配置文件中首先进入我们眼帘的是它的头部的一大段声明:

Xml代码  收藏代码
  1. <?xml version=“1.0” encoding=“UTF-8”?>  
  2. <beans xmlns=“http://www.springframework.org/schema/beans”  
  3.        xmlns:mvc=“http://www.springframework.org/schema/mvc”  
  4.        xmlns:context=“http://www.springframework.org/schema/context”  
  5.        xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”  
  6.        xsi:schemaLocation=”http://www.springframework.org/schema/beans  
  7.             http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
  8.             http://www.springframework.org/schema/context   
  9.             http://www.springframework.org/schema/context/spring-context-3.1.xsd  
  10.             http://www.springframework.org/schema/mvc  
  11.             http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd”>  
  12.       ……  
  13. </beans>  

这个部分是整个SpringMVC核心配置文件的关键所在。这一段声明,被称之为Schema-based XML的声明部分。有关Schema-based XML的概念,读者可以参考Spring官方的reference:

Appendix C. XML Schema-based configuration

Appendix D. Extensible XML authoring

为了帮助读者快速理解,我们稍后会专门开辟章节针对Schema-based XML的来龙去脉进行讲解。

【组件定义】

除了头部声明部分的其他配置部分,就是真正的组件定义部分。在这个部分中,我们可以看到两种不同类型的配置定义模式:

1. 基于Schema-based XML的配置定义模式

Xml代码  收藏代码
  1. <mvc:annotation-driven />  

2. 基于Traditional XML的配置定义模式

Xml代码  收藏代码
  1. <bean class=“org.springframework.web.servlet.view.InternalResourceViewResolver”>    
  2.        <property name=“prefix” value=“/” />    
  3.        <property name=“suffix” value=“.jsp” />    
  4. lt;/bean>  

两种不同的组件定义模式,其目的是统一的:对SpringMVC中的组件进行声明,指定组件的行为方式。

虽然两种不同的组件定义模式的外在表现看上去有所不同,但是SpringMVC在对其进行解析时最终都会将其转化为组件的定义而加载到WebApplicationContext之中进行管理。所以我们需要理解的是蕴藏在配置背后的目的而非配置本身的形式。

至于这两种不同的配置形式之间的关系,我们稍后会在Schema-based XML的讲解中详细展开。

Schema-based XML

【基本概念】

Schema-based XML本身并不是SpringMVC或者Spring Framework独创的一种配置模式。我们可以看看W3C对于其用途的一个大概解释:

W3C 写道
The purpose of an XSD schema is to define and describe a class of XML documents by using schema components to constrain and document the meaning, usage and relationships of their constituent parts: datatypes, elements and their content and attributes and their values. Schemas can also provide for the specification of additional document information, such as normalization and defaulting of attribute and element values. Schemas have facilities for self-documentation. Thus, XML Schema Definition Language: Structures can be used to define, describe and catalogue XML vocabularies for classes of XML documents.

这个解释稍微有点抽象。所以我们可以来看看Spring官方reference对于引入Schema-based XML的说法:

Spring Reference 写道
The central motivation for moving to XML Schema based configuration files was to make Spring XML configuration easier. The ‘classic’ <bean/>-based approach is good, but its generic-nature comes with a price in terms of configuration overhead.

也就是说,我们引入Schema-based XML是为了对Traditional的XML配置形式进行简化。通过Schema的定义,把一些原本需要通过几个bean的定义或者复杂的bean的组合定义的配置形式,用另外一种简单而可读的配置形式呈现出来。

所以,我们也可以由此得出一些有用的推论:

downpour 写道
Schema-based XML可以代替Traditional的XML配置形式,在Spring容器中进行组件的定义。

这里的代替一词非常重要,这就意味着传统的XML配置形式在这里会被颠覆,我们在对Schema-based XML进行解读时,需要使用一种全新的语义规范来理解。

downpour 写道
Schema-based XML可以极大改善配置文件的可读性并且缩小配置文件的规模。

这是从引入Schema-based XML的目的反过来得出的推论。因为如果引入Schema-based XML之后,整个配置变得更加复杂,那么Schema-based XML的引入也就失去了意义。

同时,笔者在这里需要特别强调的是Schema-based XML的引入,实际上是把原本静态的配置动态化、过程化。有关这一点,我们稍后会有说明。

【引入目的】

在早期的Spring版本中,只有Traditional XML一种组件定义模式。当时,XML作为Java最好的朋友,自然而然在整个框架中起到了举足轻重的作用。根据Spring的设计原则,所有纳入 WebApplicationContext中管理的对象,都被映射为XML中的一个<bean>节点,通过对于<bean>节 点的一个完整描述,我们可以有效地将整个应用程序中所有的对象都纳入到一个统一的容器中进行管理。

这种统一化的描述,带来的是管理上的便利,不过同时也带来了逻辑上的困扰。因为统一的节点,降低了配置的难度,我们几乎只需要 将<bean>节点与Java的对象模型对应起来即可。(有一定经验的Spring程序员可以回忆一下,我们在编写Spring配置文件时, 是否也是一个将配置选项与Java对象中属性或者方法对应起来的过程)但是这样的配置形式本身并不具备逻辑语义,也就是说我们无法非常直观地看出某一个特 定的<bean>定义,从逻辑上它到底想说明什么问题?

这也就是后来Schema-based XML开始被引入并流行开来的重要原因。从形式上看,Schema-based XML相比较Traditional XML至少有三个方面的优势:

  • namespace —— 拥有很明确的逻辑分类
  • element —— 拥有很明确的过程语义
  • attributes —— 拥有很简明的配置选项

这三方面的优势,我们可以用一幅图来进行说明:

在图中,我们分别用上下两层来说明某一个配置节点的结构名称以及它们的具体作用。由此可见,Schema-based XML中的配置节点拥有比较鲜明的功能特性,通过namespace、element和attributes这三大元素之间的配合,共同完成对一个动态过程的描述。

例如,<mvc:annotation-driven />这段配置想要表达的意思,就是在mvc的空间内实现Annotation驱动的配置方式。其中,mvc表示配置的有效范 围,annotation-driven则表达了一个动态的过程,实际的逻辑含义是:整个SpringMVC的实现是基于Annotation模式,请为 我注册相关的行为模式。

这种配置方式下,可读性大大提高:我们无需再去理解其中的实现细节。同时,配置的简易性也大大提高:我们甚至不用去关心哪些bean被定义了。

所以总体来说,Schema-based XML的引入,对于配置的简化是一个极大的进步。

【构成要素】

在Spring中,一个Schema-based XML有两大构成要素:过程实现配置定义

先谈谈过程实现。所谓过程实现,其实就是我们刚才所举的那个例子中,实现实际背后逻辑的过程。这个过程由两个Java接口来进行表述:

  • NamespaceHandler —— 对Schema定义中namespace的逻辑处理接口
  • BeanDefinitionParser —— 对Schema定义中element的逻辑处理接口

很显然,NamespaceHandler是入口程序,它包含了所有的属于该namespace定义下所有element的处理调 用,所以BeanDefinitionParser的实现就成为了NamespaceHandler的调用对象了。这一点,我们可以通过 NamesapceHandler的MVC实现类来加以证明:

Java代码  收藏代码
  1. public void init() {  
  2.     registerBeanDefinitionParser(“annotation-driven”new AnnotationDrivenBeanDefinitionParser());  
  3.     registerBeanDefinitionParser(“default-servlet-handler”new DefaultServletHandlerBeanDefinitionParser());  
  4.     registerBeanDefinitionParser(“interceptors”new InterceptorsBeanDefinitionParser());         
  5.     registerBeanDefinitionParser(“resources”new ResourcesBeanDefinitionParser());  
  6.     registerBeanDefinitionParser(“view-controller”new ViewControllerBeanDefinitionParser());  
  7. }  

我们可以看到,MvcNamespaceHandler的执行,只不过依次调用了不同的BeanDefinitionParser的实现类而已, 而每一个BeanDefinitionParser的实现,则对应于Schema定义中的element逻辑处理。例 如,AnnotationDrivenBeanDefinitionParser对应于:<mvc:annotation-driven />这个element实现;ResourcesBeanDefinitionParser则对应于<mvc:resources />的实现等等。

所以,要具体了解每个element的行为过程,只要研究每一个BeanDefinitionParser的实现类即可。我们以整个MVC空间中 最重要的一个节点<mvc:annotation-driven />为例,对AnnotationDrivenBeanDefinitionParser进行说明,其源码如下:

Java代码  收藏代码
  1. public BeanDefinition parse(Element element, ParserContext parserContext) {  
  2.     Object source = parserContext.extractSource(element);  
  3.   
  4.     CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);  
  5.     parserContext.pushContainingComponent(compDefinition);  
  6.   
  7.     RootBeanDefinition methodMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);  
  8.     methodMappingDef.setSource(source);  
  9.     methodMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  10.     methodMappingDef.getPropertyValues().add(“order”0);  
  11.     String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(methodMappingDef);  
  12.   
  13.     RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);  
  14.     RuntimeBeanReference validator = getValidator(element, source, parserContext);  
  15.     RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);  
  16.   
  17.     RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);  
  18.     bindingDef.setSource(source);  
  19.     bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  20.     bindingDef.getPropertyValues().add(“conversionService”, conversionService);  
  21.     bindingDef.getPropertyValues().add(“validator”, validator);  
  22.     bindingDef.getPropertyValues().add(“messageCodesResolver”, messageCodesResolver);  
  23.   
  24.     ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);  
  25.     ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);  
  26.     ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);  
  27.           
  28.     RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);  
  29.     methodAdapterDef.setSource(source);  
  30.     methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  31.     methodAdapterDef.getPropertyValues().add(“webBindingInitializer”, bindingDef);  
  32.     methodAdapterDef.getPropertyValues().add(“messageConverters”, messageConverters);  
  33.     if (element.hasAttribute(“ignoreDefaultModelOnRedirect”)) {  
  34.         Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute(“ignoreDefaultModelOnRedirect”));  
  35.   
  36. methodAdapterDef.getPropertyValues().add(“ignoreDefaultModelOnRedirect”, ignoreDefaultModel);  
  37.     }  
  38.     if (argumentResolvers != null) {  
  39.         methodAdapterDef.getPropertyValues().add(“customArgumentResolvers”, argumentResolvers);  
  40.     }  
  41.     if (returnValueHandlers != null) {  
  42.             methodAdapterDef.getPropertyValues().add(“customReturnValueHandlers”, returnValueHandlers);  
  43.     }  
  44.     String methodAdapterName = parserContext.getReaderContext().registerWithGeneratedName(methodAdapterDef);  
  45.   
  46.     RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);  
  47.     csInterceptorDef.setSource(source);  
  48.     csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);  
  49.     RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);  
  50.     mappedCsInterceptorDef.setSource(source);  
  51.         mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  52.         mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);  
  53.         mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);  
  54.     String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);  
  55.   
  56.     RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);  
  57.     methodExceptionResolver.setSource(source);  
  58.         methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  59.         methodExceptionResolver.getPropertyValues().add(“messageConverters”, messageConverters);  
  60.         methodExceptionResolver.getPropertyValues().add(“order”0);  
  61.     String methodExceptionResolverName =  
  62. parserContext.getReaderContext().registerWithGeneratedName(methodExceptionResolver);  
  63.   
  64.     RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);  
  65.     responseStatusExceptionResolver.setSource(source);  
  66.         responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  67.         responseStatusExceptionResolver.getPropertyValues().add(“order”1);  
  68.     String responseStatusExceptionResolverName =  
  69.                 parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);  
  70.   
  71.     RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);  
  72.     defaultExceptionResolver.setSource(source);  
  73.         defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  74.         defaultExceptionResolver.getPropertyValues().add(“order”2);  
  75.     String defaultExceptionResolverName =  
  76.                 parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);  
  77.   
  78.     parserContext.registerComponent(new BeanComponentDefinition(methodMappingDef, methodMappingName));  
  79.     parserContext.registerComponent(new BeanComponentDefinition(methodAdapterDef, methodAdapterName));  
  80.     parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExceptionResolverName));  
  81.     parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));  
  82.     parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));  
  83.     parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));  
  84.   
  85.     // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not “turned off”   
  86.         MvcNamespaceUtils.registerDefaultComponents(parserContext, source);  
  87.   
  88.     parserContext.popAndRegisterContainingComponent();  
  89.   
  90.     return null;  
  91. }  

整个过程看上去稍显凌乱,不过我们发现其中围绕的一条主线就是:使用编程的方式来对bean进行注册。也就是说,<mvc:annotation-driven />这样一句配置,顶上了我们如此多的bean定义。难怪Schema-based XML被誉为是简化XML配置的绝佳帮手了。

有了过程实现,我们再来谈谈配置定义。配置定义的目的非常简单,就是通过一些配置文件,将上述的过程实现类串联起来,从而完成整个Schema-based XML的定义。

整个配置定义,也分为两个部分:

  • Schema定义 —— 一个xsd文件,描述整个Schema空间中element和attribute的定义
  • 注册配置文件 —— 由META-INF/spring.handlers和META-INF/spring.schemas构成,用以注册Schema和Handler

Schema定义是由一个xsd文件完成的。这个文件在Spring发布的时候同时发布在网络上。例如SpringMVC的Schema定义,就发布在这个地址:

http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

同时,这个地址在Spring的发布包中也存有一个备份。这个备份位于SpringMVC的分发包spring-webmvc的JAR包之中。

这样做的好处在于,我们在对Schema进行引用时,可以通过本地寻址来加快加载速度。

:如果我们回顾一下之前的 核心配置文件中的头部声明部分。其中的xsi:schemaLocation声明就是用于指定映射于本地的XSD文件。所以 xsi:schemaLocation的定义不是必须的,不过声明它能够使Spring自动查找本地的缓存来进行schema的寻址。

我们在这里不对XSD文件做过多的内容分析,因为其中不外乎是对element的定义、attributes的定义等等。这些内容是我们进行Schema-based XML配置的核心基础。

配置定义的另外一个元素构成是META-INF/spring.handlers和META-INF/spring.schemas这两个文件。 它们同样位于SpringMVC的分发包下。当我们在XML的头部声明中引用了相关的Schema定义之后,Spring会自动查找 spring.schemas和spring.handlers的定义,根据其中指定的NamespaceHandler实现类加载执行。

有关这个过程,我们在之后的日志分析中还会涉及。

初始化日志的再分析

有了Schema Based XML的相关知识,就可以对DispatcherServlet的初始化启动日志做进一步的详细分析。而这次的分析,我们试图弄清楚以下问题:

  • Where —— 组件的声明在哪里?
  • How —— 组件是如何被注册的?
  • What —— 究竟哪些组件被注册了?

对于这三个问题的研究,我们需要结合日志和Schema based XML的运行机理来共同进行分析。

引用
[main] INFO /sample – Initializing Spring FrameworkServlet ‘dispatcher’
19:49:48,670  INFO XmlWebApplicationContext:495 – Refreshing WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Thu Feb 16 19:49:48 CST 2012]; parent: Root WebApplicationContext
19:49:48,674  INFO XmlBeanDefinitionReader:315 – Loading XML bean definitions from class path resource [web/applicationContext-dispatcher.xml]

## Schema定位和加载 (开始) ##

19:49:48,676 Debug DefaultDocumentLoader:72 – Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
19:49:48,678 Debug PluggableSchemaResolver:140 – Loading schema mappings from [META-INF/spring.schemas]
19:49:48,690 DEBUG PluggableSchemaResolver:118 – Found XML schema [http://www.springframework.org/schema/beans/spring-beans-3.1.xsd] in classpath: org/springframework/beans/factory/xml/spring-beans-3.1.xsd
19:49:48,710 DEBUG PluggableSchemaResolver:118 – Found XML schema [http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd] in classpath: org/springframework/web/servlet/config/spring-mvc-3.1.xsd
19:49:48,715 DEBUG PluggableSchemaResolver:118 – Found XML schema [http://www.springframework.org/schema/tool/spring-tool-3.1.xsd] in classpath: org/springframework/beans/factory/xml/spring-tool-3.1.xsd
19:49:48,722 DEBUG PluggableSchemaResolver:118 – Found XML schema [http://www.springframework.org/schema/context/spring-context-3.1.xsd] in classpath: org/springframework/context/config/spring-context-3.1.xsd

## Schema定位和加载 (结束) ##

## NamespaceHandler执行阶段 (开始) ##

19:49:48,731 DEBUG DefaultBeanDefinitionDocumentReader:108 – Loading bean definitions
19:49:48,742 DEBUG DefaultNamespaceHandlerResolver:156 – Loaded NamespaceHandler mappings: {…}

19:49:48,886 DEBUG PathMatchingResourcePatternResolver:550 – Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller]

19:49:48,896 DEBUG XmlBeanDefinitionReader:216 – Loaded 18 bean definitions from location pattern [classpath:web/applicationContext-dispatcher.xml]
19:49:48,897 DEBUG XmlWebApplicationContext:525 – Bean factory for WebApplicationContext for namespace ‘dispatcher-servlet’: org.springframework.beans.factory.support.DefaultListableBeanFactory@495c998a: defining beans [[
1. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,
2. org.springframework.format.support.FormattingConversionServiceFactoryBean#0,
3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,
4. org.springframework.web.servlet.handler.MappedInterceptor#0,
5. org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,
6. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,
7. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,
8. org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
9. org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
10. org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
11. blogController,
12. userController,
13. org.springframework.context.annotation.internalConfigurationAnnotationProcessor,
14. org.springframework.context.annotation.internalAutowiredAnnotationProcessor,
15. org.springframework.context.annotation.internalRequiredAnnotationProcessor,
16. org.springframework.context.annotation.internalCommonAnnotationProcessor,
17. org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0,
18. org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0

]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@6602e323
19:49:48,949 DEBUG XmlWebApplicationContext:794 – Unable to locate MessageSource with name ‘messageSource’: using default [org.springframework.context.support.DelegatingMessageSource@4b2922f6]
19:49:48,949 DEBUG XmlWebApplicationContext:818 – Unable to locate ApplicationEventMulticaster with name ‘applicationEventMulticaster’: using default [org.springframework.context.event.SimpleApplicationEventMulticaster@79b66b06]
19:49:48,949 DEBUG UiApplicationContextUtils:85 – Unable to locate ThemeSource with name ‘themeSource’: using default [org.springframework.ui.context.support.DelegatingThemeSource@372c9557]
19:49:49,154 DEBUG RequestMappingHandlerMapping:98 – Looking for request mappings in application context: WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Thu Feb 16 19:49:48 CST 2012]; parent: Root WebApplicationContext
19:49:49,175  INFO RequestMappingHandlerMapping:188 – Mapped “{[/blog],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto
public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.BlogController.index()
19:49:49,177  INFO RequestMappingHandlerMapping:188 – Mapped “{[/register],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto
public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.UserController.register(com.demo2do.sample.entity.User)
19:49:49,180  INFO RequestMappingHandlerMapping:188 – Mapped “{[/login],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto
public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.UserController.login(java.lang.String,java.lang.String)
19:49:49,632 DEBUG BeanNameUrlHandlerMapping:71 – Looking for URL mappings in application context: WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Thu Feb 16 19:49:48 CST 2012]; parent: Root WebApplicationContext
19:49:49,924  INFO SimpleUrlHandlerMapping:314 – Mapped URL path [/static/**] onto handler ‘org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0’

## NamespaceHandler执行阶段 (结束) ##

19:49:49,956 DEBUG DispatcherServlet:627 – Unable to locate RequestToViewNameTranslator with name ‘viewNameTranslator’: using default [org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@4d16318b]
19:49:49,980 DEBUG DispatcherServlet:667 – No ViewResolvers found in servlet ‘dispatcher’: using default
19:49:49,986 DEBUG DispatcherServlet:689 – Unable to locate FlashMapManager with name ‘flashMapManager’: using default [org.springframework.web.servlet.support.DefaultFlashMapManager@1816daa9]
19:49:49,986 DEBUG DispatcherServlet:523 – Published WebApplicationContext of servlet ‘dispatcher’ as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher]
19:49:49,986  INFO DispatcherServlet:463 – FrameworkServlet ‘dispatcher’: initialization completed in 1320 ms
19:49:49,987 DEBUG DispatcherServlet:136 – Servlet ‘dispatcher’ configured successfully

在上面的启动日志中,笔者还是把不同的日志功能使用不同的颜色进行了区分。这里进行逐一分析:

1. 黑色加粗标记区域 —— 容器的启动和结束标志

这个部分的日志比较明显,位于容器的启动阶段和结束阶段,在之前的讨论中我们已经分析过,这里不再重复。

2. 黄色注释段落 —— Schema定位和加载

这个部分的日志反应出刚才我们所分析的Schema-based XML的工作原理。这是其中的第一步:读取META-INF/spring.schemas的内容,加载schema定义。然后找到相应的NamespaceHandler,执行其实现类。

3. 蓝色注释部分 —— NamespaceHandler执行阶段

这个部分的日志,可以帮助我们回答本节一开始所提出的两个问题。绝大多数的组件,都是在BeanDefinitionParser的实现类中使用编程的方式注册的。

4. 红色标记区域 —— 组件注册细节

这个部分的日志区域彻底回答了本节一开始所提出的最后一个问题:一共有18个组件被注册,就是红色标记的那18个bean。

小结

本文所涉及到的话题,主要围绕着SpringMVC的核心配置问题展开。读者可以将本文作为上一篇文章的续篇,将两者结合起来阅读。因为从宏观上说,本文的话题实际上也属于初始化主线的一个部分。

[转载]SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 - downpour - ITeye技术网站

mikel阅读(1021)

[转载]SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 – downpour – ITeye技术网站.

本文是专栏文章(SpringMVC深度探险)系列的文章之一,博客地址为:http://downpour.iteye.com/blog/1341459

在上一篇文章中,我们给出了构成SpringMVC应用程序的三要素以及三要素的设计过程。让我们来归纳一下整个设计过程中的一些要点:

  • SpringMVC将Http处理流程抽象为一个又一个处理单元
  • SpringMVC定义了一系列组件(接口)与所有的处理单元对应起来
  • SpringMVC由DispatcherServlet贯穿始终,并将所有的组件串联起来

在整个过程中,组件和DispatcherServlet总是维持着一个相互支撑的关系:

  • DispatcherServlet —— 串联起整个逻辑主线,是整个框架的心脏
  • 组件 —— 逻辑处理单元的程序化表示,起到承上启下的作用,是SpringMVC行为模式的实际承载者

在本系列接下来的两篇文章中,我们将分别讨论DispatcherServlet和组件的相关内容。本文讨论DispatcherServlet,而下一篇则重点分析组件。

有关DispatcherServlet,我们想从构成DispatcherServlet的体系结构入手,再根据不同的逻辑主线分别加以分析,希望能够帮助读者整理出学习SpringMVC核心类的思路。

DispatcherServlet的体系结构

通过不同的角度来观察DispatcherServlet会得到不同的结论。我们在这里选取了三个不同的角度:运行特性、继承结构和数据结构。

【运行主线】

从DispatcherServlet所实现的接口来看,DispatcherServlet的核心本质:是一个Servlet。这个结论似乎很幼稚,不过这个幼稚的结论却蕴含了一个对整个框架都至关重要的内在原则:Servlet可以根据其特性进行运行主线的划分

根据Servlet规范的定义,Servlet中的两大核心方法init方法和service方法,它们的运行时间和触发条件都截然不同:

1. init方法

在整个系统启动时运行,且只运行一次。因此,在init方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作可能包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。

2. service方法

在整个系统运行的过程中处于侦听模式,侦听并处理所有的Web请求。因此,在service及其相关方法中,我们看到的则是对Http请求的处理流程。

因而在这里,Servlet的这一特性就被SpringMVC用于对不同的逻辑职责加以划分,从而形成两条互不相关的逻辑运行主线:

  • 初始化主线 —— 负责对SpringMVC的运行要素进行初始化
  • Http请求处理主线 —— 负责对SpringMVC中的组件进行逻辑调度完成对Http请求的处理

对于一个MVC框架而言,运行主线的划分非常重要。因为只有弄清楚不同的运行主线,我们才能针对不同的运行主线采取不同的研究策略。而我们在这个系列中的绝大多数分析的切入点,也是围绕着不同的运行主线进行的。

:SpringMVC运行 主线的划分依据是Servlet对象中不同方法的生命周期。事实上,几乎所有的MVC都是以此为依据来进行运行主线的划分。这进一步可以证明所有的MVC 框架的核心基础还是Servlet规范,而设计理念的差异也导致了不同的框架走向了完全不同的发展道路。

【继承结构】

除了运行主线的划分以外,我们再关注一下DispatcherServlet的继承结构:

在这个继承结构中,我们可以看到DispatcherServlet在其继承树中包含了2个Spring的支持类:HttpServletBean和FrameworkServlet。我们分别来讨论一下这两个Spring的支持类在这里所起到的作用。

HttpServletBean是Spring对于Servlet最低层次的抽象。在这一层抽象中,Spring会将这个Servlet视作是一个Spring的bean,并将init-param中的值作为bean的属性注入进来:

Java代码  收藏代码
  1. public final void init() throws ServletException {  
  2.     if (logger.isDebugEnabled()) {  
  3.         logger.Debug(“Initializing servlet ‘” + getServletName() + “‘”);  
  4.     }  
  5.   
  6.     // Set bean properties from init parameters.  
  7.     try {  
  8.         PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);  
  9.         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);  
  10.         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());  
  11.         bw.registerCustomEditor(Resource.classnew ResourceEditor(resourceLoader, this.environment));  
  12.         initBeanWrapper(bw);  
  13.         bw.setPropertyValues(pvs, true);  
  14.     }  
  15.     catch (BeansException ex) {  
  16.         logger.error(“Failed to set bean properties on servlet ‘” + getServletName() + “‘”, ex);  
  17.         throw ex;  
  18.     }  
  19.   
  20.     // Let subclasses do whatever initialization they like.  
  21.     initServletBean();  
  22.   
  23.     if (logger.isDebugEnabled()) {  
  24.         logger.debug(“Servlet ‘” + getServletName() + “‘ configured successfully”);  
  25.     }  
  26. }  

从源码中,我们可以看到HttpServletBean利用了Servlet的init方法的执行特性,将一个普通的Servlet与 Spring的容器联系在了一起。在这其中起到核心作用的代码是:BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);将当前的这个Servlet类转化为一个 BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入。BeanWrapper的相关知识属于Spring Framework的内容,我们在这里不做详细展开,读者可以具体参考HttpServletBean的注释获得更多的信息。

FrameworkServlet则是在HttpServletBean的基础之上的进一步抽象。通过FrameworkServlet真正初始化了一个Spring的容器(WebApplicationContext),并引入到Servlet对象之中:

Java代码  收藏代码
  1. protected final void initServletBean() throws ServletException {  
  2.     getServletContext().log(“Initializing Spring FrameworkServlet ‘” + getServletName() + “‘”);  
  3.     if (this.logger.isInfoEnabled()) {  
  4.         this.logger.info(“FrameworkServlet ‘” + getServletName() + “‘: initialization started”);  
  5.     }  
  6.     long startTime = System.currentTimeMillis();  
  7.   
  8.     try {  
  9.         this.webApplicationContext = initWebApplicationContext();  
  10.         initFrameworkServlet();  
  11.     } catch (ServletException ex) {  
  12.         this.logger.error(“Context initialization failed”, ex);  
  13.         throw ex;  
  14.     } catch (RuntimeException ex) {  
  15.         this.logger.error(“Context initialization failed”, ex);  
  16.         throw ex;  
  17.     }  
  18.   
  19.     if (this.logger.isInfoEnabled()) {  
  20.         long elapsedTime = System.currentTimeMillis() – startTime;  
  21.         this.logger.info(“FrameworkServlet ‘” + getServletName() + “‘: initialization completed in “ +  
  22.                 elapsedTime + ” ms”);  
  23.     }  
  24. }  

上面的这段代码就是FrameworkServlet初始化的核心代码。从中我们可以看到这个FrameworkServlet将调用其内部的方 法initWebApplicationContext()对Spring的容器(WebApplicationContext)进行初始化。同 时,FrameworkServlet还暴露了与之通讯的结构可供子类调用:

Java代码  收藏代码
  1. public abstract class FrameworkServlet extends HttpServletBean {  
  2.   
  3.     /** WebApplicationContext for this servlet */  
  4.     private WebApplicationContext webApplicationContext;  
  5.   
  6.         // 这里省略了其他所有的代码  
  7.   
  8.     /** 
  9.      * Return this servlet’s WebApplicationContext. 
  10.      */  
  11.     public final WebApplicationContext getWebApplicationContext() {  
  12.         return this.webApplicationContext;  
  13.     }  
  14. }  

我们在这里暂且不对Spring容器(WebApplicationContext)的初始化过程详加探查,稍后我们会讨论一些WebApplicationContext初始化过程中的配置选项。不过读者可以在这里体会到:FrameworkServlet 在其内部初始化了一个Spring的容器(WebApplicationContext)并暴露了相关的操作接口,因而继承自 FrameworkServlet的DispatcherServlet,也就直接拥有了与WebApplicationContext进行通信的能力。

通过对DispatcherServlet继承结构的研究,我们可以明确:

downpour 写道
结论 DispatcherServlet的继承体系架起了DispatcherServlet与Spring容器进行沟通的桥梁。

【数据结构】

在上一篇文章中,我们曾经提到过DispatcherServlet的数据结构:

我们可以把在上面这张图中所构成DispatcherServlet的数据结构主要分为两类(我们在这里用一根分割线将其分割开来):

  • 配置参数 —— 控制SpringMVC组件的初始化行为方式
  • 核心组件 —— SpringMVC的核心逻辑处理组件

可以看到,这两类数据结构都与SpringMVC中的核心要素组件有关。因此,我们可以得出这样一个结论:

downpour 写道
结论 组件是整个DispatcherServlet的灵魂所在:它不仅是初始化主线中的初始化对象,同样也是Http请求处理主线中的逻辑调度载体。

:我们可以看到被我们划为 配置参数的那些变量都是boolean类型的,它们将在DispatcherServlet的初始化主线中起到一定的作用,我们在之后会使用源码进行说 明。而这些boolean值可以通过web.xml中的init-param值进行设定覆盖(这是由HttpServletBean的特性带来的)。

SpringMVC的运行体系

DispatcherServlet继承结构和数据结构,实际上表述的是DispatcherServlet与另外两大要素之间的关系:

  • 继承结构 —— DispatcherServlet与Spring容器(WebApplicationContext)之间的关系
  • 数据结构 —— DispatcherServlet与组件之间的关系

所以,其实我们可以这么说:SpringMVC的整个运行体系,是由DispatcherServlet、组件和容器这三者共同构成的。

在这个运行体系中,DispatcherServlet是逻辑处理的调度中心,组件则是被调度的操作对象。而容器在这里所起到的作用,是协助DispatcherServlet更好地对组件进行管理。这就相当于一个工厂招了一大批的工人,并把工人划分到一个统一的工作车间而便于管理。在工厂要进行生产活动时,只需要从工作车间把工人分派到相应的生产流水线上即可。

笔者在这里引用Spring官方reference中的一幅图,对三者之间的关系进行简单的描述:

:在这幅图中,我们除了看到在图的左半边DispatcherServlet、组件和容器这三者之间的调用关系以外,还可以看到SpringMVC的运行体系与其它运行体系之间存在着关系。有关这一点,我们在之后的讨论中会详细展开。

既然是三个元素之间的关系表述,我们必须以两两关系的形式进行归纳:

  • DispatcherServlet – 容器 —— DispatcherServlet对容器进行初始化
  • 容器 – 组件 —— 容器对组件进行全局管理
  • DispatcherServlet – 组件 —— DispatcherServlet对组件进行逻辑调用

值得注意的是,在上面这幅图中,三大元素之间的两两关系其实表现得并不明显,尤其是“容器 – 组件”和“DispatcherServlet – 组件”之间的关系。这主要是由于Spring官方reference所给出的这幅图是一个静态的关系表述,如果从动态的观点来对整个过程加以审视,我们就 不得不将SpringMVC的运行体系与之前所提到的运行主线联系在一起,看看这些元素在不同的逻辑主线中所起到的作用。

接下来,我们就分别看看DispatcherServlet的两条运行主线。

DispatcherServlet的初始化主线

对于DispatcherServlet的初始化主线,我们首先应该明确几个基本观点:

  • 初始化主线的驱动要素 —— servlet中的init方法
  • 初始化主线的执行次序 —— HttpServletBean -> FrameworkServlet -> DispatcherServlet
  • 初始化主线的操作对象 —— Spring容器(WebApplicationContext)和组件

这三个基本观点,可以说是我们对之前所有讨论的一个小结。明确了这些内容,我们就可以更加深入地看看DispatcherServlet初始化主线的过程:

在这幅图中,我们站在一个动态的角度将DispatcherServlet、容器(WebApplicationContext)和组件这三者之 间的关系表述出来,同时给出了这三者之间的运行顺序和逻辑过程。读者或许对其中的绝大多数细节还很陌生,甚至有一种无从下手的感觉。这没有关系,大家可以 首先抓住图中的执行线,回忆一下之前有关DispatcherServlet的继承结构和数据结构的内容。接下来,我们就图中的内容逐一进行解释。

【WebApplicationContext的初始化】

之前我们讨论了DispatcherServlet对于WebApplicationContext的初始化是在FrameworkServlet中完成的,不过我们并没有细究其中的细节。在默认情况下,这个初始化过程是由web.xml中的入口程序配置所驱动的:

Xml代码  收藏代码
  1. <!– Processes application requests –>  
  2. <servlet>  
  3.     <servlet-name>dispatcher</servlet-name>  
  4.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  5.     <load-on-startup>1</load-on-startup>  
  6. </servlet>  
  7.           
  8. <servlet-mapping>  
  9.     <servlet-name>dispatcher</servlet-name>  
  10.     <url-pattern>/**</url-pattern>  
  11. </servlet-mapping>  

我们已经不止一次提到过这段配置,不过在这之前都没有对这段配置做过什么很详细的分析。事实上,这段入口程序的配置中隐藏了SpringMVC的 两大要素(核心分发器Dispatcher和核心配置文件[servlet-name]-servlet.xml)之间的关系表述:

downpour 写道
在默认 情况下,web.xml配置节点中<servlet-name>的值就是建立起核心分发器DispatcherServlet与核心配置文件 之间联系的桥梁。DispatcherServlet在初始化时会加载位置在/WEB-INF/[servlet-name]-servlet.xml的 配置文件作为SpringMVC的核心配置。

SpringMVC在这里采用了一个“命名约定”的方法进行关系映射,这种方法很廉价也很管用。以上面的配置为例,我们就必须在/WEB-INF /目录下,放一个名为dispatcher-servlet.xml的Spring配置文件作为SpringMVC的核心配置用以指定SpringMVC 的基本组件声明定义。

这看上去似乎有一点别扭,因为在实际项目中,我们通常喜欢把配置文件放在classpath下,并使用不同的package进行区分。例如,在基 于Maven的项目结构中,所有的配置文件应置于src/main/resources目录下,这样才比较符合配置文件统一化管理的最佳实践。

于是,Spring提供了一个初始化的配置选项,通过指定contextConfigLocation选项来自定义SpringMVC核心配置文件的位置:

Xml代码  收藏代码
  1. <!– Processes application requests –>  
  2. <servlet>  
  3.     <servlet-name>dispatcher</servlet-name>  
  4.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  5.     <init-param>  
  6.         <param-name>contextConfigLocation</param-name>  
  7.         <param-value>classpath:web/applicationContext-dispatcherServlet.xml</param-value>  
  8.     </init-param>  
  9.     <load-on-startup>1</load-on-startup>  
  10. </servlet>  
  11.           
  12. <servlet-mapping>  
  13.     <servlet-name>dispatcher</servlet-name>  
  14.     <url-pattern>/</url-pattern>  
  15. </servlet-mapping>  

这样一来,DispatcherServlet在初始化时,就会自动加载在classpath下,web这个package下名为 applicationContext-dispatcherServlet.xml的文件作为其核心配置并用以初始化容器 (WebApplicationContext)。

当然,这只是DispatcherServlet在进行WebApplicationContext初始化过程中的配置选项之一。我们可以在Spring的官方reference中找到相应的配置选项,有兴趣的读者可以参照reference的说明进行尝试:

所有的这些配置选项,实际上都是为了让DispatcherServlet对WebApplicationContext的初始化过程显得更加自 然。不过这只是完成了容器(WebApplicationContext)的构建工作,那么容器所管理的那些组件,又是如何进行初始化的呢?

downpour 写道
结论 SpringMVC核心配置文件中所有的bean定义,就是SpringMVC的组件定义,也是DispatcherServlet在初始化容器(WebApplicationContext)时,所要进行初始化的组件。

在上一篇文章我们谈到组件的时候就曾经提到,SpringMVC自身对于组件并未实现一套完整的管理机制,而是借用了Spring Framework核心框架中容器的概念,将所有的组件纳入到容器中进行管理。所以,SpringMVC的核心配置文件使用与传统Spring Framework相同的配置形式,而整个管理的体系也是一脉相承的。

:Spring3.0之后,单独为SpringMVC建立了专用的schema,从而使得我们可以使用Schema Based XML来对SpringMVC的组件进行定义。不过我们可以将其视作是传统Spring配置的一个补充,而不要过于纠结不同的配置格式。

我们知道,SpringMVC的组件是一个个的接口定义,当我们在SpringMVC的核心配置文件中定义一个组件时,使用的却是组件的实现类:

Xml代码  收藏代码
  1. <bean class=“org.springframework.web.servlet.view.InternalResourceViewResolver”>  
  2.     <property name=“prefix” value=“/” />  
  3.     <property name=“suffix” value=“.jsp” />  
  4. </bean>  

这也就是Spring管理组件的模式:用具体的实现类来指定接口的行为方式。不同的实现类,代表着不同的组件行为模式,它们在Spring容器中是可以共存的:

Xml代码  收藏代码
  1. <bean class=“org.springframework.web.servlet.view.InternalResourceViewResolver”>  
  2.     <property name=“prefix” value=“/” />  
  3.     <property name=“suffix” value=“.jsp” />  
  4. </bean>  
  5.   
  6. <bean class=“org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver”>  
  7.     <property name=“prefix” value=“/” />  
  8.     <property name=“suffix” value=“.ftl” />  
  9. </bean>  

所以,Spring的容器就像是一个聚宝盆,它只负责承载对象,管理好对象的生命周期,而并不关心一个组件接口到底有多少种实现类或者行为模式。 这也就是我们在上面那幅图中,画了多个HandlerMappings、HandlerAdapters和ViewResolvers的原因:一个组件的多种行为模式可以在容器中共存,容器将负责对这些实现类进行管理。而具体如何使用这些对象,则由应用程序自身来决定。

如此一来,我们可以大致概括一下WebApplicationContext初始化的两个逻辑层次:

  • DispatcherServlet负责对容器(WebApplicationContext)进行初始化。
  • 容器(WebApplicationContext)将读取SpringMVC的核心配置文件进行组件的实例化。

整个过程,我们把应用程序的日志级别调低,可以进行非常详细的观察:

引用
14:15:27,037 DEBUG StandardServletEnvironment:100 – Initializing new StandardServletEnvironment
14:15:27,128 DEBUG DispatcherServlet:115 – Initializing servlet ‘dispatcher’
14:15:27,438  INFO DispatcherServlet:444 – FrameworkServlet ‘dispatcher’: initialization started

14:15:27,449 DEBUG DispatcherServlet:572 – Servlet with name ‘dispatcher’ will try to create custom WebApplicationContext context of class ‘org.springframework.web.context.support.XmlWebApplicationContext’, using parent context [null]
1571 [main] INFO /sample – Initializing Spring FrameworkServlet ‘dispatcher’
14:15:27,505 DEBUG StandardServletEnvironment:100 – Initializing new StandardServletEnvironment
14:15:27,546  INFO XmlWebApplicationContext:495 – Refreshing WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy
14:15:27,689  INFO XmlBeanDefinitionReader:315 – Loading XML bean definitions from class path resource [web/applicationContext-dispatcherServlet.xml]
14:15:27,872 DEBUG PluggableSchemaResolver:140 – Loading schema mappings from [META-INF/spring.schemas]
14:15:28,442 DEBUG PathMatchingResourcePatternResolver:550 – Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller]
14:15:28,442 DEBUG PathMatchingResourcePatternResolver:612 – Searching directory [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/com/demo2do/sample/web/controller/**/*.class]
14:15:28,450 DEBUG PathMatchingResourcePatternResolver:351 – Resolved location pattern [classpath*:com/demo2do/sample/web/controller/**/*.class] to resources [file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class], file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]]
14:15:28,569 DEBUG ClassPathBeanDefinitionScanner:243 – Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class]
14:15:28,571 DEBUG ClassPathBeanDefinitionScanner:243 – Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]
14:15:28,634 DEBUG BeanDefinitionParserDelegate:497 – Neither XML ‘id’ nor ‘name’ specified – using generated bean name [org.springframework.web.servlet.view.InternalResourceViewResolver#0]
14:15:28,635 DEBUG XmlBeanDefinitionReader:216 – Loaded 19 bean definitions from location pattern [classpath:web/applicationContext-dispatcherServlet.xml]
14:15:28,635 DEBUG XmlWebApplicationContext:525 – Bean factory for WebApplicationContext for namespace ‘dispatcher-servlet’: org.springframework.beans.factory.support.DefaultListableBeanFactory@2321b59a: defining beans [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,blogController,userController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0,org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy
14:15:29,015 DEBUG RequestMappingHandlerMapping:98 – Looking for request mappings in application context: WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy
14:15:29,037  INFO RequestMappingHandlerMapping:188 – Mapped “{[/blog],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto public org.springframework.web.servlet.ModelAndView com.demo2do.station.web.controller.BlogController.index()
14:15:29,039  INFO RequestMappingHandlerMapping:188 – Mapped “{[/login],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto public org.springframework.web.servlet.ModelAndView com.demo2do.station.web.controller.UserController.login(java.lang.String,java.lang.String)
14:15:29,040 DEBUG DefaultListableBeanFactory:458 – Finished creating instance of bean ‘org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0’
14:15:29,460 DEBUG BeanNameUrlHandlerMapping:71 – Looking for URL mappings in application context: WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy
14:15:29,539 DEBUG DefaultListableBeanFactory:458 – Finished creating instance of bean ‘org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0’
14:15:29,540 DEBUG DefaultListableBeanFactory:217 – Creating shared instance of singleton bean ‘org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0’
14:15:29,555  INFO SimpleUrlHandlerMapping:314 – Mapped URL path [/static/**] onto handler ‘org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0’
14:15:29,556 DEBUG DefaultListableBeanFactory:458 – Finished creating instance of bean ‘org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0’
14:15:29,827 DEBUG DispatcherServlet:523 – Published WebApplicationContext of servlet ‘dispatcher’ as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher]
14:15:29,827  INFO DispatcherServlet:463 – FrameworkServlet ‘dispatcher’: initialization completed in 2389 ms
14:15:29,827 DEBUG DispatcherServlet:136 – Servlet ‘dispatcher’ configured successfully

4047 [main] INFO org.mortbay.log – Started SelectChannelConnector@0.0.0.0:8080
Jetty Server started, use 4267 ms

在这段启动日志(笔者进行了一定删减)中,笔者刻意将WebApplicationContext的初始化的标志日志使用红色的标进行区分,而将 核心配置文件的读取位置和组件定义初始化的标志日志使用蓝色标记加以区分。相信有了这段日志的帮助,读者应该可以对 WebApplicationContext的初始化过程有了更加直观的认识。

:启动日志是我们研究SpringMVC的主要途径之一,之后我们还将反复使用这种方法对SpringMVC的运行过程进行研究。读者应该仔细品味每一条日志的作用,从而能够与之后的分析讲解呼应起来。

或许读者对WebApplicationContext对组件进行初始化的过程还有点困惑,大家不妨先将这个过程省略,把握住整个 DispatcherServlet的大方向。我们在之后的文章中,还将对SpringMVC的组件、这些组件的定义以及组件的初始化方式做进一步的分析 和探讨。

到此为止,图中顺着FrameworkServlet的那些箭头,我们已经交代清楚,读者可以回味一下整个过程。

【独立的WebApplicationContext体系】

独立的WebApplicationContext体系,是SpringMVC初始化主线中的一个非常重要的概念。回顾一下刚才曾经提到过的 DispatcherServlet、容器和组件三者之间的关系,我们在引用的那副官方reference的示意图中,实际上已经包含了这一层意思:

downpour 写道
结论 在DispatcherServlet初始化的过程中所构建的WebApplicationContext独立于Spring自身的所构建的其他WebApplicationContext体系而存在。

稍有一些Spring编程经验的程序员,对于下面的配置应该非常熟悉:

Xml代码  收藏代码
  1. <context-param>  
  2.     <param-name>contextConfigLocation</param-name>  
  3.     <param-value>classpath:context/applicationContext-*.xml</param-value>  
  4. </context-param>  
  5.       
  6. <listener>  
  7.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  8. </listener>  

在上面的代码中,我们定义了一个Listener,它会在整个Web应用程序启动的时候运行一次,并初始化传统意义上的Spring的容器。这也是一般情况下,当并不使用SpringMVC作为我们的表示层解决方案,却希望在我们的Web应用程序中使用Spring相关功能时所采取的一种配置方式。

如果我们要在这里引入SpringMVC,整个配置看上去就像这样:

Xml代码  收藏代码
  1. <context-param>  
  2.     <param-name>contextConfigLocation</param-name>  
  3.     <param-value>classpath:context/applicationContext-*.xml</param-value>  
  4. </context-param>  
  5.       
  6. <listener>  
  7.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  8. </listener>  
  9.       
  10. <!– Processes application requests –>  
  11. <servlet>  
  12.     <servlet-name>dispatcher</servlet-name>  
  13.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  14.     <init-param>  
  15.         <param-name>contextConfigLocation</param-name>  
  16.         <param-value>classpath:web/applicationContext-dispatcherServlet.xml</param-value>  
  17.     </init-param>  
  18.     <load-on-startup>1</load-on-startup>  
  19. </servlet>  
  20.           
  21. <servlet-mapping>  
  22.     <servlet-name>dispatcher</servlet-name>  
  23.     <url-pattern>/</url-pattern>  
  24. </servlet-mapping>  

在这种情况下,DispatcherServlet和ContextLoaderListener会分别构建不同作用范围的容器 (WebApplicationContext)。我们可以引入两个不同的概念来对其进行表述:ContextLoaderListener所初始化的容 器,我们称之为Root WebApplicationContext;而DispatcherServlet所初始化的容器,是SpringMVC WebApplicationContext

同样采取日志分析的方法,加入了ContextLoaderListener之后,整个启动日志变成了这样:

引用
[main] INFO /sample – Initializing Spring root WebApplicationContext
14:56:42,261  INFO ContextLoader:272 – Root WebApplicationContext: initialization started
14:56:42,343 DEBUG StandardServletEnvironment:100 – Initializing new StandardServletEnvironment
14:56:42,365  INFO XmlWebApplicationContext:495 – Refreshing Root WebApplicationContext: startup date [Mon Feb 06 14:56:42 CST 2012]; root of context hierarchy
14:56:42,441 DEBUG PathMatchingResourcePatternResolver:550 – Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\context]
14:56:42,442 DEBUG PathMatchingResourcePatternResolver:612 – Searching directory [D:\Work\Demo2do\Sample\target\classes\context] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/context/applicationContext-*.xml]
14:56:42,446 DEBUG PathMatchingResourcePatternResolver:351 – Resolved location pattern [classpath:context/applicationContext-*.xml] to resources [file [D:\Work\Demo2do\Sample\target\classes\context\applicationContext-configuration.xml]]
14:56:42,447  INFO XmlBeanDefinitionReader:315 – Loading XML bean definitions from file [D:\Work\Demo2do\Sample\target\classes\context\applicationContext-configuration.xml]
14:56:42,486 DEBUG PluggableSchemaResolver:140 – Loading schema mappings from [META-INF/spring.schemas]
14:56:42,597 DEBUG DefaultBeanDefinitionDocumentReader:108 – Loading bean definitions
14:56:42,658 DEBUG PathMatchingResourcePatternResolver:550 – Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample]
14:56:42,699 DEBUG ClassPathBeanDefinitionScanner:243 – Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\service\impl\BlogServiceImpl.class]
14:56:42,750 DEBUG XmlBeanDefinitionReader:216 – Loaded 5 bean definitions from location pattern [classpath:context/applicationContext-*.xml]
14:56:42,750 DEBUG XmlWebApplicationContext:525 – Bean factory for Root WebApplicationContext: org.springframework.beans.factory.support.DefaultListableBeanFactory@478e4327: defining beans [blogService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy
14:56:42,860 DEBUG ContextLoader:296 – Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
14:56:42,860  INFO ContextLoader:301 – Root WebApplicationContext: initialization completed in 596 ms

14:56:42,935 DEBUG DispatcherServlet:115 – Initializing servlet ‘dispatcher’
14:56:42,974  INFO DispatcherServlet:444 – FrameworkServlet ‘dispatcher’: initialization started

14:56:42,974 DEBUG DispatcherServlet:572 – Servlet with name ‘dispatcher’ will try to create custom WebApplicationContext context of class ‘org.springframework.web.context.support.XmlWebApplicationContext’, using parent context [Root WebApplicationContext: startup date [Mon Feb 06 14:56:42 CST 2012]; root of context hierarchy]
14:56:42,979  INFO XmlWebApplicationContext:495 – Refreshing WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext
14:56:42,983  INFO XmlBeanDefinitionReader:315 – Loading XML bean definitions from class path resource [web/applicationContext-dispatcherServlet.xml]
14:56:42,987 DEBUG PluggableSchemaResolver:140 – Loading schema mappings from [META-INF/spring.schemas]
14:56:43,035 DEBUG DefaultBeanDefinitionDocumentReader:108 – Loading bean definitions
14:56:43,075 DEBUG PathMatchingResourcePatternResolver:550 – Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller]
14:56:43,075 DEBUG PathMatchingResourcePatternResolver:612 – Searching directory [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/com/demo2do/sample/web/controller/**/*.class]
14:56:43,077 DEBUG PathMatchingResourcePatternResolver:351 – Resolved location pattern [classpath*:com/demo2do/sample/web/controller/**/*.class] to resources [file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class], file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]]
14:56:43,079 DEBUG ClassPathBeanDefinitionScanner:243 – Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class]
14:56:43,080 DEBUG ClassPathBeanDefinitionScanner:243 – Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]
14:56:43,089 DEBUG XmlBeanDefinitionReader:216 – Loaded 19 bean definitions from location pattern [classpath:web/applicationContext-dispatcherServlet.xml]
14:56:43,089 DEBUG XmlWebApplicationContext:525 – Bean factory for WebApplicationContext for namespace ‘dispatcher-servlet’: org.springframework.beans.factory.support.DefaultListableBeanFactory@5e6458a6: defining beans [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,blogController,userController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0,org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@478e4327
14:56:43,323 DEBUG RequestMappingHandlerMapping:98 – Looking for request mappings in application context: WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext
14:56:43,345  INFO RequestMappingHandlerMapping:188 – Mapped “{[/blog],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.BlogController.index()
14:56:43,346  INFO RequestMappingHandlerMapping:188 – Mapped “{[/login],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.UserController.login(java.lang.String,java.lang.String)
14:56:43,707 DEBUG BeanNameUrlHandlerMapping:71 – Looking for URL mappings in application context: WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext
14:56:43,828  INFO SimpleUrlHandlerMapping:314 – Mapped URL path [/static/**] onto handler ‘org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0’
14:56:43,883 DEBUG DispatcherServlet:523 – Published WebApplicationContext of servlet ‘dispatcher’ as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher]
14:56:43,883  INFO DispatcherServlet:463 – FrameworkServlet ‘dispatcher’: initialization completed in 909 ms
14:56:43,883 DEBUG DispatcherServlet:136 – Servlet ‘dispatcher’ configured successfully

2687 [main] INFO org.mortbay.log – Started SelectChannelConnector@0.0.0.0:8080
Jetty Server started, use 2901 ms

整个启动日志被我们分为了2段。第一段的过程初始化的是Root WebApplicationContext;而第二段的过程初始化的是SpringMVC的WebApplicationContext。我们还是使用 了红色的标记和蓝色标记指出了在整个初始化过程中的一些重要事件。其中,有这样一段内容值得我们注意:

引用
14:56:42,979  INFO XmlWebApplicationContext:495 – Refreshing WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext

在这段日志中,非常明确地指出了SpringMVC WebApplicationContext与Root WebApplicationContext之间的关系:从属关系。因为根据这段日志的表述,SpringMVC WebApplicationContext能够感知到Root WebApplicationContext的存在,并且将其作为parent容器。

Spring正是使用这种Parent-Child的容器关系来对不同的编程层次进行划分。这种我们俗称的父子关系实际上不仅仅是一种从属关系,更是一种引用关系。从刚才的日志分析中,我们可以看出:SpringMVC中所定义的一切组件能够无缝地与Root WebApplicationContext中的组件整合。

到此为止,我们针对图中以web.xml为核心的箭头分支进行了讲解,读者可以将图中的内容与上面的文字说明对照再次加以理解。

【组件默认行为的指定】

DispatcherServlet的初始化主线的执行体系是顺着其继承结构依次进行的,我们在之前曾经讨论过它的执行次序。所以,只有在 FrameworkServlet完成了对于WebApplicationContext和组件的初始化之后,执行权才被正式转移到 DispatcherServlet中。我们可以来看看DispatcherServlet此时究竟干了哪些事:

Java代码  收藏代码
  1. /** 
  2.  * This implementation calls {@link #initStrategies}. 
  3.  */  
  4. @Override  
  5. protected void onRefresh(ApplicationContext context) {  
  6.     initStrategies(context);  
  7. }  
  8.   
  9. /** 
  10.  * Initialize the strategy objects that this servlet uses. 
  11.  * <p>May be overridden in subclasses in order to initialize further strategy objects. 
  12.  */  
  13. protected void initStrategies(ApplicationContext context) {  
  14.     initMultipartResolver(context);  
  15.     initLocaleResolver(context);  
  16.     initThemeResolver(context);  
  17.     initHandlerMappings(context);  
  18.     initHandlerAdapters(context);  
  19.     initHandlerExceptionResolvers(context);  
  20.     initRequestToViewNameTranslator(context);  
  21.     initViewResolvers(context);  
  22.     initFlashMapManager(context);  
  23. }  

onRefresh是FrameworkServlet中预留的扩展方法,在DispatcherServlet中做了一个基本实现:initStrategies。我们粗略一看,很容易就能明白DispatcherServlet到底在这里干些什么了:初始化组件

读者或许会问,组件不是已经在WebApplicationContext初始化的时候已经被初始化过了嘛?这里所谓的组件初始化,指的又是什么呢?让我们来看看其中的一个方法的源码:

Java代码  收藏代码
  1. /** 
  2.  * Initialize the MultipartResolver used by this class. 
  3.  * <p>If no bean is defined with the given name in the BeanFactory for this namespace, 
  4.  * no multipart handling is provided. 
  5.  */  
  6. private void initMultipartResolver(ApplicationContext context) {  
  7.     try {  
  8.         this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);  
  9.         if (logger.isDebugEnabled()) {  
  10.             logger.debug(“Using MultipartResolver [“ + this.multipartResolver + “]”);  
  11.         }  
  12.     } catch (NoSuchBeanDefinitionException ex) {  
  13.         // Default is no multipart resolver.  
  14.         this.multipartResolver = null;  
  15.         if (logger.isDebugEnabled()) {  
  16.             logger.debug(“Unable to locate MultipartResolver with name ‘” + MULTIPART_RESOLVER_BEAN_NAME +  
  17.                     “‘: no multipart request handling provided”);  
  18.         }  
  19.     }  
  20. }  

原来,这里的初始化,指的是DispatcherServlet从容器(WebApplicationContext)中读取组件的实现类,并缓存于DispatcherServlet内部的过程。还记得我们之前给出的DispatcherServlet的数据结构吗?这些位于DispatcherServlet内部的组件实际上只是一些来源于容器缓存实例,不过它们同样也是DispatcherServlet进行后续操作的基础。

:我们在第一篇文章中就曾经提到过Servlet实例内部的属性的访问有线程安全问题。而在这里,我们可以看到所有的组件都以Servlet内部属性的形式被调用,充分证实了这些组件本身也都是无状态的单例对象,所以我们在这里不必考虑线程安全的问题。

如果对上面的代码加以详细分析,我们会发现initMultipartResolver的过程是查找特定MultipartResolver实现 类的过程。因为在容器中查找组件的时候,采取的是根据特定名称(MULTIPART_RESOLVER_BEAN_NAME)进行查找的策略。由此,我们 可以看到DispatcherServlet进行组件初始化的特点:

downpour 写道
结论 DispatcherServlet中对于组件的初始化过程实际上是应用程序在WebApplicationContext中选择和查找组件实现类的过程,也是指定组件在SpringMVC中的默认行为方式的过程。

除了根据特定名称进行查找的策略以外,我们还对DispatcherServlet中指定SpringMVC默认行为方式的其他的策略进行的总结:

  • 名称查找 —— 根据bean的名字在容器中查找相应的实现类
  • 自动搜索 —— 自动搜索容器中所有某个特定组件(接口)的所有实现类
  • 默认配置 —— 根据一个默认的配置文件指定进行实现类加载

这三条策略恰巧在initHandlerMappings的过程中都有体现,读者可以从其源码中找到相应的线索:

Java代码  收藏代码
  1. private void initHandlerAdapters(ApplicationContext context) {  
  2.     this.handlerAdapters = null;  
  3.   
  4.     if (this.detectAllHandlerAdapters) {  
  5.         // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.  
  6.         Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.classtruefalse);  
  7.         if (!matchingBeans.isEmpty()) {  
  8.             this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());  
  9.             // We keep HandlerAdapters in sorted order.  
  10.             OrderComparator.sort(this.handlerAdapters);  
  11.         }  
  12.     }  
  13.     else {  
  14.         try {  
  15.             HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);  
  16.             this.handlerAdapters = Collections.singletonList(ha);  
  17.         }  
  18.         catch (NoSuchBeanDefinitionException ex) {  
  19.             // Ignore, we’ll add a default HandlerAdapter later.  
  20.         }  
  21.     }  
  22.   
  23.     // Ensure we have at least some HandlerAdapters, by registering  
  24.     // default HandlerAdapters if no other adapters are found.  
  25.     if (this.handlerAdapters == null) {  
  26.         this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);  
  27.         if (logger.isDebugEnabled()) {  
  28.             logger.debug(“No HandlerAdapters found in servlet ‘” + getServletName() + “‘: using default”);  
  29.         }  
  30.     }  
  31. }  

这里有必要对“默认策略”做一个简要的说明。SpringMVC为一些核心组件设置了默认行为方式的说明,这个说明以一个properties文件的形式位于SpringMVC分发包(例如spring-webmvc-3.1.0.RELEASE.jar)的内部:

我们可以观察一下DispatcherServlet.properties的内容:

引用
# Default implementation classes for DispatcherServlet’s strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager

结合刚才initHandlerMappings的源码,我们可以发现如果没有开启detectAllHandlerAdapters选项或者根 据HANDLER_ADAPTER_BEAN_NAME的名称没有找到相应的组件实现类,就会使用 DispatcherServlet.properties文件中对于HandlerMapping接口的实现来进行组件默认行为的初始化。

由此可见,DispatcherServlet.properties中所指定的所有接口的实现方式在Spring的容器WebApplicationContext中总有相应的定义。这一点,我们在组件的讨论中还会详谈。

这个部分我们的侧重点是图中DispatcherServlet与容器之间的关系。读者需要理解的是图中为什么会有两份组件定义,它们之间的区别在哪里,以及DispatcherServlet在容器中查找组件的三种策略。

小结

在本文中,我们对SpringMVC的核心类:DispatcherServlet进行了一番梳理。也对整个SpringMVC的两条主线之一的初始化主线做了详细的分析。

对于DispatcherServlet而言,重要的其实并不是这个类中的代码和逻辑,而是应该掌握这个类在整个框架中的作用以及与SpringMVC中其他要素的关系。

对于初始化主线而言,核心其实仅仅在于那张笔者为大家精心打造的图。读者只要掌握了这张图,相信对整个SpringMVC的初始化过程会有一个全新的认识。