购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.4 Spring.NET横空出世

3.4.1 酒文化发展史

MOL是一个喜欢喝酒的人,MOL一直认为酒是人类一个很伟大的发明。追溯到遥远的母系氏族社会,我们的祖先就已经将剩余的野果储存起来,放置一段时间后,就酿成了香甜的甘醴。

我们想一下,原始的祖先们想要喝酒,他们会怎么办?首先采一堆野果,然后放在一个瓦罐中,等几个月,就可以喝到酒了。

这是不是类似于我们写代码的时候直接new一个对象出来?采一堆野果(输出构造函数的参数),放到一个瓦罐中(分配内存),等几个月(CPU工作时间),就可以喝到酒了(得到实例化对象)。从“想要”到“得到”中间所有的过程都是自己亲力亲为。

进入了封建社会后,出现了很多商铺、酒坊。如果MOL想要喝酒,就不用自己采野果酿酒了,直接到咸亨酒店,把9个大钱排开放在柜台上,说:“温两碗酒,要一碟茴香豆”,自有酒保把酒端上来。喝酒的时候,还可以和店小二讨论关于茴字有几种写法的问题。

我们再来想一下,这是不是就是工厂模式的过程?MOL想喝酒了,只需要找咸亨酒店(工厂)表明自己的需求(需要实例化的对象),工厂就会把相应的对象实例化并输出,而MOL完全不用关心酒是怎么做出来的(实例化过程)。

到了21世纪,MOL喝酒也不用去咸亨酒店了,直接在网上订购就可以了,MOL甚至不用关心是哪个工厂做的酒,只需要表达“我要喝酒”的强烈愿望就可以了。

那么,我们在写程序的时候是否也可以做到这样?只需要说“我需要一个IuserDal对象”,然后随着一声惊雷,这个对象就呱呱坠地了。听起来是不是很神奇?但这并不是天方夜谭,接下来的内容将带你完成这个看似神奇的过程。

3.4.2 神奇的IOC

很多80后90后的程序员一定看过86版的西游记。作为影片中的男一号(貌似这个电影中的男一号很多),唐僧这个角色性格鲜明。比如他经常会对一些化妆成为美女的妖怪自我介绍:“贫僧从东土大唐而来,前往西天拜佛求经。”;又比如他经常会说:“施主莫怕,我这几个徒弟相貌虽丑陋点,但心地善良”。可是大家有没有注意过,在86版的西游记中,唐僧这个角色是由3个演员组团塑造的。

这里不方便写出这3位演员的真实名讳,暂且以演员A、演员B、演员C来说明吧。我们以唐僧把孙猴子从五行山下救出的那一集来举例说明。

在电影开拍的时候,导演基本上是不会想到扮演唐僧的演员换了一茬又一茬,所以演员A在表演把孙猴子从五行山下救出来的时候,他会说:“揭了压帖矣,你出来么。”

用程序表示就是:

演员A actor=new 演员A();
Actor.Say("揭了压帖矣,你出来么。");

事实往往就是这么不可预料,当到拍唐僧救孙猴子的那一集时,演员A已经离开了剧组,替代演员A的是演员B,所以这句台词应该由演员B来说。

用程序表示就是:

演员B actor=new 演员B();
Actor.Say("揭了压帖矣,你出来么。");

这样写程序肯定是不对的。角色(Actor)和演员之间造成了强依赖,当演员变换时,就需要重新修改程序、重新编译。

我们完全可以使用一个接口来规范演员,这个接口是这样的:

public Interface IActor 
{ 
    void Say(); 
} 

也就是说,只有实现了IActor接口的对象才可以充当演员的角色。借助工厂模式,代码如下:

IActor actor=Factory.CreateInstance("演员B的DLL所在的路径","演员B的DLL的全路径");

问题解决了,电影可以接着往下拍了!

但好景不长,演员B也离开了剧组,演员C替代了他的位置,程序不得不修改为:

IActor actor=Factory.CreateInstance("演员C的DLL所在的路径","演员C的DLL的全路径");

前面提到,工厂方法里的参数可以写在配置文件中,因此直接修改配置文件就可以了。

但这还不够完美,完美的过程是下面这样的。

编剧说:第13集中的唐僧有一个武打片断。

导演说:没问题,你不用关心演员的事情,你只需要确定“角色”就可以了。

演员XXX说:导演,我有特殊技能,我会跳远。

导演说:好的,你实现了动作演员的接口,等到动作演员需要出场的时候我叫你。

这样一来,编剧不知道演员的存在,而演员也不认识编剧。演员和编剧之间通过导演来调配,需要演员的时候,把演员叫出来上场(实例化),不需要演员的时候就可以把他辞退(销毁)。

用程序来描述就是:

private IActor actor{get;set;}; 
public void Display() 
{ 
    This.actor.Say("揭了压帖矣,你出来么。"); 
} 

有人可能疑惑了:就声明一个接口类型的对象,都没有实例化,就可以直接使用了?答案是肯定的。

声明接口类型的对象,这是编剧做的事情。表示“我需要IActor类型的演员”。

程序运行到This.actor.Say("揭了压帖矣,你出来么。")的时候,导演发现Actor还没有实例化,赶紧找一个演员补上。也就是说,编剧只需要表达需求就可以了,剩下的工作完全是由导演来完成。

原本需要编剧来创建一个演员,现在由导演来“现找”一个演员来完成。这样把原来的过程进行颠倒就是“控制反转”(Inverse Of Control,IOC)。

编剧本来是依赖具体的演员,而现在变成了依赖于抽象的导演“注入”一个演员(导演先找到一个IActor对象,然后再提供给编剧),这就是依赖注入(Dependence Injection,DI)。

这就是大家经常会听到的控制反转和依赖注入。这两个概念比较抽象、难于理解且易混淆的,MOL不要求大家能快速地说出这两个概念到底是什么意思,但是一定要理解这种“注入”的思想。

3.4.3 引出Spring.NET

了解了IOC的思想后,就需要介绍一下常见的IOC框架。

常见的IOC框架有Autofac、Castle Windsor、Unitl、Spring.NET、MEF等。在这些框架中,Autofac在IOC方面的表现是非常良好的,但MOL挑选的是Spring.NET来讲解的,这是为什么呢?

Spring.NET是开源的,这一点就足以吸引大量开源爱好者的眼球了。开源意味着程序员可以很方便地看到源代码,很容易地了解它的工作原理,并且还可以和很多志同道合的程序员交流、探讨。

Spring.NET是从Java移植到.NET平台的。如果一个项目由.NET部分和Java部分组成,那么应尽量使用双方都熟悉的框架,这样会给沟通和维护带来很大的方便,包括以后还会提到的NHibernate,都是这样。

Spring.NET不仅有IOC的功能,还集成了很多其他好用的功能,如AOP(面向切片的编程)等。除此之外,我们还能通过使用Spring.NET来体验一下Java程序员是怎么思考的,这对.NET程序员的帮助很大。

综合考虑,因此MOL选择Spring.NET来进行讲解。

1. 认识Spring.NET

前面已经说过了,Spring.NET有很多功能,还有很多好处,那么Spring.NET到底该怎么使用呢?

Spring.NET中包含有很多概念,MOL挑一些我们能用得着的概念简单说一下。

IOC容器是Spring.NET在IOC方面的一个“灵魂”概念,容器就相当于剧组中的“导演”,它负责生成一个对象给调用者(编导)。

IOC目标对象就相当于演员,编导不关心演员是谁,那就只能由导演来实例化演员了。导演(IOC容器)会从配置文件中找到对应的Object节点进行实例化。

在具体的代码中,就需要在配置文件中描述这两个角色(IOC容器、IOC目标对象)。配置文件可以是App.config或web.config。在配置文件中其配置如下:

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
  <configSections> 
    <sectionGroup name="spring"> 
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> 
      <section name="objects" type="Spring.Context.Support.DefaultSection Handler, Spring.Core"/> 
    </sectionGroup> 
  </configSections> 
</configuration> 

我们可以很清晰地看到,sectionGroup节点中描述了两个角色,分别是context和objects,context就是IOC容器,而objects就是IOC目标对象。

当然,只有这些描述还是不够的,还需要描述具体的IOC目标对象的类型。注意,IOC目标对象的描述一定要在容器中进行,而容器一定要放在spring节点中,例如下面的配置:

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
  <configSections> 
    <sectionGroup name="spring"> 
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> 
      <section name="objects" type="Spring.Context.Support.DefaultSection Handler, Spring.Core"/> 
    </sectionGroup> 
  </configSections> 
  <spring> 
    <context> 
     <object id="userDALRef" type=" Elands.JinCard.DAL.userDal" /> 
    </context> 
  </spring> 
</configuration> 

上面粗体显示的配置描述了我们要在容器中配置一个userDal的对象,这个对象由context容器来管理。

是不是so easy?

配置完成以后,代码中应该如何使用呢?

首先需要有一个容器对象,这个容器对象将管理配置文件中的所有IOC目标对象,获取容器对象的方法很简单:

IApplicationContext ctx = ContextRegistry.GetContext();

IApplicationContext接口类型是容器类型的父类,在Spring.NET中有很多的容器类型,这里就不展开说了。

有了容器对象,就可以通过容器来获取IOC目标对象了。获取userDal的方法如下:

IuserDAL userDal= (IuserDAL)ctx.GetObject("userDALRef");

其中,userDALRef就是配置文件中描述的object节点中的Id属性。

这样就可以使用IuserDAL对象了。

2. 只配置,不写代码

经过MOL的讲解,大家似乎已经知道了怎么使用Spring.NET解耦。

李冲冲:虽然通过容器来获取对象这种写法和我们以前的代码风格不一样,但还是比较好接受的。你以前说可以只声明,不实例化就可以使用IuserDAL对象,但上面的代码还是在声明之外,需要再做一下获取的步骤吗?

MOL:我们的目标是只声明IuserDAL,其他的啥都不做,这也是接下来要讲的内容。只要理解了上面讲的获取对象的方法,下面的内容就很好理解了。

通过前面的讲解我们了解到,获取一个对象,需要以下两个步骤:

(1)获取容器。

(2)通过容器来获取接口对象。

我们可以通过单例设计模式来获取容器,保证获取容器的代码不会重复。但是获取接口对象就不能幸免了,必须写大量的获取接口的代码,例如:

IuserDAL 对象A= (IuserDAL)ctx.GetObject("接口A");
IuserDAL 对象B = (IuserDAL)ctx.GetObject("接口B ");
IuserDAL 对象C = (IuserDAL)ctx.GetObject("接口C ");

在写WinForm代码的时候,这样的重复是不可避免的。但是“晋商卡”项目是一个Web应用,那么这个事情就变得简单了。

Spring.NET简直就是一个神器,它包含了许多功能。接下来要使用的MVC组件,也是其功能之一。

大家都知道,MVC项目中关于MVC的定义是在Global.asax这个文件中的,如果使用微软自带的MVC,定义是这样的:

public class MvcApplication : System.Web.HttpApplication

而MOL更愿意使用Spring.NET提供的MVC(至于原因,之后就明白了),需要把MVC的定义修改为:

public class MvcApplication : Spring.Web.Mvc.SpringMvcApplication

其他的代码不需要做任何调整。

那么为什么使用Spring.NET提供的MVC呢?使用Spring.NET的MVC可以不用关心容器是什么样的,也不用管它存放在哪里;不用去手动写代码获取IOC目标对象了。这样一来,就不会出现大量重复性的代码,真正地实现了:声明接口对象就可以直接使用。

例如,现在有一个BLL层需要调用IuserDAL对象,这个BLL层如下:

public class userBll 
{ 
    //定义一个IuserDAL类型的对象 
    Public IuserDAL userDal{get;set;} 
    Public AddUser(userModel input) 
    { 
    //调用IuserDAL的Add()方法 
        userDal.Add(input); 
    } 
} 

在这段代码里,我们没有写任何获取IuserDAL对象的代码,那么UserDAL对象是怎么来的呢?请看配置文件:

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
  <configSections> 
    <sectionGroup name="spring"> 
      <section name="context" type="Spring.Context.Support.MvcContextHandler, Spring.Web.Mvc3" /> 
      <section name="parsers" type="Spring.Context.Support.NamespaceParsers SectionHandler, Spring.Core" /> 
    </sectionGroup> 
  </configSections> 
  <spring> 
    <context> 
    <object type="Elands.JinCard.BLL.userBLL, Elands.JinCard.BLL" singleton= "false" > 
        <property name="IuserDAL" ref="IuserDALREF" /> 
    </object> 
    <object id="IuserDALREF" type="Elands.JinCard.DAL.userDAL,Elands.JinCard. DAL"  singleton="false" ></object> 
    </context> 
  </spring> 
</configuration> 

在这个配置文件中,描述了userBLL中需要一个叫IuserDAL的对象,而IuserDAL对象是引用id="IuserDALREF"的object节点的。

找到<object id="IuserDALREF"…>这个节点,就可以确定IuserDAL是要用userDAL这个类来实例化,然后容器就会按照相应的类型去实例化该对象。

所有这一切过程,都是在Spring.NET的MVC组件中完成的。细心的读者一定看出来了,我们在定义容器的时候使用的已经是Spring.Web.Mvc3了。

经过这样的配置,就达到了“只配置,不写代码”的目的。

很多读者觉得这些配置好乱,看得太晕了,还是new来得简单一些。其实学Java的人刚开始也是这样的,被各种各样的配置弄的晕头转向,但看得多了,也就习惯了。一般的同学在被虐一个月之后,基本上都觉得这种写法真是太爽了!大家自己再把MOL讲过的代码输入一遍,在输入代码的时候一定会遇到缺少引用的问题,所以在创建好解决方案之后,一定要把DLL引用到项目中。DLL路径是:源代码\第3章\简单三层\Elands.JinCard\Library\Spring。 bW+YW5tWHAF91z/KWLDDenxRueLKSRinnihKaqYRSgzNohdeYnkfIrcIcL3V4MKq

点击中间区域
呼出菜单
上一章
目录
下一章
×