【 .NET Core 3.0 】框架之十 || AOP 切面思想

本文有配套视频

https://www.bilibili.com/video/av58096866/?p=6

 前言

  

上回《【 .NET Core3.0 】框架之九 || 依赖注入IoC学习 + AOP界面编程初探》咱们说到了依赖注入Autofac的使用,不知道大家对IoC的使用是怎样的感觉,我个人表示还是比较可行的,至少不用自己再关心一个个复杂的实例化服务对象了,直接通过接口就满足需求,当然还有其他的一些功能,我还没有说到,抛砖引玉嘛,大家如果有好的想法,欢迎留言,也可以来群里,大家一起学习讨论。昨天在文末咱们说到了AOP面向切面编程的定义和思想,我个人简单使用了下,感觉主要的思路还是通过拦截器来操作,就像是一个中间件一样,今天呢,我给大家说两个小栗子,当然,你也可以合并成一个,也可以自定义扩展,因为我们是真个系列是基于Autofac框架,所以今天主要说的是基于Autofac的Castle动态代理的方法,静态注入的方式以后有时间可以再补充。

  时间真快,转眼已经十天过去了,感谢大家的鼓励,批评指正,希望我的文章,对您有一点点儿的帮助,哪怕是有学习新知识的动力也行,至少至少,可以为以后跳槽增加新的谈资 [哭笑],这些天我们从面向对象OOP的开发,后又转向了面向接口开发,到分层解耦,现在到了面向切面编程AOP,往下走将会是,分布式,微服务等等,技术真是永无止境啊!好啦,马上开始动笔。

640?wx_fmt=gif

  一、什么是 AOP 切面编程思想

 

640?wx_fmt=png

什么是AOP?引用百度百科:AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。实现AOP主要由两种方式,

一种是编译时静态织入,优点是效率高,缺点是缺乏灵活性,.net下postsharp为代表者(好像是付费了。。)。

另一种方式是动态代理,优点是灵活性强,但是会影响部分效率,动态为目标类型创建代理,通过代理调用实现拦截。

AOP能做什么,常见的用例是事务处理、日志记录等等。

常见的AOP都是配合在Ioc的基础上进行操作,上边咱们讲了Autofac这个简单强大的Ioc框架,下面就讲讲Autofac怎么实现AOP。Autofac的AOP是通过Castle(也是一个容器)项目的核心部分实现的,名为Autofac.Extras.DynamicProxy,顾名思义,其实现方式为动态代理。当然AOP并不一定要和依赖注入在一起使用,自身也可以单独使用。

是不是很拗口,没关系,网上有一个博友的图片,大概讲了AOP切面编程:

 640?wx_fmt=png

 

说的很通俗易懂的话就是,我们在 service 方法的前边和后边,各自动态增加了一个方法,这样就包裹了每一个服务方法,从而实现业务逻辑的解耦。

AOP,我们并不陌生。可能大家感觉这个切面编程思想之前没有用到过,很新鲜的一个东西,其实不是的,之前我们开发的时候也一直在使用这种思想,那就是过滤器,我们可以想想,我们之前在开发 MVC 的时候,是不是经常要对action进行控制过滤,最常见的就是全局异常处理过滤器,只要有错误,就跳出去,记录日志,然后去一个自定义的异常页面,这个其实就是一个 AOP 的思想,但是这里请注意,这个思想是广义的 AOP 编程思想,今天要说的,是真正意义上的切面编程思想,是基于动态代理的基于服务层的编程思想,也是在以后的开发中使用很多的一种编程思想。

640?wx_fmt=gif

 二、AOP 之实现日志记录

首先想一想,如果有这么一个需求,要记录整个项目的接口和调用情况,当然如果只是控制器的话,还是挺简单的,直接用一个过滤器或者一个中间件,还记得咱们开发Swagger拦截权限验证的中间件么,那个就很方便的把用户调用接口的名称记录下来,当然也可以写成一个切面,但是如果想看下与Service或者Repository层的调用情况呢,好像目前咱们只能在Service层或者Repository层去写日志记录了,那样的话,不仅工程大(当然你可以用工厂模式),而且耦合性瞬间就高了呀,想象一下,如果日志要去掉,关闭,修改,需要改多少地方!您说是不是,好不容易前边的工作把层级的耦合性降低了。别慌,这个时候就用到了AOP和Autofac的Castle结合的完美解决方案了。

  经过这么多天的开发,几乎每天都需要引入Nuget包哈,我个人表示也不想再添加了,现在都已经挺大的了(47M当然包括全部dll文件),今天不会啦!其实都是基于昨天的两个Nuget包中已经自动生成的Castle组件。请看以下步骤:

1、定义服务接口与实现类

在上一篇文章中,我们说到了使用

AdvertisementServices.cs 和 IAdvertisementServices.cs 

这个服务,我们新建两个层,分别包含这两个 cs 文件:

640?wx_fmt=png

然后我们模拟下数据,再新建一个 Model 层,添加 AdvertisementEntity 实体类

namespace Blog.Core.Model	
{	public class AdvertisementEntity	{	public int id { get; set; }	public string name { get; set; }	}	
}

然后在上边的 service 方法中,返回一个List数据:

 // 接口	public interface IAdvertisementServices	{	int Test();	List<AdvertisementEntity> TestAOP();	}	// 实现类	public class AdvertisementServices : IAdvertisementServices	{	public int Test()	{	return 1;	}	public List<AdvertisementEntity> TestAOP() => new List<AdvertisementEntity>() { new AdvertisementEntity() { id = 1, name = "laozhang" } };	}	

2、在API层中添加对该接口引用

还是在默认的控制器——weatherForecastController.cs 里,添加调用方法:

 /// <summary>	/// 测试AOP	/// </summary>	/// <returns></returns>	[HttpGet]	public List<AdvertisementEntity> TestAdsFromAOP()	{	return _advertisementServices.TestAOP();	}	

这里采用的是依赖注入的方法,把 _advertisementServices 注入到控制器的,如果还不会,请看我上一篇文章。

 

3、添加AOP拦截器

在api层新建文件夹AOP,添加拦截器BlogLogAOP,并设计其中用到的日志记录Logger方法或者类

 

640?wx_fmt=png

 

关键的一些知识点,注释中已经说明了,主要是有以下:

1、继承接口IInterceptor2、实例化接口IINterceptor的唯一方法Intercept3、void Proceed();表示执行当前的方法4、执行后,输出到日志文件。

namespace blog.core.test3._0.AOP	
{	/// <summary>	/// 拦截器BlogLogAOP 继承IInterceptor接口	/// </summary>	public class BlogLogAOP : IInterceptor	{	/// <summary>	/// 实例化IInterceptor唯一方法 	/// </summary>	/// <param name="invocation">包含被拦截方法的信息</param>	public void Intercept(IInvocation invocation)	{	// 事前处理: 在服务方法执行之前,做相应的逻辑处理	var dataIntercept = "" +	$"【当前执行方法】:{ invocation.Method.Name} \r\n" +	$"【携带的参数有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n";	// 执行当前访问的服务方法,(注意:如果下边还有其他的AOP拦截器的话,会跳转到其他的AOP里)	invocation.Proceed();	// 事后处理: 在service被执行了以后,做相应的处理,这里是输出到日志文件	dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}");	// 输出到日志文件	Parallel.For(0, 1, e =>	{	LogLock.OutSql2Log("AOPLog", new string[] { dataIntercept });	});	}	}	
}

 

提示:这里展示了如何在项目中使用AOP实现对 service 层进行日志记录,如果你想实现异常信息记录的话,很简单,

注意,这个方法仅仅是针对同步的策略,如果你的service是异步的,这里获取不到,正确的写法,在文章底部的 GitHub 代码里,因为和 AOP 思想没有直接的关系,这里就不赘述。

640?wx_fmt=png

 

 

4、将拦截器注入容器,代理服务

 

还记得昨天的Autofac容器 ConfigureContainer 么,我们继续对它进行处理:

1、先把拦截器注入容器;

2、然后对程序集的注入方法中匹配拦截器服务;

 

 public void ConfigureContainer(ContainerBuilder builder)	{	var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;	//直接注册某一个类和接口	//左边的是实现类,右边的As是接口	builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();	builder.RegisterType<BlogLogAOP>();//可以直接替换其他拦截器!一定要把拦截器进行注册	//注册要通过反射创建的组件	var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");	var assemblysServices = Assembly.LoadFrom(servicesDllFile);	builder.RegisterAssemblyTypes(assemblysServices)	.AsImplementedInterfaces()	.InstancePerLifetimeScope()	.EnableInterfaceInterceptors()	.InterceptedBy(typeof(BlogLogAOP));//可以放一个AOP拦截器集合	}	

 

注意其中的两个方法

.EnableInterfaceInterceptors()//对目标类型启用接口拦截。拦截器将被确定,通过在类或接口上截取属性, 或添加 InterceptedBy ()

.InterceptedBy(typeof(BlogLogAOP));//允许将拦截器服务的列表分配给注册。

说人话就是,将拦截器添加到要注入容器的接口或者类之上。

 

5、运行项目,查看效果

这个时候,我们运行项目,然后访问api 的 TestAdsFromAOP() 接口,你就看到这根目录下生成了一个Log文件夹,里边有日志记录,当然记录很简陋,里边是获取到的实体类,大家可以自己根据需要扩展。

640?wx_fmt=png

这里,面向服务层的日志记录就完成了,大家感觉是不是很平时的不一样?我们几乎什么都没做,只是增加了一个AOP的拦截器,就可以控制 service 层的任意一个方法,这就是AOP思想的精髓——业务的解耦。

那AOP仅仅是做日志记录么,还有没有其他的用途,这里我随便举一个例子——缓存。

640?wx_fmt=gif

 三、AOP 实现数据缓存功能

想一想,如果我们要实现缓存功能,一般咱们都是将数据获取到以后,定义缓存,然后在其他地方使用的时候,在根据key去获取当前数据,然后再操作等等,平时都是在API接口层获取数据后进行缓存,今天咱们可以试试,在接口之前就缓存下来 —— 基于service层的缓存策略。

 

640?wx_fmt=png

1、定义 Memory 缓存类和接口

这里既然要用到缓存,那我们就定义一个缓存类和接口,在 Helper 文件夹下,新建两个类文件,ICaching.cs 和 MemoryCaching.cs

你会问了,为什么上边的日志没有定义,因为我会在之后讲Redis的时候用到这个缓存接口

    /// <summary>	/// 简单的缓存接口,只有查询和添加,以后会进行扩展	/// </summary>	public interface ICaching	{	object Get(string cacheKey);	void Set(string cacheKey, object cacheValue);	}	/// <summary>	/// 实例化缓存接口ICaching	/// </summary>	public class MemoryCaching : ICaching	{	//引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了	private IMemoryCache _cache;	//还是通过构造函数的方法,获取	public MemoryCaching(IMemoryCache cache)	{	_cache = cache;	}	public object Get(string cacheKey)	{	return _cache.Get(cacheKey);	}	public void Set(string cacheKey, object cacheValue)	{	_cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(7200));	}	}

 

 

2、定义一个缓存拦截器

还是继承IInterceptor,并实现Intercept,这个过程和上边 日志AOP 是一样,不多说,大家也正好可以自己动手练习一下。

新建缓存AOP:BlogCacheAOP.cs

 /// <summary>	/// 面向切面的缓存使用	/// </summary>	public class BlogCacheAOP : AOPbase	{	//通过注入的方式,把缓存操作接口通过构造函数注入	private readonly ICaching _cache;	public BlogCacheAOP(ICaching cache)	{	_cache = cache;	}	//Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义	public override void Intercept(IInvocation invocation)	{	//获取自定义缓存键	var cacheKey = CustomCacheKey(invocation);	//根据key获取相应的缓存值	var cacheValue = _cache.Get(cacheKey);	if (cacheValue != null)	{	//将当前获取到的缓存值,赋值给当前执行方法	invocation.ReturnValue = cacheValue;	return;	}	//去执行当前的方法	invocation.Proceed();	//存入缓存	if (!string.IsNullOrWhiteSpace(cacheKey))	{	_cache.Set(cacheKey, invocation.ReturnValue);	}	}	}	

代码中注释的很清楚,需要注意是两点:

1、采用依赖注入,把缓存注入到当前拦截器里;

2、继承了一个 AOPBase 抽象类,里边有如何定义缓存 key 等内容;

 

namespace blog.core.test3._0.AOP	
{	public abstract class AOPbase : IInterceptor	{	/// <summary>	/// AOP的拦截方法	/// </summary>	/// <param name="invocation"></param>	public abstract void Intercept(IInvocation invocation);	/// <summary>	/// 自定义缓存的key	/// </summary>	/// <param name="invocation"></param>	/// <returns></returns>	protected string CustomCacheKey(IInvocation invocation)	{	var typeName = invocation.TargetType.Name;	var methodName = invocation.Method.Name;	var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,最多三个	string key = $"{typeName}:{methodName}:";	foreach (var param in methodArguments)	{	key = $"{key}{param}:";	}	return key.TrimEnd(':');	}	/// <summary>	/// object 转 string	/// </summary>	/// <param name="arg"></param>	/// <returns></returns>	protected static string GetArgumentValue(object arg)	{	if (arg is DateTime || arg is DateTime?)	return ((DateTime)arg).ToString("yyyyMMddHHmmss");	if (arg is string || arg is ValueType || arg is Nullable)	return arg.ToString();	if (arg != null)	{	if (arg.GetType().IsClass)	{	return MD5Encrypt16(Newtonsoft.Json.JsonConvert.SerializeObject(arg));	}	}	return string.Empty;	}	/// <summary>	/// 16位MD5加密	/// </summary>	/// <param name="password"></param>	/// <returns></returns>	public static string MD5Encrypt16(string password)	{	var md5 = new MD5CryptoServiceProvider();	string t2 = BitConverter.ToString(md5.ComputeHash(Encoding.Default.GetBytes(password)), 4, 8);	t2 = t2.Replace("-", string.Empty);	return t2;	}	}	
}	

3、注入拦截器到服务

具体的操作方法,上边我们都已经说到了,大家依然可以自己练习一下,这里直接把最终的代码展示一下:

注意:

//将 TService 中指定的类型的范围服务添加到实现 services.AddScoped<ICaching, MemoryCaching>();//记得把缓存注入!!!

640?wx_fmt=png

 

4、运行,查看效果

你会发现,首次缓存是空的,然后将serv中取出来的数据存入缓存,第二次使用就是有值了,其他所有的地方使用,都不用再写了,而且也是面向整个程序集合的

640?wx_fmt=png

 

5、多个AOP执行顺序问题 

在我最新的 Github 项目中,我定义了四个 AOP :除了上边两个 LogAOP和 CacheAOP 以外,还有一个 RedisCacheAOP 和 事务BlogTranAOP,并且通过开关的形式在项目中配置是否启用:

640?wx_fmt=png

 

那具体的执行顺序是什么呢,这里说下,就是从上至下的顺序,或者可以理解成挖金矿的形式,执行完上层的,然后紧接着来下一个AOP,最后想要回家,就再一个一个跳出去,在往上层走的时候,矿肯定就执行完了,就不用再操作了,直接出去,就像 break 一样。

6、无接口如何实现AOP

 

上边我们讨论了很多,但是都是接口框架的,

比如:Service.dll 和与之对应的 IService.dll,Repository.dll和与之对应的 IRepository.dll,我们可以直接在对应的层注入的时候,匹配上 AOP 信息,但是如果我们没有使用接口怎么办?

这里大家可以安装下边的实验下:

 

Autofac它只对接口方法 或者 虚virtual方法或者重写方法override才能起拦截作用。  

 

如果没有接口

案例是这样的:

 如果我们的项目是这样的,没有接口,会怎么办:640?wx_fmt=png

    // 服务层类	public class StudentService	{	StudentRepository _studentRepository;	public StudentService(StudentRepository studentRepository)	{	_studentRepository = studentRepository;	}	public string Hello()	{	return _studentRepository.Hello();	}	}	// 仓储层类	public class StudentRepository	{	public StudentRepository()	{	}	public string Hello()	{	return "hello world!!!";	}	}	// controller 接口调用	StudentService _studentService;	public ValuesController(StudentService studentService)	{	_studentService = studentService;	}

 

 

如果是没有接口的单独实体类

public class Love	
{	// 一定要是虚方法	public virtual string SayLoveU()	{	return "I ♥ U";	}	
}	//---------------------------	//只能注入该类中的虚方法	
builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))	.EnableClassInterceptors()	.InterceptedBy(typeof(BlogLogAOP));

 

到了这里,我们已经明白了什么是AOP切面编程,也通过两个业务逻辑学会了如何去使用AOP编程,那这里有一个小问题,如果我某些service类和方法并不想做相应的AOP处理,该如何筛选呢?请继续看。

640?wx_fmt=gif

 四、给缓存增加验证筛选

1、自定义缓存特性

在解决方案中添加新项目Blog.Core.Common,然后在该Common类库中添加 特性文件夹 和 特性实体类,以后特性就在这里

   /// <summary>	/// 这个Attribute就是使用时候的验证,把它添加到要缓存数据的方法中,即可完成缓存的操作。注意是对Method验证有效	/// </summary>	[AttributeUsage(AttributeTargets.Method, Inherited = true)]	public class CachingAttribute : Attribute	{	//缓存绝对过期时间	public int AbsoluteExpiration { get; set; } = 30;	}

 

2、在AOP拦截器中进行过滤

添加Common程序集引用,然后修改缓存AOP类方法 BlogCacheAOP=》Intercept,简单对方法的方法进行判断

/// <summary>	
/// 面向切面的缓存使用	
/// </summary>	
public class BlogCacheAOP : AOPbase	
{	//通过注入的方式,把缓存操作接口通过构造函数注入	private readonly ICaching _cache;	public BlogCacheAOP(ICaching cache)	{	_cache = cache;	}	//Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义	public override void Intercept(IInvocation invocation)	{	var method = invocation.MethodInvocationTarget ?? invocation.Method;	//对当前方法的特性验证	var qCachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute;	//只有那些指定的才可以被缓存,需要验证	if (qCachingAttribute != null)	{	//获取自定义缓存键	var cacheKey = CustomCacheKey(invocation);	//根据key获取相应的缓存值	var cacheValue = _cache.Get(cacheKey);	if (cacheValue != null)	{	//将当前获取到的缓存值,赋值给当前执行方法	invocation.ReturnValue = cacheValue;	return;	}	//去执行当前的方法	invocation.Proceed();	//存入缓存	if (!string.IsNullOrWhiteSpace(cacheKey))	{	_cache.Set(cacheKey, invocation.ReturnValue);	}	}	}	
}	

我们增加了一个 if 判断,只有那些带有缓存特性的类和方法才会被执行这个 AOP 拦截。

 

3、在service层中增加缓存特性

在指定的Service层中的某些类的某些方法上增加特性(一定是方法,不懂的可以看定义特性的时候AttributeTargets.Method)

640?wx_fmt=png

4、特定缓存效果展示

运行项目,打断点,就可以看到,普通的Query或者CURD等都不继续缓存了,只有咱们特定的 getBlogs()方法,带有缓存特性的才可以

640?wx_fmt=png

 

当然,这里还有一个小问题,就是所有的方法还是走的切面,只是增加了过滤验证,大家也可以直接把那些需要的注入,不需要的干脆不注入Autofac容器,我之所以需要都经过的目的,就是想把它和日志结合,用来记录Service层的每一个请求,包括CURD的调用情况。

 

640?wx_fmt=gif

五、基于AOP的Redis缓存

 

1、核心:Redis缓存切面拦截器

 在上篇文章中,我们已经定义过了一个拦截器,只不过是基于内存Memory缓存的,并不适应于Redis,上边咱们也说到了Redis必须要存入指定的值,比如字符串,而不能将异步对象 Task<T> 保存到硬盘上,所以我们就修改下拦截器方法,一个专门应用于 Redis 的切面拦截器:

 /// <summary>	/// 面向切面的缓存使用	/// </summary>	public class BlogRedisCacheAOP : CacheAOPbase	{	//通过注入的方式,把缓存操作接口通过构造函数注入	private readonly IRedisCacheManager _cache;	public BlogRedisCacheAOP(IRedisCacheManager cache)	{	_cache = cache;	}	//Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义	public override void Intercept(IInvocation invocation)	{	var method = invocation.MethodInvocationTarget ?? invocation.Method;	//对当前方法的特性验证	var qCachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute;	if (qCachingAttribute != null)	{	//获取自定义缓存键	var cacheKey = CustomCacheKey(invocation);	//注意是 string 类型,方法GetValue	var cacheValue = _cache.GetValue(cacheKey);	if (cacheValue != null)	{	//将当前获取到的缓存值,赋值给当前执行方法	var type = invocation.Method.ReturnType;	var resultTypes = type.GenericTypeArguments;	if (type.FullName == "System.Void")	{	return;	}	object response;	if (typeof(Task).IsAssignableFrom(type))	{	//返回Task<T>	if (resultTypes.Any())	{	var resultType = resultTypes.FirstOrDefault();	// 核心1,直接获取 dynamic 类型	dynamic temp = Newtonsoft.Json.JsonConvert.DeserializeObject(cacheValue, resultType);                          	response = Task.FromResult(temp);	}	else	{	//Task 无返回方法 指定时间内不允许重新运行	response = Task.Yield();	}	}	else	{	// 核心2,要进行 ChangeType	response = Convert.ChangeType(_cache.Get<object>(cacheKey), type);	}	invocation.ReturnValue = response;	return;	}	//去执行当前的方法	invocation.Proceed();	//存入缓存	if (!string.IsNullOrWhiteSpace(cacheKey))	{	object response;	//Type type = invocation.ReturnValue?.GetType();	var type = invocation.Method.ReturnType;	if (typeof(Task).IsAssignableFrom(type))	{	var resultProperty = type.GetProperty("Result");	response = resultProperty.GetValue(invocation.ReturnValue);	}	else	{	response = invocation.ReturnValue;	}	if (response == null) response = string.Empty;	_cache.Set(cacheKey, response, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration));	}	}	else	{	invocation.Proceed();//直接执行被拦截方法	}	}	}	

上边的代码和memory缓存的整体结构差不多的,相信都能看的懂的,最后我们就可以很任性的在Autofac容器中,进行任意缓存切换了,是不是很棒!

再次感觉小伙伴JoyLing,不知道他博客园地址。

 

 

 

 六、一些其他问题需要考虑

1、时间问题,阻塞,浪费资源问题等

  定义切面有时候是方便,初次使用会很别扭,使用多了,可能会对性能有些许的影响,因为会大量动态生成代理类,性能损耗,是特别高的请求并发,比如万级每秒,还是要深入的研究,不可随意使用,但是基本平时开发的时候,还是可以使用的,毕竟性价比挺高的,我说的也是九牛一毛,大家继续加油吧!

 

2、静态注入

基于Net的IL语言层级进行注入,性能损耗可以忽略不计,Net使用最多的Aop框架PostSharp(好像收费了;)采用的即是这种方式。

大家可以参考这个博文:https://www.cnblogs.com/mushroom/p/3932698.html

 

 七、CODE

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core

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

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

相关文章

[ASP.NET Core 3框架揭秘] 跨平台开发体验: Docker

对于一个 .NET Core开发人员&#xff0c;你可能没有使用过Docker&#xff0c;但是你不可能没有听说过Docker。Docker是Github上最受欢迎的开源项目之一&#xff0c;它号称要成为所有云应用的基石&#xff0c;并把互联网升级到下一代。Docker是dotCloud公司开源的一款产品&#…

统计学习笔记(4) 线性回归(1)

Basic Introduction In this chapter, we review some of the key ideas underlying the linear regression model, as well as the least squares approach that is most commonly used to fit this model. Basic form: “≈” means “is approximately modeled as”, to …

敏捷这么久,你知道如何开敏捷发布火车吗?

译者&#xff1a;单冰从事项目管理十几年&#xff0c;先后管理传统型项目团队及敏捷创新型团队。负责京东AI事业部敏捷创新、团队工程效率改进及敏捷教练工作。曾经负责手机端京东App项目管理工作5年&#xff0c;带领千人团队实施敏捷转型工作&#xff0c;版本发布从2个月提升为…

Newton Method in Maching Learning

牛顿方法&#xff1a;转自http://blog.csdn.net/andrewseu/article/details/46771947 本讲大纲&#xff1a; 1.牛顿方法(Newton’s method) 2.指数族(Exponential family) 3.广义线性模型(Generalized linear models) 1.牛顿方法 假设有函数&#xff1a;&#xff0c;我们希…

一键分享博客或新闻到Teams好友或频道

在最近的开发者工具更新中&#xff0c;Teams提供了一个Share to Teams的能力&#xff0c;就是在你的网页上面&#xff0c;放置一个按钮&#xff0c;用户点击后&#xff0c;就可以很方便地将当前网页或者你指定的其他网页&#xff0c;分享到Teams好友或频道中。这个开发文档在这…

C#刷遍Leetcode面试题系列连载(3): No.728 - 自除数

点击蓝字“dotNET匠人”关注我哟加个“星标★”&#xff0c;每日 7:15&#xff0c;好文必达&#xff01;前言前文传送门&#xff1a;上篇文章中我们分析了一个递归描述的字符串问题&#xff0c;今天我们来分析一个数学问题&#xff0c;一道除法相关的面试题。今天要给大家分析的…

【.NET Core 3.0】框架之十二 || 跨域 与 Proxy

本文有配套视频&#xff1a;https://www.bilibili.com/video/av58096866/?p8一、为什么会出现跨域的问题跨域问题由来已久&#xff0c;主要是来源于浏览器的”同源策略”。何为同源&#xff1f;只有当协议、端口、和域名都相同的页面&#xff0c;则两个页面具有相同的源。只要…

.NET 时间轴:从出生到巨人

点击上方蓝字关注“汪宇杰博客”“ 自1995年互联网战略日以来最雄心勃勃的事业—— 微软.NET战略, 2000年6月30日”2002-02-13.NET Framework 1.0CLR 1.0Visual Studio .NET关键词&#xff1a;跨语言、托管代码2003-04-24.NET Framework 1.1CLR 1.1Visual Studio 2003关键词&am…

Boltzmann Machine 入门(2)

发现RBM 中的能量函数概念需要从Hopfield网络的角度理解&#xff0c;于是找到 http://blog.csdn.net/roger__wong/article/details/43374343 和关于BM的最经典论文 http://www.cs.toronto.edu/~hinton/papers.html#1983-1976 一、限制玻尔兹曼机的感性认识 要回答这个问题大…

针对深度学习的GPU芯片选择

转自&#xff1a;http://timdettmers.com/2014/08/14/which-gpu-for-deep-learning/ It is again and again amazing to see how much speedup you get when you use GPUs for deep learning: Compared to CPUs 10x speedups are typical, but on larger problems one can achi…

C# 8 - Range 和 Index(范围和索引)

C# 7 的 Span C# 7 里面出现了Span这个数据类型&#xff0c;它可以表示另一个数据结构里连续相邻的一串数据&#xff0c;并且它是内存安全的。 例子&#xff1a; 这个图的输出是3&#xff0c;4&#xff0c;5&#xff0c;6。 C# 8 的Range类型 而C# 8里面我们可以从一个序列里面…

DCT变换学习

http://blog.csdn.net/timebomb/article/details/5960624 timebomb的博客 DCT变换的基本思路是将图像分解为88的子块或1616的子块&#xff0c;并对每一个子块进行单独的DCT变换&#xff0c;然后对变换结果进行量化、编码。随着子块尺寸的增加&#xff0c;算法的复杂度急剧上升…

敏捷回顾会议的套路与实践分享

01—关于敏捷回顾会议实践过敏捷的人都知道&#xff0c;在敏捷中会有很多的会议要开&#xff0c;比如计划会议&#xff08;Planning&#xff09;、站立会议&#xff08;Daily Scrum&#xff09;、评审会议&#xff08;Review&#xff09;以及回顾会议&#xff08;Retrospective…

.Net Core AA.FrameWork应用框架介绍

开发多年&#xff0c;一直在从社区获取开源的便利&#xff0c;也深感社区力量的重要性&#xff0c;今天开源一个应用基础框架AA.FrameWork,也算是回馈社区&#xff0c;做出一点点贡献&#xff0c;希望能够帮助类似当年入行的我。AA.FrameWork 是基于.NET core流行的开源类库创建…

RBM/DBN训练中的explaining away概念

可以参照 Stanford大神DaphneKoller的概率图模型&#xff0c;里面贝叶斯网络一节讲到了explaining away。我看过之后试着谈谈自己的理解。 explainingaway指的是这样一种情况&#xff1a;对于一个多因一果的问题&#xff0c;假设各种“因”之间都是相互独立的&#xff0c;如果…

.NET Core使用gRPC打造服务间通信基础设施

一、什么是RPCrpc&#xff08;远程过程调用&#xff09;是一个古老而新颖的名词&#xff0c;他几乎与http协议同时或更早诞生&#xff0c;也是互联网数据传输过程中非常重要的传输机制。利用这种传输机制&#xff0c;不同进程&#xff08;或服务&#xff09;间像调用本地进程中…

DBN训练学习-A fast Learning algorithm for deep belief nets

转载自&#xff1a;http://blog.sciencenet.cn/blog-110554-889016.html DBN的学习一般都是从Hinton的论文A Fast Learning Algorithm for Deep Belief Nets开始。如果没有相关的背景知识&#xff0c;不知道问题的来由&#xff0c;这篇论文读起来是相当费劲的。 学习过程中主…

程序员家的大闸蟹:青壳、白底、肉多、爆黄,现在是吃大闸蟹的最佳时期

其实&#xff0c;我跟大家一样&#xff0c;也是dotNET跨平台和张队长的忠实粉丝&#xff0c;也是一名程序员。上上周&#xff0c;我在dotNET跨平台的优选商城买了队长推荐人生果&#xff0c;也是第一次吃这个人生果&#xff0c;味道鲜甜、汁水也特别多&#xff0c;但由于快递的…

环形链表II

1、题目描述 给定一个链表&#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 为了表示给定链表中的环&#xff0c;我们使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索引从 0 开始&#xff09;。 如果 pos 是 -1&#xff0c;则…

.NET Core Love gRPC

这篇内容主要来自Microsoft .NET团队程序经理Sourabh Shirhatti的博客文章&#xff1a;https://grpc.io/blog/grpc-on-dotnetcore/&#xff0c; .NET Core 3.0现已提供grpc的.NET 托管实现 grpc-dotnet&#xff0c; gRpc 取代WCF成为 .NET的一等公民。自2018年11月以来&#xf…