• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
无忧岛主
实践是检验真理的唯一标准
博客园    首页    新随笔       管理    订阅  订阅
微软企业库4.1学习笔记前十六章完整版

(一) 开篇乱弹
我是一个.NET技术为主的程序员,每次开发一个新的应用的时候,都会被人问道“.NET有没有框架可以用,有没有类似hibernate,structs,spring之类的框架,怎么什么都没有呢,.NET有没有开源的现成的好用的框架,怎么每次都要造轮子呢?你们在干什么呢,数据访问和日志这种东西怎么每次你们都要花功夫来写呢,没有可以参考的吗”,大部分人的回答往往是“没有,我们自己也没有积累,别人的不如自己写呢,还要看明白才会用,没有java那么多的现成框架”。

其实不是那样的,.NET由于不是开源的,所以没有像java那么多的开源社区支持,那么多的开源框架可以参考,而且大多很是成熟,好像我们.NET就被微软一个人绑架了。什么都要自己写,如果自己再没有积累的话,每次都要造轮子,每次造的轮子还不一样,哎。。。。。。。。。。。。。。。

其实,有很多的框架还是可以用的,而且微软这两年对开源的态度也有所转变,只是由于商业上的考虑,开的力度不像大家想象的那么大,http://www.codeplex.com/就是微软创建的一个开源站点,微软还有一个Patterns & Practices小组,会出产很多的框架来帮助他的开发伙伴来完成日常项目的开发,帮助他们更好的使用.NET开发企业级应用和互联网应用。

知识点:
微软"模式与实践"宗旨在于通过提供一系列的成熟设计模式及经验乃至原型系统源代码,帮助广大开发团队进行更好的设计及技术选型;同时也通过提供经过微软自身实践验证的高效软件开发流程及实践,帮助开发团队顺利的搭建团队并实现高效开发。目前主要研究方向有:敏捷软件开发,Enterprise Library,Application Architecture,Composite Application Guidance, Software Factory等等。

开源框架还有很多,国内的牛人也写过很多。国外大名鼎鼎的CSLA.NET就是其中一个,它也在随着.NET的版本不断更新。它还有配套的图书《Apress - Expert C# 2008 Business Objects (Dec 2008).pdf》,这本书的pdf版可以在网上下载,在CSDN里面就有下载,包括图书和源码,我以后还准备写一个CSLA.NET的系列学习笔记。这是佩服国外的家伙,考一个框架就可以生活了,不像我们,从底层到UI你全部都要会,哎。。。。。。。。。。。。除了锻炼人,没有别的好处了,搞得我们拿起什么都会一点,但是都不够专啊,这也是国内为什么很少有优秀的框架产生。(说完这句话,希望不要有太多人给我板砖)

还是回来说说Enterprise Library吧,在4.1中他包含了
Caching 缓存
Cryptography 密码管理
Data Access 数据库访问
Exception Handling 异常处理
Interception 拦截
Logging 日志管理
Security 安全管理
Unity 依赖注入
Validation 数据验证

都是我们做任何应用开发,所必需的内容,而且使用非常频繁,几乎的系统必备,也是我们重复造的最多的轮子,而且对于我们的整体业务实现没有直观帮助,但是又很重要的基础部件。(领导不关心啊,我们做的工作都在这里了,55555555555555)

它在codeplex上地址是http://entlib.codeplex.com/,最近发布了5.0 BETA2,真是快啊,每次发布他都会融入.NET的最新技术。估计这次又会加入.NET4.0的一些特性吧。

Enterprise Library 4.1可以工作运行在VS2008、Windows Management Instrumentation(WMI)2.0,以及.NET 3.5之下。

(二)各功能之间的依赖关系以及对象创建

在4.1中你可以使用Unity Application Block(Unity)在函数或者是功能运行之前或者之后拦截企业库的对象,生成这些对象,并且将他们注入到其他对象中。

 

企业库中包含了很多的类,允许你从Unity容器中获取在企业库配置中定义的,类似SqlDatabase或者是CacheManager对象的实例。另外,一些有好的注入代替了过去的静态方法。使用Unity的依赖注入能力可以很好的简化你的代码。

 

使用4.1的系统需要

windows xp,2003,2008,vista

.NET FRAMEWORK 3.5或者更高

VS2008

 

一、框架的依赖关系

准确的说就是程序集的依赖性,也就是要实现什么样的功能,都需要那些程序集引用。

1、所有功能都依赖的程序集

1.1 Microsoft.Practices.EnterpriseLibrary.Common.dl,里面包含了企业库的核心服务,被企业库的其他模块共享使用(除了Unity没有使用,你可以单独使用Unity Application Block实现依赖注入功能)。

1.2 Microsoft.Practices.ObjectBuilder2.dll,对象创建子系统,它执行了全部有关创建和回收对象的重复且必要的工作任务,同时提供了高级别的灵活性。

 

2、缓存模块额外依赖的程序集

2.1 如果使用数据库作为后段数据存储,你需要Data Access模块

2.2 如果你想加密缓存中的数据,你需要Cryptoraphy模块

 

3、异常处理额外需要的程序集

3。1 如果你想很实用日志模块记录异常信息,需要引用Microsoft.Practices.EnterpriseLibrary.Logging.dll和Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.dll。

3.2 还可能需要其他的程序集,例如Data Access模块,因为你可能想要记录异常信息。

 

4、策略注入Policy Injection额外需要的程序集

4.1 Policy Injection 提供了标准的处理过程,如果你的代码不使用内置的处理,你可以不部署policy injection依赖的程序集。

4.2 如果要使用policy injection 内置的权限处理,需要添加Security模块

4.3 如果使用policy injection内置的异常处理,你需要添加异常处理模块

4.4 如果使用policy injection内置的日志处理,需要添加日志模块

4.5 如果使用policy injection内置的数据验证,需要添加验证模块

 

5、安全模块额外依赖的程序集

5.1 缓存模块,安全模块使用缓存模块将安全信息缓存起来,用的时候可以获取出来。你也可以使用自己的缓存Provider代替企业库中的缓存模块,在配置缓存模块的时候还可能需要数据访问模块,更多的信息可以查看缓存模块的依赖关系。

 

二、依赖注入模块的使用

Unity Applicaiton Block提供了一个轻量的、可扩展的依赖注入容器,支持构造函数、属性以及方法调用注入。你可以在自己的应用中使用Unity或者自定义的依赖注入方式,创建企业库的对象或者自定义的依赖于其他对象或者服务的业务对象或者是服务。

 

你也可以不这么做,还是按照企业库以前的方式创建对象。

 

如果你决定使用Unity,你就需要添加对这个程序集的依赖。而且,和其他模块一样,Unity依赖于ObjectBuilder。关于更多的信息和依赖关系,可以查看依赖关系图。

 

三、依赖关系图

 

有很多种方法可以创建企业库的对象实例,你可以使用下面的方法之一来达到目的;

使用每一个模块的工厂类和企业库的核心类库创建对象实例

使用Unity模块创建任何类型的对象实例

在不使用企业库核心类库的情况下,使用Unity或者是第三方的依赖注入方法创建对象实例

 

1、使用企业库的工厂方法

企业库4.1支持以前版本的对象创建方法。每一个功能模块,核心库,都可以用工厂方法创建对象实例。例如:你可以使用下面的代码创建一个配置好的CacheManager类的实例。

 

上面的代码在企业库的核心类库中很常见,它直接调用了ObjectBuilder的工具类。在下图中,你会发现,所有的功能都依赖于企业库的核心和ObjectBuilder。

 

使用这种方法创建对象,需要部署下列程序集:

核心库Microsoft.Practices.EnterpriseLibrary.Common.dll

对象创建程序集Microsoft.Practices.ObjectBuilder2.dll

 

2、使用Unity Application Block创建对象

Unity允许开发者用依赖注入的模式创建各种对象。如果需要的话,自动注入需要依赖的对象。可以使用这种方式创建企业库对象和自定义的业务对象。下图显示使用Unity改变应用程序和企业库核心的依赖关系。

 

在这种情况下,除去Unity,所有的模块都依赖于核心类库的配置系统。但是,尽管Unity使用了存储在应用程序目录下的app.config和web.config文件中的配置信息,但是Unity容器不会被自动创建和配置。相反,需要开发者创建容器并且加载配置信息。

 

创建并且配置了容器之后,开发者可以使用下面的语法创建对象。

 

(三)企业库迁移和并行使用,以及企业库的扩展

一、迁移和并行使用

通常来说,企业库是建立在.NET 2.0 的基础上,使用后续版本的企业库也不需要改变任何代码。不需要将引用更新到新的程序集,也不需要在配置文件中指明程序集的正确版本。

 

这个版本4.1的企业库可以和以前版本的企业库同时安装,你可以将使用新版本的应用程序和使用旧版本的应用程序一起部署。也你可以一次性的将现有应用的企业库应用升级到新版本。

 

如果你选择并行使用不同版本的企业库,你一定要将他们部署在不同的文件夹。在任何情况下,不能将不同版本的企业库程序集混合在一起。例如,你不能将4.0的Data Access相关程序集和3.0的Caching相关程序集放在一个目录。

 

可以在应用项目下面的AssemblyInfo.cs在存在不同版本的程序集信息。这样就可以通过强命名的方式,并行使用GAC中的不同版本程序集。

 

1、部分迁移

应用中的每个程序集只能引用一个版本的企业库,但是多个程序集可以引用多个版本的企业库。例如,你的应用有两个程序集,原来都是用企业库3.0,现在可以将一个程序集迁移到使用4.0,保留另外一个对3.0的引用。这就是说你可以逐步迁移,一次一次的替换。尽管支持分步骤的迁移,但是实现起来可能是复杂的;所以,不推荐这种做法。

注意:企业库的配置界面是根据企业库的版本定制的,所以在配置不同版本的企业库的时候,请使用不能版本下面的配置程序。

 

二、企业库的扩展指导

企业库可以作为自定义类库的基础,你可以利用每一个功能模块的扩展点,提供新的支持。

 

你也可以修改企业库的源代码,组成新的功能。扩展的时候可以遵循下面的指导。

 

可以通过三种方式扩展企业库。你可以写自定义的provider,可以修改现有功能模块的源代码,可以写一个新的功能模块。如果你要扩展企业库,可以参考下面的建议:

功能模块应该暴露方法,方便开发者不需要修改就可以使用它。

功能模块应该很容易修改和扩展。

功能模块应该包含Microsoft Patterns & Practices指导

功能模块应该包含一些仪表

功能模块和其他功能模块应该是松耦合的关系

 

1、方法暴露

一个功能模块应该提供公用的API来暴露自己的方法,API应该独立于内部实现。开发者应该不需要理解模块的设计和实现,就可以高效的使用模块提供的即开即用功能。

 

无论什么时候,API应该为为特殊的功能提供通用的解决方案。使用配置界面设置值,而不是使用代码设置,可能对特殊的情况和用户很有用,例如数据库的名称,可以使用的缓存数量等。

 

2、扩展和自定义功能模块  

一个功能模块应该提供扩展点给开发者,使得开发者可以根据自己的需要裁剪功能模块。最常用的扩展点就是允许开发者使用自己的provider。例如,一个管理在数据库中存储数据的功能模块,应该允许开发者很容易的添加不同类型的数据库。

 

开发者应该可以不修改功能模块的源代码,就可以扩展功能模块。为了实现这些,你的扩展点应该包括公共基类或者是接口。开发者可以扩展基类或者实现接口,然后通过修改配置文件来扩展功能模块。通过这种方式,不需要修改或者重新编译功能模块。开发者会发现扩展功能模块很有意思,很好组织,很容易理解,同时应该遵守Microsoft patters & practices的指导。

 

当提供扩展的时候,也需要考虑给易用性带来的影响。大量的扩展点,可能会给功能模块的配置和使用带来很大的困难。

 

一些开发者更喜欢自定义的代码,意味着他们可能会修改源代码,而不是使用扩展点。提供这种功能,模块设计应该提供下面的指导。

 

在任何实际情况下,都应该遵循面向对象的设计原则

应该正确的使用设计模式

应该高效的使用资源

应该支持安全准则,例如,不信任用户的输入和最小优先级原则。

 

代码风格和命名规范都应该和.NET类库保持一致,这方面可以查看MSDN的Design Guidelines for Class Library Developers。有一个这方面的代码检查工具,叫做FxCop,这个工具可以检查代码是否和微软的类库设计建议保持一致。

 

3、包括patterns & practices

有一点很重要,需要记住,企业库只是Microsoft patterns & practices的一小部分。一个功能模块应该包含patterns & practices的相关指导。

 

4、包含性能仪表

一个功能模块应该包含性能仪表,使得开发者,测试者和系统管理员都可以监测功能模块的运行行为和性能。仪表的数量依赖于功能模块的方法,需要足够的仪表提供给开发人员、测试人员和管理员,使他们可以可以获取足够的信息,保证功能模块正在像设计的时候那样运行,并且性能在可接受的范围之内。

 

5、使用松耦合

如果一个企业库功能模块需要其他的模块来完成工作,他们之间依赖应该是松耦合的,方便开发者可以用自己的实现来替换他们。如果有可能,使用插件模型的方式。分离和一个provider的代码依赖。开发者可以在需要的时候换成一个不同的provider。

 

三、企业库的修改指导

企业库被设计成可以在任何应用中使用,提供通用的工具类和方法。使用扩展点,你可以调整企业库来满足任何特殊的需要。但是,如果你想要在功能模块中添加新的feature,你可以对源代码进行一些修改。在这种情况下,可以参考下面的建议:

 

通过阅读文档,保证你理解了企业库的功能模块是如何工作的。

 

如果你改变代码的签名或者在使用原有版本的同时使用自定义的版本,你可能要考虑修改命名空间。如果修改命名空间,你需要修改所有相关的程序集的命名空间,使得他们和新的命名空间保持一致。

 

使用强命名。强命名使得程序集具有唯一标识、版本和完整性检查。你需要自己的签名来表示修改的程序集。

 

你一定要重新编译你的代码,查看修改产生的影响。开发VS,重新编译整个解决方案。 

(四)主要的功能模块简介

一、企业库的主要内容

  企业库是一个可重用的组件集合,同时也是一个受支持的基础框架。企业库包含的内容:

  企业库功能模块

  企业库核心

  企业库配置工具

  对象创建和依赖注入方法

  工具集,示例,和开发指导

 

二、强命名企业库程序集

如果你使用源代码生成企业库程序集,你就要考虑是否给程序集添加强命名。强命名由程序集的标识组成,包括一个名称,版本号,和一个可选的区域信息,加上一个公共的key和一个签名。

 

强命名的程序集可以保证唯一性,可以添加到GAC中,可以保证你的应用使用了正确版本的程序集。可以使用Visual Studio创建强命名程序集,在项目的右键菜单中选择属性,然后添加签名就可以了,相信很多人平时都使用过。

 

知识点:我们知道带有internal限制的类只能在相同程序集下面才可以访问,其实我们还是可以在不同的程序集下面也可以访问的internal的类,这个需要在assembly.cs文件中添加internalsVisibleTo,意思就是这个程序集的internal部分可以让那个程序集引用。

 

三、企业库的功能模块

企业库可以帮助开发者解决在开发每个项目中都可能遇到的常见问题,他们包含了微软推荐的关于.NET类库的最佳实践。另外,他们可以快速,且很容易加入.NET应用中,例如Data Access模块,可以提高开发者的生产效率。

 

每个应用都有自己的需求,并不是所有的应用都要使用全部的企业库共呢个模块。在使用企业库之前,要对你的应用和每一个企业库的功能模块都有一个理解,这样才可以确定你的应用需要那些模块,不需要哪些模块。

 

功能模块简介:

  缓存模块Caching,开发者可以使用这个模块在应用中组成本地缓存。

  加密模块Cryptography,开发者可以用来在应用中使用哈希和对称加密

  数据访问模块Data Access,开发者可以使用这个模块在应用中组成标准的数据访问功能

  异常处理模块Exception Handling,开发者和决策者可以使用这个模块在系统架构中提供一致的异常处理流程

  日志模块Logging,开发者可以用这个模块在应用中实现标准的日子功能。

  策略注入模块Policy Injection,开发者可以使用这个模块在应用中实现拦截策略,可以简化一些常用功能,例如日志,缓存,异常处理,验证等功能的实现。

  安全模块Security,开发者可以用这个模块在应用中实现授权和安全缓存功能

  依赖注入模块Unity,开发者可以用这个模块在应用中实现一个轻量的,可扩展的依赖注入容器,容器支持构造函数注入、属性注入、方法调用注入。

      验证模块Validation,开发者可以用这个模块在应用的不同层的业务对象创建验证规则。

 

企业库还有一个很好用的配置工具,可以简化我们很多的配置工作。EntLibConfig.exe

(五)对象创建和依赖注入方法

企业库4.0和4.1的主要改进是加入了全新的,而且很容易使用的对象创建方式,包括其他的依赖对象。这个功能在企业库的Unity模块中通过依赖注入暴露,它使用了下面这些常用的设计模式。

Dependency Injection依赖注入

Inversion of Control(IoC)反转控制

Service Locator服务定位

Service Container服务容器

Factory工厂模式

Builder创建者模式

 

一、企业库中的对象创建和依赖注入

开始之初,企业库中的应用模块,核心模块、配置系统,用户自己的一个用,一直都使用微软Patterns & Practices 团队的ObjectBuilder系统创建对象,包括了Singleton实例。

 

ObjectBuiler实现了前面列出来的很多模式,但是对于大多数程序员来说,它不是一个很容易使用的工具。使用它需要开发者拥有显著的开发知识和开发时间来认识到它的好处。因此,4.1的企业库在ObjectBuilder的基础上推出了一个轻量级的方法,提供了前面提到所有模式的实现。

 

新的依赖注入容器暴露的接口比较简单,它还提供扩展,方便开发者根据需要改变它的行为,而且开发者可以将它作为一个独立的容器或者是依赖注入框架在自己的系统中使用。它允许开发者使用很现代的方式,例如构造函数、属性和注入方法;相关对象的依赖注入;容器中的类型和接口映射;一些简单的配置。另外,开发者可以用它暴露的一些API直接和容器进行交互,这些对于开发者将依赖注入容器作为单独依赖注入框架使用,或者是使用第三方的依赖注入框架都很有帮助。

 

二、Unity模块

企业库中的依赖注入方法通过一个全新的,叫做Unity的模块暴露。这个模块实现了容器方式,和ObjectBuilder一起使用,包括了需要支持的程序集对象创建功能,依赖对象和服务的实例创建,对象创建时候的属性输入和方法调用。

 

你可以以多种方式来使用依赖注入容器,你可以以下面的方式使用Unity模块:

通过各个模块和核心模块中提供的标准方法创建企业库所需对象。

获取singleton实例的引用。

对现有的对象使用依赖注入。

创建自定义的对象,通常还需要创建和他们相关的对象。

为接口和实现类提供映射

使用容器扩展可以扩展功能,也可以改变行为

使用第三方的依赖注入框架,例如Castle,Windsor,Spring,在Unity容器中创建任何类型的对象。

(六)创建对象
一、创建应用模块对象
在你的应用中经常会需要创建例如Database或者是CacheManager之类的对象,企业库支持一系列的方法,可以创建应用需要的对象。

你可以使用工厂方法创建这些对象,企业库的在每一个功能模块和核心库都包含有工厂方法来完成这个任务。这些静态的工厂方法自动读取应用的配置信息,provider的工厂方法允许你手工在配置文件中创建适当的配置,然后用工厂方法在代码中创建provider。

还有,你也可以创建配置信息中没有描述的对象。例如,通过在构造函数中添加数据库连接信息,你可以创建一个SqlDatabase对象。

另外,企业库4.1还包含了一个轻量的、可以扩展的,支持构造函数、属性、方法调用注入的依赖注入容器,Unity模块。

下面将介绍使用不同的方法创建对象:
使用静态方法创建对象
使用Provider的工厂方法创建对象
使用Unity创建对象
直接使用构造函数创建对象
Tooltip:使用Unity除了可以创建企业库对象之外,还可以创建自定义的业务对象。

1、使用静态方法创建对象
企业库的静态方法实现了工厂模式,如果使用这些工厂方法创建对象,意味着你不需要实现任何创建对象所需的provider信息。
下图显示了核心库如何创建对象
下面的代码示例了使用Data Access模块的DatabaseFactory方法创建Database对象

1.Database db=DatabaseFactory.CreateDatabase("Northwind");

Database是一个抽象类,定义了为实现provider定义了常用的接口。静态方法CreateDatabase返回一个在provider中描述的对象(例如SqlDatabase),对象的类型由配置信息决定。

静态的工厂方法使用默认配置源中的配置信息,你可以通过命令行制定默认的配置源。如果你没有定义配置源,静态方法将使用系统配置源,这就意味着你的配置信息必须要存储在应用的app.config和web.config文件中。

 

在上面的xml中显示了一段定义在应用配置文件中的配置节,里面定义了两个配置源。一个对应文件test.exe.config,一个就是系统配置源。

关于配种源使用的一些注意点:
静态方法使用配置文件来决定默认的配置源,这就意味着你在使用静态方法的时候必须有一个应用的配置文件。

如果你的配置文件不包含</enterpriseLibrary.ConfigurationSource>配置节,企业库将会使用系统配置源,这就意味着你必须将配置信息直接存储在你的配置文件中,而不是放在配置节定义的配置源中。

所有的静态方法都是用默认的配置源,意味着创建所有模块对象所需要的配置信息都放在同一个位置。

2、使用provider的工厂方法创建对象
首先用一个静态方法创建适当的配置源对象,然后构造一个provider对象。你也可以在应用中使用provider的工厂方法创建对象。

使用provider的工厂方法创建对象,需要完成下面的几步:
1)创建配置源对象
2)利用配置源对象,创建provider工厂对象
3)使用provider工厂对象创建provider

2.1创建配置源
可以使用ConfigurationSourceFactory类创建配置源对象,Create方法接受一个参数,参数是配置源的名称。这个配置源一定要先在应用的配置文件中定义,如果没有定义会抛出异常。意味着你如果使用ConfigurationSourceFactory,应用程序必须要有一个配置文件。下面的代码显示了使用ConfigurationSourceFactory类创建配置源对象。

你也可以直接构造配置源对象,构造函数需要配置源的信息。

Tooltip:企业库包括一个SqlConfigurationSource配置源provider的例子。使用Data Access读取存放在SQL server数据库的配置信息,这个provider使用的配置节需要继承自SerializableConfigurationSection类。具体的可以参考企业库中的SqlConfiguration QuickStart。

(七)创建对象 续集1
3.2使用Unity模块创建企业库对象
下面介绍如何使用前面的方法获取企业库对象的实例。代码示例如下

 

首先创建一个Unity容器,并且添加企业库核心配置扩展。
默认情况下,这个扩展会从应用的配置文件(app.config和web.config)中读取配置信息。如果你使用自定义的配置源,你可以使用下面的代码创建容器并添加配置扩展

 

在你添加任何企业库扩展(你可以在任何阶段添加其他不是企业库扩展的扩展)之前, 必须先在容器中添加EnterpriseLibraryCoreExtension。如果在容器中已经存在一个企业库扩展,你可以使用RemoveAllExtensions删除它们,然后以正确的顺序添加进去。

下一步就是在容器中添加你要使用的模块对应的模块扩展,如果又需要,可以添加多个进去。扩展的命名是以模块的名称+BlockExtension,例如DataAccessBlockExtension,LoggingBlockExtension等。代码示例

 

如果你使用的模块和其他模块有依赖的话,也要添加依赖模块的BlockExtension,当前版本提供的BlockExtension包括:  EnterpriseLibraryBlockExtension、DataAccessBlockExtension、CachingBlockExtension、SecurityBlockExtension、CryptographyBlockExtension、ExceptionHandlingBlockExtension、LoggingBlockExtension。 

现在你可以使用容器的Resolve创建provider的对象和在企业库标准配置中定义的对象。下面的代码示例了创建CacheMananger实例

 

当容器被回收之后,容器扩展产生的企业库配置将会丢失。所以,在代码中需要对容器有一个强引用。另外,你用企业库扩展创建,并且用Resolve方法获取的对象都是singleton模式,生命周期是由Unity容器来控制的。当容器被回收Dispose或者超出变量范围之后,他们也会消失。

 

(八)创建对象 续集2
3.3通过配置指定和Unity的整合
另外一种方法是在配置源中指定配置的需要,你可以指定下面的一条或者多条:
你可以在Unity配置中指定想要的BlockExtensions
你可以在Unity配置中的type配置节指定如何创建企业库对象,指定类型映射的关系,例如,当使用resolve方法的时候返回一个SqlDatabase,或者是返回指定的注入Database对象。也可以指定生命周期和其他构造策略。
企业库的配置Scheme和Unity的配置Scheme是独立的。但是你可以将Unity的配置节信息和企业库的配置信息放在同一个文件中,例如Unity配置中的一个或者多个容器,返回类型,映射关系,返回实例,每个容器使用的extensions。关于Unity的具体配置Scheme可以参看Unity的文档。
  
3.4使用企业库整合facades
你可以使用Unity在企业库对象实例中注入自定义的业务对象和类。但是一些企业库提供的静态的facades不能被注入。相反,你可以使用企业库提供的非静态facades,下面显示一个静态的facades和对应的非静态facades。
  Existing Facades            New facade for use with Unity
  ExceptionPolicy              ExceptionManager
  Tracer                    TracerManager
  Cryptographer              CryptographyManager

3.5将企业库对象注入到自定义业务对象中
你可以使用Unity将企业库对象实例和服务注入到自定义业务对象和组件中,下面的代码显示了一个类如何依赖一个LogWriter实例。

 

你可以在容器中载入企业库核心extension和Logging模块的extension,然后初始化自定义的类来完成对LogWriter的注入引用。

 

4、通过构造函数直接创建企业库对象
下面的代码显示了如何通过构造函数创建SqlDatabase对象

 

(九)企业库程序集的部署
企业库包含很多的程序集,除去Unity模块,企业库的每个程序集的名称都以“Microsoft.Practices.EnterpriseLibrary”开头。另外,其他的模块都可能会依赖企业库的Common程序集。使用一个模块的应用也可能会需要其他模块的程序集,例如使用Caching模块,有可能会需要Data Access模块的程序集。

在应用中使用企业库,可以用下面的两种方式来部署企业库的程序集。

在应用程序的目录中,作为应用的私有程序集。

放在文件系统的共享目录,或者是GAC中。

一、准备和版本
如果编译企业库的源代码,默认生成的程序集是没有签名的。你就不能将他们放入GAC,你就不能享受签名程序集的好处。
1.1使用XCopy
你可以不使用签名,也不用将企业库的程序集部署到GAC中。你可以只将他们放在应用程序的目录中。对于这种简单的部署方式,你可以使用xcopy将应用的程序集和企业库的程序集一起安装在目标计算机。但是,如果在目标计算机上有多个应用都在使用企业库,你就会在每个应用的目录下存放一份企业库程序集的拷贝。
1.2使用GAC ( Global Assembly Cache )
另外,你可以对企业库程序集进行签名,这就保证他们的名字全局唯一,并且有了版本。如果这么做,你就可以在多个应用使用企业库的情况下,将他们部署在共享的位置。例如,可以将企业库部署在GAC中,这样计算机的任何应用都可以使用企业库程序集。

可以使用下面的方式部署程序集到GAC中

拖拽,打开C:\WINDOWS\assembly目录,直接将签名的程序集拖入C:\WINDOWS\assembly目录。

使用GACUTILI.EXE,使用这个命令拷贝到C:\WINDOWS\assembly目录。

使用.NET FRAMEWORK的配置工具mscorcfg.msc
1.3版本
程序集签名之后,就有了版本和命名保护,可以部署在GAC中,供所有的应用来使用。
不推荐你在应用中直接使用企业库的命名空间,因为这样会造成和企业库的源码很难区分,建议你选择一个和你的项目相关的命名空间。
二、升级企业库的程序集
如果企业库提供了新的版本,你可以安装新版程序集。但是如果新版本对于特定的应用存在兼容性问题,你可以在GAC中安装新版本,并且配置一些应用使用GAC安装的新版本,其他存在兼容性的应用继续使用旧版本的企业库。
2.1升级私有程序集
如果企业库程序集作为一个应用的私有程序集来部署的话,你部署的时候只需要用新的dll代替旧版本的dll就可以了。
注意:你应用在升级之前拷贝一份旧版本的企业库程序集,万一出现兼容性问题的话,可以用旧版本的再替换回来。
2.2升级共享的程序集
升级共享程序集的最简单办法就是用新的dll替换GAC中的旧dll。默认的,CLR会自动加载主要版本和次要版本相同,但是内部版本和修订版本是最新的程序集,作为应用程序使用。因此,如果主要版本和次要版本号没有变化,在GAC中添加最新的程序集,会自动被所有的应用使用。
如果主要版本和次要版本有增加,或者是新版本对现有应用有兼容性问题,你可以修改默认的版本策略。在每个应用下面都以一个配置文件,在里面可以指定所使用的程序集的版本。在里面你可以指定没有兼容性问题的程序集使用新版本的企业库程序集。

知识点:主要版本,次要版本,内部版本和修订版本
.Net Framework 风格的版本号命名格式:主版本号.子版本号.编译版本号.修正版本号
英文对照:
Major_Version_Number.Minor_Version_Number.Build_Number.Revision_Number
Net Framework风格的版本号命名格式
版本号由二至四个部分组成:主版本号、次版本号、内部版本号和修订号。主版本号和次版本号是必选的;内部版本号和修订号是可选的,但是如果定义了修订号部分,则内部版本号就是必选的。所有定义的部分都必须是大于或等于 0 的整数。

应根据下面的约定使用这些部分:
Major :具有相同名称但不同主版本号的程序集不可互换。例如,这适用于对产品的大量重写,这些重写使得无法实现向后兼容性。
Minor :如果两个程序集的名称和主版本号相同,而次版本号不同,这指示显著增强,但照顾到了向后兼容性。例如,这适用于产品的修正版或完全向后兼容的新版本。
Build :内部版本号的不同表示对相同源所作的重新编译。这适合于更改处理器、平台或编译器的情况。
Revision :名称、主版本号和次版本号都相同但修订号不同的程序集应是完全可互换的。这适用于修复以前发布的程序集中的安全漏洞。
程序集的只有内部版本号或修订号不同的后续版本被认为是先前版本的修补程序更新。
  
三、部分信任的环境
使用企业库3.0之前版本的应用要求有足够的权限,以便程序集可以在完全信任的情况下访问一些资源。最新版本的企业库,包括4.1你可以在部分信任的情况下使用企业库。

(十)企业库的设计
在设计整个企业库的过程中,使用了一系列的最佳实践。下面列出一些里面的最佳实践:
在核心库中使用了Common模块
使用了统一的命名约定和统一的版本
在设计的过程中使用单元测试
在所有模块中包含基础结构

在企业库的模块中包含了下面的模式,当然还包括其他模式:
工厂模式,一个创建型模式,使用指定的类型创建另外一个对象。
插件模式,这种模式扩展了类的行为。在特殊的环境中创建一个子类,只包含所需的功能。
依赖注入模式,使用这个模式,你可以在类中注入对象,而不是依赖类创建对象。

一、工厂模式
工厂模式是软件设计模式中,创建模式的一种。企业库中广泛的使用了工厂模式。例如,在Caching模块,CacheFactory初始化一个CacheManger对象的实例,CacheManager创建一个CacheManagerFactory对象,然后创建一个Cache对象。Cache是后端数据在内存中的副本。创建Cache对象之后,应用程序可以使用CacheManager对象获取缓存中的数据,向缓存中添加数据,从缓存中移除对象。更多的信息请参看缓存模块。

数据访问模块以类似的方式使用工厂模式,客户端代码调用DatabaseFactory的静态方法CreateDatabase创建Database对象的实例。更多的信息参看数据访问模块。

二、插件模式
插件模式是一种在运行的时候创建对象或者接口的实例的软件模式。插件模式扩展了现有代码的行为,以便它可以实现更多的目的。它和使用类继承不一样,类继承可以对行为进行修改或者是重写,插件模式行为的修改是有限的。

使用插件模式,修改的行为(插件)和一个分布partial类相连接,轮流的和核心类连接。插件使用这个接口实现被核心类调用的方法,同时也可以调用核心类的新方法。

三、依赖注入模式
依赖注入模式允许你向类中注入对象,而不是依赖这个类来创建对象。这在下面的情况非常有帮助,在你想解耦一个你的应用中的特殊实现或者是部署的细节。依赖注入模式解耦服务或者是其他代码可能会依赖的在容器中存放。然后,处理代码相互依赖的责任,包括对象创建和连接,从对象本身移除,转移到其他实体。不像工厂模式,使用依赖注入模式,容器中的代码是被多个应用共享的。

在System.ComponentModel命名空间中,是一个微软关于依赖注入模式的实现。在企业库中,ObjectBuilder 和 Unity模块可以用来实现依赖注入模式。

(十一)企业库的核心类
在企业库的很多模块中都实现了很多常用的功能,他们同样对企业库以外的应用代码也很有用。例如,数据序列化和访问配置信息。为了提供可用性,这些代码都放在企业库核心的Common程序集中。

另外,所有的模块都被设计为最小的依赖,以便他们可以单独使用。除了Unity,所有的企业库都依赖于企业库的核心库,核心库包含了下面的子系统:
Common程序集
模块的仪表盘,模块的运行情况
配置信息的帮助类,和设计时支持组件

1、Common程序集
企业库中包含一个叫做Common的程序集,里面包含了一些被其他模块使用的,非常有用的功能。使用企业库的任何一个模块(除去Unity模块)都需要添加对这个程序集的引用。通过提供一些通用的功能,来减少各个模块之间的依赖程度。

2、仪表盘
大部分的模块都提供了仪表盘,仪表盘的类型如下:
性能计数器
事件日志
WMI事件

仪表类是核心类库的一部分,是在Common程序集中。可以在你的应用中使用它。

默认,仪表功能是被禁用的,你可以使用企业库的配置工具来激活每种类型的仪表功能。如果你具有管理员权限,你能使用installutil.exe安装仪表功能。

有一些功能模块依赖的仪表功能被包装在单个模块中。例如,异常处理模块使用日志模块记录异常信息,但是这不是必须要的,你可以将异常信息记录在其他地方,可以记录在事件日志中,或者你可以扩展异常处理模块,提供自定义的异常信息日志provider。

3、配置信息帮助类和设计时支持组件
配置信息使用System.Configuration命名空间,可以很容易的使用企业库模块的配置信息。用工厂创建在配置文件中配置企业库对象。还可以用其他类型的东西替代xml文件作为配置源,例如,可以用SQL Server数据库。你也可以在没有配置文件的情况下创建对象,可以用构造函数创建对象。

为了支持扩展,除了使用System.Configuration命名空间,还可以使用配置帮助类。帮助类支持多态集合,泛型集合,和配置源。这些提高要求配置是以依赖注入模式为基础,而不是provider模式为基础。

企业库的配置工具使得你不用修改xml文件就可以修改配置信息。使用具有设计时支持组件的配置工具,可以带给你更友好的用户体验。

(十二)中间人Providers,设计时支持Design-Time Configuration和仪表盘Instrumentation
一、Provider

 

  在.NET frameword中每一个provider都是一段代码的中间人,通过它,你的应用可以连接到一个服务或者是数据源,可以获取或者是修改服务和数据源的数据。在企业库中包含很多的providers。另外,你也可以创建自己的provider,在你的应用中满足特殊的需求。

 

  一个provider type定义了和一个模块相关的功能的接口,一个provider是一个provider type的实现。每一个模块中,对于一个provider type,都实现了很多的provider。也可以为模块添加自定义的provider。

 

  通过特殊的实现来分离模块的功能,可以达到下面的目标:

 

  变化性,这样可以根据应用的需求,解耦相同功能的多个实现。

  扩展性,允许你在某些强制实现的情况下使用模块。例如,应用在特殊的环境中,要求满足一种特殊的加密算法。

  封装性,使用provider,功能不再是模块的一部分,可以被代替和升级,而不对模块的其他部分有影响。

  跨环境的轻便型,你可以在新环境中部署模块,使用为这个环境编写的provider。你也可以编写一个provider,运行在一个环境中,却模拟另外一个环境。

  使得模块之间的联系最小化,如果一个模块依赖于另一个模块,可以将这种依赖封装在provider中,一个模块更新之后,如果使用provider,它依赖的模块不用更新。例如,异常处理模块,依赖于日志模块来记录异常信息。一个新版本的日志模块需要一个新的日志provider,但是异常处理模块可以不用更新。

  二、设计时配置

 

  所有的模块对于配置节中都包括运行时支持和设计时支持。运行时支持包括配置中的类,在模块加载的时候使用配置中的类。从配置中读取信息,给模块返回包含在配置中的对象。

 

  设计时支持包含一些类,这些类使你可以用配置工具改变配置信息。

 

  下面的图中可以看出运行时支持和设计时支持的关系

 

简单的来所运行时支持的类,使得你可以在运行的时候利用配置节中的信息动态创建对象或者是其他的功能;设计时支持的类,使得你可以在设计的时候使用可视化的界面来设置配置节中的信息,不用手写xml配置节了。

 

  设计时的类依赖于运行时的类,因为通过工具修改配置之后,需要保存,这时候就需要调用运行时的类将配置保存起来。运行时的类不依赖于设计时的类。每一个模块的设计时支持用的类都是单独的程序集。例如,Microsoft.Practices.EnterpriseLibrary.Security.Cache.Configuration.Design.dll,都是包含.Design的。这些程序集在你的应用运行的时候不需要他们,但是在你使用配置工具的时候就需要他们了。

 

  三、仪表盘

  在企业库中,激发事件的代码,和激发事件之后指定应该发生那些行为的代码是分开的。激发事件的代码和provider有关系,对事件作出响应的代码则和listener代码有关。这种分离允许你在事件发生的时候改变行为,而不用重新编译provider代码。但是,重新编译listener还是需要的。

 

  在你感兴趣的东西发生的时候会激活一些事件,例如连接数据库,记录日志。激活事件会在代码运行的时候利用反射调用相关的listener。listener决定了在发生事件的时候,会有那些行为,例如,写日志的时候,可以记录写日志事件,或者是增加计数器。没有listener的模块也可以正常运行。

 

  3.1使用特性Attribute

 

  你可以用下面的特性,来给应用添加listener和instrument

 

  InstrumentationListener

  InstrumentationProvider

  InstrumentationConsumer

  InstrumentationListener特性出现在provider类的上面,告诉它初始化了那一个listener。InstrumentationProvider特性用在provider类的事件上面。InstrumentationConsumer特性用在listener类的事件处理方法上面,标识的名称必须和InstrumentationProvider标识的名称相同。

 

  下面是一段示例代码

public class MyListener

    {

        public MyListener (string instanceName,bool a,bool b,bool c){}

        [InstrumentationConsumerAttribute ("DbConnect")]

        public void ConnectObserved(object sender,EventArgs e)

        {

            Console .WriteLine ("I saw a database connect.");

        }

    }

    [InstrumentationListener (typeof (MyListener ))]

    public class MyApplication

    {

        [InstrumentationProvider("DbConnect")]

        public event EventHandler <EventArgs > OnDbConnect;

    }

  MyApplication就是一个provider,OnDbConnect是provider的一个事件,MyListener中的ConnectObserved就是响应OnDbConnect事件的处理代码。

 

  当系统初始化MyApplication类的时候,会检查特性中是否存在一个MyListener类型的listener,如果存在,也会初始化一个MyListener。然后会检查MyApplication中所有的事件,查看那些被标识了InstrumentationProvider特性。同时也会在MyListener中查看标有InstrumentationConsumer特性的,相同名称的方法。

 

  3.2为组件提供仪表功能

 

  一个应用会包含很多的组件,例如数据库组件。在这时候,可能需要仪表的不是应用本身,而是这个组件。为组件实现仪表功能,要求你的应用实现IInstrumentationEventProvider接口。下面的示例中,MyApplication就是一个应用。

代码 

public class MyListener

    {

        public MyListener (string instanceName,bool a,bool b,bool c){}

        [InstrumentationConsumerAttribute ("DbConnect")]

        public void ConnectObserved(object sender,EventArgs e)

        {

            Console .WriteLine ("I saw a database connect.");

        }

    }

    public class MyApplication:IInstrumentationEventProvider 

    {

        private MyInstrumentationProvider instrumentationProvider=

            new MyInstrumentationProvider ();

        public object GetInstrumentationEventProvider()

        {

            return instrumentationProvider ;

        }

    }

    [InstrumentationListener (typeof (MyListener ))]

    public class MyInstrumentationProvider

    {

        [InstrumentationProvider("DbConnect")]

        public event EventHandler <EventArgs> OnDbConnect;

        public bool IsWired

        {

            get{return OnDbConnect !=null;}

        }

        

    }

   通常系统会从MyApplication类的第一行开始查找InstrumentationListener特性。但是,在上面的例子中,没有这个特性。因此,当系统看到IInstrumentationEventProvider接口的时候,会执行GetInstrumentationEventProvider方法,查找方法返回类型的InstrumentationListener特性,然后查找InstrumentationProvider特性。

 

  3.3安装仪表盘功能

  在listener类中的仪表盘会添加HasInstallableResources特性。下面是一段代码示例。

代码 

    [HasInstallableResources ]

    [PerformanceCountersDefinitionAttribute ("Enterprise Library Data Counters",

                                             "CounterCategoryHelpResourceName")]

    public class DataInstrumentationListener:InstrumentationListener 

    {

        [PerformanceCounter ("Connections Opend/sec","ConnectionOpenedCounterHelpResource",

                             System.Diagnostics.PerformanceCounterType .RateOfCountsPerSecond32 )]

        EnterpriseLibraryPerformanceCounter  connectionOpenedCounter;

        

    }

未完待续。。。。。。。。。。。。。。。。。。。。。

(十三)缓存模块

企业库的缓存模块允许开发者在应用中包含本地缓存。它支持内存缓存,内存中缓存的数据,可以用数据库或者其他分离的存储作为后端数据存储。缓存模块可以不经修改而直接使用,它提供了获取数据、添加数据到缓存、清空缓存中的数据所需的全部方法。同样支持可配置的缓存过期和清除策略。

 

  在构建企业级分布式应用的时候,架构师和开发者将会面对许多挑战。缓存可以帮助他们克服下面的一些挑战:

 

  性能,缓存通过存储和数据消费者最相关的数据,可以提升应用的性能。避免了重复的数据创建,处理和传输。

  可扩展性,在缓存中存储资源帮助节省资源,在有增加应用的需要情况下,提高可扩展性。

  可获得性,在本地存储数据,应用在网络发生潜在问题、webservice发生问题、硬件发生问题的情况下,应用还是可以生存下来,可以继续使用。

  在缓存模块将会讲述下面的主题

 

  缓存模块简介

  使用缓存模块进行开发

  关键的解决方案

  缓存模块的设计

  通过修改来扩展缓存模块的功能

  部署和实施

  缓存模块的QuickStart

  一、缓存模块简介

 

  将通过下面的一些话题,帮助你决定缓存模块是否适合你的应用需求。

 

  通用的解决方案

  示例代码

  缓存模块的突出点

  什么时候使用缓存模块

  缓存模块的取舍

  安全

  除了上面的话题,还将介绍下面的一些内容

 

  使用缓存模块进行开发,首先会介绍如何在你的应用中配置并且使用缓存模块,然后介绍如何选择缓存对应的后端存储方式。

  关键的解决方案,介绍如何使用缓存模块完成典型的缓存操作。

  缓存模块的设计,解释设计模块的相关决定,以及这些决定背后的原因。

  扩展和修改缓存模块,介绍如果通过添加自定义的后端存储以及过期策略来扩展模块,也会介绍如何通过修改源代码来实现。

  部署和实施,介绍如何部署和升级模块的程序集。

  缓存模块的QuickStart,介绍如何安装和配置Caching QuickStart,然后用一系列的代码实践在应用中常用的缓存操作。

  1.1常用的解决方案

 

      在下列情况,很适合使用缓存。

 

      重复访问很少变化,或者没有变化的数据

      当数据访问在创建,访问,传输的时候非常耗时的情况下

      在一些情况下,数据必须要可以获得,例如,不能访问服务的时候。

      你可以在下面的任何一种应用中使用缓存

 

      Windows Form

      Console Form

      Windows Service

      COM+ server

      ASP.NET Web应用或者是webservice

 

  你应该在单个的应用程序域中部署缓存模块。每个应用程序域有一个或者多个缓存,有或者没有对应的后端存储。缓存的数据不能在不同的应用程序域中共享。

 

  缓存模块可以优化性能,并且是线程安全和异常安全的。你可以加入自定义的过期策略和自定义的后端存储。

 

  1.2代码示例

 

  下面的代码演示了添加数据到缓存中,从缓存中获取数据。向缓存中添加Goods对象,优先级是2,如果过期不更新缓存,过期时间为最后一次访问之后的5分钟。

public class Goods

    {

        public string ID{get;set;}

        public string Name{get;set;}

        public int Price{get;set;}

    }

    public class CachingTest

    {

        public static void Test()

        {

        ICacheManager goodsCache=CacheFactory.GetCacheManager();

        string id="001";

        string name="seed";

        int price=100;

        Goods goods=new Goods();

        goods.ID =id;

        goods .Name=name;

        goods .Price=price ;

        goodsCache .Add(goods .ID,goods ,CacheItemPriority .Normal ,

                          null,new SlidingTime (TimeSpan.FromMinutes (5)));

        

        //Retrieve the item

        goods =(Goods )goodsCache .GetData (id );

        }

    }

1.3使用缓存的突出点

 

  企业库的缓存模块包含下面的功能:

 

  你可以使用图形化的配置工具进行配置

  你可以配置一个持久化的存储位置,用企业库的数据访问模块,和内存中的缓存保持同步。

  你可以通过自定义过期策略和自定义后端存储来扩展缓存模块。

  你可以以线程安全的方式保证应用的性能。

  1.4决定什么时候使用缓存

 

  缓存模块适用于大多数的需要缓存的情况,

  1.5可替代的缓存模块

 

  在一些情况,需要另外一个缓存解决方案,例如存在多个应用,缓存和应用不在同一个系统之上。你不能跨web farm同步缓存数据。但是,当你需要改变模块基础行为的时候,你可以用自定义类代替CacheManager。更多的信息,请关注后面的缓存模块的扩展开发。

 

  ASP.NET Cache

 

  .NET框架的System.Web命名空间下面包含了一个缓存,在ASP.NET应用中可以通过System.Web.HTTPContext.Cache使用缓存,这个缓存是给ASP.NET应用开发的。当然了,也可以在ASP.NET以外使用缓存,通过System.Web.HTTPRuntime.Cache来访问。ASP.NET缓存要求安装System.Web程序集。在使用之前,开发者要验证在目标平台和系统上是否支持这个程序集。

 

  1.6安全

 

  尽管你可以在后端存储中加密缓存的数据,缓存模块却不支持加密内存中的数据。如果一个恶意的用户找到一个损害系统的办法,访问应用进程中的内存,便会访问到内存中缓存的数据。如果这对你的系统是一种威胁,就不要将敏感数据放入缓存,例如性用卡号和密码等。

 

(十四)缓存模块2 使用缓存模块进行开发

本节讲述如何在开发应用的过程中使用缓存模块。包括配置缓存模块,在解决方案中引入缓存模块,选择后端存储。

 

  配置缓存模块

  添加应用代码

  选择后端存储

  所有的模块都包括程序集和一份源代码。如果你想使用源代码,在使用QuickStart和配置工具之前需要编译源代码。

 

  1.添加配置信息

 

  如果你是用数据访问模块作为后端存储的访问手段,在配置缓存模块之前需要先配置数据访问模块。

 

  打开企业库安装目录的EntLibConfig.exe工具,如下图所示,首先新建应用,可以通过工具的file菜单中的New Application,然后如图所示创建缓存模块的配置节。\

 

  会自动添加Cache Manager节点作为默认设置

 

 配置CacheManager

 

1)Caching Application Block节点,在右侧的面板中可以修改默认CacheManager的Name。默认CacerManager代表如果没有指定一个Cache Manager的话,就是用默认的。可以修改Name或者从下拉框中重新选择一个。如下图所示,我新建了一个CacheManager,修改名称为MyCacheManager1,然后将它作为默认的CacheManager。

 

2)如上图所示,选中一个CacheManager节点,选中我的MyCacheManager1,可以修改Name。可以设置ExpirationPollFrequencyInSeconds属性,这个属性代表每个多长时间检查一次缓存是否有过期项目,以秒为单位,最小时间间隔是1秒,默认值是60秒。

 

  3)设置MaximunElementsInCacheBeforeScavenging属性,这个属性代表可以在缓存中存放的数据个数的最大值,默认是1000.

 

  4)设置NumberToRemoveWhenScavenging属性,代表在一次过期扫描之后,会有多少个项目从缓存中移除,默认一次移除10个。

 

  默认情况,缓存是存放在内存中,没有对应的后端存储。你可以配置数据库、独立存储或者是自定义的存储作为后端存储。使用数据库作为后端存储,可以用数据访问模块。

 

  配置数据库作为后端存储

 

如果本文引用了你的文章而未注明,请及时联系我。
posted on 2012-07-01 08:51  无忧岛主  阅读(157)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3