ASP.NET Core中间件初始化探究

前言

    在日常使用ASP.NET Core开发的过程中我们多多少少会设计到使用中间件的场景,ASP.NET Core默认也为我们内置了许多的中间件,甚至有时候我们需要自定义中间件来帮我们处理一些请求管道过程中的处理。接下来,我们将围绕着以下几个问题来简单探究一下,关于ASP.NET Core中间件是如何初始化的

  • 首先,使用UseMiddleware注册自定义中间件和直接Use的方式有何不同

  • 其次,使用基于约定的方式定义中间件和使用实现IMiddleware接口的方式定义中间件有何不同

  • 再次,使用基于约定的方式自定义中间件的究竟是如何约束我们编写的类和方法格式的

  • 最后,使用约定的方式定义中间件,通过构造注入和通过Invoke方法注入的方式有何不同

接下来我们将围绕这几个核心点来逐步探究关于ASP.NET Core关于中间件初始化的神秘面纱,来指导我们以后使用它的时候需要有注意点,来减少踩坑的次数。

自定义的方式

使用自定义中间件的方式有好几种,咱们简单来演示一下三种比较常用方式。

Use方式

首先,也是最直接最简单的使用Use的方式,比如

app.Use(async (context, next) =>
{var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;if (endpoint != null){ResponseCacheAttribute responseCache = endpoint.Metadata.GetMetadata<ResponseCacheAttribute>();if (responseCache != null){//做一些事情}}await next();
});
基于约定的方式

然后使用UseMiddleware也是我们比较常用的一种方式,这种方式使用起来相对于第一种来说,虽然使用起来可能会稍微繁琐一点,毕竟需要定义一个类,但是更好的符合符合面向对象的封装思想,它的使用方式大致如下,首先定义一个Middleware的类

public class RequestCultureMiddleware
{private readonly RequestDelegate _next;public RequestCultureMiddleware(RequestDelegate next){_next = next;}public async Task InvokeAsync(HttpContext context){var cultureQuery = context.Request.Query["culture"];if (!string.IsNullOrWhiteSpace(cultureQuery)){var culture = new CultureInfo(cultureQuery);CultureInfo.CurrentCulture = culture;CultureInfo.CurrentUICulture = culture;}await _next(context);}
}

编写完成之后,需要手动的将类注册到管道中才能生效,注册方式如下所示

app.UseMiddleware<RequestCultureMiddleware>();
实现IMiddleware的方式

还有一种方式是实现IMiddleware接口的方式,这种方式比如前两种方式常用,但是也确确实实的存在于ASP.NET Core中,既然存在也就有它存在的理由,我们也可以探究一下,它的使用方式也是需要自定义一个类去实现IMiddleware接口,如下所示

public class RequestCultureOtherMiddleware:IMiddleware
{public async Task InvokeAsync(HttpContext context, RequestDelegate next){var cultureQuery = context.Request.Query["culture"];if (!string.IsNullOrWhiteSpace(cultureQuery)){var culture = new CultureInfo(cultureQuery);CultureInfo.CurrentCulture = culture;CultureInfo.CurrentUICulture = culture;}await next(context);}
}

这种方式和第二种方式略有不同,需要手动将中间件注册到容器中,至于声明周期也没做特殊要求,可以直接注册为单例模式

services.AddSingleton<IMiddleware,RequestCultureOtherMiddleware>();

完成上步操作之后,同样也需要将其注册到管道中去

app.UseMiddleware<RequestCultureOtherMiddleware>();

这种方式相对于第二种方式的主要区别在于灵活性方面的差异,它实现了IMiddleware接口,那就要受到IMiddleware接口的约束,也就是我们常说的里氏代换原则,首先我们可以先来看下IMiddleware接口的定义[点击查看源码????]

public interface IMiddleware
{/// <summary>/// 请求处理方法/// </summary>/// <param name="context">当前请求上下文</param>/// <param name="next">请求管道中下一个中间件的委托</param>Task InvokeAsync (HttpContext context, RequestDelegate next);
}

通过这个接口也就看出来InvokeAsync只能接受HttpContext和RequestDelegate参数,无法定义其他形式的参数,也没办法通过注入的方式编写InvokeAsync方法参数,说白了就是没有第二种方式灵活,受限较大。
关于常用的自定义中间件的方式,我们就先说到这里,我们也知道了如何定义使用中间件。接下来我们就来探讨一下,这么多种方式之间到底存在怎样的联系。

源码探究

上面我们已经演示了关于使用中间件的几种方式,那么这么几种使用方式之间有啥联系或区别,我们只看到了表面的,接下来我们来看一下关于中间件初始化的源码来一探究竟。
首先,无论那种形式都是基于IApplicationBuilder这个接口扩展而来的,所以我们先从这里下手,找到源码IApplicationBuilder位置[点击查看源码????]可以看到以下代码

/// <summary> 
/// 将中间件委托添加到应用程序的请求管道。
/// </summary> 
/// <param name="middleware">中间件委托</param> 
/// <returns>The <see cref="IApplicationBuilder"/>.</returns> 
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

IApplicationBuilder接口里只有Use的方式可以添加中间件,由此我们可以大致猜到两点信息

  • 其它添加中间件的方式,都是在扩展自IApplicationBuilder,并不是IApplicationBuilder本身的方法。

  • 其它添加中间件的形式,最终都会转换为Use的方式。

Use扩展方法

上面我们看到了IApplicationBuilder只包含了一个Use方法,但是我们日常编程中最常使用到的却并不是这一个,而是来自UseExtensions扩展类的Use扩展方法,实现如下所示[点击查看源码????]

public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
{//将middleware转换为Use(Func<RequestDelegate, RequestDelegate> middleware)的形式return app.Use(next =>{return context =>{Func<Task> simpleNext = () => next(context);return middleware(context, simpleNext);};});
}

如预料的那样,Use的扩展方法最终都会转换为Use(Func<RequestDelegate, RequestDelegate> middleware)的形式去执行。Use扩展方法的形式还是比较清晰的,毕竟也是基于委托的形式,而且参数是固定的。

UseMiddleware

上面我们看到了Use的扩展方法,它最终还是转换为Use(Func<RequestDelegate, RequestDelegate> middleware)的形式去执行。接下来我们来看下通过编写类的形式定义中间件会是怎样的转换操作。找到UseMiddleware扩展方法所在的地方,也就是UseMiddlewareExtensions扩展类里[点击查看源码????],我们最常用的是UseMiddleware这个方法,而且这个方法是UseMiddlewareExtensions扩展类的入口方法[点击查看源码????],说白了就是它是完全调用别的方法没有自己的实现逻辑

/// <summary> 
/// 将中间件类型添加到应用程序的请求管道.
/// </summary> 
/// <typeparam name="TMiddleware">中间件类型</typeparam> 
/// <param name="args">传递给中间件类型实例的构造函数的参数.</param> 
/// <returns>The <see cref="IApplicationBuilder"/> instance.</returns> 
public static IApplicationBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)]TMiddleware>(this IApplicationBuilder app, params object[] args) 
{ return app.UseMiddleware(typeof(TMiddleware), args); 
}

继续向下看找到它调用的扩展方法,在展示该方法之前我们先罗列一下该类的常量属性,因为类中的方法有用到,如下所示

internal const string InvokeMethodName = "Invoke"; 
internal const string InvokeAsyncMethodName = "InvokeAsync";

从这里我们可以得到一个信息,基于约定的形式自定义的中间件触发方法名可以是Invoke或InvokeAsync,继续看执行方法的实现代码

public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object[] args)
{//判断自定义的中间件是否是实现了IMiddleware接口if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())){//Middleware不支持直接传递参数//因为它是注册到容器中的,所以不能通过构造函数传递自定义的参数,否则抛出异常if (args.Length > 0){throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}//实现IMiddleware接口的中间件走的是这个逻辑,咱们待会看return UseMiddlewareInterface(app, middleware);}var applicationServices = app.ApplicationServices;return app.Use(next =>{//获取自定义中间件类的非静态public方法var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);//查找方法名为Invoke或InvokeAsync的方法var invokeMethods = methods.Where(m =>string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)).ToArray();//方法名为Invoke或InvokeAsync的方法只能有有一个,存在多个话会抛出异常if (invokeMethods.Length > 1){throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));}//自定义的中间件类中必须包含名为Invoke或InvokeAsync的方法,否则也会抛出异常if (invokeMethods.Length == 0){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));}//名为Invoke或InvokeAsync的方法的返回值类型必须是Task类型,否则会抛出异常var methodInfo = invokeMethods[0];if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));}//获取Invoke或InvokeAsync方法的参数var parameters = methodInfo.GetParameters();//如果该方法不存在参数或方法的第一个参数不是HttpContext类型的实例,会抛出异常if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));}//定义新的数组比传递的参数长度多一个,为啥呢?往下看。var ctorArgs = new object[args.Length + 1];//因为方法数组的首元素是RequestDelegate类型的next//也就是基于约定定义的中间件构造函数的第一个参数是RequestDelegate类型的实例ctorArgs[0] = next;Array.Copy(args, 0, ctorArgs, 1, args.Length);//创建基于约定的中间件实例//又看到ActivatorUtilities这个类了,关于这个类有兴趣的可以研究一下,可以根据容器创建类型实例,非常好用var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);//如果Invoke或InvokeAsync方法只有一个参数,则直接创建RequestDelegate委托返回if (parameters.Length == 1){//RequestDelegate其实就是public delegate Task RequestDelegate(HttpContext context);return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);}//编译Invoke或InvokeAsync方法,关于Compile的实现等会咱们再看var factory = Compile<object>(methodInfo, parameters);//返回这个委托//看着这个委托的格式有点眼熟,其实就是RequestDelegate即public delegate Task RequestDelegate(HttpContext context);return context =>{var serviceProvider = context.RequestServices ?? applicationServices;//serviceProvider不能为空,否则没法玩了if (serviceProvider == null){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}//返回委托执行结果return factory(instance, context, serviceProvider);};});
}

这个方法其实是工作的核心方法,通过这里可以看出来,自定义中间件的大致执行过程。代码中的注释我写的比较详细,有兴趣的可以仔细了解一下,如果懒得看我们就大致总结一下大致的核心点

  • 首先UseMiddleware的本质确实还是执行的Use方法

  • 实现IMiddleware接口的中间件走的是独立的处理逻辑,而且构造函数传递自定义的参数,因为它的数据来自于容器的注入。

  • 基于约定定义中间件的情况,即不实现IMiddleware的情况下。
    ①基于约定定义的中间件,构造函数的第一个参数需要是RequestDelegate类型
    ②查找方法名可以为Invoke或InvokeAsync,且存在而且只能存在一个
    ③Invoke或InvokeAsync方法返回值需为Task,且方法的第一个参数必须为HttpContext类型
    ④Invoke或InvokeAsync方法如果只包含HttpContext类型参数,则该方法直接转换为RequestDelegate
    ⑤我们之所以可以通过构造注入在中间件中获取服务是因为基于约定的方式是通过ActivatorUtilities类创建的实例

通过上面的源码我们了解到了实现IMiddleware接口的方式自定义中间件的方式是单独处理的即在UseMiddlewareInterface方法中[点击查看源码????],接下来我们查看一下该方法的代码

private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type middlewareType)
{return app.Use(next =>{return async context =>{var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));if (middlewareFactory == null){// 没有middlewarefactory直接抛出异常throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));}//创建middleware实例var middleware = middlewareFactory.Create(middlewareType);if (middleware == null){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));}try{//执行middleware的InvokeAsync方法await middleware.InvokeAsync(context, next);}finally{//释放middlewaremiddlewareFactory.Release(middleware);}};});
}

通过上面的代码我们可以看到,IMiddleware实例是通过IMiddlewareFactory实例创建而来,ASP.NET Core中IMiddlewareFactory默认注册的实现类是MiddlewareFactory,接下来我们看下这个类的实现[点击查看源码????]

public class MiddlewareFactory : IMiddlewareFactory{    private readonly IServiceProvider _serviceProvider;public MiddlewareFactory(IServiceProvider serviceProvider)    {        _serviceProvider = serviceProvider;    }public IMiddleware? Create(Type middlewareType)    {        //根据类型从容器中获取IMiddleware实例        return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware;    }public void Release(IMiddleware middleware)    {        //因为容器控制了对象的生命周期,所以这里啥也没有    }}
好吧,其实就是在容器中获取的IMiddleware实例,通过这个我们就可以总结出来实现IMiddleware接口的形式创建中间件的操作
  • 需要实现IMiddleware接口,来约束中间件的行为,方法名只能为InvokeAsync

  • 需要手动注册IMiddleware和实现类到容器中,生命周期可自行约束,如果生命周期为Scope或瞬时,那么每次请求都会创建新的中间件实例

  • 没办法通过InvokeAsync方法注入服务,因为受到了IMiddleware接口的约束

上面我们看到了实现IMiddleware接口的方式中间件是如何被初始化的,接下来我们继续来看,基于约定的方式定义的中间件是如何被初始化的。通过上面我们展示的源码可知,实现逻辑在Compile方法中,该方法整体实现方式就是基于Expression,主要原因个人猜测有两点,一个是形式比较灵活能应对的场景较多,二是性能稍微比反射好一点。在此之前,我们先展示一下Compile方法依赖的操作,首先反射是获取UseMiddlewareExtensions类的GetService方法操作

private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static)!;

其中GetService方法的实现如下所示,其实就是在容器ServiceProvider中获取指定类型实例

private static object GetService(IServiceProvider sp, Type type, Type middleware)
{var service = sp.GetService(type);if (service == null){throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));}return service;
}

好了上面已将Compile外部依赖已经展示出来了,接下来我们就可以继续探究Compile方法了[点击查看源码????]

private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodInfo, ParameterInfo[] parameters)
{var middleware = typeof(T);//构建三个Parameter名为httpContext、serviceProvider、middlewarevar httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");var instanceArg = Expression.Parameter(middleware, "middleware");//穿件Expression数组,且数组第一个参数为httpContextArgvar methodArguments = new Expression[parameters.Length];methodArguments[0] = httpContextArg;//因为Invoke或InvokeAsync方法第一个参数为HttpContext,且methodArguments第一个参数占位,所以跳过第一个参数for (int i = 1; i < parameters.Length; i++){//获取方法参数var parameterType = parameters[i].ParameterType;//不支持ref类型操作if (parameterType.IsByRef){throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));}//构建参数类型表达式,即用户构建方法参数的操作var parameterTypeExpression = new Expression[]{providerArg,Expression.Constant(parameterType, typeof(Type)),Expression.Constant(methodInfo.DeclaringType, typeof(Type))};//声明调用GetServiceInfo的表达式var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);//将getServiceCall操作转换为parameterTypemethodArguments[i] = Expression.Convert(getServiceCall, parameterType);}//获取中间件类型表达式Expression middlewareInstanceArg = instanceArg;if (methodInfo.DeclaringType != null && methodInfo.DeclaringType != typeof(T)){//转换中间件类型表达式类型与声明类型一致middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType);}//调用middlewareInstanceArg(即当前中间件)的methodInfo(即获取Invoke或InvokeAsync)方法参数(methodArguments)var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments);//转换为lambdavar lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);return lambda.Compile();
}

上面的代码比较抽象,其实主要是因为它是基于表达式树进行各种操作的,如果对表达式树比较熟悉的话,可能对上面的代码理解起来还好一点,如果不熟悉表达式树的话,可能理解起来比较困难,不过还是建议简单学习一下Expression相关的操作,慢慢的发现还是挺有意思的,它的性能整体来说比传统的反射性能也会更好一点。其实Compile主要实现的操作转化为我们比较容易理解的代码的话就是下面所示的操作,如果我们编写了一个如下的中间件代码

public class Middleware
{public Task Invoke(HttpContext context, ILoggerFactory loggerFactory){}
}

那么通过Compile方法将转换为类似以下形式的操作,这样说的话可能会好理解一点

Task Invoke(Middleware instance, HttpContext httpContext, IServiceProvider provider)
{return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
}

通过上面的源码分析我们了解到,基于约定的方式定义的中间件实例是通过ActivatorUtilities类创建的,而且创建实例是在返回RequestDelegate委托之前,IApplicationBuilder的Use方法只会在首次运行的时候执行,后续管道串联执行的其实正是它返回的结果RequestDelegate这个委托。但是执行转换Invoke或InvokeAsync方法为执行委托的操作却是在返回的RequestDelegate委托当中,也就是我们每次请求管道会处理的逻辑中。这个逻辑可以在IApplicationBuilder默认的实现类ApplicationBuilder类的Build方法中可以得知[点击查看源码????],它的实现逻辑如下所示

public RequestDelegate Build()
{//最后的管道处理,即请求未能匹配到任何终结点的情况RequestDelegate app = context =>{var endpoint = context.GetEndpoint();var endpointRequestDelegate = endpoint?.RequestDelegate;if (endpointRequestDelegate != null){var message =$"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +$"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +$"routing.";throw new InvalidOperationException(message);}//执行管道的重点是404,只有未命中任何终结点的情况下才会走到这里context.Response.StatusCode = StatusCodes.Status404NotFound;return Task.CompletedTask;};//_components即我们通过Use添加的中间件foreach (var component in _components.Reverse()){//得到执行结果即RequestDelegateapp = component(app);}//返回第一个管道中间件return app;
}

通过上面的代码我们可以清楚的看到,管道最终执行的就是执行Func<RequestDelegate, RequestDelegate>这个委托的返回结果RequestDelegate。由此得到结论,基于约定的中间件形式,通构造函数注入的服务实例,是和应用程序的生命周期一致的。通过Invoke或InvokeAsync方法注入的服务实例每次请求都会被执行到,即生命周期是Scope的。

总结

    通过本次对源码的研究,我们认识到了自定义的ASP.NET Core中间件是如何被初始化的。虽然自定义的中间件的形式有许多种方式,但是最终还都是转换为IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)这种方式。将中间件抽离为独立的类有两种方式,即基于约定的方式和实现IMiddleware接口的形式,通过分析源码我们也更深刻的了解两种方式的不同之处。基于约定的方式更灵活,它的声明周期是单例的,但是通过它的Invoke或InvokeAsync方法注入的服务实例生命周期是Scope的。实现IMiddleware接口的方式生命周期取决于自己注册服务实例时候声明的周期,而且这种方式没办法通过方法注入服务,因为有IMiddleware接口InvokeAsync方法的约束。
    当然不仅仅是我们在总结中说的的这些,还存在更多的细节,这些我们在分析源码的时候都有涉及,相信阅读文章比较仔细的同学肯定会注意到这些。阅读源码收获正是这些,解决心中的疑问,了解更多的细节,有助于在实际使用中避免一些不必要的麻烦。本次讲解就到这里,愿各位能有所收获。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/303984.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Python实现的微信好友数据分析

最近微信迎来了一次重要的更新&#xff0c;允许用户对”发现”页面进行定制。不知道从什么时候开始&#xff0c;微信朋友圈变得越来越复杂&#xff0c;当越来越多的人选择”仅展示最近三天的朋友圈”&#xff0c;大概连微信官方都是一脸的无可奈何。逐步泛化的好友关系&#xf…

java虚拟机和javaGC_Java虚拟机(三):GC算法和种类

一、介绍GC(Garbage Collection)&#xff0c;垃圾收集Java中&#xff0c;GC的对象是堆空间和永久区二、GC算法1. 引用计数法老牌垃圾回收算法通过引用计算来回收垃圾Java中未使用&#xff0c;使用者有COM、ActionScript3、Python实现&#xff1a;1> 对于一个对象A&#xff0…

看似简单但容易忽视的编程常识

这些年写了很多的代码、也读过很多的人写的代码&#xff0c;这几年&#xff0c;写代码的机会越来越少&#xff0c;但是每次写代码&#xff0c;感觉需要思考的东西越来越多&#xff0c;好的代码确实难能可贵&#xff0c;在国内业界中&#xff0c;好的软件不少&#xff0c;但是好…

ASP.NET : Kerberos网络认证过程

今天抽时间初略学习了一下kerberos网络认证过程&#xff0c;作为笔记整理如下&#xff0c;希望与大家分享。 一、Kerberos初步定义: Kerberos这一名词来源于希腊神话“三个头的狗——地狱之门守护者”。Kerberos 是一种网络认证协议&#xff0c;其设计目标是通过密钥系统为客户…

MIPS投RISC-V是龙芯新征程的开始

日前&#xff0c;外媒报道MIPS Technologies宣布将放弃继续设计MIPS处理器&#xff0c;转向了RISC-V。在MIPS加盟RISC-V阵营后&#xff0c;有人鼓吹龙芯要完&#xff0c;但事实上&#xff0c;这完全是不了解龙芯具体情况的臆测。特别是在龙芯开发自主指令集LoongArch之后&#…

近期GitHub上最热门的开源项目(附链接)

2 月份 GitHub 上最热门的开源项目又出炉了&#xff0c;又有哪些新的项目挤进热门榜单了呢&#xff0c;一起来看看。1、nocodehttps://github.com/kelseyhightower/nocode Star 16256这是 2 月份新出炉的项目&#xff0c;可以说是 2018 年最火的佛系编程了&#xff0c;这个项目…

fb静态区域_fb 静态数据

在STAT中定义静态变量&#xff0c;并在INITIAL VALUE中设定初始值&#xff0c;静态变量的初始值会自动存如对应的背景数据块中回答者&#xff1a; 天晴09 - 初级工程师&nbsp&nbsp第9级2008-09-10 17:01:08你可以在fb中定义的时候直接输入初始值&#xff0c;也可以在ob…

9年没涨价,上太空……这些树莓派的冷知识你知道多少?

作为最成功的微型计算机&#xff0c;开源的树莓派&#xff08;Raspberry Pi&#xff09;在技术圈和学术界一直广受编程爱好者的好评&#xff0c;各路大神基于树莓派制作的新奇设备层出不穷&#xff0c;围绕这款微型计算机已经形成了一种独特的 DIY 文化&#xff0c;相关的开源软…

汇编语言入门教程

学习编程其实就是学高级语言&#xff0c;即那些为人类设计的计算机语言。但是&#xff0c;计算机不理解高级语言&#xff0c;必须通过编译器转成二进制代码&#xff0c;才能运行。学会高级语言&#xff0c;并不等于理解计算机实际的运行步骤。计算机真正能够理解的是低级语言&a…

轻量易用的微信Sdk发布——Magicodes.Wx.Sdk

概述最简洁最易于使用的微信Sdk&#xff0c;包括公众号Sdk、小程序Sdk、企业微信Sdk等&#xff0c;以及Abp VNext集成。名称NugetMagicodes.Wx.PublicAccount.SdkMagicodes.Wx.PublicAccount.Sdk.AspNetMagicodes.Wx.PublicAccount.Sdk.Abp如何贡献&#xff1f;如何快速封装一个…

数学界的高冷之王,N次拒绝巨额奖金:我穷,但是我不缺钱。。。

在现实生活中&#xff0c;你和谁在一起的确很重要&#xff0c;甚至能改变你的成长轨迹&#xff0c;决定你的人生成败。是否还记得&#xff0c;当你跟学霸做同学的时候&#xff0c;你总会莫名其妙跟他一起撸题目&#xff1b;当宿舍其他兄弟正在打游戏的时候&#xff0c;你也想着…

php-7.1.0,PHP 7.4.0 Alpha 1 v7.4.0 官方最新版

PHP团队近期宣布推出PHP 7.4.0首个版本PHP 7.4.0 Alpha 1&#xff0c;且下一个Alpha 2版本也在计划推出&#xff0c;不过作为早期测试版本&#xff0c;建议不要在生产环境中使用&#xff0c;想体验PHP最新运行逻辑的可以下载体验PHP 7.4.0 Alpha 1源码。基本简介PHP原始为Perso…

算法分析的正确姿势

一、前言在进一步学习数据结构与算法前&#xff0c;我们应该先掌握算法分析的一般方法。算法分析主要包括对算法的时空复杂度进行分析&#xff0c;但有些时候我们更关心算法的实际运行性能如何&#xff0c;此外&#xff0c;算法可视化是一项帮助我们理解算法实际执行过程的实用…

浏览器缓存机制的研究分享

源宝导读&#xff1a;互联网Web应用大行其道的今天&#xff0c;浏览器已经成为Web应用运行的重要平台。而Web应用对浏览器缓存机制的高效利用&#xff0c;可以大幅提升应用性能和用户体验。本文将对浏览器缓存机制进行系统化的梳理&#xff0c;分享我们的经验。一、背景计算机读…

Windows2008应用之配置客户端自动添加打印机

打印机对我们每一个人来说都是习以为常的东西了&#xff0c;给你一条远程打印机共享路径&#xff0c;你只要轻轻的双击想安装的打印机图标&#xff0c;等上个三五秒打印机就安装好&#xff0c;这台打印机就任你摆布了。但相对我们IT人员来说&#xff0c;全面的自动化将是我们的…

现代CSS进化史

英文&#xff1a;Peter Jang 编译&#xff1a;缪斯segmentfault.com/a/1190000013191860CSS一直被web开发者认为是最简单也是最难的一门奇葩语言。它的入门确实非常简单——你只需为元素定义好样式属性和值&#xff0c;看起来似乎需要做的工作也就这样嘛&#xff01;然而在一些…

一日一技:Ocelot网关使用IdentityServer4认证

概述Ocelot是一个用.NET Core实现的开源API网关技术。IdentityServer4是一个基于OpenID Connect和OAuth2.0的针对ASP.NET Core的框架&#xff0c;以中间件的形式存在。OAuth是一种授权机制。系统产生一个短期的token&#xff0c;用来代替密码&#xff0c;供第三方应用使用。下面…

php windows共享内存,关于php的共享内存的使用和研究之由起

最近遇到一个场景&#xff0c;服务寻址的时候&#xff0c;需要请求远程的服务&#xff0c;获取一批可用的ip和端口地址及其权重。根据权重和随机算法选择最合适的一个服务地址&#xff0c;进行请求。由于服务地址在短时间之内不会发生变化&#xff0c;因此为了避免无限制的进行…

联想继续为其硬件产品完善Linux支持

喜欢就关注我们吧&#xff01;此前&#xff0c;联想曾宣布为其台式机/笔记本电脑预装 Fedora/Ubuntu 等 Linux 发行版。并通过与 RedHat 等达成合作&#xff0c;为 Linux 带来了更多的上游工作支持。时至今日&#xff0c;据 Phoronix 称&#xff0c;自联想开始提供 Linux 预装以…

Excel有哪些需要熟练掌握而很多人不会的技能!

看完这篇Excel攻略&#xff0c;你会感觉这么多年的excel都白学了&#xff01;来自知乎用户“未央之末”的分享。从今年年初的excel盲&#xff0c;到现在经常从大拿那偷师&#xff0c;也算是成长了不少&#xff0c;慢慢写下来算是对学习excel做个短期回顾——1排版篇给他人发送e…