• 深入分析MVC中通过IOC实现Controller依赖注入的原理


    这几天利用空闲时间,我将ASP.NET反编译后的源代码并结合园子里几位大侠的写的文章认真的看了一遍,收获颇丰,同时也摘要了一些学习内容,存入了该篇文章:《ASP.NET运行机制图解》,在对整个ASP.NET的运行机制有所了解后,我又对MVC的运行机制也进行了源码分析,因为网上已经有很多的关于MVC实现原理的介绍,所以我这里不再重复讨论这方面的内容,而主要讲解一下Controller的的创建、执行以及如何实现依赖注入,注入的步骤是什么?

    首先,我们来看一下正常的Controller的的创建与执行顺序:

    大家都应该知道,用于处理ASP.NET请求是由实现了IHttpHandler的对象来进行处理的,我们所常见的Handler包括但不限于:Page,MvcHandler等

    如下是MvcHandler类中的方法及执行步骤说明:

    处理入口方法:异步-->BeginProcessRequest,同步-->  ProcessRequest,源代码如下:

            IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
            {
                return this.BeginProcessRequest(context, cb, extraData);
            }
    
            void IHttpHandler.ProcessRequest(HttpContext httpContext)
            {
                this.ProcessRequest(httpContext);
            }

    注意这两个方法是显示实现IHttpHandler的同名方法的,不能直接调用,必需转换成IHttpHandler类型后才能调用,调用转到如下方法:

            protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state)
            {
                HttpContextBase httpContext2 = new HttpContextWrapper(httpContext);
                return this.BeginProcessRequest(httpContext2, callback, state);
            }
    
            protected virtual void ProcessRequest(HttpContext httpContext)
            {
                HttpContextBase httpContext2 = new HttpContextWrapper(httpContext);
                this.ProcessRequest(httpContext2);
            }

    当然这两个方法均又分别调动了各自的重载方法,在重载方法中都调用了ProcessRequestInit,源代码如下:

            private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
            {
                if (ValidationUtility.IsValidationEnabled(HttpContext.Current) == true)
                {
                    ValidationUtility.EnableDynamicValidation(HttpContext.Current);
                }
                this.AddVersionHeader(httpContext);
                this.RemoveOptionalRoutingParameters();
                string requiredString = this.RequestContext.RouteData.GetRequiredString("controller");
                factory = this.ControllerBuilder.GetControllerFactory();
                controller = factory.CreateController(this.RequestContext, requiredString);
                if (controller == null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[]
                    {
                        factory.GetType(),
                        requiredString
                    }));
                }
            }

    红色标明的就是创建Controller的地方,创建完后就开始执行Controller,异步与同步方法的执行有所不同,源代码如下:

            protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
            {
                return SecurityUtil.ProcessInApplicationTrust<IAsyncResult>(delegate
                {
                    IController controller;
                    IControllerFactory factory;
                    this.ProcessRequestInit(httpContext, out controller, out factory);
                    IAsyncController asyncController = controller as IAsyncController;
                    if (asyncController != null)
                    {
                        BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
                        {
                            IAsyncResult result;
                            try
                            {
                                result = asyncController.BeginExecute(this.RequestContext, asyncCallback, asyncState);
                            }
                            catch
                            {
                                factory.ReleaseController(asyncController);
                                throw;
                            }
                            return result;
                        };
                        EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
                        {
                            try
                            {
                                asyncController.EndExecute(asyncResult);
                            }
                            finally
                            {
                                factory.ReleaseController(asyncController);
                            }
                        };
                        SynchronizationContext synchronizationContext = SynchronizationContextUtil.GetSynchronizationContext();
                        AsyncCallback callback2 = AsyncUtil.WrapCallbackForSynchronizedExecution(callback, synchronizationContext);
                        return AsyncResultWrapper.Begin(callback2, state, beginDelegate, endDelegate, MvcHandler._processRequestTag);
                    }
                    Action action = delegate
                    {
                        try
                        {
                            controller.Execute(this.RequestContext);
                        }
                        finally
                        {
                            factory.ReleaseController(controller);
                        }
                    };
                    return AsyncResultWrapper.BeginSynchronous(callback, state, action, MvcHandler._processRequestTag);
                });
            }
    
    
            protected internal virtual void EndProcessRequest(IAsyncResult asyncResult)
            {
                SecurityUtil.ProcessInApplicationTrust(delegate
                {
                    AsyncResultWrapper.End(asyncResult, MvcHandler._processRequestTag);
                });
            }
    
            protected internal virtual void ProcessRequest(HttpContextBase httpContext)
            {
                SecurityUtil.ProcessInApplicationTrust(delegate
                {
                    IController controller;
                    IControllerFactory controllerFactory;
                    this.ProcessRequestInit(httpContext, out controller, out controllerFactory);
                    try
                    {
                        controller.Execute(this.RequestContext);
                    }
                    finally
                    {
                        controllerFactory.ReleaseController(controller);
                    }
                });
            }
    View Code

     通过上述代码,我们知道Controller执行步骤是:异步(BeginExecute-->EndExecute-->ReleaseController),同步(Execute-->  ReleaseController)

    我们知道了Controller的创建与执行原理,就可以针对这些代码规则来扩展我们自定义的一些实现代码,比如我们本文要讲的:通过IOC实现Controller依赖注入。

    通过上面源代码的分析,我们知道,Controller是由ControllerFactory来创建的,而ControllerFactory又是由ControllerBuilder,也就是我们只要能够改变ControllerBuilder.GetControllerFactory返回的值,也就改变了ControllerFactory,这样就给自定义创建Controller提供可能,我们先来看一下,ControllerBuilder.GetControllerFactory方法的定义:

    public IControllerFactory GetControllerFactory()
    {
        return this._serviceResolver.Current;
    }

    方法很简单,直接通过IResolver<IControllerFactory>.Current返回实现了IControllerFactory对象,而对于方法中的_serviceResolver是在构造函数中实例化的,源代码如下:

            internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
            {
                IResolver<IControllerFactory> arg_6A_1 = serviceResolver;
                if (serviceResolver == null)
                {
                    arg_6A_1 = new SingleServiceResolver<IControllerFactory>(() => this._factoryThunk(), new DefaultControllerFactory
                    {
                        ControllerBuilder = this
                    }, "ControllerBuilder.GetControllerFactory");
                }
                this._serviceResolver = arg_6A_1;
            }

    通过构造函数,可以看出_serviceResolver是由SingleServiceResolver<IControllerFactory>实例化得来的,那么结合上面的this._serviceResolver.Current,就可以知道其实就是访问SingleServiceResolver<IControllerFactory>的Current属性来获得IControllerFactory对象,源代码如下:

            public TService Current
            {
                get
                {
                    if (this._resolverThunk != null)
                    {
                        lock (this._currentValueThunk)
                        {
                            if (this._resolverThunk != null)
                            {
                                this._currentValueFromResolver = this._resolverThunk().GetService<TService>();
                                this._resolverThunk = null;
                                if (this._currentValueFromResolver != null && this._currentValueThunk() != null)
                                {
                                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, new object[]
                                    {
                                        typeof(TService).Name.ToString(),
                                        this._callerMethodName
                                    }));
                                }
                            }
                        }
                    }
                    TService arg_D2_0;
                    if ((arg_D2_0 = this._currentValueFromResolver) == null && (arg_D2_0 = this._currentValueThunk()) == null)
                    {
                        arg_D2_0 = this._defaultValue;
                    }
                    return arg_D2_0;
                }
            }

    SingleServiceResolver构造函数如下:

            public SingleServiceResolver(Func<TService> currentValueThunk, TService defaultValue, string callerMethodName)
            {
                if (currentValueThunk == null)
                {
                    throw new ArgumentNullException("currentValueThunk");
                }
                if (defaultValue == null)
                {
                    throw new ArgumentNullException("defaultValue");
                }
                this._resolverThunk = (() => DependencyResolver.Current);
                this._currentValueThunk = currentValueThunk;
                this._defaultValue = defaultValue;
                this._callerMethodName = callerMethodName;
            }

    这里我们结合ControllerBuilder的构造函数及SingleServiceResolver构造函数得知在Current属性的逻辑代码中if (this._currentValueFromResolver != null && this._currentValueThunk() != null)是不成立的,因为this._currentValueThunk = currentValueThunk;而currentValueThunk又是ControllerBuilder中默认的值: Func<IControllerFactory> _factoryThunk = () => null;所以就会走到arg_D2_0 = this._defaultValue,而_defaultValue又是等于ControllerBuilder构造函数中传来的DefaultControllerFactory,所以最终返回了DefaultControllerFactory,如果需要实现自定义的ControllerFactory并且能够被ControllerBuilder.GetControllerFactory返回,我们只需要自定义实现IControllerFactory的类,如:CustomControllerFactory,以及使用ControllerBuilder.SetControllerFactory方法来使_factoryThunk 的值等于()=>CustomControllerFactory即可,实现的代码如下:

        public class CustomControllerFactory : DefaultControllerFactory
        {
            private UnityContainer iocContainer;
    
            public CustomControllerFactory()
            {
                iocContainer = new UnityContainer();
                AddBindings();
            }
    
            protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
            {
                IController controller=null;
                if (controllerType != null)
                {
                    controller = (IController)iocContainer.Resolve(controllerType);
                }
                return controller;
            }
    
            private void AddBindings()
            {
                iocContainer.RegisterType<IUserService, UserService>();
            }
             
        }

    我这里采用Unity容器,并重写了GetControllerInstance方法,如下代码是实现注入CustomControllerFactory到ControllerBuilder:

            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
    
                RegisterGlobalFilters(GlobalFilters.Filters);
                RegisterRoutes(RouteTable.Routes);
    
                ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
            }

    这样我们就实现了返回自己定义的CustomControllerFactory,并在CustomControllerFactory通过IOC容器来实现自己所需要的Controller。

    以上虽然通过自定义ControllerFactory实现了IOC的注入,但我仍然觉得有些烦锁,且存在不安全性,因为自己实现的CustomControllerFactory是可以去重写、覆盖改变DefaultControllerFactory中的相应的属性方法,如果自己写的代码存在漏洞或不健全,则会造成无法预料的后果,因此一般不建议直接这样做,而应该采用风险更小的其它方法来实现,那是什么方法呢?请继续往下看。

    通过分析源码得知,默认情况下,Controller是由DefaultControllerFactory.GetControllerInstance得来的,那我们先来看看这个方法定义:

            protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
            {
                if (controllerType == null)
                {
                    throw new HttpException(404, string.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_NoControllerFound, new object[]
                    {
                        requestContext.HttpContext.Request.Path
                    }));
                }
                if (!typeof(IController).IsAssignableFrom(controllerType))
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase, new object[]
                    {
                        controllerType
                    }), "controllerType");
                }
                return this.ControllerActivator.Create(requestContext, controllerType);
            }

    注意红色标注的地方,这里是通过ControllerActivator属性对象来创建的,ControllerActivator属性定义如下:

            private IControllerActivator ControllerActivator
            {
                get
                {
                    if (this._controllerActivator != null)
                    {
                        return this._controllerActivator;
                    }
                    this._controllerActivator = this._activatorResolver.Current;
                    return this._controllerActivator;
                }
            }

    由此可知,ControllerActivator属性又是(私有字段:_activatorResolver) IResolver<IControllerActivator>.Current得来的,那么这个_activatorResolver又是如何得来的呢?通过上下源码的分析,得知,DefaultControllerFactory是在ControllerBuilder中构造的,那么我看一下DefaultControllerFactory的构造函数:

            public DefaultControllerFactory() : this(null, null, null)
            {
            }
    
            internal DefaultControllerFactory(IControllerActivator controllerActivator, IResolver<IControllerActivator> activatorResolver, IDependencyResolver dependencyResolver)
            {
                if (controllerActivator != null)
                {
                    this._controllerActivator = controllerActivator;
                    return;
                }
                IResolver<IControllerActivator> arg_44_1 = activatorResolver;
                if (activatorResolver == null)
                {
                    arg_44_1 = new SingleServiceResolver<IControllerActivator>(() => null, new DefaultControllerFactory.DefaultControllerActivator(dependencyResolver), "DefaultControllerFactory contstructor");
                }
                this._activatorResolver = arg_44_1;
            }

    ControllerBuilder的构造函数中是采用的无参构造函数,而无参构造函数最终均会调用带有三个参数的构造函数,在这个函数中,我标出了重点需要关注的地方,有没有发现眼熟的地方,对的SingleServiceResolver又出现了,只不过泛型参数不同而已,我在上面分析时用绿色标记出了重要的地方:构造函数中的this._resolverThunk = (() => DependencyResolver.Current);以及Current属性中的this._currentValueFromResolver = this._resolverThunk().GetService<TService>();认真分析得知, DependencyResolver.Current是一个静态属性,看一下该属性的定义:

    public static IDependencyResolver Current
    {
        get
        {
            return DependencyResolver._instance.InnerCurrent;
        }
    }

    该属性的值来源于一个私有字段,这个字段是静态的并默认就实例化为DependencyResolver:

    private static DependencyResolver _instance = new DependencyResolver();

    得出结论DependencyResolver.Current调用DependencyResolver.InnerCurrent属性,而该属性又直接返回_current字段的值,代码如下:

    private IDependencyResolver _current = new DependencyResolver.DefaultDependencyResolver();
    
            public IDependencyResolver InnerCurrent
            {
                get
                {
                    return this._current;
                }
            }

    _current字段默认是实例化DependencyResolver.DefaultDependencyResolver,而该类型是一个内部类:

            private class DefaultDependencyResolver : IDependencyResolver
            {
                public object GetService(Type serviceType)
                {
                    object result;
                    try
                    {
                        result = Activator.CreateInstance(serviceType);
                    }
                    catch
                    {
                        result = null;
                    }
                    return result;
                }
    
                public IEnumerable<object> GetServices(Type serviceType)
                {
                    return Enumerable.Empty<object>();
                }
            }
    View Code

    这个类没有什么复杂的方法及属性,只是实现了IDependencyResolver接口的两个方法,分别是  GetService、  GetServices(该方法返回空集合,即无用)。

    到此一切就都明了了,如果说想要实现通过DefaultControllerFactory.GetControllerInstance来返回我们IOC注入后的Controller,只需要改变  SingleServiceResolver.Current属性返回值,而改变该值则需要改变该类的_resolverThunk字段值,而_resolverThunk的值又来自构造函数中的如下语句:

    this._resolverThunk = (() => DependencyResolver.Current);

    最终我们只要改变DependencyResolver.Current属性返回值即可,而该值通过上面的分析知道是来自DependencyResolver的字段:_current,所以只要改变这个字段的值,就能改变最终返回Controller实例对象。那如何改变这个私有字段呢?不急,通过源码代码得知,DependencyResolver提供了SetResolver多个静态重载方法,我们只需要将实现了IDependencyResolver接口实例对象传入进去,就可以改变_current字段,从而最终实现我们想要的结果。

            public static void SetResolver(IDependencyResolver resolver)
            {
                DependencyResolver._instance.InnerSetResolver(resolver);
            }
    
            public static void SetResolver(object commonServiceLocator)
            {
                DependencyResolver._instance.InnerSetResolver(commonServiceLocator);
            }
    
            public static void SetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices)
            {
                DependencyResolver._instance.InnerSetResolver(getService, getServices);
            }
    
            public void InnerSetResolver(IDependencyResolver resolver)
            {
                if (resolver == null)
                {
                    throw new ArgumentNullException("resolver");
                }
                this._current = resolver;
            }
    
            public void InnerSetResolver(object commonServiceLocator)
            {
                if (commonServiceLocator == null)
                {
                    throw new ArgumentNullException("commonServiceLocator");
                }
                Type type = commonServiceLocator.GetType();
                MethodInfo method = type.GetMethod("GetInstance", new Type[]
                {
                    typeof(Type)
                });
                MethodInfo method2 = type.GetMethod("GetAllInstances", new Type[]
                {
                    typeof(Type)
                });
                if (method == null || method.ReturnType != typeof(object) || method2 == null || method2.ReturnType != typeof(IEnumerable<object>))
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.DependencyResolver_DoesNotImplementICommonServiceLocator, new object[]
                    {
                        type.FullName
                    }), "commonServiceLocator");
                }
                Func<Type, object> getService = (Func<Type, object>)Delegate.CreateDelegate(typeof(Func<Type, object>), commonServiceLocator, method);
                Func<Type, IEnumerable<object>> getServices = (Func<Type, IEnumerable<object>>)Delegate.CreateDelegate(typeof(Func<Type, IEnumerable<object>>), commonServiceLocator, method2);
                this._current = new DependencyResolver.DelegateBasedDependencyResolver(getService, getServices);
            }
    
            public void InnerSetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices)
            {
                if (getService == null)
                {
                    throw new ArgumentNullException("getService");
                }
                if (getServices == null)
                {
                    throw new ArgumentNullException("getServices");
                }
                this._current = new DependencyResolver.DelegateBasedDependencyResolver(getService, getServices);
            }
    View Code

     自定义实现IDependencyResolver接口的类型(采用Unity需要注意,使用常规的解析方法会存在错误,可参见DUDU的这篇文章《Unity+MVC:实现IDependencyResolver接口需要注意的地方》,我这里直接借鉴Unity.MVC3里面定义的类),如:

        public class CustomDependencyResolver:IDependencyResolver
        {
            private const string HttpContextKey = "perRequestContainer";
    
            private readonly IUnityContainer container;
    
    
            public CustomDependencyResolver()
            {
                this.container = BuildAndInitContainer();
            }
    
            public object GetService(Type serviceType)
            {
                if (typeof(IController).IsAssignableFrom(serviceType))
                {
                    return ChildContainer.Resolve(serviceType);
                }
    
                return IsRegistered(serviceType) ? ChildContainer.Resolve(serviceType) : null;      
            }
    
            public IEnumerable<object> GetServices(Type serviceType)
            {
                if (IsRegistered(serviceType))
                {
                    yield return ChildContainer.Resolve(serviceType);
                }
    
                foreach (var service in ChildContainer.ResolveAll(serviceType))
                {
                    yield return service;
                }
            }
    
            protected IUnityContainer ChildContainer
            {
                get
                {
                    var childContainer = HttpContext.Current.Items[HttpContextKey] as IUnityContainer;
    
                    if (childContainer == null)
                    {
                        HttpContext.Current.Items[HttpContextKey] = childContainer = container.CreateChildContainer();
                    }
    
                    return childContainer;
                }
            } 
    
            public static void DisposeOfChildContainer()
            {
                var childContainer = HttpContext.Current.Items[HttpContextKey] as IUnityContainer;
    
                if (childContainer != null)
                {
                    childContainer.Dispose();
                }
            }
    
            private bool IsRegistered(Type typeToCheck)
            {
                var isRegistered = true;
    
                if (typeToCheck.IsInterface || typeToCheck.IsAbstract)
                {
                    isRegistered = ChildContainer.IsRegistered(typeToCheck);
    
                    if (!isRegistered && typeToCheck.IsGenericType)
                    {
                        var openGenericType = typeToCheck.GetGenericTypeDefinition();
    
                        isRegistered = ChildContainer.IsRegistered(openGenericType);
                    }
                }
    
                return isRegistered;
            }
    
            private IUnityContainer BuildAndInitContainer()
            {
                var container = new UnityContainer();
    
                container.RegisterType<IUserService, UserService>();
                //这里添加其它类型映射
    
                return container;
            }
        }
    View Code

    在Global文件的Application_Start方法中添加注入代码,如下:

            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
    
                RegisterGlobalFilters(GlobalFilters.Filters);
                RegisterRoutes(RouteTable.Routes);
    
                DependencyResolver.SetResolver(new MvcApplication1.Models.CustomDependencyResolver());
            }

    使用方法很简单,如下是全部代码:

        public interface IUserService
        {
            bool Login(string userName, string password, out string failureMsg);
        }
    
        public class UserService : IUserService
        {
            public bool Login(string userName, string password, out string failureMsg)
            {
                failureMsg = null;
                if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
                {
                    failureMsg = "用户名或密码不能为空!";
                    return false;
                }
    
                if (userName != "admin" || password != "web.admin")
                {
                    failureMsg = "用户名或密码不正确!";
                    return false;
                }
    
                return true;
            }
        }
    
    
        public class UserController : Controller
        {
            private readonly IUserService userService;
    
            public UserController(IUserService service)
            {
                userService = service;
            }
    
            public ActionResult Login()
            {
                return View();
            }
    
            [HttpPost]
            [ActionName("Login")]
            public ActionResult LoginExecute(string username, string password)
            {
                string msg = null;
                if (!userService.Login(username, password,out msg))
                {
                    return Content("<p style='color:red;'>登录失败,原因如下:<br/>"+ msg +"</p>", "text/html", Encoding.UTF8);
                }
                return Content("<p style='color:green;'>登录成功!</p>", "text/html", Encoding.UTF8); 
            }
    
        }
    View Code

    VIEW视图代码:

    @{
        ViewBag.Title = "Login";
    }
    
    <h2>Login</h2>
    
    @using(Html.BeginForm())
    {
    <p>
        <span>用户名:</span>
        @Html.TextBox("username")
    </p>
    <p>
        <span>密 码:</span>
        @Html.Password("password")
    </p>
    <input type="submit" value="登 录" />
    }
    View Code

    最终的效果如下图示:

                            

                            

  • 相关阅读:
    给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1},
    输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
    定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
    用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
    输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
    给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。
    在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3
    Oracle-SQL-按月统计自助终端交易量
    Hibernate table schema 的设置与应用
    Oracle函数之chr
  • 原文地址:https://www.cnblogs.com/zuowj/p/4958591.html
Copyright © 2020-2023  润新知