使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(五)-- Filter
在上一篇里,介绍了中间件的相关内容和使用方法。本篇将介绍Asp.Net Core MVC框架的过滤器的相关内容和使用方法,并简单说明一下与中间件的区别。
第一部分、MVC框架内置过滤器
下图展示了Asp.Net Core MVC框架默认实现的过滤器的执行顺序:

Authorization Filters:身份验证过滤器,处在整个过滤器通道的最顶层。对应的类型为: AuthorizeAttribute.cs
Resource Filters:资源过滤器。因为所有的请求和响应都将经过这个过滤器,所以在这一层可以实现类似缓存的功能。对应的接口有同步和异步两个版本: IResourceFilter.cs 、 IAsyncResourceFilter.cs
Action Filters:方法过滤器。在控制器的Action方法执行之前和之后被调用,一个很常用的过滤器。对应的接口有同步和异步两个版本: IActionFilter.cs 、 IAsyncActionFilter.cs
Exception Filters:异常过滤器。当Action方法执行过程中出现了未处理的异常,将会进入这个过滤器进行统一处理,也是一个很常用的过滤器。对应的接口有同步和异步两个版本: IExceptionFilter.cs 、 IAsyncExceptionFilter.cs
Result Filters:返回值过滤器。当Action方法执行完成的结果在组装或者序列化前后被调用。对应的接口有同步和异步两个版本: IResultFilter.cs 、 IAsyncResultFilter.cs
下面通过代码示例来演示上面图示里的流程顺序:
1. 在工程里分别添加如下几个过滤器
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class SimpleResourceFilterAttribute : Attribute, IResourceFilter
{
private readonly ILogger<SimpleResourceFilterAttribute> logger; public SimpleResourceFilterAttribute(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<SimpleResourceFilterAttribute>();
} public void OnResourceExecuted(ResourceExecutedContext context)
{
logger.LogInformation("ResourceFilter Executed!");
} public void OnResourceExecuting(ResourceExecutingContext context)
{
logger.LogInformation("ResourceFilter Executing!");
}
}
}
SimpleResourceFilterAttribute.cs
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class SimpleActionFilterAttribute : Attribute, IActionFilter
{
private readonly ILogger<SimpleActionFilterAttribute> logger; public SimpleActionFilterAttribute(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<SimpleActionFilterAttribute>();
} public void OnActionExecuted(ActionExecutedContext context)
{
logger.LogInformation("ActionFilter Executed!");
} public void OnActionExecuting(ActionExecutingContext context)
{
logger.LogInformation("ActionFilter Executing!");
}
}
}
SimpleActionFilterAttribute.cs
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class SimpleExceptionFilterAttribute : Attribute, IExceptionFilter
{
private readonly ILogger<SimpleExceptionFilterAttribute> logger; public SimpleExceptionFilterAttribute(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<SimpleExceptionFilterAttribute>();
} public void OnException(ExceptionContext context)
{
logger.LogError("Exception Execute! Message:" + context.Exception.Message);
context.ExceptionHandled = true;
}
}
}
SimpleExceptionFilterAttribute.cs
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class SimpleResultFilterAttribute : Attribute, IResultFilter
{
private readonly ILogger<SimpleResultFilterAttribute> logger; public SimpleResultFilterAttribute(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<SimpleResultFilterAttribute>();
} public void OnResultExecuted(ResultExecutedContext context)
{
logger.LogInformation("ResultFilter Executd!");
} public void OnResultExecuting(ResultExecutingContext context)
{
logger.LogInformation("ResultFilter Executing!");
}
}
}
SimpleResultFilterAttribute.cs
2. 修改 Startup.cs 内的ConfigureServices方法,作为全局过滤器添加到MVC框架内
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WebApiFrame.Core.Filters; namespace WebApiFrame
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 注入MVC框架
services.AddMvc(options =>
{
options.Filters.Add(typeof(SimpleResourceFilterAttribute));
options.Filters.Add(typeof(SimpleActionFilterAttribute));
options.Filters.Add(typeof(SimpleExceptionFilterAttribute));
options.Filters.Add(typeof(SimpleResultFilterAttribute));
});
} public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
// 添加日志支持
loggerFactory.WithFilter(new FilterLoggerSettings()
{
{ "Microsoft", LogLevel.Warning }
})
.AddConsole().AddDebug(); // 添加NLog日志支持
//loggerFactory.AddNLog(); // 添加MVC中间件
app.UseMvc();
}
}
}
Startup.cs
3. 控制器添加两个方法,一个方法正常返回内容,另一个方法抛出一个未处理的异常
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using WebApiFrame.Models; namespace WebApiFrame.Controllers
{ [Route("api/[controller]")]
public class UsersController : Controller
{
private ILogger<UsersController> _logger; public UsersController(ILogger<UsersController> logger){
_logger = logger;
} [HttpGet]
public IActionResult GetAll(){
throw new Exception("GetAll function failed!");
} [HttpGet("{id}")]
public IActionResult Get(int id)
{
var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
return new ObjectResult(user);
} [HttpPost]
public IActionResult Post([FromBody] User user){
if(user == null){
return BadRequest();
} // TODO:新增操作
user.Id = new Random().Next(, );
return CreatedAtAction("Get", new { id = user.Id }, user);
} [HttpPut("{id}")]
public IActionResult Put(int id, [FromBody] User user){
if(user == null){
return BadRequest();
} // TODO: 更新操作
return new NoContentResult();
} [HttpDelete("{id}")]
public void Delete(int id){
// TODO: 删除操作 }
}
}
UsersController.cs
4. 打开cmd窗口,使用命令行 dotnet run 启动程序,访问地址 http://localhost:5000/api/users/1 ,查看窗口日志,会发现日志打印顺序与图片标识顺序相符。

再次访问地址 http://localhost:5000/api/users/ ,查看窗口日志,发现异常过滤器被调用,输出了异常日志

第二部分、过滤器的引用
先创建一个自定义的ActionFilter作为演示例子
using System;
using Microsoft.AspNetCore.Mvc.Filters; namespace WebApiFrame.Core.Filters
{
public class MyActionFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{ } public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header");
}
}
}
一、作为特性标识引用
标识在控制器上,则访问这个控制器下的所有方法都将调用这个过滤器
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using WebApiFrame.Core.Filters;
using WebApiFrame.Models; namespace WebApiFrame.Controllers
{ [Route("api/[controller]")]
[MyActionFilter]
public class UsersController : Controller
{
private ILogger<UsersController> _logger; public UsersController(ILogger<UsersController> logger)
{
_logger = logger;
} [HttpGet]
public IActionResult GetAll()
{
throw new Exception("GetAll function failed!");
} [HttpGet("{id}")]
public IActionResult Get(int id)
{
var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
return new ObjectResult(user);
} #region 其他方法
// ......
#endregion
}
}
通过Fiddle工具访问地址 http://localhost:5000/api/users/1 ,查看响应内容,可以发现响应头部增加了自定义内容

也可以标识在方法上,则只有被标识的方法被调用时才会调用过滤器
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using WebApiFrame.Core.Filters;
using WebApiFrame.Models; namespace WebApiFrame.Controllers
{ [Route("api/[controller]")]
public class UsersController : Controller
{
private ILogger<UsersController> _logger; public UsersController(ILogger<UsersController> logger)
{
_logger = logger;
} [HttpGet]
public IActionResult GetAll()
{
throw new Exception("GetAll function failed!");
} [HttpGet("{id}")]
[MyActionFilter]
public IActionResult Get(int id)
{
var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
return new ObjectResult(user);
} #region 其他方法
// ......
#endregion
}
}
二、全局过滤器
在第一部分的示例中采用的就是全局过滤器的方式。使用了全局过滤器后,所有的控制器下的所有方法被调用时都将调用这个过滤器。
下面的代码示例是通过生成实例的形式注册过滤器
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WebApiFrame.Core.Filters; namespace WebApiFrame
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 注入MVC框架
services.AddMvc(options =>
{
options.Filters.Add(new MyActionFilterAttribute());
});
} public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
// 添加日志支持
loggerFactory.WithFilter(new FilterLoggerSettings()
{
{ "Microsoft", LogLevel.Warning }
})
.AddConsole().AddDebug(); // 添加NLog日志支持
//loggerFactory.AddNLog(); // 添加MVC中间件
app.UseMvc();
}
}
}
也可以通过类型进行注册
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WebApiFrame.Core.Filters; namespace WebApiFrame
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 注入MVC框架
services.AddMvc(options =>
{
// 实例注册
//options.Filters.Add(new MyActionFilterAttribute()); // 类型注册
options.Filters.Add(typeof(MyActionFilterAttribute));
});
} public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
// 添加日志支持
loggerFactory.WithFilter(new FilterLoggerSettings()
{
{ "Microsoft", LogLevel.Warning }
})
.AddConsole().AddDebug(); // 添加NLog日志支持
//loggerFactory.AddNLog(); // 添加MVC中间件
app.UseMvc();
}
}
}
三、通过ServiceFilter引用
通过在控制器或者Action方法上使用ServiceFilter特性标识引用过滤器。通过此方法可以将通过构造方法进行注入并实例化的过滤器引入框架内。
修改一下 MyActionFilterAttribute.cs 内容,添加一个带参数的构造方法,引入日志记录
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class MyActionFilterAttribute : Attribute, IActionFilter
{
private readonly ILogger<MyActionFilterAttribute> logger; public MyActionFilterAttribute(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<MyActionFilterAttribute>();
} public void OnActionExecuted(ActionExecutedContext context)
{ } public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header");
logger.LogInformation("MyActionFilterAttribute Executiong!");
}
}
}
修改 Startup.cs 的ConfigureServices方法,将过滤器类型注入到DI(依赖注入)容器里
public void ConfigureServices(IServiceCollection services)
{
// 注入MVC框架
services.AddMvc(options =>
{
// 实例注册
//options.Filters.Add(new MyActionFilterAttribute()); // 类型注册
//options.Filters.Add(typeof(MyActionFilterAttribute));
}); // 将过滤器类型添加到DI容器里
services.AddScoped<MyActionFilterAttribute>();
}
四、通过TypeFilter引入
再次修改 MyActionFilterAttribute.cs 的构造器方法,添加普通的参数
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class MyActionFilterAttribute : Attribute, IActionFilter
{
private readonly string _key;
private readonly string _value; public MyActionFilterAttribute(string key, string value)
{
_key = key;
_value = value;
} public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_key, _value);
} public void OnActionExecuted(ActionExecutedContext context)
{ }
}
}
在 UsersController.cs 控制器的Action方法上添加特性标识,同时注释掉 Startup.cs 的ConfigureServices的类型注入方法。因为用TypeFilter引用过滤器不需要将类型注入到DI容器
         [HttpGet("{id}")]
         [TypeFilter(typeof(MyActionFilterAttribute), Arguments = new object[]{ "My-Header", "WebApiFrame-Header" })]
         public IActionResult Get(int id)
         {
             var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
             return new ObjectResult(user);
         }
另外,也可以通过TypeFilter引用需要通过构造方法注入进行实例化的过滤器。将上面第三点的例子里的进行改写
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class MyActionFilterAttribute : TypeFilterAttribute
{
public MyActionFilterAttribute() : base(typeof(MyActionFilterImpl))
{ } private class MyActionFilterImpl : IActionFilter
{
private readonly ILogger<MyActionFilterImpl> logger; public MyActionFilterImpl(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<MyActionFilterImpl>();
} public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header");
logger.LogInformation("MyActionFilterAttribute Executiong!");
} public void OnActionExecuted(ActionExecutedContext context)
{ }
}
}
}
修改 UsersController.cs 控制器的Action方法的特性标识
         [HttpGet("{id}")]
         [MyActionFilter]
         public IActionResult Get(int id)
         {
             var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
             return new ObjectResult(user);
         }
第三部分、自定义过滤器执行顺序
以ActionFilter执行顺序为例,默认执行顺序如下图
1. Controller OnActionExecuting
2. Global OnActionExecuting
3. Class OnActionExecuting
4. Method OnActionExecuting
5. Method OnActionExecuted
6. Class OnActionExecuted
7. Global OnActionExecuted
8. Controller OnActionExecuted
下面用代码验证这个顺序
修改 MyActionFilterAttribute.cs 内容。添加实现接口 IOrderedFilter.cs ,设置默认顺序为0
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters
{
public class MyActionFilterAttribute : Attribute, IActionFilter, IOrderedFilter
{
private readonly int _order; private readonly string _target; private readonly ILogger<MyActionFilterAttribute> logger; public int Order
{
get
{
return _order;
}
} public MyActionFilterAttribute(string target, int order = )
{
_order = order;
_target = target; ILoggerFactory loggerFactory = new LoggerFactory();
loggerFactory.WithFilter(new FilterLoggerSettings()
{
{ "Microsoft", LogLevel.Warning }
})
.AddConsole().AddDebug(); logger = loggerFactory.CreateLogger<MyActionFilterAttribute>();
} public void OnActionExecuted(ActionExecutedContext context)
{
logger.LogInformation($"{_target} Executed!");
} public void OnActionExecuting(ActionExecutingContext context)
{
logger.LogInformation($"{_target} Executing!");
}
}
}
在 UsersController.cs 重写OnActionExecuting和OnActionExecuted。同时分别注册全局过滤器、标识控制器过滤器和方法过滤器
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using WebApiFrame.Core.Filters;
using WebApiFrame.Models; namespace WebApiFrame.Controllers
{ [Route("api/[controller]")]
[MyActionFilter("Class")]
public class UsersController : Controller
{
private ILogger<UsersController> _logger; public UsersController(ILogger<UsersController> logger)
{
_logger = logger;
} [HttpGet]
public IActionResult GetAll()
{
throw new Exception("GetAll function failed!");
} [HttpGet("{id}")]
[MyActionFilter("Method")]
public IActionResult Get(int id)
{
var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
return new ObjectResult(user);
} public override void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("Controller Executing!");
} public override void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation("Controller Executd!");
} #region 其他方法
// ......
#endregion
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WebApiFrame.Core.Filters; namespace WebApiFrame
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 注入MVC框架
services.AddMvc(options =>
{
// 实例注册
options.Filters.Add(new MyActionFilterAttribute("Global"));
});
} // ......
}
}
启动程序,访问地址 http://localhost:5000/api/users/1 ,查看日志

接下来,修改一下标识方法控制器的参数
         [HttpGet("{id}")]
         [MyActionFilter("Method", -1)]
         public IActionResult Get(int id)
         {
             var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
             return new ObjectResult(user);
         }
再次启动程序,访问地址 http://localhost:5000/api/users/1 ,查看日志

当顺序被设置为-1时,对应标识位置的过滤器将优先调用。但是无法先于控制器的重写方法调用。
第四部分、过滤器与中间件
1. 过滤器是MVC框架的一部分,中间件属于Asp.Net Core管道的一部分。
2. 过滤器在处理请求和响应时更加的精细一些,在用户权限、资源访问、Action执行、异常处理、返回值处理等方面都能进行控制和处理。而中间件只能粗略的过滤请求和响应。
使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(五)-- Filter的更多相关文章
- 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(一)-- 起步
		
本文记录了在Windows环境下安装Visual Studio Code开发工具..Net Core 1.0 SDK和开发一个简单的Web-Demo网站的全过程. 一.安装Visual Studio ...
 - 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(八)-- 多环境开发
		
本篇将演示Asp.Net Core如何在多环境下进行开发适配. 在一个正规的开发流程里,软件开发部署将要经过三个阶段:开发.测试.上线,对应了三个环境:开发.测试.生产.在不同的环境里,需要编写不同的 ...
 - 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(十)-- 发布(Windows)
		
本篇将在这个系列演示的例子上继续记录Asp.Net Core在Windows上发布的过程. Asp.Net Core在Windows上可以采用两种运行方式.一种是自托管运行,另一种是发布到IIS托管运 ...
 - 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(九)-- 单元测试
		
本篇将结合这个系列的例子的基础上演示在Asp.Net Core里如何使用XUnit结合Moq进行单元测试,同时对整个项目进行集成测试. 第一部分.XUnit 修改 Project.json 文件内容, ...
 - 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(三)-- Logger
		
本篇是在上一篇的基础上添加日志功能,并记录NLog在Asp.Net Core里的使用方法. 第一部分:默认Logger支持 一.project.json添加日志包引用,并在cmd窗口使用 dotnet ...
 - [转]使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(三)-- Logger
		
本文转自:https://www.cnblogs.com/niklai/p/5662094.html 本篇是在上一篇的基础上添加日志功能,并记录NLog在Asp.Net Core里的使用方法. 第一部 ...
 - 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(七)-- 结构化配置
		
本篇将记录.Net Core里颇有特色的结构化配置的使用方法. 相比较之前通过Web.Config或者App.Config配置文件里使用xml节点定义配置内容的方式,.Net Core在配置系统上发生 ...
 - 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(六)-- 依赖注入
		
本篇将介绍Asp.Net Core中一个非常重要的特性:依赖注入,并展示其简单用法. 第一部分.概念介绍 Dependency Injection:又称依赖注入,简称DI.在以前的开发方式中,层与层之 ...
 - 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(四)-- Middleware
		
本文记录了Asp.Net管道模型和Asp.Net Core的Middleware模型的对比,并在上一篇的基础上增加Middleware功能支持. 在演示Middleware功能之前,先要了解一下Asp ...
 
随机推荐
- JMeter中3种参数值的传递
			
小伙伴们在使用JMeter的过程中,肯定会遇到参数值传递的问题,下面来和大家总结下,在使用JMeter做压力测试的时候,常见的3种参数值的传递是怎样的. (一)从CSV文件读取要批量输入的变量 假如我 ...
 - channelartlist标签调用实例
			
channelartlist标签,大家都知道在DedeCMS的系统中,我们可以用这个标签进行循环子栏目及其栏目的文档数据,这也是DedeCMS系统中,唯一一个支持标签嵌套的调用标签,以DedeV5.6 ...
 - Java中this关键字在构造方法中的使用
			
1. Java中this关键字代表对象本身.用this关键字可以在类的内部调用属性和方法,这样代码的可读性比较高,因为它明确的指出了这个属性或方法的来源. 2. 同时在构造函数中也可以使用this关键 ...
 - centos 6.5 安装 buildbot-slave 0.8.9
			
公司服务器多数都用的centos,这个是大环境,改不了,只好研究怎么安装buildbot-slave. buildbot-master倒是没有问题,可控,自己弄了一个ubuntu14来玩. cento ...
 - #获取本机IP地址时排除IPv6类型,只返回IPv4地址的方法
			
public static string GetLocalIP(){try{string HostName = Dns.GetHostName(); //得到主机名IPHostEntry IpEntr ...
 - cocos2d-x 在xcode IOS模拟器中 开启IOS多点触控
			
在初始化代码中,开启当前层接受触摸 this->setTouchEnabled(true); 在AppController.mm文件中,设置开启多点触控 在- (BOOL)application ...
 - JavaFX Application应用实例
			
下面代码演示的是JavaFX进程命令行参数的实例.大家可以参阅一下. /*原文地址:http://www.manongjc.com/article/134.html */ import java.ut ...
 - Android——Handler总结
			
一.Handler的定义: 主要接受子线程发送的数据, 并用此数据配合主线程更新UI. 解释: 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线 ...
 - 海外支付:遍布全球的Paypal
			
海外支付:遍布全球的Paypal 吴剑 2015-11-26 原创文章,转载必需注明出处:http://www.cnblogs.com/wu-jian 吴剑 http://www.cnblogs.co ...
 - python学习笔记(递归函数)
			
博主看了看递归.说的简单点就是程序里面再调用程序本身,或者是方法里面再调研方法本身.或者是函数里面再调研函数本身 用于什么场景呢,博主这里是父子节点排序,父子节点的查询 直接上代码: #!/usr/b ...