声明

本文欢迎转载,原始地址:http://www.cnblogs.com/DjlNet/p/7603654.html


前言

先说一点废话,在此之前博主也在早期就接触了或者看了些许AOP相关的文章,然后再去做了一些相关的实验,但是始终没有将AOP内化到自己的内功心法当中去,包括从概念还是应用环境,以及当前生态当中的AOP工具等等,所以这里博主还是按照以往的套路,在前人的基础之上学习然后吸收到集成到系统当中去。


什么是AOP

还是先看官方解释AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。OOP是关注将需求功能划分为不同的并且相对独立,封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等来定义彼此的关系;AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可AOP是使用切面(aspect)将横切关注点模块化,OOP是使用类将状态和行为模块化。在OOP的世界中,程序都是通过类和接口组织的,使用它们实现程序的核心业务逻辑是十分合适。但是对于实现横切关注点(跨越应用程序多个模块的功能需求)则十分吃力,比如日志记录,权限验证,异常拦截等。这里可以看到当我们在应用程序中使用AOP来编程的话,是大大的节约我的代码量而且做到了职责依然分明,所以有这么多好处的情况下,我们就应该勇敢的尝试来构建我的应用程序...


.NET当中的AOP

根据上文关于AOP的两种方式来实现了面向切面编程,而在.NET领域当中有些比较出名的,例如在动态代理方面就是castle.core(原名:castle dynamic proxy)通过Reflect.Emit的方式在原始基础上面实现的动态代理方式,加上缓冲机制提升性能,该项目很好的屏蔽了底层使用emit+opcode的方式带来的复杂性和难度,使用友好的API调用方式提供外部程序访问castle.coree nuget地址,小吐槽:.net core已经2.0这么久了,castle家族虽然也还在努力,但是依然还没支持.net standrad2.0,可能是因为castle.core现在打包了不止dynamicproxy一个包的原因,需要一些时间,不过在@Lemon努力下率先提供了基于.net core的aop框架,这里博主也是对此框架目前也是浅尝辄止的状态还没有应用到项目中,所以不好对比评价哈,哈哈!),所以基于我们当前的项目而已动态中的castle不失为我们的一个好的选择;然后在.net静态编织方面当属postsharp首当其冲了,当然这玩意儿是收费不过你可以使用express免费版但是和企业版对dll的修改差异就不得而知了,当初博主实验安装的企业版也是在免费期内使用后面做了一些小手脚才得以继续使用,当然这里不推荐破解使用了,有钱的主儿还是支持正版吧,注意使用postsharp需要vs安装插件(需要插件支持才可以编译后期对dll动手脚)以及nuget package支持,所以团队里面如果使用需要大家统一安装,还有就是目前许多都是基于Jenkins来做持续集成的,所以还得注意使用了postsharp的项目编译问题(使用了postsharp如果没有插件支持会报错)!!当然我这里的指出相对比较计较的性能问题,静态编织是比动态代理来的更快,因为dll在编译之后都已经形成了AOP之势,动态代理毕竟是运行时才得以去构建各种代理对象不过还好有缓冲,所以相对一般情况动态代理足以满足我们的需求,还是那个句话:在已经解决显而易见的瓶颈之后,才考虑一些锱铢必较的性能缺失!!!最后.NET周边其他AOP框架或者产品不管是动态还是静态,大家可自行搜索是有的,不过使用率以及可用性、稳定性、性能等等就不好说了!


Castle DynamicProxy文档带来了什么

首先Castle DynamicProxy是一个在运行时即时生成轻量级.NET代理的库,然后除了介绍AOP使用场景就是当前一些流行项目对于castle.core的依赖,侧面也反应除了此AOP的地位,好吧,233333,官方文档是要捧一下自己的。我们会发现 moq(mock的实现)、NHibernate(延迟加载virtual+getter拦截实现等)等都依赖了castle.core,说明了上面的框架功能提供肯定是需要动态代理的支撑的,然后根据官方说明在版本2.5之后原先独立的castle.dynamicproxy.dll就合并了,官方高能如果您有一个长时间运行的进程(网站,Windows服务等),并且必须创建许多动态代理,那么您应该确保重用相同的ProxyGenerator实例。如果没有,请注意,您将绕过缓存机制,副作用是CPU使用率高,内存消耗不断增加。

这里引用官方对于catle dynamicproxy的工作原理及流程的理解,显示给出执行流程图:



我们这里外部的蓝色框就是代理区域,黄色箭头将会层层进入各级代理对象,接着代理执行PreAction逻辑,然后调用invocation.Proceed()(每个代理只能调用一次不然出爆发异常)进入下一个代理或者原始对象逻辑,就这样一直进入到最下层也就是被代理的对象,执行原始逻辑之后,再按照层层代理执行PostAction逻辑弹出也就是绿色箭头所表达的意思,就是一条完成的传递链就形成了多级代理模式。注意点:每一层代理对象都可以拿到目标的对象也就是被代理对象IInvocation,该接口包含了一些重要属性和方法,例如:invocation.MethodInfo、invocation.ResultValue 等对象,我们接下来对API实验详细看看这些对象的真实面目,至此根据官方文档就这样没了,还有些stackoverflow的参考代码,我们同时翻看园中其他使用代码和对API的探究完善对castle dynamicproxy的了解....


Castle DynmaicProxy API提供了什么

首先这里要说明框架当中几个重要的对象:

1、IInterceptor 拦截器该接口提供了可以自定义拦截器逻辑的入口,实现接口的 Intercept 方法即可,在后面创建代理对象需要接口实例来控制代理对象行为,这里框架也提供了StandardInterceptor标准的拦截器继承MarshalByRefObject对象以及实现了IInterceptor接口,它包含了protected virtual void PerformProceed(IInvocation invocation);protected virtual void PostProceed(IInvocation invocation);protected virtual void PreProceed(IInvocation invocation);三个常用的接口方法....

public interface IInterceptor
{
void Intercept(IInvocation invocation);
}

2、IProxyGenerator 代理对象的创建者,包含了两个属性:LoggerProxyBuilder(只读,具体由它来创建代理类型),包含如下几个重要的API方法:

(1)动态创建类代理 proxyGenerator.CreateClassProxy 包含重载:Creates proxy object intercepting calls to virtual members of type TClass on newly created instance of that type with given interceptors. (创建一个新的代理对象subclass,通过配置的拦截器拦截原始对象标记了公开public的虚方法virtual的method产生效果,包含使用方法传递进来的代理配置项);

(2)动态创建类代理通过既有实例 proxyGenerator.CreateClassProxyWithTarget 包含重载:Creates proxy object intercepting calls to virtual members of type TClass on newly created instance of that type with given interceptors.(创建一个新的代理对象subclass,通过配置的拦截器拦截原始对象标记了公开public的虚方法virtual的method产生效果,提供了方法参数传递一个既有的目标实例对象,包含使用方法传递进来的代理配置项);

(3)动态创建接口代理不需要实现接口的实例对象 proxyGenerator.CreateInterfaceProxyWithoutTarget:Creates proxy object intercepting calls to members of interface TInterface on target object generated at runtime with given interceptor.(动态创建接口对象实现的实例且不需要实现了接口实例参数,通过拦截器凑效于接口方法实现拦截,注意这里如果接口方法要求了返回值,就需要在拦截器中指定返回值,类似于:invocation.ReturnValue=2;

(4)动态创建接口代理通过实现接口的实例对象 proxyGenerator.CreateInterfaceProxyWithTarget:Creates proxy object intercepting calls to members of interface TInterface on target object with given interceptors.(动态创建接口代理对象,通过传递实现了接口的对象实例,使用配置的拦截器对象作用于接口的每个方法,这里实现接口的对象实例的实现方法就可以不需要配置为vritual了,因为在接口代理对象中已经包裹住了原始对象是采用了类似于注入的方式而不是继承,可以参考下面的示例代码

(5)动态创建接口代理通过实现了接口的实例对象 proxyGenerator.CreateInterfaceProxyWithTargetInterface:Creates proxy object intercepting calls to members of interface TInterface on target object with given interceptors. Interceptors can use Castle.DynamicProxy.IChangeProxyTarget interface to provide other target for method invocation than default target.(与上述CreateInterfaceProxyWithTarget相似,那么它们的区别在于哪里呐,这里博主本着研究的精神找了一下,不至于翻看源码了....,找到类似代码提供者的一点描述如下:http://kozmic.net/2009/11/13/interfaceproxywithtarget-interfaceproxywithtargetinterface-ndash-whatrsquos-the-difference/,大体总结就是:一般情况下调用者需要的就是CreateInterfaceProxyWithTargetInterface这个API的调用,其中它提供了两个优点:当使用InterfaceProxyWithTargetInterface时,它的调用实现了IChangeProxyTarget接口,该接口允许拦截途中更改目标对象。然后最重要的是 InterfaceProxyWithTargetInterface更好地使用缓存,详情参考链接代码验证过程,当然博主也亲自测试如同文中作者如出一辙!!。)博主实验参考如下:

  IMyCompare oneCompare = proxyGenerator.CreateInterfaceProxyWithTarget<IMyCompare>(new MyCompareOne(), new MyStandradInterceptor());
IMyCompare twoCompare = proxyGenerator.CreateInterfaceProxyWithTarget<IMyCompare>(new MyCompareTwo(), new MyStandradInterceptor()); Type oneCompareType = oneCompare.GetType();
Type twoCompareType = twoCompare.GetType(); Console.WriteLine("{0}", object.ReferenceEquals(oneCompareType, twoCompareType)); // false IMyCompare oneCompare1 = proxyGenerator.CreateInterfaceProxyWithTargetInterface<IMyCompare>(new MyCompareOne(), new MyStandradInterceptor());
IMyCompare twoCompare1 = proxyGenerator.CreateInterfaceProxyWithTargetInterface<IMyCompare>(new MyCompareTwo(), new MyStandradInterceptor());
Type oneCompare1Type = oneCompare1.GetType();
Type twoCompare1Type = twoCompare1.GetType();
Console.WriteLine("{0}", object.ReferenceEquals(oneCompare1Type, twoCompare1Type)); // ture

关于castle dynamicproxy动态代理中的对与class与interface的处理方式大致原理探究:关于class的代理,相信很多同学都应该直到,也就是设计模式当中的代理模式的利用,具体就是继承原始类对象实现对原始对象虚方法的重写实现注入拦截的逻辑,不过这一切都是动态的不需要我们去构建了,参考代码如下(这里只考虑一级代理,多级也就是多层继承关系):

 public class Caller
{
public virtual void Call()
{
Console.WriteLine("calling...");
}
} public class CallerProxy : Caller
{
public override void Call()
{
// 执行前
Console.WriteLine("pre action");
base.Call();
// 执行后
Console.WriteLine("post action");
}
} static void Main(string[] args)
{
Caller caller = new CallerProxy();
caller.Call(); Console.ReadKey();
}

接着关于interface的代理的大致原型是,通过实现接口产生一个包含了传递的接口实习实例对象的接口代理对象,可能这里有点绕,不过依然还是代理模式,关系从继承变成了包含,同理这些东西castle已经帮我用动态的方式构建好了,我们看一示例代码就知道了,这里展示一层代理多级代理就是层层包含了

public interface IService
{
void Process();
} public class Service : IService
{
public void Process()
{
// do something
Console.WriteLine("processing...");
}
} public class ServiceProxy : IService
{
private readonly Service _service;
public ServiceProxy(Service service)
{
_service = service;
} public void Process()
{
// pre action
Console.WriteLine("pre action");
_service.Process();
// post action
Console.WriteLine("post action");
}
} IService service = new ServiceProxy(new Service());
service.Process();
Console.ReadKey();

相信看到这里你也觉得,我去,动态代理这么简单么,其实不然,虽然道理大家一看就懂是简单就是设计模式的代理模式的运用嘛,但是将这个动作泛化为一个通用的API支持可变拦截器数量配置以及各种代理配置将是一项繁杂而小心的工作,既要考虑友好的API还有重中之重的性能保证,也就是使用上面提到的 Reflect.Emit + OpCode 来实现接近于元数据编程....,可怕!!!所以,这里给写AOP的同学点赞!@Lemon

3、代理生成配置对象 ProxyGenerationOptions,在生成代理对象是可传递自定义配置对象,实现可控的拦截,该对象主要配置项:



public IProxyGenerationHook Hook { get; set; } (决定了该方法是否受拦截器拦截,可以实现自定义Hook)

public IInterceptorSelector Selector { get; set; } (决定了该方法受那些拦截器拦截,可以实现自定义Selector)

public Type BaseTypeForInterfaceProxy { get; set; } (决定了接口代理的基础类型,详情使用参考链接:https://stackoverflow.com/questions/2969274/castle-dynamicproxy-how-to-proxy-equals-when-proxying-an-interface



具体例子参考如下:

public class MyInterceptorSelector : IInterceptorSelector
{
public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
{
if (method.Name.EndsWith("Repository"))
{
return interceptors.Where(x => x is TestInterceptor).ToArray();
}
return interceptors.Where(x => x is TestInterceptor2 || x is MyStandradInterceptor).ToArray();
}
}
public class MyGenerationProxyHook : IProxyGenerationHook
{
public void MethodsInspected()
{
} public void NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
{
} public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
{
return methodInfo.Name.EndsWith("Service");
}
}

这里相信大家一看就明白了,就不多说了.....这里注意点就是: 拦截器的植入顺序与生效顺序是一致的....


从其他框架复盘认识castle与集成

了解过[abp]:(https://aspnetboilerplate.com/) 的同学,肯定就知道此框架强制把castle家族的castle.core+Castle.Windsor(依赖前者)融入进abp当中了,采用了接口代理实现了日志记录、异常处理、权限审查、审计日志等等,当然abp框架当中确实不止一处是值得我们学习,不过此框架构建在一个及集众家之所长的情况下就显得复杂,从知名度可以看出选择caslte来作为aop+ioc的集成是个不错的选择,接着从moq的部分实例代码中可以看出,就是利用了proxyGenerator.CreateInterfaceProxyWithoutTarget等等,之类的细节就可以回想起原始API做起底层的支撑作用,再者castle与许多第三方ioc有着比较好的集成,例如比较出名的ioc框架autofac+castle也是很多人的选择,参考链接:[AOP系列]Autofac+Castle实现AOP事务:http://www.cnblogs.com/jianxuanbing/p/7199457.html

这里博主为何要把ioc扯进来一起说呐?

从castle的api来看大家觉得有木有点觉着好像有着一点ioc的功能,但是为何我们要明确概念说aop是aop,ioc是ioc呐,这里博主的理解就是,它们各自的职责是不同,它们各自只需要各司其职就行了,也不要越界也是编程开发当中的单一职责的体现了,虽然多多少少大家都有些动态创建对象那么回事儿(不管是emit还是activor.CreateInstance)!但是我们从ioc的职能分析得到的是:1、负责对象的存储(注册)与创建(解析) 2、负责对象的依赖关系维护 3、负责对象的生命周期,这三点就可以看出与AOP功能不一致,但是我们看到对象创建这个时候,想一下是否可以在对象创建的时植入Interceptor???回答是:肯定是可以的,所以这就是为何我们常常把ioc与aop一起来食用了,听说这样用更配哦!!


总结

我们从AOP的定义到AOP的使用场景,然后.net下面的AOP的介绍,接着重点介绍了动态代理中的castle.core的官方说明与文档,后面尤其重要的详解了框架当中重要的一些对象和API以及原理和实践,也途中参考一些文章和stackoverflow,也请教AOP相关人士,这里感谢!好了,时间也不早了,相信学无止境,那么就保持持续学习,持续内化知识,就像修炼内功心法一样,半途而废还容易走火入魔,一知半解说出去的东西自己都没搞明白,岂不是笑话了!!加油吧,骚年

这里引用知乎大大的一段话以此激励:成长必须经历一个步骤,就是把知识内化成能力。知识是用脑记住的,能力是用手练习出来的。在工作的几年里,我们可能看过很多书,听过很多技术讲座和视频,但是通过听和看只是让你能记住这些知识,这些知识还不能转换成你的能力。听和看只是第一步,更重要的是实践,通过刻意练习把听到和看到的知识内化成你的能力。刻意练习,就是有目的的练习,先规划好,再去练习。首先给自己定一个目标,目标可以有效的引导你学习,然后使用3F练习法:1: 专注(Focus),专注在眼前的任务上,在学习过程中保持专注,可以尝试使用番茄工作法。2:反馈(Feedback),意识到自己的不足,学习完之后进行反思,思考下自己哪些方面不足,为什么不足,3: 修正(Fix),改进自己的不足。不停的练习和思考可以改变大脑结构,大脑像肌肉一样,挑战越大,影响越大,学习更高效,并且也会产生突破性。 -- 原始链接: https://www.zhihu.com/question/26572626/answer/246901769?utm_medium=social&utm_source=qq


收集备注园中相关AOP好文

1、Asp.Net Core轻量级Aop解决方案:AspectCore http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html

2、C# 实现AOP 的几种常见方式http://www.cnblogs.com/zuowj/p/7501896.html

3、.Net基于RealProxy实现AOP : http://www.cnblogs.com/lflyq/p/6286925.html

4、Aspect-Oriented Programming : 使用 RealProxy 类进行面向方面的编程 : https://msdn.microsoft.com/zh-cn/magazine/dn574804.aspx


补充更新(2017年10月20日15:07:33)

关于 castle api 当中对于 IInvocation对象的解释少了一些,这里补充一下:

1、invocation.Arguments: 方法执行被拦截时的方法参数数组

2、invocation.GenericArguments: 被拦截方法的泛型参数类型数组,如果没有就是null

3、invocation.InvocationTarget: 获取当前执行的目标对象,例如:如果是class代理就是YourClassProxy,接口代理就是实现接口的对象实例,如果没有则为null,也就是当使用xxxWithoutTarget的时候

4、invocation.Method:获取代理对象的方法信息,例如:如果是class代理的时候就是YourClass.YourMethod对象的MethodInfo且这个时候invocation.Method == invocation.MethodInvocationTarget;如果是interface代理就是接口对象的方法信息,例如:ICall.Call 这个方法的MethodInfo信息且这个时候invocation.Method != invocation.MethodInvocationTarget,因为invocation.MethodInvocationTarget是接口对应实现的目标对象的方法信息,也就是例如:MyCall.Call 方法对应上面的 ICall 接口来说,当然也可以使用 WithoutTarget方式,这样就会导致 invocation.MethodInvocationTarget==null的情况

5、invocation.MethodInvocationTarget: 指向真正的目标对象的方法信息MethodInfo,大致可以根据第四点给出了说明

6、invocation.Proxy : 获取拦截时的代理对象,例如:YourClassProxy(类代理) 或者 ICallProxy(接口代理) 对象

7、invocation.ResultValue: 获取或者设置代理方法的返回值

8、invocation.TargetType: 获取真实目标对象的类型

9、invocation.GetArgumentValue(int index);: 通过index获取参数值

10、invocation.GetConcreteMethod();: 同理第四点

11、invocation.GetConcreteMethodInvocationTarget();: 同理第五点

12、invocation.Proceed();: 调用下一个拦截器直到目标方法

13、invocation.SetArgumentValue(int index, object value);: 设置更改参数值通过下标

这里博主列举除了拦截途中对象IInvocation的所有成员,大家在使用拦截器的时候可根据自己逻辑使用以上API到达要求!


如果觉得阅读了博主的小文觉得对您有帮助,您的点赞和评论都是对博主最大的支持!!!

【框架学习与探究之AOP--Castle DynamicProxy】的更多相关文章

  1. 【框架学习与探究之消息队列--EasyNetQ(2)】

    声明 本文欢迎转载,系博主原创,本文原始链接地址:http://www.cnblogs.com/DjlNet/p/7654902.html 前言 此文章,是承接上篇:[框架学习与探究之消息队列--Ea ...

  2. 在 CAP 中使用 AOP ( Castle.DynamicProxy )

    简介 本篇文章主要介绍如何在 CAP 中集成使用 Castle.DynamicProxy,Castle DynamicProxy 是一个用于在运行时动态生成轻量级.NET代理的库.代理对象允许在不修改 ...

  3. 【框架学习与探究之定时器--Quartz.Net 】

    声明 本文欢迎转载,原文地址:http://www.cnblogs.com/DjlNet/p/7572174.html 前言 这里相信大部分玩家之前现在都应该有过使用定时器的时候或者需求,例如什么定时 ...

  4. 【框架学习与探究之定时器--Hangfire】

    声明 本文欢迎转载,请注明文章原始出处:http://www.cnblogs.com/DjlNet/p/7603632.html 前言 在上篇文章当中我们知道关于Quartz.NET的一些情况,其实博 ...

  5. 【框架学习与探究之日志组件--Log4Net与NLog】

    前言 本文欢迎转载,作者原创地址:http://www.cnblogs.com/DjlNet/p/7604340.html 序 近日,天气渐冷,懒惰的脑虫又开始作祟了,导致近日内功修炼迟迟未能进步,依 ...

  6. 【框架学习与探究之消息队列--EasyNetQ(1)】

    前言 本文欢迎转载,实属原创,本文原始链接地址:http://www.cnblogs.com/DjlNet/p/7603554.html 废话 既然都是废话了,所以大家就可以跳过了,这里是博主有事没事 ...

  7. spring框架学习(四)——注解方式AOP

    注解配置业务类 使用@Component("s") 注解ProductService 类 package com.how2java.service; import org.spri ...

  8. spring框架学习(六)AOP

    AOP(Aspect-OrientedProgramming)面向方面编程,与OOP完全不同,使用AOP编程系统被分为方面或关注点,而不是OOP中的对象. AOP的引入 在OOP面向对象的使用中,无可 ...

  9. 【框架学习与探究之宿主服务--Topshelf】

    前言 此文欢迎转载,原始链接地址:http://www.cnblogs.com/DjlNet/p/7603819.html 正文 原先也偶然见过这个关键词,当时只是有个大致了解貌似和WinServic ...

随机推荐

  1. 【Beta】阶段 第四次Daily Scrum Meeting

    每日任务 1.本次会议为第四次 Meeting会议: 2.本次会议在周四下午16:40,课间休息时间在陆大楼召开,召开本次会议为10分钟. 一.今日站立式会议照片 二.每个人的工作 (有work it ...

  2. 团队作业8——第二次项目冲刺(Beta阶段)5.22

    1.当天站立式会议照片 会议内容: ①:检查总结上次任务完成情况 ②:安排本次任务的分工 ③:反思前三次自己的不足 ④:协商解决代码进度.成员投入时间等问题 2.每个人的工作 工作中遇到的困难: 代码 ...

  3. 201521123066 《Java程序设计》第四周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 1.多态性: (1)概念:相同的方法名,不同的实现方法 (2)instanceof运算符:判 ...

  4. Java课程设计--GUI密码生成器201521123033

    1.团队课程设计题目 基于GUI的密码生成器 团队博客链接 2.个人负责模块 (1)界面设计 (2)部分错误输入的提示 (3)一键复制密码功能的实现 3.个人代码的提交记录截图 4.个人代码展示以及代 ...

  5. 201521123038 《Java程序设计》 第十一周学习总结

    201521123038 <Java程序设计> 第十一周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多 ...

  6. jstl-初步认知

    JSTL是java提供的JSP标签库 1,在项目中加入 jsf-api.jar jsf-impl.jar jstl-1.2.jar 三个包 2, 如何在jsp页面引入标签库 使用 <@tagli ...

  7. 【转】MySQL分库分表环境下全局ID生成方案

    转载一篇博客,里面有很多的知识和思想值得我们去思考. —————————————————————————————————————————————————————————————————————— 在大 ...

  8. Java http请求和调用

    关于http get和post请求调用代码以及示例. 参考:http://www.cnblogs.com/zhuawang/archive/2012/12/08/2809380.html http请求 ...

  9. 数据库服务器构建和部署列表(For SQL Server 2012)

    前言 我们可能经常安装和部署数据库服务器,但是可能突然忘记了某个设置,为后来的运维造成隐患.下面是国外大牛整理的的检查列表. 其实也包含了很多我们平时数据库配置的最佳实践.比如TEMPDB 文件的个数 ...

  10. Message:Unable to locate element 问题解决方法

    Python断断续续学了有一段时间了,总感觉不找个小项目练练手心里没底,哪成想出门就遇到"拦路虎",一个脚本刚写完就运行报错,还好做足了心里准备,尝试自行解决. 或许网上有相关解决 ...