前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,因此相对比较枯燥。今天就来根据上篇的理论一步一步进行实践,经过本身写的一个简易MVC框架逐步理解,相信经过这一篇的实践,你会对MVC有一个更加清晰的认识。css
本文原创地址:http://www.cnblogs.com/landeanfen/p/6000978.htmlhtml
MVC源码学习系列文章目录:jquery
这篇博主打算从零开始一步一步来加上MVC里面用到的一些技术,整篇经过三个版本,逐步完善。git
经过上篇的介绍,咱们知道,MVC里面两个最核心的部件:MvcHandler和UrlRoutingModule。如今咱们就来一步一步实现它们。为了更加真实,咱们彻底从零开始。github
咱们新建两个文件,而后实现IHttpHandler和IHttpModule。咱们知道这两个接口都在System.Web里面,首先咱们在类库项目里面引用Syste.Web这个dll,而后来看具体的代码。web
MvcHandler.cs代码:bootstrap
namespace Swift.MVC { public class MvcHandler : IHttpHandler { public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { context.Response.Write("当前页面地址:" + context.Request.Url.AbsoluteUri + " "); context.Response.Write("Hello MVC"); } } }
UrlRoutingModule.cs代码:swift
namespace Swift.MVC { public class UrlRoutingModule : IHttpModule { public void Dispose() { //throw new NotImplementedException(); } public void Init(HttpApplication app) { app.PostResolveRequestCache += app_PostResolveRequestCache; } void app_PostResolveRequestCache(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.RemapHandler(new MvcHandler()); } } }
若是你看过博主的上篇,这个应该很好理解。UrlRoutingModule注册PostResolveRequestCache事件,经过这个事件拦截当前的请求,拦截到请求以后,再交由MvcHandler去处理当前的http请求。整个过程就是这么简单,咱们最最基础的“框架”就搭好了。app
第一步,新建一个空的Web项目,添加对Swift.MVC的引用,或者直接将Swift.MVC.dll拷贝到web项目的bin目录下面,两种方式都行,这里为了方便测试,咱们直接添加解决方案中的项目引用。框架
第二步,配置Web项目的web.config文件。上篇咱们就介绍过,HttpHandler和HttpModule的实现类要生效,就必需要在Web.config里面注册。注册以后整个Web.config的内容以下:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> <system.webServer> <handlers> <add name="swifthandler" verb="*" path="*" type="Swift.MVC.MvcHandler, Swift.MVC" preCondition="integratedMode" /> </handlers> <modules> <add name="swiftmodule" type="Swift.MVC.UrlRoutingModule, Swift.MVC" preCondition="integratedMode" /> </modules> </system.webServer> </configuration>
获得结果
这里博主想要说明两点:
这里经过以上实现和配置,咱们的Swift.MVC已经具备处理http请求的能力,但还不能算一个完整意义上的框架,下面来继续完善。
这个版本,UrlRoutingModule咱们仍是沿用的System.Web.Routing里面的机制,咱们主要来看看MvcHandler这部分的实现。
UrlRoutingModule.cs的完整代码以下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; using System.Web.Routing; namespace Swift.MVC { public class UrlRoutingModule : IHttpModule { #region Property private RouteCollection _routeCollection; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This needs to be settable for unit tests.")] public RouteCollection RouteCollection { get { if (_routeCollection == null) { _routeCollection = RouteTable.Routes; } return _routeCollection; } set { _routeCollection = value; } } #endregion public void Dispose() { //throw new NotImplementedException(); } public void Init(HttpApplication app) { app.PostResolveRequestCache += app_PostResolveRequestCache; } void app_PostResolveRequestCache(object sender, EventArgs e) { var app = (HttpApplication)sender; //0.将HttpContext转换为HttpContextWrapper对象(HttpContextWrapper继承HttpContextBase) var contextbase = new HttpContextWrapper(app.Context); PostResolveRequestCache(contextbase); } public virtual void PostResolveRequestCache(HttpContextBase context) { //1.传入当前上下文对象,获得与当前请求匹配的RouteData对象 RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData == null) { return; } //2.从RouteData对象里面获得当前的RouteHandler对象。 IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) { return; } //3.根据HttpContext和RouteData获得RequestContext对象 RequestContext requestContext = new RequestContext(context, routeData); context.Request.RequestContext = requestContext; //4.根据RequestContext对象获得处理当前请求的HttpHandler(MvcHandler)。 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { return; } //5.请求转到HttpHandler进行处理(进入到ProcessRequest方法)。这一步很重要,由这一步开始,请求才由UrlRoutingModule转到了MvcHandler里面 context.RemapHandler(httpHandler); } } }
上述代码基本都是从Framework源码里面拷贝出来的,注释中的0、一、二、三、四、5分别对应着MVC路由过程当中的各个步骤,详见上篇。
这里咱们自定义了一个实现IRouteHandler的类型,用来返回处理请求的HttpHandler是哪一个,好比这里咱们定义的MvcRouteHandler返回的HttpHandler是MvcHandler。它的代码以下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Routing; namespace Swift.MVC { public class MvcRouteHandler:IRouteHandler { public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(); } } }
首先仍是抛出MvcHandler.cs的源码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; namespace Swift.MVC { public class MvcHandler : IHttpHandler { public virtual bool IsReusable { get { return false; } } public virtual void ProcessRequest(HttpContext context) { //写入MVC的版本到HttpHeader里面 //AddVersionHeader(httpContext); //移除参数 //RemoveOptionalRoutingParameters(); //步骤1.从上下文的Request.RequestContext中取到RouteData对象。这里和UrlRoutingModule里面的context.Request.RequestContext = requestContext;对应。 var routeData = context.Request.RequestContext.RouteData; //步骤2.从当前的RouteData里面获得请求的控制器名称 string controllerName = routeData.GetRequiredString("controller"); //步骤3.获得控制器工厂 IControllerFactory factory = new SwiftControllerFactory(); //步骤4.经过默认控制器工厂获得当前请求的控制器对象 IController controller = factory.CreateController(context.Request.RequestContext, controllerName); if (controller == null) { return; } try { //步骤5.执行控制器的Action controller.Execute(context.Request.RequestContext); } catch { } finally { //步骤6.释放当前的控制器对象 factory.ReleaseController(controller); } } } }
关于上述代码,咱们说明如下几点。
上述代码注释中的步骤一、2不难理解,就是从配置的路由规则中获取当前请求控制器的名称。要理解步骤3,须要先说一说MVC源码里面的控制器工厂。先来看看源码里面这段如何实现:
在源码里面的MvcHandler的ProcessRequest方法里面有这么一句: factory = ControllerBuilder.GetControllerFactory(); 。在MvcHandler里面ControllerBuilder这样定义:
internal ControllerBuilder ControllerBuilder { get { if (_controllerBuilder == null) { _controllerBuilder = ControllerBuilder.Current; } return _controllerBuilder; } set { _controllerBuilder = value; } }
原来在MvcHandler中建立控制器工厂并非直接使用IControllerFactroy的实现,而是使用了ControllerBuilder这个对象,这个对象采用了单例模式的实现;MvcHandler经过ControllerBuilder对象获取到一个实例,而后经过ControllerBuilder建立出IControllerFactory实现,ControllerBuilder管理着IControllerFactory的建立过程。
关于ControllerBuilder里面的GetControllerFactory()方法的实现,咱们没必要细究,可是咱们须要知道的是在MVC里面有一个默认的控制器工厂的实现类DefaultControllerFactory。咱们来看看
IControllerFactory接口的定义:
public interface IControllerFactory { IController CreateController(RequestContext requestContext, string controllerName); SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); void ReleaseController(IController controller); }
DefaultControllerFactory的定义:
public class DefaultControllerFactory : IControllerFactory { public virtual IController CreateController(RequestContext requestContext, string controllerName) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (String.IsNullOrEmpty(controllerName) && !requestContext.RouteData.HasDirectRouteMatch()) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName"); } Type controllerType = GetControllerType(requestContext, controllerName); IController controller = GetControllerInstance(requestContext, controllerType); return controller; } public virtual void ReleaseController(IController controller) { IDisposable disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } } //...... }
上述的两个方法CreateController()和ReleaseController()经过名字均可以很好理解,分别对应着建立控制器和释放控制器。
了解了上述MVC里面控制器工厂的实现细节,咱们本身也来建一个本身的控制器工厂,不过为了简化,咱们这里直接去new了一个工厂的实现类。先来看看咱们Swift.MVC的控制器工厂。
控制器工厂接口:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Routing; namespace Swift.MVC { //控制器建立工厂 public interface IControllerFactory { //建立控制器 IController CreateController(RequestContext requestContext, string controllerName); //释放控制器 void ReleaseController(IController controller); } }
控制器工厂实现类:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace Swift.MVC { public class SwiftControllerFactory:IControllerFactory { #region Public //经过当前的请求上下文和控制器名称获得控制器的对象 public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (string.IsNullOrEmpty(controllerName)) { throw new ArgumentException("controllerName"); } //获得当前的控制类型 Type controllerType = GetControllerType(requestContext, controllerName); if (controllerType == null) { return null; } //获得控制器对象 IController controller = GetControllerInstance(requestContext, controllerType); return controller; } //释放控制器对象 public void ReleaseController(IController controller) { IDisposable disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } } #endregion #region Privates //获得当前请求的控制器实例 private IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) { var oRes = Activator.CreateInstance(controllerType) as IController; return oRes; } //获得当前请求的控制器类型 private Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName) { //从路由配置信息里面读取命名空间和程序集 object routeNamespaces; object routeAssembly; requestContext.RouteData.Values.TryGetValue("namespaces", out routeNamespaces); requestContext.RouteData.Values.TryGetValue("assembly", out routeAssembly); //经过反射获得控制器的类型 var type = Assembly.Load(routeAssembly.ToString()).GetType(routeNamespaces.ToString() + "." + controllerName + "Controller"); return type; } #endregion } }
这里博主主要用到了反射去实例化控制器实例。
上述介绍了控制器工厂的实现。除了控制器工厂,还有咱们的控制器接口以及父类的相关实现。
控制器接口的定义:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Routing; namespace Swift.MVC { public interface IController { void Execute(RequestContext requestContext); } }
控制器抽象Base类的实现:(这个抽象类的做用更多在于定义一些约束、检查之类)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Swift.MVC { //这个类主要定义约束 public abstract class ControllerBase:IController { public abstract void Execute(System.Web.Routing.RequestContext requestContext); } }
控制器抽象子类的实现:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Swift.MVC { public abstract class Controller:ControllerBase,IDisposable { public override void Execute(System.Web.Routing.RequestContext requestContext) { //反射获得Action方法 Type type = this.GetType(); string actionName = requestContext.RouteData.GetRequiredString("action"); System.Reflection.MethodInfo mi = type.GetMethod(actionName); //执行该Action方法 mi.Invoke(this, new object[] { });//调用方法 } public void Dispose() { //throw new NotImplementedException(); } } }
这里让Controller类实现IDispose接口,照应了上文控制器工厂里面的ReleaseController()方法,主要起到释放资源的做用。
因为上述代码用到了System.Web.Routing里面的组件,因此,须要在测试项目里面配置路由规则,这里须要注意一点,咱们上面的MvcRouteHandler就是在这里注入进去的。在测试项目里面新建一个全局配置文件以下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Routing; using System.Web.Security; using System.Web.SessionState; namespace MyTestMVC { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.Add("defaultRoute", new Route("{controller}/{action}/{id}", new RouteValueDictionary(new { controller = "Home", action = "Index", id = "", namespaces = "MyTestMVC.Controllers", assembly = "MyTestMVC" }), new Swift.MVC.MvcRouteHandler())); } protected void Application_BeginRequest(object sender, EventArgs e) { } } }
而后在测试项目里面模拟MVC新建一个Controllers文件夹,里面新建一个测试的控制器HomeController:
using Swift.MVC; using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MyTestMVC.Controllers { public class HomeController : Controller { public void Index() { HttpContext.Current.Response.Write("Hello MVC"); } } }
而后启动项目,访问http://localhost:16792/Home/Index。运行过程以及代码释疑以下:
(1)来看看 RouteData routeData = this.RouteCollection.GetRouteData(context); 这一句
经过上图可知,this.RouteCollection里面保存的是上述全局配置文件里面添加进去的路由规则,而后调用GetRouteData(context)方法,传入当前请求的上下文,获得当前请求的RouteData对象,咱们能够看到在这个RouteData对象里面,已经包含了当前请求的控制器和action的名称。
(2)监视 IRouteHandler routeHandler = routeData.RouteHandler;
经过上图能够看到在routeData里面咱们的RouteHandler已是MvcRouteHandler对象了,还记得咱们在全局配置文件里面有这样一个配置:
RouteTable.Routes.Add("defaultRoute", new Route("{controller}/{action}/{id}", new RouteValueDictionary(new { controller = "Home", action = "Index", id = "", namespaces = "MyTestMVC.Controllers", assembly = "MyTestMVC" }), new Swift.MVC.MvcRouteHandler()));
在Add方法的最后须要传一个IRouteHandler的对象,咱们上文定义过一个MvcRouteHandler去实现了IRouteHandler,这个MvcRouteHandler在这里就派上用场了,原来咱们的RouteHandler是能够自定义的。就是由于这里配置过这个,因此在GetRouteData()方法里面,就将MvcRouteHandler对象给了routeData对象的RouteHandler属性,终于知道这里的MvcRouteHandler是如何过来的了。这里可配置IRouteHandler也说明了MVC原理的灵活性,咱们能够自定义RouteHandler,而后再IRouteHandler接口的GetHttpHandler()方法里面自定义处理当前请求的HttpHandler。
(3) RequestContext requestContext = new RequestContext(context, routeData);context.Request.RequestContext = requestContext; 这两句看上去不起眼,就是封装了一个RequestContext对象,而后将它给到了当前上下文的Request.RequestContext。实际上,这里很是重要,由于这个requestContext对象包含了咱们当前请求的路由信息,后面MvcHandler里面须要从这里取到当前请求的控制器和Action的名称,待会看了后面的代码,你会更加清晰。
(4)再来看 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 这一句
上文注释(2) 里面说了,routeHandler对象其实是一个MvcRouteHandler对象,当它调用GetHttpHandler(),看下定义便可明白:
public class MvcRouteHandler:IRouteHandler { public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(); } }
这里就是返回了一个MvcHandler,用来处理Http请求。
(5) context.RemapHandler(httpHandler); 这一句天然没必要多说,请当前拦截到的请求交给MvcHandler的ProcessRequest方法处理,这一句执行完成以后,请求便转入到MvcHandler的ProcessRequest方法里面。纵观上述几个过程,能够说是一环扣一环,每一句都有它的意义所在,最后封装完成以后,真正处理请求仍是在MvcHandler里面。接下来咱们来看看ProcessRequest里面的代码。
(6)下面咱们来看看MvcHandler类里面ProcessRequest方法这一句: var routeData = context.Request.RequestContext.RouteData; 。还记得上述注释3中封装的RequestContext对象吗,没错,这里就用到了这个对象,咱们从这个对象里面取到当前请求的RouteData对象。
(7) string controllerName = routeData.GetRequiredString("controller"); 这一句不难理解:取到当前请求的的Controller名称。结果以下:
(8)获得控制器工厂这个没什么说的,为了简化,咱们直接new了一个默认的控制器工厂。下面重点来看看 IController controller = factory.CreateController(context.Request.RequestContext, controllerName); 这一句。在控制器工厂的实现类里面实现了CreateController()这个方法。
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (string.IsNullOrEmpty(controllerName)) { throw new ArgumentException("controllerName"); } //获得当前的控制类型 Type controllerType = GetControllerType(requestContext, controllerName); if (controllerType == null) { return null; } //获得控制器对象 IController controller = GetControllerInstance(requestContext, controllerType); return controller; } //获得当前请求的控制器实例 private IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) { var oRes = Activator.CreateInstance(controllerType) as IController; return oRes; } //获得当前请求的控制器类型 private Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName) { //从路由配置信息里面读取命名空间和程序集 object routeNamespaces; object routeAssembly; requestContext.RouteData.Values.TryGetValue("namespaces", out routeNamespaces); requestContext.RouteData.Values.TryGetValue("assembly", out routeAssembly); //经过反射获得控制器的类型 var type = Assembly.Load(routeAssembly.ToString()).GetType(routeNamespaces.ToString() + "." + controllerName + "Controller"); return type; }
原理不难理解,主要仍是反射,由于咱们当前请求的控制器类在测试项目里面,因此反射的时候须要指定当前测试项目的程序集,经过这里的代码能够看出,在UrlRoutingModule里面封装的RequestContext对象实在是过重要了,由于各个地方都须要用到它。博主以为这里还有待优化,等想到更好的办法再来逐步优化。此步获得结果:
(9)获得控制器对象以后,就是执行Action方法了: controller.Execute(context.Request.RequestContext); 。这里博主按照源码里面的构造封装了IController、ControllerBase、Controller三个级别的接口以及父类。Execute方法的实如今Controller里面:
public abstract class Controller:ControllerBase,IDisposable { public override void Execute(System.Web.Routing.RequestContext requestContext) { //反射获得Action方法 Type type = this.GetType(); string actionName = requestContext.RouteData.GetRequiredString("action"); System.Reflection.MethodInfo mi = type.GetMethod(actionName); //执行该Action方法 mi.Invoke(this, new object[] { });//调用方法 } }
这里再次用到了RequestContext对象,由此能够看出,RequestContext对象几乎贯穿整个MvcHandler,再次应征了上述注释(3)中说的它的重要性。
上述代码就是经过反射Action方法,而后执行该方法,以后请求就会尽到咱们HomeController的Index()方法里面。
(10)执行Index()方法
请求进入到Index()方法以后,而后就是从Model里面获取数据,再而后就是返回View,整个MVC的原理就是如此。固然博主这里的Swift.MVC还只是将请求转到了Index里面,剩余的Model能够本身写,可是View的部分还彻底没有,待有时间完善。
(11)执行完Action以后,最后就是释放当前的Controller对象了。在finally里面有这么一句: factory.ReleaseController(controller); 。仍是来看看ReleaseController方法的定义:
//释放控制器对象 public void ReleaseController(IController controller) { IDisposable disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } }
至此,咱们经过地址http://localhost:16792/Home/Index访问,整个请求的开始、执行、资源释放就基本结束。如今回过头来理解这个原理,你以为还难吗~~
若是你还嫌上述例子太简单,咱们能够来个稍微复杂点的例子。咱们向测试项目里面引入jquery、bootstrap等组件,添加Models文件夹,向下面加入User.cs
namespace MyTestMVC.Models { public class User { public int Id { get; set; } public string UserName { get; set; } public int Age { get; set; } public string Address { get; set; } public string Remark { get; set; } } }
而后咱们向HomeController里面另外一个Action定义以下:
public void BootstrapTest() { var lstUser = new List<User>(); lstUser.Add(new User() { Id = 1, UserName = "Admin", Age = 20, Address = "北京", Remark = "超级管理员" }); lstUser.Add(new User() { Id = 2, UserName = "张三", Age = 37, Address = "湖南", Remark = "呵呵" }); lstUser.Add(new User() { Id = 3, UserName = "王五", Age = 32, Address = "广西", Remark = "呵呵" }); lstUser.Add(new User() { Id = 4, UserName = "韩梅梅", Age = 26, Address = "上海", Remark = "呵呵" }); lstUser.Add(new User() { Id = 5, UserName = "呵呵", Age = 18, Address = "广东", Remark = "呵呵" }); string strUser = string.Empty; foreach (var oUser in lstUser) { strUser += "<tr><td>" + oUser.Id + "</td><td>" + oUser.UserName + "</td><td>" + oUser.Age + "</td><td>" + oUser.Address + "</td><td>" + oUser.Remark + "</td></tr>"; } HttpContext.Current.Response.Write(@" <html> <head> <link href='/Content/bootstrap/css/bootstrap.min.css' rel='stylesheet' /> <script src='/Content/jquery-1.9.1.min.js'></script> <script src='/Content/bootstrap/js/bootstrap.min.js'></script> </head> <body> <div class='panel-body' style='padding-bottom:0px;'> <div class='panel panel-primary'> <div class='panel-heading'>bootstrap表格</div> <div class='panel-body'> <table id='tbarrivequeue' class='table table-bordered table-striped'> <thead> <tr> <th>用户ID</th> <th>用户名</th> <th>年龄</th> <th>地址</th> <th>备注</th> </tr> </thead> <tbody> " + strUser + @" </tbody> </table> </div> </div> </div> </div> </body> </html>");
获得结果:
按照MVC的机制,咱们的Swift.MVC里面“M”和“C”都有了,就差“V”了,等有时间咱们也来封装一个本身的“V”。
有了第二个版本的支持,博主已经对MVC的原理有了一个清晰的认识。在第三个版本里面,博主打算不用System.Web.Routing里面的属性和方法,彻底本身去解析请求,封装上下文,执行MVC,这样才算是真正意义上本身的MVC。考虑到篇幅的问题,也给博主一些准备时间,第三个版本留在下篇发,有兴趣的园友能够先看看版本二,若是你能透彻理解版本二里面的原理,相信你对MVC 已经有一个清晰的认识了。
至此,此篇到此结束,版本二里面咱们MvcHandler基本上是彻底重写的,虽然不少原理是参考MVC源码里面来进行的,可是它却和源码已经没有任何联系。而UrlRoutingModule部分仍然使用的System.Web.Routing里面的组件去解析路由的,这个将在第三个版本里面完善。源码已经在github上面开源,有兴趣能够看看:源码下载
若是你以为本文可以帮助你,能够右边随意 打赏 博主,也能够 推荐 进行精神鼓励。你的支持是博主继续坚持的不懈动力。
本文原创出处:http://www.cnblogs.com/landeanfen/
欢迎各位转载,可是未经做者本人赞成,转载文章以后必须在文章页面明显位置给出做者和原文链接,不然保留追究法律责任的权利