一. 加载dll,读取相关信息

1. 加载程序集的三种方式

 调用Assembly类下的三个方法:Load、LoadFile、LoadFrom。

        //1.1 Load方法:动态默认加载当前路径下的(bin)下的dll文件,不需要后缀
Assembly assembly = Assembly.Load("DB.SQLServer");
//1.2 LoadFile方法:程序集的绝对路径
Assembly assembly2 = Assembly.LoadFile(@"D:\我的框架之路\DotNet体系\02-DotNet进阶\02-反射\01-code\Reflection\bin\Debug\DB.SQLServer.dll");
//1.3 LoadFrom方法:可以是当前路径(需要写上后缀.dll),也可以是绝对路径
Assembly assembly3 = Assembly.LoadFrom("DB.SQLServer.dll");

2. 获取程序集中所有的类

 通过方法GetTypes来实现。

 Assembly assembly = Assembly.Load("DB.SQLServer");
Type[] t1 = assembly.GetTypes();

通过方法GetType("类名全写")来实现获取单个类(DBHelper)。

 Type tItem = assembly.GetType("DB.SQLServer.DBHelper");

3. 获取类中的所有构造函数

  通过方法GetConstructors来实现。

 //1.加载程序集
Assembly assembly = Assembly.Load("DB.SQLServer");
//2.获取程序集中的特定类
Type tItem = assembly.GetType("DB.SQLServer.DBHelper");
//3.获取特定类中的所有构造函数
ConstructorInfo[] cInfor = tItem.GetConstructors();

4. 获取类中的所有属性

 通过方法GetProperties来实现。

  //1.加载程序集
Assembly assembly = Assembly.Load("DB.SQLServer");
//2.获取程序集中的特定类
Type tItem = assembly.GetType("DB.SQLServer.DBHelper");
//3.获取特定类中的所有属性
PropertyInfo[] propertyInfo = tItem.GetProperties();

5. 获取类中的所有方法

通过方法GetMethods来实现。

 //1.加载程序集
Assembly assembly = Assembly.Load("DB.SQLServer");
//2.获取程序集中的特定类
Type tItem = assembly.GetType("DB.SQLServer.DBHelper");
//3.获取特定类中的所有方法
MethodInfo[] methordInfo = tItem.GetMethods();

6. 获取类中的所有接口

  通过方法GetInterfaces来实现。

 //1.加载程序集
Assembly assembly = Assembly.Load("DB.SQLServer");
//2.获取程序集中的特定类
Type tItem = assembly.GetType("DB.SQLServer.DBHelper");
//3.获取特定类中的所有接口
Type[] type = tItem.GetInterfaces();

二. 反射创建对象

1. 反射创建对象

 通过Activator.CreateInstance()方法来创建对象

1.1 ReflectionTest类的代码

  public class ReflectionTest
{
public int Id { get; set; }
public string Name { get; set; } public string Field = null;
public static string FieldStatic = null; #region 构造函数
public ReflectionTest()
{
Console.WriteLine("这里是{0}无参数构造函数", this.GetType());
} public ReflectionTest(string name)
{
Console.WriteLine("这里是{0} 有1个参数构造函数", this.GetType());
} public ReflectionTest(int id, string name)
{
Console.WriteLine("这里是{0} 有2个参数构造函数", this.GetType());
}
#endregion public void Show1()
{
Console.WriteLine("这里是{0}的Show1", this.GetType());
} public void Show2(int id)
{ Console.WriteLine("这里是{0}的Show2", this.GetType());
} public static void ShowStatic(string name)
{
Console.WriteLine("这里是{0}的ShowStatic", typeof(ReflectionTest));
} public void Show3()
{
Console.WriteLine("这里是{0}的Show3_1", this.GetType());
} public void Show3(int id, string name)
{
Console.WriteLine("这里是{0}的Show3", this.GetType());
} public void Show3(string name, int id)
{
Console.WriteLine("这里是{0}的Show3_2", this.GetType());
} public void Show3(int id)
{ Console.WriteLine("这里是{0}的Show3_3", this.GetType());
} public void Show3(string name)
{ Console.WriteLine("这里是{0}的Show3_4", this.GetType());
} private void Show4(string name)
{
Console.WriteLine("这里是{0}的Show4", this.GetType());
}
public void ShowGeneric<T>(T name)
{
Console.WriteLine("这里是{0}的ShowStatic T={1}", this.GetType(), typeof(T));
}
}

1.2 反射创建对象的代码

 //1.加载程序集
Assembly assembly = Assembly.Load("DB.SQLServer");
//2.获取程序集中的特定类
Type tItem = assembly.GetType("DB.SQLServer.ReflectionTest");
//3.1 无参构造函数
Activator.CreateInstance(tItem);
//3.2 一个参数的构造函数
Activator.CreateInstance(tItem ,"");
//3.3 两个参数的构造函数
Activator.CreateInstance(tItem , ,"");

2. 反射破坏单例,调用私有构造函数

   单例代码

  public sealed class Singleton
{
private Singleton()
{
Console.WriteLine("初始化一次");
} private static Singleton Instance = new Singleton(); public static Singleton CreateInstance()
{
return Instance;
}
}

   破坏单例,调用私有构造函数代码

 //1.加载程序集
Assembly assembly = Assembly.Load("DB.SQLServer");
//2. 获取单例类
Type tc3 = assembly .GetType("DB.SQLServer.Singleton");
//3. 创建对象
Activator.CreateInstance(tc3, true);

3. 反射创建泛型(扩展)

  //1.加载程序集
Assembly assembly = Assembly.Load("DB.SQLServer");
//2. 获取单例类
Type tc4 = assembly4.GetType("DB.SQLServer.GenericClass`1");
tc4 = tc4.MakeGenericType(typeof(int));
Activator.CreateInstance(tc4);

三. IOC(反射+简单工厂+配置文件)

  背景:有三套相同的数据库,分别是SQLServer、MySQL、Oracle数据库,要求可以分别连接这三个不同的数据库,并且发布后,可以在不重新发布的情况下,切换连接数据库。

  对应上述背景,建立相应的解决方案,目录如下,并介绍介绍几种传统的解决方案

方案一:在Reflection中直接添加对DB.SQLServer的引用

  Console.WriteLine("------------------------------------1. 传统方式调用DBHelper中的Query方法--------------------------------------");
//1.传统的方式调用(需要对 DB.SQLServer添加引用)
DBHelper db1 = new DBHelper("");
db1.Query();

方案二:在Reflection中直接添加对DB.SQLServer、DB.Interface的引用

   Console.WriteLine("------------------------------------2. 接口的方式调用--------------------------------------");
//2. 接口的方式调用(只需要引用接口的程序集即可)
IDBHelper idb1 = new DBHelper("");
idb1.Query();

点评:以上两种方案实质上都是通过对相应的实现类添加引用,new出来对象,然后调用方法来实现,没法发布后动态修改数据库。

方案三:通过反射来创建对象,只需要添加对DB.Interface的引用即可,但需要把DB.SQLServer、DB.MySQL、DB.Oracle的生成dll的目录改成Reflection程序下

  Console.WriteLine("------------------------------------3. 反射的方式创建对象--------------------------------------");
//3. 反射的方式创建对象(不需要直接添加对其引用,只需要把相应的程序生成路径改成Reflection中即可)
Assembly assembly4 = Assembly.Load("DB.SQLServer");
Type tc = assembly4.GetType("DB.SQLServer.DBHelper");
//object myDbHelper = Activator.CreateInstance(tc, "123"); //调用带参数的构造函数
object myDbHelper = Activator.CreateInstance(tc); //默认调用无参构造函数
IDBHelper idb2 = (IDBHelper)myDbHelper;
idb2.Query();

点评:该方案只需要对接口添加引用,符合了面向接口编程的思想,但是发布后在不修改代码的情况下,不能切换数据库。

方案四:IOC(反射+简单工厂+配置文件),需要添加对DB.Interface的引用,并且把DB.SQLServer、DB.MySQL、DB.Oracle的生成dll的目录改成Reflection程序下

配置文件:

   <appSettings>
<!--直接修改配置文件,可以修改数据库连接牛逼,可以直接切换 oracle 、mysql数据库,发布后可以直接通过改配置文件,切换数据库,代码什么也不用改,体会:反射+面向接口编程-->
<!--前提:相应的DBHelper类必须满足接口约束,需要把Oracle或MySql的dll文件拷贝到Reflection中的bin文件中 -->
<!--SQLServer改为: -->
<!--<add key="IDBHelper-dllName" value="DB.SQLServer"/>
<add key="IDBHelper-className" value="DB.SQLServer.DBHelper"/>-->
<!--Oracle改为: -->
<!--<add key="IDBHelper-dllName" value="DB.Oracle"/>
<add key="IDBHelper-className" value="DB.Oracle.DBHelper"/>-->
<!--MySql改为: -->
<add key="IDBHelper-dllName" value="DB.MySql"/>
<add key="IDBHelper-className" value="DB.MySql.DBHelper"/>
</appSettings>

简单工厂:

 /// <summary>
/// 简单工厂,创建对象
/// </summary>
public class SimpleFactory
{
private static string IDBHelperdllName = ConfigurationManager.AppSettings["IDBHelper-dllName"];
private static string IDBHelperClassName = ConfigurationManager.AppSettings["IDBHelper-className"]; public static IDBHelper CreateDBHelper()
{
Assembly assembly = Assembly.Load(IDBHelperdllName);
Type type = assembly.GetType(IDBHelperClassName);
object obj = Activator.CreateInstance(type);
return (IDBHelper)obj;
} }

调用:

   Console.WriteLine("------------------------------------4. IOC(反射+简单工厂+配置文件)--------------------------------------");
//4. IOC(反射+简单工厂+配置文件)(不需要直接添加对其引用,只需要把相应的程序生成路径改成Reflection中即可)
IDBHelper idb3 = SimpleFactory.CreateDBHelper();
idb3.Query();

四. 反射调用实例方法、静态方法、重载方法

         //2. 实例方法、静态方法、重载方法的调用
{
object obj = Activator.CreateInstance(tc5);
Console.WriteLine("---------------------------------2.1 调用无参、有参的实例方法-------------------------------------");
//2.1 调用无参、有参的实例方法
{
MethodInfo methord = tc5.GetMethod("Show1");
methord.Invoke(obj, null);
}
{
MethodInfo methord = tc5.GetMethod("Show2");
methord.Invoke(obj, new object[]{});
} Console.WriteLine("---------------------------------2.2 调用静态方法-------------------------------------");
//2.2 调用静态方法
{
MethodInfo methord = tc5.GetMethod("ShowStatic");
methord.Invoke(obj, new object[] { "ShowStatic1234" });
} Console.WriteLine("---------------------------------2.3 调用重载方法(需要在创建方法的时候,把类型传进去)-------------------------------------");
//2.3 调用重载方法(需要在创建方法的时候,把类型传进去)
{
MethodInfo methord = tc5.GetMethod("Show3", new Type[] { });
methord.Invoke(obj, null);
}
{
MethodInfo methord = tc5.GetMethod("Show3", new Type[] { typeof(int)});
methord.Invoke(obj, new object[] { });
}
{
MethodInfo methord = tc5.GetMethod("Show3", new Type[] { typeof(string) });
methord.Invoke(obj, new object[] { "" });
}
{
MethodInfo methord = tc5.GetMethod("Show3", new Type[] { typeof(int), typeof(string) });
methord.Invoke(obj, new object[] { ,""});
}
{
MethodInfo methord = tc5.GetMethod("Show3", new Type[] { typeof(string), typeof(int) });
methord.Invoke(obj, new object[] { "", });
} Console.WriteLine("---------------------------------2.4 调用私有方法-------------------------------------");
//2.4 调用私有方法
{
MethodInfo methord = tc5.GetMethod("Show4", BindingFlags.Instance|BindingFlags.NonPublic);
methord.Invoke(obj, new object[] { "" });
}
Console.WriteLine("---------------------------------2.5 调用泛型方法-------------------------------------");
//2.5 调用泛型方法
{
MethodInfo methord = tc5.GetMethod("ShowGeneric");
methord = methord.MakeGenericMethod(typeof(string));
methord.Invoke(obj, new object[] { "" });
} }

五. 反射字段和属性,获取值和设置值

  {
//实例化对象
ReflectionTest rTest = new ReflectionTest();
//反射
Assembly assembly5 = Assembly.Load("DB.SQLServer");
Type type5 = assembly5.GetType("DB.SQLServer.ReflectionTest");
object object5 = Activator.CreateInstance(type5);
//1.获取类的所有属性
Console.WriteLine("------------------------------------1.获取类的属性--------------------------------------");
var pInfor= type5.GetProperties();
foreach (var item in pInfor)
{
Console.WriteLine(item.Name);
if (item.Name.Equals("Id"))
{
item.SetValue(object5, );
}
}
Console.WriteLine("------------------------------------输出ID属性值--------------------------------------");
rTest = (ReflectionTest)object5;
Console.WriteLine(rTest.Id);
//2.获取类的字段
Console.WriteLine("------------------------------------2.获取类的字段--------------------------------------");
foreach (var item in type5.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public))
{
Console.WriteLine(item.Name);
} }

六. 反射的好处和局限

  {
//反射的好处:
//反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类、结构、委托、接口和枚举等)的成员和成员的信息。
//有了反射,即可对每一个类型了如指掌。另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道。 //性能局限
Console.WriteLine("------------------------------测试普通方法和反射的耗时情况--------------------------------------");
{
//1.普通方法
Stopwatch watch = new Stopwatch();
watch.Start();
for (var i = ; i < ; i++)
{
DBHelper2 dh = new DBHelper2();
dh.Id = ;
dh.Name = "maru";
dh.Query();
}
watch.Stop();
Console.WriteLine("普通方式花费{0}ms:",watch.ElapsedMilliseconds);
}
{
//2. 反射的方法
Stopwatch watch = new Stopwatch();
watch.Start();
Assembly assembley6 = Assembly.Load("DB.SQLServer");
Type type6 = assembley6.GetType("DB.SQLServer.DBHelper2");
for (var i = ; i < ; i++)
{
object obj6 = Activator.CreateInstance(type6);
foreach (var item in type6.GetProperties())
{
if (item.Name.Equals("Id"))
{
item.SetValue(obj6, );
}
if (item.Name.Equals("Name"))
{
item.SetValue(obj6, "maru");
}
}
MethodInfo m6 = type6.GetMethod("Query");
m6.Invoke(obj6, null);
}
watch.Stop();
Console.WriteLine("反射方式花费{0}ms:", watch.ElapsedMilliseconds);
}
}

运行结果:

 七. 补充:获取类的属性、方法、特性、构造函数等,设置属性值,获取属性值

函数                                                                         说明
GetConstructor(s)                  取得此类型的创建函数,其将回传一个ConstructorInfo对象或数组
GetField(s)                             取得此类型中成员变量,其将回传一个FiledInfo对象或数组
GetMember(s)                        取得此类中的成员,其类型可以是变量、事件、属性、方法及Nested Type,其将回传一个MemberInfo对象或数组
GetEvent(s)                            取得此类型中的事件,其将回传一个EventInfo对象或数组
GetProperty/GetProperties         取得此类型中的属性,其将回传一个PropertyInfo对象或数组
GetNestedType(s)                  取得声明于此类型内类型,其将回传一个Type对象或数组
GetCustomAttibutes                    取得绑定于此类型的Attitudes
GetValue(t)                                  获取t对象的的属性值
SetValue(t,"XXX")                        设置t对象的属性值为XXX

实体代码:

  [Description("我是Person类")]
public class Person
{
//1. 构造函数
public Person()
{ }
public Person(string sex)
{
this._Sex = sex;
}
//2. 属性 [Description("我是id")]
public string id { get; set; } [ReadOnly(true)]
public string userName { get; set; } public string userPwd { get; set; } //3.成员变量 public string _Sex = null; public int _Age; //4. 特性 [Description("我是id")] [ReadOnly(true)] //5. 方法
public string GetOwnSex()
{
return this._Sex;
} //6. 事件
public event Action MyEvent; }

调用代码:

  Person person1 = new Person()
{
id = "",
userName = "ypf",
userPwd = "",
};
//获取类
Type type = person1.GetType();
{
Console.WriteLine("---------------1. 获取构造函数-----------------");
ConstructorInfo[] constructorInfoList = type.GetConstructors();
for (int i = ; i < constructorInfoList.Length; i++)
{
Console.WriteLine(constructorInfoList[i]);
}
}
{
Console.WriteLine("---------------2. 获取成员变量-----------------");
FieldInfo[] fielInforList = type.GetFields();
for (int i = ; i < fielInforList.Length; i++)
{
Console.WriteLine(fielInforList[i]);
}
}
{
Console.WriteLine("---------------3. 获取成员-----------------");
MemberInfo[] memberInfoList = type.GetMembers();
for (int i = ; i < memberInfoList.Length; i++)
{
Console.WriteLine(memberInfoList[i]);
}
}
{
Console.WriteLine("---------------4. 获取事件-----------------");
EventInfo[] eventInfoList = type.GetEvents();
for (int i = ; i < eventInfoList.Length; i++)
{
Console.WriteLine(eventInfoList[i]);
}
}
{
Console.WriteLine("---------------5. 获取属性-----------------");
PropertyInfo[] propertyInfoList = type.GetProperties();
for (int i = ; i < propertyInfoList.Length; i++)
{
Console.WriteLine(propertyInfoList[i]);
}
}
{
Console.WriteLine("---------------6. 获取特性-----------------");
//1. 获取属性上的特性
//因为这些测试所用的特性都是加在属性上的,所以要先获取属性
PropertyInfo[] propertyInfoList = type.GetProperties();
foreach (var item in propertyInfoList)
{
//获取该属性上的所有特性
object[] attributeInfoList = item.GetCustomAttributes(true);
foreach (var item2 in attributeInfoList)
{
Console.WriteLine("{0}属性上的特性为{1}", item, item2);
} }
//2. 获取类上的属性
object[] attributeInfoList2 = type.GetCustomAttributes(true);
foreach (var item3 in attributeInfoList2)
{
Console.WriteLine("{0}类上的特性为{1}", type, item3);
}
}
{
Console.WriteLine("---------------7. 获取id属性的值-----------------");
PropertyInfo idProperty = type.GetProperty("id");
Console.WriteLine("属性名为:{0}", idProperty.Name);
Console.WriteLine("属性值为:{0}", idProperty.GetValue(person1));
//设置属性值
idProperty.SetValue(person1, "");
Console.WriteLine("设置后的属性值为:{0}", idProperty.GetValue(person1)); }

结果:

第六节:反射(几种写法、好处和弊端、利用反射实现IOC)的更多相关文章

  1. 风炫安全web安全学习第三十六节课-15种上传漏洞讲解(一)

    风炫安全web安全学习第三十六节课 15种上传漏洞讲解(一) 文件上传漏洞 0x01 漏洞描述和原理 文件上传漏洞可以说是日常渗透测试用得最多的一个漏洞,因为用它获得服务器权限最快最直接.但是想真正把 ...

  2. [.net 面向对象程序设计进阶] (21) 反射(Reflection)(下)设计模式中利用反射解耦

    [.net 面向对象程序设计进阶] (21) 反射(Reflection)(下)设计模式中利用反射解耦 本节导读:上篇文章简单介绍了.NET面向对象中一个重要的技术反射的基本应用,它可以让我们动态的调 ...

  3. MYSQL 之 JDBC(六): 增删改查(四)利用反射及JDBC元数据编写通用的查询

    1.先利用SQL进行查询,得到结果集2.利用反射创建实体类的对象:创建Student对象3.获取结果集的列的别名:idCard.studentName4.再获取结果集的每一列的值,结合3得到一个Map ...

  4. [.net 面向对象程序设计进阶] (20) 反射(Reflection)(上)利用反射技术实现动态编程

    [.net 面向对象程序设计进阶] (20) 反射(Reflection)(上)利用反射技术实现动态编程 本节导读:本节主要介绍什么是.NET反射特性,.NET反射能为我们做些什么,最后介绍几种常用的 ...

  5. java设计模——反射的应用 (利用反射来去除if判断语句)

    利用反射来去除if判断语句 我的以前写的一个查分系统,就是部长让我写的那个,使用一个分发器(函数),他会根据传递进来的字符串参数调用不同的方. If(“add”.equalsIgnoreCase(fu ...

  6. 【java】工厂模式Factory,利用反射改进

    package 反射; interface Product{ public void produce(); } class socks implements Product{ @Override pu ...

  7. 利用反射修改final数据域

    当final修饰一个数据域时,意义是声明该数据域是最终的,不可修改的.常见的使用场景就是eclipse自动生成的serialVersionUID一般都是final的. 另外还可以构造线程安全(thre ...

  8. ASP.NET MVC深入浅出(被替换) 第一节: 结合EF的本地缓存属性来介绍【EF增删改操作】的几种形式 第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery ) 第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法 第六节: EF高级属性(二) 之延迟加载、立即加载、显示加载(含导航属性) 第十节: EF的三种追踪

    ASP.NET MVC深入浅出(被替换)   一. 谈情怀-ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态 ...

  9. 第九节: 利用RemoteScheduler实现Sheduler的远程控制 第八节: Quartz.Net五大构件之SimpleThreadPool及其四种配置方案 第六节: 六类Calander处理六种不同的时间场景 第五节: Quartz.Net五大构件之Trigger的四大触发类 第三节: Quartz.Net五大构件之Scheduler(创建、封装、基本方法等)和Job(创建、关联

    第九节: 利用RemoteScheduler实现Sheduler的远程控制   一. RemoteScheduler远程控制 1. 背景: 在A服务器上部署了一个Scheduler,我们想在B服务器上 ...

  10. ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借

    ASP.NET MVC深入浅出系列(持续更新)   一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...

随机推荐

  1. LeetCode算法题-Find Mode in Binary Search Tree(Java实现)

    这是悦乐书的第246次更新,第259篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第113题(顺位题号是501).给定具有重复项的二叉搜索树(BST),找到给定BST中的 ...

  2. China Tightens Recycling Import Rules

    China Tightens Recycling Import Rules We have all seen the pictures of cities in China with air poll ...

  3. for循环和foreach循环遍历集合的效率比较

    先上代码 package com.test; import java.util.ArrayList; import java.util.LinkedList; import java.util.Lis ...

  4. python之sqlalchemy的使用

    准备数据 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column from sqla ...

  5. Python开发【前端篇】HTML

    1.html概述和基本结构 html概述 HTML是 HyperText Mark-up Language 的首字母简写,意思是超文本标记语言,超文本指的是超链接,标记指的是标签,是一种用来制作网页的 ...

  6. day 12 装饰器

    nonlocal关键字 # 作用:将 L 与 E(E中的名字需要提前定义) 的名字统一​# 应用场景:如果想在被嵌套的函数中修改外部函数变量(名字)的值​# 案例:​def outer():    n ...

  7. 22 python 初学(类,面向对象)

    python: 函数式 + 面向对象 函数式可以做所有的事,是否合适? 面向对象: 一.定义: 函数: def + 函数名(参数) 面向对象: class  -> 名字叫 Bar 类 def   ...

  8. Linux内存管理 (6)vmalloc

    专题:Linux内存管理专题 关键词:vmalloc.页对齐.虚拟地址连续.物理不连续 至此,已经介绍了集中内核中内存分配函数,在开始简单做个对比总结Linux中常用内存分配函数的异同点,然后重点介绍 ...

  9. C# GDI+绘制一维条码打印模糊的解决办法

    最近遇到使用zxing生成的一维条码打印出来的条码图形很模糊根本识别不了.其实原因只有一句话: bitmap没有直接使用PrintDocument的Graphics画布进行绘制,而是中间处理了一下外部 ...

  10. 一篇博客带你入门Flask

    一. Python 现阶段三大主流Web框架 Django Tornado Flask 对比 1.Django 主要特点是大而全,集成了很多组件,例如: Models Admin Form 等等, 不 ...