65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Spring.net Framework 进行即插即用 (DI)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.38/5 (4投票s)

2014年3月16日

CPOL

7分钟阅读

viewsIcon

23401

downloadIcon

209

使用 Spring.net Framework 进行即插即用,我们还将了解如何使用 Spring.Net 配置来连接我们的应用程序。

引言

在本文中,我们将探讨当我们在其他类中创建直接依赖时遇到的一些问题,以及如何使用 spring.net 解决这些问题。我们还将了解如何使用 spring.net 配置将我们所有的类/层连接在一起。本文的示例代码是用 Visual Studio 2013 编写的,但它不依赖于 Visual Studio 版本。如果您手动配置 spring.net DLL,则需要选择适当的 spring.net 版本。

我希望收到关于本文的反馈。请随时分享您的评论,您也可以发送电子邮件至 shettyashwin@outlook.com。如果您喜欢本文,请不要忘记评分。

直接依赖

为了演示直接依赖,我创建了一个示例应用程序。这个示例应用程序是一个控制台应用程序。控制台应用程序将与外部世界进行交互。控制台应用程序接收客户 ID。其余逻辑由其下方的 3 个层管理。这三个层是外观层、业务层和数据层。

internal class Program
    {
        public static void Main()
        {
            Console.WriteLine("Enter customer id & press enter");
            int customerId = Convert.ToInt32(Console.ReadLine().ToString());
            DoWork doWork = new DoWork();
            doWork.DoProcessingWork(customerId);
            Console.ReadLine();
        }
    } 

外观层将用于协调验证过程,然后在验证成功执行后通过业务层添加客户 ID。

 public class DoWork 
    {
        public DoWork()
        {} 

        public virtual void DoProcessingWork(int customerId)
        {
            CustomerProcessor _processor = new CustomerProcessor();
            if (!IsCustomerValidate(customerId))
            {
                Console.WriteLine("Adding new customer ID");
                _processor.Add(customerId);
            }
            else
            {
                Console.WriteLine("Customer Id should be greater than 10");
            }
        }
 
        public virtual bool IsCustomerValidate(int customerId)
        {
            CustomerValidate _CustomerValidate = new CustomerValidate();
            return _CustomerValidate.ValidateCustomer(customerId);
        }
    } 

业务层将与数据层交互以验证客户 ID,然后添加它。

 public class CustomerProcessor
    {
        public void Add(int customerId)
        {
            Customer _customer = new Customer();
            if(customerId < 1) throw new Exception("Customer Id can't be less 1");
            _customer.Add(customerId);
        }
    } 

  public class CustomerValidate
    {
        public virtual bool ValidateCustomer(int customerId)
        {
            Search _search = new Search();
            return _search.IsCustomerIdRegistered(customerId);
        }
    }  

由于我创建此应用程序仅用于演示目的,因此我不会创建数据库。我已经硬编码了客户 ID 进行验证,并且当客户 ID 通过数据层添加时,它会将一条消息写回控制台。直接写入控制台不是理想的解决方案,但现在我们可以忽略它。

public class Search 
    {
        public bool IsCustomerIdRegistered(int customerId)
        {
            if (customerId < 10)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    } 
 public class Customer 
    {
        public void Add(int customerId)
        {
            Console.WriteLine("Added new Customer Id :" + customerId.ToString());
        }
    } 

下面的顺序图将提供有关层和流程如何流动的 ধারণা。

问题区域

  1. 控制台应用程序、外观层和业务层直接创建它们引用的实例。
  2. 所有类/层都紧密绑定在一起。要将其映射到不同的类/层,我们将不得不进行代码更改。
  3. 编写单元测试用例会很麻烦,因为我们无法直接模拟任何层。
  4. 为不同客户应用定制逻辑会很困难。这将涉及大量代码中的 if 和 else,这会降低可维护性。

什么是 Spring.Net

Spring.net 是一个开源应用程序框架,用于构建企业级 .NET 应用程序。它源自 Java 版本的 Spring 框架。它允许您在使用基础类库时消除偶然的复杂性,并遵循最佳实践。您可以在 这里 找到有关 Spring.net 的更多详细信息。

为了演示,在本文中,我们将使用支持依赖注入的 Spring.Core。

使用 Spring.Net 的 DI 解决方案

为了解决上述问题,我们必须使用依赖注入。对于依赖注入,我将使用 Spring.Net 框架。通过使用 Spring 框架,我们将创建应用程序上下文。我们处理数据所需的所有类都将预先配置好,并且对象实例将使用应用程序上下文创建。我们将逐步实现这一目标。但首先,我们需要将所需的 Spring 引用放在一起。

为了获取 spring.net 引用 DLL,我使用了 NuGet。Nuget 管理器会在控制台应用程序中设置所有必需的引用。其他层将不需要 spring DLL 引用,因为我们将使用控制台应用程序中的 *app.configuration* 文件将它们全部连接在一起。

在 *app.config* 中,我们将不得不提及节、上下文和对象集合详细信息。这是我们在 *app.config* 中配置节详细信息的方式。

<configSections>
   <sectionGroup name="spring">
     <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
     <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
   </sectionGroup>
</configSections> 

现在我们可以继续在 *app.config* 中创建一个单独的节。如上所述,我在控制台应用程序的 *app.config* 中创建了 spring 节。

 <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmls="http://www.SpringFramework.net">
      <object name="BusinessDoWork" 
      type="SpringSample.BusinessLayer.CustomerValidate, 
      SpringSample.BusinessLayer" singleton="false">
        <property name="Search" ref="DataLayerSearch"/>
      </object>
      <object name="BusinessCustomerProcessor" 
      type="SpringSample.BusinessLayer.CustomerProcessor, 
      SpringSample.BusinessLayer" singleton="false">
        <property name="Customer" ref="DataLayerCustomer"/>
      </object>
      <object name="FacadeCustomer" 
      type="SpringSample.FacadeLayer.DoWork, SpringSample.FacadeLayer" 
      singleton="false">
        <constructor-arg ref="BusinessDoWork"/>
        <constructor-arg ref="BusinessCustomerProcessor"/>
      </object>
      <object name="FacadeCustomerValidate" 
      type="SpringSample.FacadeLayer.DoWork, SpringSample.FacadeLayer" 
      singleton="false">
        <constructor-arg ref="BusinessDoWork"/>
      </object>
      <object name="DataLayerSearch" 
      type="SpringSample.DataLayer.Search, SpringSample.DataLayer" 
      singleton="false"/>
      <object name="DataLayerCustomer" 
      type="SpringSample.DataLayer.Customer, SpringSample.DataLayer" 
      singleton="false"/>
    </objects>
  </spring> 

在 spring 节内,您可以看到两个子节(Context 和 objects)。Context 定义了需要从哪里加载/映射对象的。Spring.net 还可以从外部源(如 XML 文件)加载对象。因此,我们需要指定一个已配置的位置,从中可以加载对象及其关系。Objects 子节定义了实例及其映射。一旦我们回顾我们所做的更改,我将详细解释这个部分。

作为依赖注入的一部分,我们还需要进行的另一个重要更改是定义契约,然后在我们创建的类中实现它。在当前的示例中,我使用接口创建了契约,但您也可以使用 `abstract` 类来实现此目的,如果您有需要共享的通用逻辑。这些契约是在单独的项目中创建的,以便逻辑和契约保留在单独的程序集中。

public interface ICustomerProcessor
    {
        ICustomer Customer { get; set; }
        void Add(int customerId);
    } 

public interface ICustomerValidate
    {
        ISearch Search { get; set; }
        bool ValidateCustomer(int customerId);
    }

 public interface ICustomer
    {
        void Add(int customerId);
    } 

 public interface ISearch
    {
        bool IsCustomerIdRegistered(int customerId);
    }

public interface IDoWork
    {
        void DoProcessingWork(int customerId);
        bool IsCustomerValidate(int customerId);
    }  

为了展示不同的注入方式,我在 `ICustomerProcessor`(`Customer`)和 `ICustomerValidate`(`Search`)中添加了其他属性。在业务层,我们将使用属性注入;在外观层,我们将通过构造函数注入对象。数据层变化不大,您看到的唯一变化是继承了 `ISearch` 和 `ICustomer` 接口。

 public class Customer : ICustomer
    {
        public void Add(int customerId)
        {
            Console.WriteLine("Added new Customer Id :" + customerId.ToString());
        }
    }

 public class Search : ISearch
    {
        public bool IsCustomerIdRegistered(int customerId)
        {
            if (customerId < 10)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

这就是我们在 *app.config* 的 spring.net 配置中配置数据层的方式

 <object name="DataLayerSearch" 
    type="SpringSample.DataLayer.Search, 
    SpringSample.DataLayer" singleton="false"/>
      <object name="DataLayerCustomer" type="SpringSample.DataLayer.Customer, 
       SpringSample.DataLayer" singleton="false"/> 

同样在业务层,我们将继承为业务层创建的接口(`ICustomerProcessor` 和 `ICustomerValidate`),并更改对象创建逻辑。此后,我们不会创建任何对象,而是将引用注入到属性中。另请注意,我们将从业务层项目中删除对数据层项目的引用。由于我们只编写针对接口签名的代码,因此我们需要添加对契约项目的引用。

 public class CustomerProcessor : ICustomerProcessor
    {
        public ICustomer Customer { get; set; }

        public void Add(int customerId)
        {
            if(customerId < 1) throw new Exception("Customer Id can't be less 1");
            Customer.Add(customerId);
        }
    }

 public class CustomerValidate : ICustomerValidate
    {
        public ISearch Search { get; set; }

        public virtual bool ValidateCustomer(int customerId)
        {
            return this.Search.IsCustomerIdRegistered(customerId);
        }
    }

相同的更改将是 Spring 配置。我已经添加了 `property` 标签,它引用为数据层创建的对象存储。当创建业务层的对象时,Spring.net 将根据引用注入数据层对象。

<object name="BusinessDoWork" type="SpringSample.BusinessLayer.CustomerValidate, 
                              SpringSample.BusinessLayer" singleton="false">
    <property name="Search" ref="DataLayerSearch"/>
</object>
<object name="BusinessCustomerProcessor" 
type="SpringSample.BusinessLayer.CustomerProcessor, 
SpringSample.BusinessLayer" singleton="false">
    <property name="Customer" 
    ref="DataLayerCustomer"/>
</object> 

外观层也需要进行相同的更改。将继承新创建的接口,并删除对象创建逻辑。在外观层,对象将通过 `Constructor` 注入。与业务层项目类似,我们将删除对业务层和数据层的引用,并将对外观层中的契约项目的引用。

 public class DoWork : IDoWork
    {
        protected ICustomerValidate _CustomerValidate;
        protected ICustomerProcessor _processor;
        public DoWork(ICustomerValidate customerValidate, ICustomerProcessor processor)
        {
            _CustomerValidate = customerValidate;
            _processor = processor;
        }

        public DoWork(ICustomerValidate customerValidate)
        {
            _CustomerValidate = customerValidate;
        }

        public virtual void DoProcessingWork(int customerId)
        {
            if (!IsCustomerValidate(customerId))
            {
                Console.WriteLine("Adding new customer ID");
                _processor.Add(customerId);
            }
            else
            {
                Console.WriteLine("Customer Id should be greater than 10");
            }
        }

        public virtual bool IsCustomerValidate(int customerId)
        {
            return _CustomerValidate.ValidateCustomer(customerId);
        }
    }

这就是我们在 *app.config* 的 spring 配置中配置外观层的方式。要通过构造函数注入对象,我添加了 `constructor-arg` 标签。此标签通知 spring.net 根据 `ref` 属性中提到的键名注入依赖项对象。

<object name="FacadeCustomer" type="SpringSample.FacadeLayer.DoWork, 
                                    SpringSample.FacadeLayer" singleton="false">
        <constructor-arg ref="BusinessDoWork"/>
        <constructor-arg ref="BusinessCustomerProcessor"/>
</object>

在 Spring.net 中,我们可以选择构造函数或属性来创建对象。这只能根据您为对象创建配置的参数来实现。我在外观层为验证创建了一个单独的构造函数。如果逻辑要求只获取验证结果,那么可以通过单参数构造函数实现。*app.config* 中的配置如下所示

<object name="FacadeCustomerValidate" type="SpringSample.FacadeLayer.DoWork, 
                                            SpringSample.FacadeLayer" singleton="false">
        <constructor-arg ref="BusinessDoWork"/>
</object> 

通过构造函数/属性引用所有对象,我们消除了直接依赖。对象现在被注入了。没有任何类创建自己的对象,而是通过构造函数或属性引用它们。整个流程由配置文件连接。由于这些类/层没有直接链接,因此可以根据我们的需要进行调整。唯一剩下的部分是将外观层与 UI 连接起来。我们通过这种方式实现:

internal class Program
    {
        public static void Main()
        {
            IApplicationContext appContext = ContextRegistry.GetContext();
            Console.WriteLine("Enter customer id & press enter");
            int customerId = Convert.ToInt32(Console.ReadLine().ToString());

            IDoWork doWork = appContext["FacadeCustomerValidate"] as IDoWork;
            doWork.IsCustomerValidate(customerId);

            doWork = appContext["FacadeCustomer"] as IDoWork;
            doWork.DoProcessingWork(customerId);

            Console.ReadLine();
        }
    } 

通过使用 `ContextRegistry`,我们可以获取 `ApplicationContext`。我们处理用户输入所需的所有对象实例都通过 `ApplicationContext` 获取。我们需要传递我们在 *App.Config* 中配置的 `KeyName`。

有关使用 MOQ 编写测试用例,您可以参考我另一篇文章 TDD using MOQ

关注点

在本文中,我们看到了如何消除直接依赖并使用 spring.net 配置将所有层连接在一起。希望这能让您对如何解耦类/层有所了解。当我们可以预见到大量定制化时,类/层的解耦变得非常重要。当我们需要编写 100% 可测试的代码时,这种策略也很有帮助。

参考文献

© . All rights reserved.