Spring.Net在MVC中实现注入的原理解析

前言

本文将介绍Spring.Net(不仅仅是Spring.Net,其实所有的IoC容器要向控制器中进行注入,原理都是差不多的)在MVC控制器中依赖注入的实现原理,本文并没有关于在MVC使用Spring怎么配置,怎么使用,怎么实现。

引言放在前面,只是为了避免浪费你的时间。

望你能静心片刻,认真阅读。

情景

public class HomeController : Controller
  {
    //这是一个很神奇的注入
    private IBLL.IUserInfoService UserInfoService { get; set; }
    public ActionResult Index()
    {
      return Content(UserInfoService.GetName());
    }
  }

每次看代码都有不一样的理解,今天我在看MVC控制器中一个通过Spring.Net依赖注入的UserInfoService属性时,突然有些疑问,注入的前提是控制反转,这么说我的Controller是从IoC容器中来的了?但是我不记得在哪个地方有配置额,对此我展开了深入的研究。

从MVC本身开始

首先我们要搞懂MVC本身是通过什么方式获取控制器对象的,本质如果都没有搞懂,又何来扩展呢?

在MVC模式下,通过实现IControllerFactory接口的对象来获取当前请求的控制器对象,实现IControllerFactory接口的对象也就是控制器的创建工厂。

简单看下IControllerFactory

//
  // 摘要:
  //   定义控制器工厂所需的方法。
  public interface IControllerFactory
  {
    //
    // 摘要:
    //   使用指定的请求上下文来创建指定的控制器。
    //
    // 参数:
    //  requestContext:
    //   请求上下文。
    //
    //  controllerName:
    //   控制器的名称。
    //
    // 返回结果:
    //   控制器。
    IController CreateController(RequestContext requestContext, string controllerName);
    //
    // 摘要:
    //   获取控制器的会话行为。
    //
    // 参数:
    //  requestContext:
    //   请求上下文。
    //
    //  controllerName:
    //   你想要获取器其会话行为的控制器的名称。
    //
    // 返回结果:
    //   控制器的会话行为。
    SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
    //
    // 摘要:
    //   释放指定的控制器。
    //
    // 参数:
    //  controller:
    //   控制器。
    void ReleaseController(IController controller);
  }

一个Http请求过来,选择哪个控制器是通过MvcHandler来处理的

控制器工厂是通过ControllerBuilder的Current属性提供给MvcHandler使用的

下面的代码是反编译过来的,简单看下即可(因为我要标记黄色高亮部分,所以没有折叠)

internal ControllerBuilder ControllerBuilder
{
  get
  {
    if (this._controllerBuilder == null)
    {
      this._controllerBuilder = ControllerBuilder.Current;
    }
    return this._controllerBuilder;
  }
  set
  {
    this._controllerBuilder = value;
  }
}
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
{
  // Fields
  private ControllerBuilder _controllerBuilder;
  private static readonly object _processRequestTag;
  internal static readonly string MvcVersion;
  public static readonly string MvcVersionHeaderName;

  // Methods
  static MvcHandler();
  public MvcHandler(RequestContext requestContext);
  protected internal virtual void AddVersionHeader(HttpContextBase httpContext);
  protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state);
  protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state);
  protected internal virtual void EndProcessRequest(IAsyncResult asyncResult);
  private static string GetMvcVersionString();
  protected virtual void ProcessRequest(HttpContext httpContext);
  protected internal virtual void ProcessRequest(HttpContextBase httpContext);
  private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory);
  private void RemoveOptionalRoutingParameters();
  IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
  void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result);
  void IHttpHandler.ProcessRequest(HttpContext httpContext);

  // Properties
  internal ControllerBuilder ControllerBuilder { get; set; }
  public static bool DisableMvcResponseHeader { get; [CompilerGenerated] set; }
  protected virtual bool IsReusable { get; }
  public RequestContext RequestContext { get; [CompilerGenerated] private set; }
  bool IHttpHandler.IsReusable { get; }

  // Nested Types
  [Serializable, CompilerGenerated]
  private sealed class <>c
  {
    // Fields
    public static readonly MvcHandler.<>c <>9;
    public static BeginInvokeDelegate<MvcHandler.ProcessRequestState> <>9__20_0;
    public static EndInvokeVoidDelegate<MvcHandler.ProcessRequestState> <>9__20_1;
    public static Func<KeyValuePair<string, object>, bool> <>9__26_0;

    // Methods
    static <>c();
    public <>c();
    internal IAsyncResult <BeginProcessRequest>b__20_0(AsyncCallback asyncCallback, object asyncState, MvcHandler.ProcessRequestState innerState);
    internal void <BeginProcessRequest>b__20_1(IAsyncResult asyncResult, MvcHandler.ProcessRequestState innerState);
    internal bool <RemoveOptionalRoutingParameters>b__26_0(KeyValuePair<string, object> entry);
  }

  [StructLayout(LayoutKind.Sequential)]
  private struct ProcessRequestState
  {
    internal IAsyncController AsyncController;
    internal IControllerFactory Factory;
    internal RequestContext RequestContext;
    internal void ReleaseController();
  }
}

默认工厂

默认情况下,在ControllerBuilder内部会创建一个DefaultControllerFactory类型的对象,以提供处理请求。

DefaultControllerFactory是实现IControllerFactory接口的。

//
  // 摘要:
  //   表示默认情况下已注册的控制器工厂。
  public class DefaultControllerFactory : IControllerFactory
  {
    //
    // 摘要:
    //   初始化 System.Web.Mvc.DefaultControllerFactory 类的新实例。
    public DefaultControllerFactory();
    //
    // 摘要:
    //   使用控制器激活器来初始化 System.Web.Mvc.DefaultControllerFactory 类的新实例。
    //
    // 参数:
    //  controllerActivator:
    //   实现控制器激活器接口的对象。
    public DefaultControllerFactory(IControllerActivator controllerActivator);

    //
    // 摘要:
    //   使用指定的请求上下文来创建指定的控制器。
    //
    // 参数:
    //  requestContext:
    //   HTTP 请求的上下文,其中包括 HTTP 上下文和路由数据。
    //
    //  controllerName:
    //   控制器的名称。
    //
    // 返回结果:
    //   控制器。
    //
    // 异常:
    //  T:System.ArgumentNullException:
    //   requestContext 参数为 null。
    //
    //  T:System.ArgumentException:
    //   controllerName 参数为 null 或为空。
    public virtual IController CreateController(RequestContext requestContext, string controllerName);
    //
    // 摘要:
    //   释放指定的控制器。
    //
    // 参数:
    //  controller:
    //   要释放的控制器。
    public virtual void ReleaseController(IController controller);
    //
    // 摘要:
    //   检索指定请求上下文和控制器类型的控制器实例。
    //
    // 参数:
    //  requestContext:
    //   HTTP 请求的上下文,其中包括 HTTP 上下文和路由数据。
    //
    //  controllerType:
    //   控制器的类型。
    //
    // 返回结果:
    //   控制器实例。
    //
    // 异常:
    //  T:System.Web.HttpException:
    //   controllerType 为 null。
    //
    //  T:System.ArgumentException:
    //   无法分配 controllerType。
    //
    //  T:System.InvalidOperationException:
    //   无法创建 controllerType 的实例。
    protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType);
    //
    // 摘要:
    //   返回控制器的会话行为。
    //
    // 参数:
    //  requestContext:
    //   请求上下文。
    //
    //  controllerType:
    //   控制器的类型。
    //
    // 返回结果:
    //   控制器的会话行为。
    protected internal virtual SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType);
    //
    // 摘要:
    //   检索指定名称和请求上下文的控制器类型。
    //
    // 参数:
    //  requestContext:
    //   HTTP 请求的上下文,其中包括 HTTP 上下文和路由数据。
    //
    //  controllerName:
    //   控制器的名称。
    //
    // 返回结果:
    //   控制器类型。
    protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName);
  }

默认情况下,Controller类需要提供默认的构造函数,因为DefaultControllerFactory是通过反射来创建Controller对象实例的。

如果我们定义的Controller需要通过构造函数创建,或者通过某个IoC容器管理Controller,可以通过自定义控制器工厂来实现。

自定义控制器工厂

为什么说这么多关于控制器工厂的东西呢,其实Spring.Net就是通过继承DefaultControllerFactory创建SpringControllerFactory的。

说了这么多就是为了后面可以更容易的理解Spring.Net的控制器工厂源码罢了。

回归正题,接着创建自己的控制器工厂。

1.Home控制器内容如下

public class HomeController : Controller
  {
    private IUserInfoService UserInfoService { get; set; }
    public HomeController(IUserInfoService userInfoService)
    {
      UserInfoService = userInfoService;
    }
    public ActionResult Index()
    {
      return Content(UserInfoService.GetName());
    }
  }

这里的UserInfoService只是一个很简陋的测试类,只有一个GetName()方法用来返回“小明”。

接下来将通过自定义控制器工厂实现构造注入UserInfoService

2.创建控制器工厂MyControllerFactory

为了方便我直接继承了DefaultControllerFactory,当然也可以通过实现IControllerFactory来创建

public class MyControllerFactory : DefaultControllerFactory
  {
    private static readonly IBLL.IUserInfoService userInfoService = new BLL.UserInfoService();

    //重写CreateController
    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
      IController controller = null;
      if (controllerName == "Home")
      {
        //如果是我们制定的Home控制器则给其实例化,并通过构造参数注入userInfoService
        controller = new HomeController(userInfoService);
      }
      else
      {
        //通过默认控制器工厂创建控制器
        controller = base.CreateController(requestContext, controllerName);
      }
      return controller;
    }
  }

3.在Global.asax中注册

protected void Application_Start()
    {
      MyControllerFactory myControllerFactory = new MyControllerFactory();
      //通过ControllerBuilder设置制定的控制器工厂
      ControllerBuilder.Current.SetControllerFactory(myControllerFactory);
      AreaRegistration.RegisterAllAreas();
      RouteConfig.RegisterRoutes(RouteTable.Routes);
    }

4.运行测试(神奇不再神奇)

意料之外,情理之中,我们并没有在控制器中实例化,结果却出来了

(实例化在工厂中完成了)

Spring.Net注入原理

说了这么多,回头看看标题“Spring.Net是怎么在MVC中实现注入的”,你倒是说啊,等的花都谢了,连Spring.Net的毛都没看到.....

其实,如果你是认真读过来的,答案在你心中应该已经有了。

答案如下

namespace Spring.Web.Mvc
{
  /// <summary>
  /// Controller Factory for ASP.NET MVC
  /// </summary>
  public class SpringControllerFactory : DefaultControllerFactory
  {
    private static IApplicationContext _context;

    /// <summary>
    /// Gets the application context.
    /// </summary>
    /// <value>The application context.</value>
    public static IApplicationContext ApplicationContext
    {
      get
      {
        if (_context == null || _context.Name != ApplicationContextName)
        {
          if (string.IsNullOrEmpty(ApplicationContextName))
          {
            _context = ContextRegistry.GetContext();
          }
          else
          {
            _context = ContextRegistry.GetContext(ApplicationContextName);
          }
        }

        return _context;
      }
    }

    /// <summary>
    /// Gets or sets the name of the application context.
    /// </summary>
    /// <remarks>
    /// Defaults to using the root (default) Application Context.
    /// </remarks>
    /// <value>The name of the application context.</value>
    public static string ApplicationContextName { get; set; }

    /// <summary>
    /// Creates the specified controller by using the specified request context.
    /// </summary>
    /// <param name="requestContext">The context of the HTTP request, which includes the HTTP context and route data.</param>
    /// <param name="controllerName">The name of the controller.</param>
    /// <returns>A reference to the controller.</returns>
    /// <exception cref="T:System.ArgumentNullException">The <paramref name="requestContext"/> parameter is null.</exception>
    /// <exception cref="T:System.ArgumentException">The <paramref name="controllerName"/> parameter is null or empty.</exception>
    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
      IController controller;

      if (ApplicationContext.ContainsObjectDefinition(controllerName))
      {
        controller = ApplicationContext.GetObject(controllerName) as IController;
      }
      else
      {
        controller = base.CreateController(requestContext, controllerName);
      }

      AddActionInvokerTo(controller);

      return controller;
    }

    /// <summary>
    /// Retrieves the controller instance for the specified request context and controller type.
    /// </summary>
    /// <param name="requestContext">The context of the HTTP request, which includes the HTTP context and route data.</param>
    /// <param name="controllerType">The type of the controller.</param>
    /// <returns>The controller instance.</returns>
    /// <exception cref="T:System.Web.HttpException">
    ///   <paramref name="controllerType"/> is null.</exception>
    /// <exception cref="T:System.ArgumentException">
    ///   <paramref name="controllerType"/> cannot be assigned.</exception>
    /// <exception cref="T:System.InvalidOperationException">An instance of <paramref name="controllerType"/> cannot be created.</exception>
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
      IController controller = null;

      if (controllerType != null)
      {
        var controllers = ApplicationContext.GetObjectsOfType(controllerType);
        if (controllers.Count > 0)
        {
          controller = (IController)controllers.First().Value;
        }
      }

      if (controller == null)
      {
        //pass to base class for remainder of handling if can't find it in the context
        controller = base.GetControllerInstance(requestContext, controllerType);
      }

      AddActionInvokerTo(controller);

      return controller;
    }

    /// <summary>
    /// Adds the action invoker to the controller instance.
    /// </summary>
    /// <param name="controller">The controller.</param>
    protected virtual void AddActionInvokerTo(IController controller)
    {
      if (controller == null)
        return;

      if (typeof(Controller).IsAssignableFrom(controller.GetType()))
      {
        ((Controller)controller).ActionInvoker = new SpringActionInvoker(ApplicationContext);
      }
    }
  }
}

关于代码我想就不用过多解释了,有了上面的知识基础,这就是一看就懂的那种。

算了,我还是说一下CreateController方法吧,防止有不熟悉Spring.Net的小伙伴。

ApplicationContext:这就是相当于IoC容器的东西

ApplicationContext.ContainsObjectDefinition(controllerName):返回容器中是否存在名称为controllerName的对象

总结

仔细品味每一行代码,会发现任何东西都没有表面上那么简单,每一个实现的背后都值得深入研究。

码了这么长时间,希望能对正在阅读的你有所帮助。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解spring-cloud与netflixEureka整合(注册中心)

    基础依赖 compile('org.springframework.boot:spring-boot-starter-actuator') compile('org.springframework.boot:spring-boot-starter-web') compile('org.springframework.cloud:spring-cloud-starter') eureka(单机) 服务端: 依赖 compile('org.springframework.cloud:spring-c

  • Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)

    网上好多例子都是群发的,本文实现一对一的发送,给指定客户端进行消息推送 1.本文使用到netty-socketio开源库,以及MySQL,所以首先在pom.xml中添加相应的依赖库 <dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> <version>1.7.11</version&

  • MVC使用Spring.Net应用IOC(依赖倒置)学习笔记3

    到现在,我们已经基本搭建起了项目的框架,但是项目中还存在一个问题,就是尽管层与层之间使用了接口进行隔离,但实例化接口的时候,还是引入了接口实现类的依赖,如下面的代码: private IUserService _userService; private IUserService UserService { get { return _userService ?? (_userService = new UserService()); } set { _userService = value; }

  • Spring Boot集成netty实现客户端服务端交互示例详解

    前言 Netty 是一个高性能的 NIO 网络框架,本文主要给大家介绍了关于SpringBoot集成netty实现客户端服务端交互的相关内容,下面来一起看看详细的介绍吧 看了好几天的netty实战,慢慢摸索,虽然还没有摸着很多门道,但今天还是把之前想加入到项目里的 一些想法实现了,算是有点信心了吧(讲真netty对初学者还真的不是很友好......) 首先,当然是在SpringBoot项目里添加netty的依赖了,注意不要用netty5的依赖,因为已经废弃了 <!--netty--> <

  • Spring Cloud Netflix架构浅析(小结)

    最近接触微服务这块的东西,对这方面有了一些了解,拿出来和大家分享一下. 1. 微服务框架Spring Boot+Spring Cloud  Spring Cloud是基于Spring Boot的一整套实现微服务的框架,可以说,Spring Boot作为框架,Spring Cloud作为微服务,一起构成了一种不可忽视的.新生的框架体系.它提供了微服务开发所需的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等组件,方便易用.Spring Cloud

  • spring+netty服务器搭建的方法

    游戏一般是长连接,自定义协议,不用http协议,BIO,NIO,AIO这些我就不说了,自己查资料 我现在用spring+netty搭起简单的游戏服 思路:1自定义协议和协议包:2spring+netty整合:3半包粘包处理,心跳机制等:4请求分发(目前自己搞的都是单例模式) 下个是测试用的,结构如下 首先自定义包头 Header.java package com.test.netty.message; /** * Header.java * 自定义协议包头 * @author janehuang

  • Spring.Net在MVC中实现注入的原理解析

    前言 本文将介绍Spring.Net(不仅仅是Spring.Net,其实所有的IoC容器要向控制器中进行注入,原理都是差不多的)在MVC控制器中依赖注入的实现原理,本文并没有关于在MVC使用Spring怎么配置,怎么使用,怎么实现. 引言放在前面,只是为了避免浪费你的时间. 望你能静心片刻,认真阅读. 情景 public class HomeController : Controller { //这是一个很神奇的注入 private IBLL.IUserInfoService UserInfoS

  • Spring Boot和Vue跨域请求问题原理解析

    这篇文章主要介绍了Spring Boot和Vue跨域请求问题原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用Spring Boot + Vue 做前后端分离项目搭建,实现登录时,出现跨域请求 Access to XMLHttpRequest at 'http://localhost/open/login' from origin 'http://localhost:8080' has been blocked by CORS pol

  • SpringBoot2.0 中 HikariCP 数据库连接池原理解析

    作为后台服务开发,在日常工作中我们天天都在跟数据库打交道,一直在进行各种CRUD操作,都会使用到数据库连接池.按照发展历程,业界知名的数据库连接池有以下几种:c3p0.DBCP.Tomcat JDBC Connection Pool.Druid 等,不过最近最火的是 HiKariCP. HiKariCP 号称是业界跑得最快的数据库连接池,自从 SpringBoot 2.0 将其作为默认数据库连接池后,其发展势头锐不可当.那它为什么那么快呢?今天咱们就重点聊聊其中的原因. 一.什么是数据库连接池

  • Android中微信抢红包插件原理解析及开发思路

    一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导致了.或许是网络的原因,而且这个也是最大的原因.但是其他的不可忽略的因素也是要考虑到进去的,比如在手机充电锁屏的时候,我们并不知道有人已经开始发红包了,那么这时候也是让我们丧失了一大批红包的原因.那么关于网络的问题,我们开发者可能用相关技术无法解决(当然在Google和Facebook看来的话,他们

  • spring mvc中直接注入的HttpServletRequst安全吗

    HttpServletRequest介绍 HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息. 引言 本文主要介绍的是关于spring mvc直接注入HttpServletRequst安全的相关内容,看似很简单的一个问题,借此追踪下spring的源码处理 在写springMVC的Control中有很多这种代码, 如需要获取request对象去做某些事情

  • Spring mvc JSON数据交换格式原理解析

    什么是JSON JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛. 采用完全独立于编程语言的文本格式来存储和表示数据. 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言. 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率. 在 JavaScript 语言中,一切都是对象.因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串.数字.对象.数组等.看看他的要求和

  • .NET中的HashSet及原理解析

    目录 哈希表原理 HashSet实现 总结 参考文章 在.NET中,System.Collection及System.Collection.Generic命名空间中提供了一系列的集合类,HashSet定义在System.Collections.Generic中,是一个不重复.无序的泛型集合,本文学习下HashSet的工作原理. 哈希表原理 HashSet是基于哈希表的原理实现的,学习HashSet首先要了解下哈希表. 哈希表(hash table, 也叫散列表)是根据key直接访问存储位置的数据

  • spring mvc中的@ModelAttribute注解示例介绍

    前言 本文介绍在spring mvc中非常重要的注解@ModelAttribute.这个注解可以用在方法参数上,或是方法声明上.这个注解的主要作用是绑定request或是form参数到模型对象.可以使用保存在request或session中的对象来组装模型对象.注意,被@ModelAttribute注解的方法会在controller方法(@RequestMapping注解的)之前执行.因为模型对象要先于controller方法之前创建. 请看下面的例子 ModelAttributeExample

  • spring mvc中注解@ModelAttribute的妙用分享

    前言 本文主要给大家介绍了关于spring mvc注解@ModelAttribute妙用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 在Spring mvc中,注解@ModelAttribute是一个非常常用的注解,其功能主要在两方面: 运用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用: 运用在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值

  • Spring MVC 中获取session的几种方法(小结)

    Spring MVC 中使用session是一种常见的操作,但是大家上网搜索一下可以看到获取session的方式方法五花八门,最近,自己终结了一下,将获取session的方法记录下来,以便大家共同学习进步. 第一种:将HttpSession作为Spring MVC 的方法参数传入,直接获取. 直接在Spring MVC 的方法中将参数传入: public void getSessionAction(HttpSession session){ } 这种方法我再网上搜索时发现很多人并不推荐使用,但是

随机推荐