zoukankan      html  css  js  c++  java
  • NopCommerce使用Autofac实现依赖注入

    NopCommerce的依赖注入是用的AutoFac组件,这个组件在nuget可以获取,而IOC反转控制常见的实现手段之一就是DI依赖注入,而依赖注入的方式通常有:接口注入、Setter注入和构造函数注入。

    NopCommerce将所有和Autofac注入相关的工作都放到了EngineContext中,在Global.asax的Application_Start函数的第一句代码即是:

    //initialize engine context
    EngineContext.Initialize(false);

    从这里开始EngineContext的初始化工作,初始化时会创建一个新的NopEngine,参数false指定当NopEngine不为空时是否重新生成一个新的NopEngine。

    [MethodImpl(MethodImplOptions.Synchronized)]
    public static IEngine Initialize(bool forceRecreate)
    {
        if (Singleton<IEngine>.Instance == null || forceRecreate)
        {
            var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;
            Debug.WriteLine("Constructing engine " + DateTime.Now);
            Singleton<IEngine>.Instance = CreateEngineInstance(config);
            Debug.WriteLine("Initializing engine " + DateTime.Now);
            Singleton<IEngine>.Instance.Initialize(config);
        }
        return Singleton<IEngine>.Instance;
    }

    NopEngine使用单例模式,在整个程序运行期间存在一个实例,代码首先会判断NopEngine是否为空,为空的话则根据web.config中配置的NopConfig节点信息创建一个新的NopEngine实例,然后对该实例进行初始化操作。web.config中的配置信息如下:

     <configSections>
        <section name="NopConfig" type="Easy.Core.Configuration.NopConfig, Easy.Core" requirePermission="false" />
      </configSections>
      <NopConfig>
        <DynamicDiscovery Enabled="true" />
        <Engine Type="" />
        <Themes basePath="~/Themes/" />
      </NopConfig>
    CreateEngineInstance函数中使用new NopEngine()创建了一个NopEngine实例,在NopEngine的构造函数处对Autofac的容器(Container)作了初始化,如下代码:
    public NopEngine(EventBroker broker, ContainerConfigurer configurer)
    {
        var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;
        InitializeContainer(configurer, broker, config);
    }
    private void InitializeContainer(ContainerConfigurer configurer, EventBroker broker, NopConfig config)
    {
        var builder = new ContainerBuilder();
     
        _containerManager = new ContainerManager(builder.Build());
        configurer.Configure(this, _containerManager, broker, config);
    }

    NopCommerce通过ContainerManager对容器做了一层封装,方便对其他类型的IOC框架的扩充和支持。Configure函数完成了所有依赖的注入,同时查找所有实现了IDependencyRegistrar接口的类,并调用其Register方法,注册内容包括Http context、web helper、controller、data layer、plugin、cache manager、work context、services、settings、event consumers等等。

    关于ContainerManager/ContainerConfigurer和IDependencyRegistrar是实现IOC的关键,下面对这两个部分做详细的讨论。

    IOC和DI

    IOC中文名被称作控制反转(Inversion of Control),DI被称为依赖注入(Dependency Injection),可参考Martin Fowler的这篇文章来了解这两个概念:IoC容器和DependencyInjection模式。使用控制反转模式开发项目流程是先建立接口,然后再实现类,或许有人不习惯这样的开发方法,但在规模较大的软件架构中,这种方法却可以有效的降低类之间的互相依赖的情况,不但能增加架构的弹性,也能有效的降低软件的复杂度。

    如果不考虑控制反转的情况,采用直接创建类,并直接在应用层调用该类,如此一来,应用层的对象就会与BLL(业务逻辑层)对象高度依赖,这样的依赖 会导致这两个类无法拆开,从而增加了这个类的维护难度,同时导致了单元测试难以进行。为了解决耦合度问题,从而引入了控制反转的概念。

    Autofac介绍

     Autofac是一款IOC框架,比较于其他的IOC框架,如Spring.NET、Unity、Castle等,它更显得轻量级,同时保证了高性能。它具有以下优点:

    1. 和C#语言联系紧密,可以使用C#语言的很多特性,譬如Lambda表达式等;
    2. 较低的学习曲线,只需了解IoC和DI的概念以及在何时需要使用它们即可;
    3. XML配置支持;
    4. 自动装配;
    5. 与ASP.NET MVC3集成;(Orchard也是使用Autofac实现IOC的)

    在MVC3项目中使用Autofac

     在MVC3工程中使用Autofac的最好也是最简单的方法是使用NuGet来安装Autofac.Mvc3,安装完成以后,在Global.asax的Application_Start方法中添加如下代码:

    1. protected void Application_Start()  
    2. {  
    3.     var builder = new ContainerBuilder();  
    4.     builder.RegisterControllers(typeof(MvcApplication).Assembly);  
    5.     var container = builder.Build();  
    6.     DependencyResolver.SetResolver(new AutofacDependencyResolver(container));  
    7.     // Other MVC setup...  

    这样就开启了Controller的依赖注入功能。其中的DependencyResolver是一个全局静态类,MVC3提供了对依赖注入的支 持,SetResolver函数用于设置使用哪个Resolver(解析器)来进行依赖注入,这里使用的是Autofac的依赖注入解析器。如果要使用自 己的解析器,必须在这里使用SetResolver函数设置。

    1. 注册Controller

    可以使用下面的方法对特定的Controller进行注册:

    1. var builder =  new ContainerBuilder();  
    2. builder.RegisterType<HomeController>().InstancePerRequest();  
    1. 同时可以使用Autofac提供的RegisterControllers扩展方法来对程序集中所有的Controller一次性的完成注册:  
    1. var builder = new ContainerBuilder();  
    2. builder.RegisterControllers(Assembly.GetExecutingAssembly());  

    2. 注册Model Binder

    与控制器的注册类似,模型绑定也可以再Global.asax.cs中注册。您可以通过如下操作完成整个程序集的注册:

    1. var builder = newContainerBuilder();  
    2. builder.RegisterModelBinders(Assembly.GetExecutingAssembly());  
    3. builder.RegisterModelBinderProvider();  

    您也必须记住使用RegisterModelBinderProvider扩展方法来注册RegisterModelBinderProvider。这个方法用是Autofac对IModelBinderProvider接口的实现

    因为RegisterModelBinders扩展方法通过扫描程序集来添加模型绑定的,所以您需要指定IModelBuilder注册的目标类是什么类型。

    1. [ModelBinderType(typeof(string))]  
    2. public class StringBinder : IModelBinder  
    3. {  
    4.     public override object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext)  
    5.     {  
    6.         //do implementation here  
    7.     }  
    8. }  

    多行的ModelBuilderTypeAttribute实例可以添加到需要对个类型注册的类中。

    3. 注入HTTP抽象类

    MVC集成的Autofac模块将会为HTTP抽象类添加HTTP 请求的生命收起范围内的注册。包括依稀抽象类:
    • HttpContextBase
    • HttpRequestBase
    • HttpResponseBase
    • HttpServerUtilityBase
    • HttpSessionStateBase
    • HttpApplicationStateBase
    • HttpBrowserCapabilitiesBase
    • HttpCachePolicyBase
    • VirtualPathProvider
    需要使用上面的抽象应该使用容器的RegisterModule方法来添加AutofacWebTypesModule
    1. builder.RegisterModule(newAutofacWebTypesModule());  

    4. 注入View page

    您可以通过在容器创建之前添加ViewRegistrationSource 到容器中使属性注入来使MVC页面可用。
    1. builder.RegisterSource(newViewRegistrationSource());  
    您的viewpage必须继承MVC类中用于创建,当使用Razor试图引擎时将需要继承WebViewPage类:
    1. public abstract class CustomViewPage : WebViewPage  
    2. {  
    3.     public IDependencyDependency { get; set; }  
    4. }  
    使用的是webform的试图引擎时,ViewPage,ViewMasterPage和ViewUserControl类都得到相应的支持。
    1. public abstract class CustomViewPage : ViewPage  
    2. {  
    3.     public IDependencyDependency { get; set; }  
    4. }  
    必须确保您实际的试图页面继承了您自定义的基类。在Razor视图引擎.cshtml中可以使用@inherits指令来实现
    1. @inherits Example.Views.Shared.CustomViewPage  
    使用webform时可以做如下设置
    1. <%@ PageLanguage="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="Example.Views.Shared.CustomViewPage" %>  

    5. 对Filter Attribute进行属性注入

    为过滤器使用属性注入必须在容器创建之前调用RegisterFilterProvider方法,并将其传到AutofacDependencyResolver
    1. ContainerBuilder builder = new ContainerBuilder();  
    2. builder.RegisterControllers(Assembly.GetExecutingAssembly());  
    3. builder.Register(c => new Logger()).As<ILogger>().InstancePerHttpRequest();  
    4. builder.RegisterFilterProvider();  
    5. IContainer container = builder.Build();  
    6. DependencyResolver.SetResolver(new AutofacDependencyResolver(container));  
    然后您就可以为您的过滤器添加属性了,并且
    1. public class CustomActionFilter : ActionFilterAttribute  
    2. {  
    3.     public ILogger Logger { get; set; }  
    4.    
    5.     public override void OnActionExecuting(ActionExecutingContext filterContext)  
    6.     {  
    7.         Logger.Log("OnActionExecuting");  
    8.     }  
    9. }  
    下面是类似用户验证过滤器的自定义特性
    1. public class CustomAuthorizeAttribute : AuthorizeAttribute  
    2. {  
    3.     public ILogger Logger { get; set; }  
    4.    
    5.     protected override bool AuthorizeCore(HttpContextBase httpContext)  
    6.     {  
    7.         Logger.Log("AuthorizeCore");  
    8.         return true;  
    9.     }  
    10. }  
    应用如下:
    1. [CustomActionFilter]  
    2. [CustomAuthorizeAttribute]  
    3. public ActionResult Index()  
    4. {  
    5.     // ...  
    6. }  
    1. 关于Autofac更多的信息,可以参考autofac在google code上的wiki文档:http://code.google.com/p/autofac/wiki/Mvc3Integration  

    NopCommerce是如何使用Autofac实现依赖注入的?

     NopCommerce将所有和Autofac注入相关的工作都放到了EngineContext中,在Global.asax的Application_Start函数的第一句代码即是:

    1. //initialize engine context  
    2. EngineContext.Initialize(false);  

    从这里开始EngineContext的初始化工作,初始化时会创建一个新的NopEngine,参数false指定当NopEngine不为空时是否重新生成一个新的NopEngine。

    1. [MethodImpl(MethodImplOptions.Synchronized)]  
    2. public static IEngine Initialize(bool forceRecreate)  
    3. {  
    4.     if (Singleton<IEngine>.Instance == null || forceRecreate)  
    5.     {  
    6.         var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;  
    7.         Debug.WriteLine("Constructing engine " + DateTime.Now);  
    8.         Singleton<IEngine>.Instance = CreateEngineInstance(config);  
    9.         Debug.WriteLine("Initializing engine " + DateTime.Now);  
    10.         Singleton<IEngine>.Instance.Initialize(config);  
    11.     }  
    12.     return Singleton<IEngine>.Instance;  
    13. }  

    NopEngine 使用单例模式,在整个程序运行期间存在一个实例,代码首先会判断NopEngine是否为空,为空的话则根据web.config中配置的 NopConfig节点信息创建一个新的NopEngine实例,然后对该实例进行初始化操作。web.config中的配置信息如下:

    1.   <configSections>  
    2.     <section name="NopConfig" type="Easy.Core.Configuration.NopConfig, Easy.Core" requirePermission="false" />  
    3.   </configSections>  
    4.   <NopConfig>  
    5.     <DynamicDiscovery Enabled="true" />  
    6.     <Engine Type="" />  
    7.     <Themes basePath="~/Themes/" />  
    8.   </NopConfig>  

    CreateEngineInstance函数中使用new NopEngine()创建了一个NopEngine实例,在NopEngine的构造函数处对Autofac的容器(Container)作了初始化,如下代码:

    1. public NopEngine(EventBroker broker, ContainerConfigurer configurer)  
    2. {  
    3.     var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;  
    4.     InitializeContainer(configurer, broker, config);  
    5. }  
    1. private void InitializeContainer(ContainerConfigurer configurer, EventBroker broker, NopConfig config)  
    2. {  
    3.     var builder = new ContainerBuilder();  
    4.    
    5.     _containerManager = new ContainerManager(builder.Build());  
    6.     configurer.Configure(this, _containerManager, broker, config);  
    7. }  

    NopCommerce通过ContainerManager对容器做了一层封装,方便对其他类型的IOC框架的扩充和支持。Configure函数完成了所有依赖注入,同时查找所有实现了IDependencyRegistrar接 口的类,并调用其Register方法,注册内容包括Http context、web helper、controller、data layer、plugin、cache manager、work context、services、settings、event consumers等等。

    关于ContainerManager/ContainerConfigurer和IDependencyRegistrar是实现IOC的关键,下面对这两个部分做详细的讨论。

    // todo:仍需继续分析具体实现

    ContainerManager/ContainerConfigurer

    1. ContainerManagerContainerManager对依赖注入中使用的容器做了一层封装,提供了这些函数:  
    • AddComponent/AddComponentInstance/AddComponentWithParameters
    • Resolve/ResolveAll/ResovleUnregistered
    • UpdateContainer

    DependencyRegistrar

    • web helper
    • controller
    • data layer
    • plugin
    • cache manager
    • work context
    • services
    • settings
    • event consumers

    from:aneasystone ==>http://www.cnblogs.com/aneasystone/archive/2012/08/27/2659176.html

    IOC和DI

    IOC中文名被称作控制反转(Inversion of Control),DI被称为依赖注入(Dependency Injection),可参考Martin Fowler的这篇文章来了解这两个概念:IoC容器和DependencyInjection模式。使用控制反转模式开发项目流程是先建立接口,然后再实现类,或许有人不习惯这样的开发方法,但在规模较大的软件架构中,这种方法却可以有效的降低类之间的互相依赖的情况,不但能增加架构的弹性,也能有效的降低软件的复杂度。

    如果不考虑控制反转的情况,采用直接创建类,并直接在应用层调用该类,如此一来,应用层的对象就会与BLL(业务逻辑层)对象高度依赖,这样的依赖 会导致这两个类无法拆开,从而增加了这个类的维护难度,同时导致了单元测试难以进行。为了解决耦合度问题,从而引入了控制反转的概念。

    Autofac介绍

     Autofac是一款IOC框架,比较于其他的IOC框架,如Spring.NET、Unity、Castle等,它更显得轻量级,同时保证了高性能。它具有以下优点:

    1. 和C#语言联系紧密,可以使用C#语言的很多特性,譬如Lambda表达式等;
    2. 较低的学习曲线,只需了解IoC和DI的概念以及在何时需要使用它们即可;
    3. XML配置支持;
    4. 自动装配;
    5. 与ASP.NET MVC3集成;(Orchard也是使用Autofac实现IOC的)

    在MVC3项目中使用Autofac

     在MVC3工程中使用Autofac的最好也是最简单的方法是使用NuGet来安装Autofac.Mvc3,安装完成以后,在Global.asax的Application_Start方法中添加如下代码:

    1. protected void Application_Start()  
    2. {  
    3.     var builder = new ContainerBuilder();  
    4.     builder.RegisterControllers(typeof(MvcApplication).Assembly);  
    5.     var container = builder.Build();  
    6.     DependencyResolver.SetResolver(new AutofacDependencyResolver(container));  
    7.     // Other MVC setup...  

    这样就开启了Controller的依赖注入功能。其中的DependencyResolver是一个全局静态类,MVC3提供了对依赖注入的支 持,SetResolver函数用于设置使用哪个Resolver(解析器)来进行依赖注入,这里使用的是Autofac的依赖注入解析器。如果要使用自 己的解析器,必须在这里使用SetResolver函数设置。

    1. 注册Controller

    可以使用下面的方法对特定的Controller进行注册:

    1. var builder =  new ContainerBuilder();  
    2. builder.RegisterType<HomeController>().InstancePerRequest();  
    1. 同时可以使用Autofac提供的RegisterControllers扩展方法来对程序集中所有的Controller一次性的完成注册:  
    1. var builder = new ContainerBuilder();  
    2. builder.RegisterControllers(Assembly.GetExecutingAssembly());  

    2. 注册Model Binder

    与控制器的注册类似,模型绑定也可以再Global.asax.cs中注册。您可以通过如下操作完成整个程序集的注册:

    1. var builder = newContainerBuilder();  
    2. builder.RegisterModelBinders(Assembly.GetExecutingAssembly());  
    3. builder.RegisterModelBinderProvider();  

    您也必须记住使用RegisterModelBinderProvider扩展方法来注册RegisterModelBinderProvider。这个方法用是Autofac对IModelBinderProvider接口的实现

    因为RegisterModelBinders扩展方法通过扫描程序集来添加模型绑定的,所以您需要指定IModelBuilder注册的目标类是什么类型。

    1. [ModelBinderType(typeof(string))]  
    2. public class StringBinder : IModelBinder  
    3. {  
    4.     public override object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext)  
    5.     {  
    6.         //do implementation here  
    7.     }  
    8. }  

    多行的ModelBuilderTypeAttribute实例可以添加到需要对个类型注册的类中。

    3. 注入HTTP抽象类

    MVC集成的Autofac模块将会为HTTP抽象类添加HTTP 请求的生命收起范围内的注册。包括依稀抽象类:
    • HttpContextBase
    • HttpRequestBase
    • HttpResponseBase
    • HttpServerUtilityBase
    • HttpSessionStateBase
    • HttpApplicationStateBase
    • HttpBrowserCapabilitiesBase
    • HttpCachePolicyBase
    • VirtualPathProvider
    需要使用上面的抽象应该使用容器的RegisterModule方法来添加AutofacWebTypesModule
    1. builder.RegisterModule(newAutofacWebTypesModule());  

    4. 注入View page

    您可以通过在容器创建之前添加ViewRegistrationSource 到容器中使属性注入来使MVC页面可用。
    1. builder.RegisterSource(newViewRegistrationSource());  
    您的viewpage必须继承MVC类中用于创建,当使用Razor试图引擎时将需要继承WebViewPage类:
    1. public abstract class CustomViewPage : WebViewPage  
    2. {  
    3.     public IDependencyDependency { get; set; }  
    4. }  
    使用的是webform的试图引擎时,ViewPage,ViewMasterPage和ViewUserControl类都得到相应的支持。
    1. public abstract class CustomViewPage : ViewPage  
    2. {  
    3.     public IDependencyDependency { get; set; }  
    4. }  
    必须确保您实际的试图页面继承了您自定义的基类。在Razor视图引擎.cshtml中可以使用@inherits指令来实现
    1. @inherits Example.Views.Shared.CustomViewPage  
    使用webform时可以做如下设置
    1. <%@ PageLanguage="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="Example.Views.Shared.CustomViewPage" %>  

    5. 对Filter Attribute进行属性注入

    为过滤器使用属性注入必须在容器创建之前调用RegisterFilterProvider方法,并将其传到AutofacDependencyResolver
    1. ContainerBuilder builder = new ContainerBuilder();  
    2. builder.RegisterControllers(Assembly.GetExecutingAssembly());  
    3. builder.Register(c => new Logger()).As<ILogger>().InstancePerHttpRequest();  
    4. builder.RegisterFilterProvider();  
    5. IContainer container = builder.Build();  
    6. DependencyResolver.SetResolver(new AutofacDependencyResolver(container));  
    然后您就可以为您的过滤器添加属性了,并且
    1. public class CustomActionFilter : ActionFilterAttribute  
    2. {  
    3.     public ILogger Logger { get; set; }  
    4.    
    5.     public override void OnActionExecuting(ActionExecutingContext filterContext)  
    6.     {  
    7.         Logger.Log("OnActionExecuting");  
    8.     }  
    9. }  
    下面是类似用户验证过滤器的自定义特性
    1. public class CustomAuthorizeAttribute : AuthorizeAttribute  
    2. {  
    3.     public ILogger Logger { get; set; }  
    4.    
    5.     protected override bool AuthorizeCore(HttpContextBase httpContext)  
    6.     {  
    7.         Logger.Log("AuthorizeCore");  
    8.         return true;  
    9.     }  
    10. }  
    应用如下:
    1. [CustomActionFilter]  
    2. [CustomAuthorizeAttribute]  
    3. public ActionResult Index()  
    4. {  
    5.     // ...  
    6. }  
    1. 关于Autofac更多的信息,可以参考autofac在google code上的wiki文档:http://code.google.com/p/autofac/wiki/Mvc3Integration  

    NopCommerce是如何使用Autofac实现依赖注入的?

     NopCommerce将所有和Autofac注入相关的工作都放到了EngineContext中,在Global.asax的Application_Start函数的第一句代码即是:

    1. //initialize engine context  
    2. EngineContext.Initialize(false);  

    从这里开始EngineContext的初始化工作,初始化时会创建一个新的NopEngine,参数false指定当NopEngine不为空时是否重新生成一个新的NopEngine。

    1. [MethodImpl(MethodImplOptions.Synchronized)]  
    2. public static IEngine Initialize(bool forceRecreate)  
    3. {  
    4.     if (Singleton<IEngine>.Instance == null || forceRecreate)  
    5.     {  
    6.         var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;  
    7.         Debug.WriteLine("Constructing engine " + DateTime.Now);  
    8.         Singleton<IEngine>.Instance = CreateEngineInstance(config);  
    9.         Debug.WriteLine("Initializing engine " + DateTime.Now);  
    10.         Singleton<IEngine>.Instance.Initialize(config);  
    11.     }  
    12.     return Singleton<IEngine>.Instance;  
    13. }  

    NopEngine 使用单例模式,在整个程序运行期间存在一个实例,代码首先会判断NopEngine是否为空,为空的话则根据web.config中配置的 NopConfig节点信息创建一个新的NopEngine实例,然后对该实例进行初始化操作。web.config中的配置信息如下:

    1.   <configSections>  
    2.     <section name="NopConfig" type="Easy.Core.Configuration.NopConfig, Easy.Core" requirePermission="false" />  
    3.   </configSections>  
    4.   <NopConfig>  
    5.     <DynamicDiscovery Enabled="true" />  
    6.     <Engine Type="" />  
    7.     <Themes basePath="~/Themes/" />  
    8.   </NopConfig>  

    CreateEngineInstance函数中使用new NopEngine()创建了一个NopEngine实例,在NopEngine的构造函数处对Autofac的容器(Container)作了初始化,如下代码:

    1. public NopEngine(EventBroker broker, ContainerConfigurer configurer)  
    2. {  
    3.     var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;  
    4.     InitializeContainer(configurer, broker, config);  
    5. }  
    1. private void InitializeContainer(ContainerConfigurer configurer, EventBroker broker, NopConfig config)  
    2. {  
    3.     var builder = new ContainerBuilder();  
    4.    
    5.     _containerManager = new ContainerManager(builder.Build());  
    6.     configurer.Configure(this, _containerManager, broker, config);  
    7. }  

    NopCommerce通过ContainerManager对容器做了一层封装,方便对其他类型的IOC框架的扩充和支持。Configure函数完成了所有依赖注入,同时查找所有实现了IDependencyRegistrar接 口的类,并调用其Register方法,注册内容包括Http context、web helper、controller、data layer、plugin、cache manager、work context、services、settings、event consumers等等。

    关于ContainerManager/ContainerConfigurer和IDependencyRegistrar是实现IOC的关键,下面对这两个部分做详细的讨论。

    // todo:仍需继续分析具体实现

    ContainerManager/ContainerConfigurer

    1. ContainerManagerContainerManager对依赖注入中使用的容器做了一层封装,提供了这些函数:  
    • AddComponent/AddComponentInstance/AddComponentWithParameters
    • Resolve/ResolveAll/ResovleUnregistered
    • UpdateContainer

    DependencyRegistrar

    • web helper
    • controller
    • data layer
    • plugin
    • cache manager
    • work context
    • services
    • settings
  • 相关阅读:
    「疫期集训day11」沙漠
    「树形DP」洛谷P2607 [ZJOI2008]骑士
    「疫期集训day10」玫瑰
    「疫期集训day9」七月
    核心容器(概念)
    初识Spring
    IOC(控制反转思想)原型理论推导
    图片在上,文字在下并且等间距的三个菜单按钮
    编写登陆接口
    001使用gltf创建3d模型
  • 原文地址:https://www.cnblogs.com/xchit/p/4727071.html
Copyright © 2011-2022 走看看