利用C#实现AOP常见的几种方法详解

前言

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的中统一处理业务逻辑的一种技术,比较常见的场景是:日志记录,错误捕获、性能监控等

AOP的本质是通过代理对象来间接执行真实对象,在代理类中往往会添加装饰一些额外的业务代码,比如如下代码:

 class RealA
 {
 public virtual string Pro { get; set; }

 public virtual void ShowHello(string name)
 {
 Console.WriteLine($"Hello!{name},Welcome!");
 }
 }

//调用:

 var a = new RealA();
 a.Pro = "测试";
 a.ShowHello("梦在旅途");

这段代码很简单,只是NEW一个对象,然后设置属性及调用方法,但如果我想在设置属性前后及调用方法前后或报错都能收集日志信息,该如何做呢?可能大家会想到,在设置属性及调用方法前后都加上记录日志的代码不就可以了,虽然这样是可以,但如果很多地方都要用到这个类的时候,那重复的代码是否太多了一些吧,所以我们应该使用代理模式或装饰模式,将原有的真实类RealA委托给代理类ProxyRealA来执行,代理类中在设置属性及调用方法时,再添加记录日志的代码就可以了,这样可以保证代码的干净整洁,也便于代码的后期维护。(注意,在C#中若需被子类重写,父类必需是虚方法或虚属性virtual)

如下代码:

class ProxyRealA : RealA
 {

 public override string Pro
 {
 get
 {
 return base.Pro;
 }
 set
 {
 ShowLog("设置Pro属性前日志信息");
 base.Pro = value;
 ShowLog($"设置Pro属性后日志信息:{value}");
 }
 }

 public override void ShowHello(string name)
 {
 try
 {
 ShowLog("ShowHello执行前日志信息");
 base.ShowHello(name);
 ShowLog("ShowHello执行后日志信息");
 }
 catch(Exception ex)
 {
 ShowLog($"ShowHello执行出错日志信息:{ex.Message}");
 }
 }

 private void ShowLog(string log)
 {
 Console.WriteLine($"{DateTime.Now.ToString()}-{log}");
 }
 }

//调用:
 var aa = new ProxyRealA();
 aa.Pro = "测试2";
 aa.ShowHello("zuowenjun.cn");

这段代码同样很简单,就是ProxyRealA继承自RealA类,即可看成是ProxyRealA代理RealA,由ProxyRealA提供各种属性及方法调用。这样在ProxyRealA类内部属性及方法执行前后都有统一记录日志的代码,不论在哪里用这个RealA类,都可以直接用ProxyRealA类代替,因为里氏替换原则,父类可以被子类替换,而且后续若想更改日志记录代码方式,只需要在ProxyRealA中更改就行了,这样所有用到的ProxyRealA类的日志都会改变,是不是很爽。

上述执行结果如下图示:

以上通过定义代理类的方式能够实现在方法中统一进行各种执行点的拦截代码逻辑处理,拦截点(或者称为:横切面,切面点)一般主要为:执行前,执行后,发生错误,虽然解决了之前直接调用真实类RealA时,需要重复增加各种逻辑代码的问题,但随之而来的新问题又来了,那就是当一个系统中的类非常多的时候,如果我们针对每个类都定义一个代理类,那么系统的类的个数会成倍增加,而且不同的代理类中可能某些拦截业务逻辑代码都是相同的,这种情况同样是不能允许的,那有没有什么好的办法呢?答案是肯定的,以下是我结合网上资源及个人总结的如下几种常见的实现AOP的方式,各位可以参考学习。

第一种:静态织入,即:在编译时,就将各种涉及AOP拦截的代码注入到符合一定规则的类中,编译后的代码与我们直接在RealA调用属性或方法前后增加代码是相同的,只是这个工作交由编译器来完成。

PostSharp:PostSharp的Aspect是使用Attribute实现的,我们只需事先通过继承自OnMethodBoundaryAspect,然后重写几个常见的方法即可,如:OnEntry,OnExit等,最后只需要在需要进行AOP拦截的属性或方法上加上AOP拦截特性类即可。由于PostSharp是静态织入的,所以相比其它的通过反射或EMIT反射来说效率是最高的,但PostSharp是收费版本的,而且网上的教程比较多,我就不在此重复说明了。

第二种:EMIT反射,即:通过Emit反射动态生成代理类,如下Castle.DynamicProxy的AOP实现方式,代码也还是比较简单的,效率相对第一种要慢一点,但对于普通的反射来说又高一些,代码实现如下:

using Castle.Core.Interceptor;
using Castle.DynamicProxy;
using NLog;
using NLog.Config;
using NLog.Win32.Targets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
 class Program
 {
 static void Main(string[] args)
 {
 ProxyGenerator generator = new ProxyGenerator();
 var test = generator.CreateClassProxy<TestA>(new TestInterceptor());
 Console.WriteLine($"GetResult:{test.GetResult(Console.ReadLine())}");
 test.GetResult2("test");
 Console.ReadKey();
 }
 }

 public class TestInterceptor : StandardInterceptor
 {
 private static NLog.Logger logger;

 protected override void PreProceed(IInvocation invocation)
 {
 Console.WriteLine(invocation.Method.Name + "执行前,入参:" + string.Join(",", invocation.Arguments));
 }

 protected override void PerformProceed(IInvocation invocation)
 {
 Console.WriteLine(invocation.Method.Name + "执行中");
 try
 {
 base.PerformProceed(invocation);
 }
 catch (Exception ex)
 {
 HandleException(ex);
 }
 }

 protected override void PostProceed(IInvocation invocation)
 {
 Console.WriteLine(invocation.Method.Name + "执行后,返回值:" + invocation.ReturnValue);
 }

 private void HandleException(Exception ex)
 {
 if (logger == null)
 {
 LoggingConfiguration config = new LoggingConfiguration();

 ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget();
 consoleTarget.Layout = "${date:format=HH\\:MM\\:ss} ${logger} ${message}";
 config.AddTarget("console", consoleTarget);

 LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget);
 config.LoggingRules.Add(rule1);
 LogManager.Configuration = config;

 logger = LogManager.GetCurrentClassLogger(); //new NLog.LogFactory().GetCurrentClassLogger();
 }
 logger.ErrorException("error",ex);
 }
 }

 public class TestA
 {
 public virtual string GetResult(string msg)
 {
 string str = $"{DateTime.Now.ToString("yyyy-mm-dd HH:mm:ss")}---{msg}";
 return str;
 }

 public virtual string GetResult2(string msg)
 {
 throw new Exception("throw Exception!");
 }
 }
}

简要说明一下代码原理,先创建ProxyGenerator类实例,从名字就看得出来,是代理类生成器,然后实例化一个基于继承自StandardInterceptor的TestInterceptor,这个TestInterceptor是一个自定义的拦截器,最后通过generator.CreateClassProxy<TestA>(new TestInterceptor())动态创建了一个继承自TestA的动态代理类,这个代理类只有在运行时才会生成的,后面就可以如代码所示,直接用动态代理类对象实例Test操作TestA的所有属性与方法,当然这里需要注意,若需要被动态代理类所代理并拦截,则父类的属性或方法必需是virtual,这点与我上面说的直接写一个代理类相同。

上述代码运行效果如下:

第三种:普通反射+利用Remoting的远程访问对象时的直实代理类来实现,代码如下,这个可能相比以上两种稍微复杂一点:

以上代码实现步骤说明:

1.这里定义的一个真实类AopClass必需继承自ContextBoundObject类,而ContextBoundObject类又直接继承自MarshalByRefObject类,表明该类是上下文绑定对象,允许在支持远程处理的应用程序中跨应用程序域边界访问对象,说白了就是可以获取这个真实类的所有信息,以便可以被生成动态代理。

2.定义继承自ProxyAttribute的代理特性标识类AopAttribute,以表明哪些类可以被代理,同时注意重写CreateInstance方法,在CreateInstance方法里实现通过委托与生成透明代理类的过程,realProxy.GetTransparentProxy() 非常重要,目的就是根据定义的AopProxy代理类获取生成透明代理类对象实例。

3.实现通用的AopProxy代理类,代理类必需继承自RealProxy类,在这个代理类里面重写Invoke方法,该方法是统一执行被代理的真实类的所有方法、属性、字段的出入口,我们只需要在该方法中根据传入的IMessage进行判断并实现相应的拦截代码即可。

4.最后在需要进行Aop拦截的类上标注AopAttribute即可(注意:被标识的类必需是如第1条说明的继承自ContextBoundObject类),在实际调用的过程中是感知不到任何的变化。且AopAttribute可以被子类继承,也就意味着所有子类都可以被代理并拦截。

如上代码运行效果如下:

这里顺便分享微软官方如果利用RealProxy类实现AOP的,详见地址:https://msdn.microsoft.com/zh-cn/library/dn574804.aspx

第四种:反射+ 通过定义统一的出入口,并运用一些特性实现AOP的效果,比如:常见的MVC、WEB API中的过滤器特性 ,我这里根据MVC的思路,实现了类似的MVC过滤器的AOP效果,只是中间用到了反射,可能性能不佳,但效果还是成功实现了各种拦截,正如MVC一样,既支持过滤器特性,也支持Controller中的Action执行前,执行后,错误等方法实现拦截

实现思路如下:

A.过滤器及Controller特定方法拦截实现原理:

1.获取程序集中所有继承自Controller的类型;

2.根据Controller的名称找到第1步中的对应的Controller的类型:FindControllerType

3.根据找到的Controller类型及Action的名称找到对应的方法:FindAction

4.创建Controller类型的实例;

5.根据Action方法找到定义在方法上的所有过滤器特性(包含:执行前、执行后、错误)

6.执行Controller中的OnActionExecuting方法,随后执行执行前的过滤器特性列表,如:ActionExecutingFilter

7.执行Action方法,获得结果;

8.执行Controller中的OnActionExecuted方法,随后执行执行后的过滤器特性列表,如:ActionExecutedFilter

9.通过try catch在catch中执行Controller中的OnActionError方法,随后执行错误过滤器特性列表,如:ActionErrorFilter

10.最后返回结果;

B.实现执行路由配置效果原理:

1.增加可设置路由模板列表方法:AddExecRouteTemplate,在方法中验证controller、action,并获取模板中的占位符数组,最后保存到类全局对象中routeTemplates;

2.增加根据执行路由执行对应的Controller中的Action方法的效果:Run,在该方法中主要遍历所有路由模板,然后与实行执行的请求路由信息通过正则匹配,若匹配OK,并能正确找到Controller及Action,则说明正确,并最终统一调用:Process方法,执行A中的所有步骤最终返回结果。

需要说明该模拟MVC方案并没有实现Action方法参数的的绑定功能,因为ModelBinding本身就是比较复杂的机制,所以这里只是为了搞清楚AOP的实现原理,故不作这方面的研究,大家如果有空可以实现,最终实现MVC不仅是ASP.NET MVC,还可以是Console MVC,甚至是Winform MVC等。

以下是实现的全部代码,代码中我已进行了一些基本的优化,可以直接使用:

public abstract class Controller
{
 public virtual void OnActionExecuting(MethodInfo action)
 {

 }

 public virtual void OnActionExecuted(MethodInfo action)
 {

 }

 public virtual void OnActionError(MethodInfo action, Exception ex)
 {

 }

}

public abstract class FilterAttribute : Attribute
{
 public abstract string FilterType { get; }
 public abstract void Execute(Controller ctrller, object extData);
}

public class ActionExecutingFilter : FilterAttribute
{
 public override string FilterType => "BEFORE";

 public override void Execute(Controller ctrller, object extData)
 {
 Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutingFilter中拦截发出的消息!-{DateTime.Now.ToString()}");
 }
}

public class ActionExecutedFilter : FilterAttribute
{
 public override string FilterType => "AFTER";

 public override void Execute(Controller ctrller, object extData)
 {
 Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutedFilter中拦截发出的消息!-{DateTime.Now.ToString()}");
 }
}

public class ActionErrorFilter : FilterAttribute
{
 public override string FilterType => "EXCEPTION";

 public override void Execute(Controller ctrller, object extData)
 {
 Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionErrorFilter中拦截发出的消息!-{DateTime.Now.ToString()}-Error Msg:{(extData as Exception).Message}");
 }
}

public class AppContext
{
 private static readonly Type ControllerType = typeof(Controller);
 private static readonly Dictionary<string, Type> matchedControllerTypes = new Dictionary<string, Type>();
 private static readonly Dictionary<string, MethodInfo> matchedControllerActions = new Dictionary<string, MethodInfo>();
 private Dictionary<string,string[]> routeTemplates = new Dictionary<string, string[]>();

 public void AddExecRouteTemplate(string execRouteTemplate)
 {
 if (!Regex.IsMatch(execRouteTemplate, "{controller}", RegexOptions.IgnoreCase))
 {
  throw new ArgumentException("执行路由模板不正确,缺少{controller}");
 }

 if (!Regex.IsMatch(execRouteTemplate, "{action}", RegexOptions.IgnoreCase))
 {
  throw new ArgumentException("执行路由模板不正确,缺少{action}");
 }

 string[] keys = Regex.Matches(execRouteTemplate, @"(?<={)\w+(?=})", RegexOptions.IgnoreCase).Cast<Match>().Select(c => c.Value.ToLower()).ToArray();

 routeTemplates.Add(execRouteTemplate,keys);
 }

 public object Run(string execRoute)
 {
 //{controller}/{action}/{id}
 string ctrller = null;
 string actionName = null;
 ArrayList args = null;
 Type controllerType = null;
 bool findResult = false;

 foreach (var r in routeTemplates)
 {
  string[] keys = r.Value;
  string execRoutePattern = Regex.Replace(r.Key, @"{(?<key>\w+)}", (m) => string.Format(@"(?<{0}>.[^/\\]+)", m.Groups["key"].Value.ToLower()), RegexOptions.IgnoreCase);

  args = new ArrayList();
  if (Regex.IsMatch(execRoute, execRoutePattern))
  {
  var match = Regex.Match(execRoute, execRoutePattern);
  for (int i = 0; i < keys.Length; i++)
  {
   if ("controller".Equals(keys[i], StringComparison.OrdinalIgnoreCase))
   {
   ctrller = match.Groups["controller"].Value;
   }
   else if ("action".Equals(keys[i], StringComparison.OrdinalIgnoreCase))
   {
   actionName = match.Groups["action"].Value;
   }
   else
   {
   args.Add(match.Groups[keys[i]].Value);
   }
  }

  if ((controllerType = FindControllerType(ctrller)) != null && FindAction(controllerType, actionName, args.ToArray()) != null)
  {
   findResult = true;
   break;
  }
  }
 }

 if (findResult)
 {
  return Process(ctrller, actionName, args.ToArray());
 }
 else
 {
  throw new Exception($"在已配置的路由模板列表中未找到与该执行路由相匹配的路由信息:{execRoute}");
 }
 }

 public object Process(string ctrller, string actionName, params object[] args)
 {
 Type matchedControllerType = FindControllerType(ctrller);

 if (matchedControllerType == null)
 {
  throw new ArgumentException($"未找到类型为{ctrller}的Controller类型");
 }

 object execResult = null;
 if (matchedControllerType != null)
 {
  var matchedController = (Controller)Activator.CreateInstance(matchedControllerType);
  MethodInfo action = FindAction(matchedControllerType, actionName, args);
  if (action == null)
  {
  throw new ArgumentException($"在{matchedControllerType.FullName}中未找到与方法名:{actionName}及参数个数:{args.Count()}相匹配的方法");
  }

  var filters = action.GetCustomAttributes<FilterAttribute>(true);
  List<FilterAttribute> execBeforeFilters = new List<FilterAttribute>();
  List<FilterAttribute> execAfterFilters = new List<FilterAttribute>();
  List<FilterAttribute> exceptionFilters = new List<FilterAttribute>();

  if (filters != null && filters.Count() > 0)
  {
  execBeforeFilters = filters.Where(f => f.FilterType == "BEFORE").ToList();
  execAfterFilters = filters.Where(f => f.FilterType == "AFTER").ToList();
  exceptionFilters = filters.Where(f => f.FilterType == "EXCEPTION").ToList();
  }

  try
  {
  matchedController.OnActionExecuting(action);

  if (execBeforeFilters != null && execBeforeFilters.Count > 0)
  {
   execBeforeFilters.ForEach(f => f.Execute(matchedController, null));
  }

  var mParams = action.GetParameters();
  object[] newArgs = new object[args.Length];
  for (int i = 0; i < mParams.Length; i++)
  {
   newArgs[i] = Convert.ChangeType(args[i], mParams[i].ParameterType);
  }

  execResult = action.Invoke(matchedController, newArgs);

  matchedController.OnActionExecuted(action);

  if (execBeforeFilters != null && execBeforeFilters.Count > 0)
  {
   execAfterFilters.ForEach(f => f.Execute(matchedController, null));
  }

  }
  catch (Exception ex)
  {
  matchedController.OnActionError(action, ex);

  if (exceptionFilters != null && exceptionFilters.Count > 0)
  {
   exceptionFilters.ForEach(f => f.Execute(matchedController, ex));
  }
  }

 }

 return execResult;

 }

 private Type FindControllerType(string ctrller)
 {
 Type matchedControllerType = null;
 if (!matchedControllerTypes.ContainsKey(ctrller))
 {
  var assy = Assembly.GetAssembly(typeof(Controller));

  foreach (var m in assy.GetModules(false))
  {
  foreach (var t in m.GetTypes())
  {
   if (ControllerType.IsAssignableFrom(t) && !t.IsAbstract)
   {
   if (t.Name.Equals(ctrller, StringComparison.OrdinalIgnoreCase) || t.Name.Equals($"{ctrller}Controller", StringComparison.OrdinalIgnoreCase))
   {
    matchedControllerType = t;
    matchedControllerTypes[ctrller] = matchedControllerType;
    break;
   }
   }
  }
  }
 }
 else
 {
  matchedControllerType = matchedControllerTypes[ctrller];
 }

 return matchedControllerType;
 }

 private MethodInfo FindAction(Type matchedControllerType, string actionName, object[] args)
 {
 string ctrlerWithActionKey = $"{matchedControllerType.FullName}.{actionName}";
 MethodInfo action = null;
 if (!matchedControllerActions.ContainsKey(ctrlerWithActionKey))
 {
  if (args == null) args = new object[0];
  foreach (var m in matchedControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public))
  {
  if (m.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase) && m.GetParameters().Length == args.Length)
  {
   action = m;
   matchedControllerActions[ctrlerWithActionKey] = action;
   break;
  }
  }
 }
 else
 {
  action = matchedControllerActions[ctrlerWithActionKey];
 }

 return action;
 }
}

使用前,先定义一个继承自Controller的类,如:TestController,并重写相应的方法,或在指定的方法上加上所需的过滤器特性,如下代码所示:

public class TestController : Controller
{
 public override void OnActionExecuting(MethodInfo action)
 {
 Console.WriteLine($"{action.Name}执行前,OnActionExecuting---{DateTime.Now.ToString()}");
 }

 public override void OnActionExecuted(MethodInfo action)
 {
 Console.WriteLine($"{action.Name}执行后,OnActionExecuted--{DateTime.Now.ToString()}");
 }

 public override void OnActionError(MethodInfo action, Exception ex)
 {
 Console.WriteLine($"{action.Name}执行,OnActionError--{DateTime.Now.ToString()}:{ex.Message}");
 }

 [ActionExecutingFilter]
 [ActionExecutedFilter]
 public string HelloWorld(string name)
 {
 return ($"Hello World!->{name}");
 }

 [ActionExecutingFilter]
 [ActionExecutedFilter]
 [ActionErrorFilter]
 public string TestError(string name)
 {
 throw new Exception("这是测试抛出的错误信息!");
 }

 [ActionExecutingFilter]
 [ActionExecutedFilter]
 public int Add(int a, int b)
 {
 return a + b;
 }
}

最后前端实际调用就非常简单了,代码如下:

class MVCProgram
{
 static void Main(string[] args)
 {
 try
 {
  var appContext = new AppContext();
  object rs = appContext.Process("Test", "HelloWorld", "梦在旅途");
  Console.WriteLine($"Process执行的结果1:{rs}");

  Console.WriteLine("=".PadRight(50, '='));

  appContext.AddExecRouteTemplate("{controller}/{action}/{name}");
  appContext.AddExecRouteTemplate("{action}/{controller}/{name}");

  object result1 = appContext.Run("HelloWorld/Test/梦在旅途-zuowenjun.cn");
  Console.WriteLine($"执行的结果1:{result1}");

  Console.WriteLine("=".PadRight(50, '='));

  object result2 = appContext.Run("Test/HelloWorld/梦在旅途-zuowenjun.cn");
  Console.WriteLine($"执行的结果2:{result2}");

  Console.WriteLine("=".PadRight(50, '='));

  appContext.AddExecRouteTemplate("{action}/{controller}/{a}/{b}");
  object result3 = appContext.Run("Add/Test/500/20");
  Console.WriteLine($"执行的结果3:{result3}");

  object result4 = appContext.Run("Test/TestError/梦在旅途-zuowenjun.cn");
  Console.WriteLine($"执行的结果4:{result4}");
 }
 catch (Exception ex)
 {
  Console.ForegroundColor = ConsoleColor.Red;
  Console.WriteLine($"发生错误:{ex.Message}");
  Console.ResetColor();
 }

 Console.ReadKey();
 }
}

可以看到,与ASP.NET MVC有点类似,只是ASP.NET MVC是通过URL访问,而这里是通过AppContext.Run 执行路由URL 或Process方法,直接指定Controller、Action、参数来执行。

通过以上调用代码可以看出路由配置还是比较灵活的,当然参数配置除外。如果大家有更好的想法也可以在下方评论交流,谢谢!

MVC代码执行效果如下:

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • C#开源的AOP框架--KingAOP基础

    AOP面向切面编程(Aspect Oriented Programming),是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.Spring框架用的核心技术就是AOP,是函数式编程的一种衍生范型.利用AOP的好处就是可以对业务逻辑进行隔离,降低耦合度,提高程序的可重用性,同时提高了开发的效率.开源的AOP也有不少,我这里用的KingAOP. 1 项目结构 2 定义一个日志记录的实体类User和LoggingAspect切面日志类 namespace AOPDemo.Logging

  • 利用AOP实现SqlSugar自动事务

    本文实例为大家分享了如何利用AOP实现SqlSugar自动事务,供大家参考,具体内容如下 先看一下效果,带接口层的三层架构: BL层: public class StudentBL : IStudentService { private ILogger mLogger; private readonly IStudentDA mStudentDa; private readonly IValueService mValueService; public StudentService(IStude

  • 利用C#实现AOP常见的几种方法详解

    前言 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的中统一处理业务逻辑的一种技术,比较常见的场景是:日志记录,错误捕获.性能监控等 AOP的本质是通过代理对象来间接执行真实对象,在代理类中往往会添加装饰一些额外的业务代码,比如如下代码: class RealA { public virtual string Pro { get; set; } public virtual void ShowHello(str

  • Java实现AOP代理的三种方式详解

    目录 1.JDK实现 2.CGLIB实现 3.boot注解实现[注意只对bean有效] 业务场景:首先你有了一个非常好的前辈无时无刻的在“教育”你.有这么一天,它叫你将它写好的一个方法进行改进测试,这时出现了功能迭代的情况.然后前辈好好“教育”你的说,不行改我的代码!改就腿打折!悲催的你有两条路可走,拿出你10年跆拳道的功夫去火拼一波然后拍拍屁股潇洒走人,要么就是悲催的开始百度...这时你会发现,我擦怎么把AOP代理这种事给忘了?[其实在我们工作中很少去手写它,但是它又是很常见的在使用(控制台日

  • Python实现解析参数的三种方法详解

    目录 先决条件 使用 argparse 使用 JSON 文件 使用 YAML 文件 最后的想法 今天我们分享的主要目的就是通过在 Python 中使用命令行和配置文件来提高代码的效率 Let's go! 我们以机器学习当中的调参过程来进行实践,有三种方式可供选择.第一个选项是使用 argparse,它是一个流行的 Python 模块,专门用于命令行解析:另一种方法是读取 JSON 文件,我们可以在其中放置所有超参数:第三种也是鲜为人知的方法是使用 YAML 文件!好奇吗,让我们开始吧! 先决条件

  • Python识别二维码的两种方法详解

    目录 前言 pyzbar + PIL cv2 前言 最近在搜寻资料时,发现了一则10年前的新闻:二维码将成线上线下关键入口.从今天的移动互联网来看,支付收款码/健康码等等与我们息息相关,二维码确实成为了我们生活中不可或缺的一部分. 在学习Python处理二维码的过程中,我们看到的大多是“用python生成酷炫二维码”.“用Python制作动图二维码”之类的文章.而关于使用Python批量识别二维码的教程,并不多见.所以今天我会给大家分享两种批量识别二维码的Python技巧! pyzbar + P

  • Java唤醒本地应用的两种方法详解

    目录 引言 1. Runtime使用方式 2. ProcessBuilder使用方式 3. 小结 引言 作为一个后端同学,经常被安全的小伙伴盯上,找一找安全漏洞:除了常说的注入之外,还有比较吓人的执行远程命令,唤醒本地应用程序等:然后有意思的问题就来了,写了这么多年的代码,好像还真没有尝试过用java来唤醒本地应用程序的 比如说一个最简单的,打开本地的计算器,应该怎么搞? 接下来本文将介绍一下如何使用java打开本地应用,以及打开mac系统中特殊一点的处理方式(直白来说就是不同操作系统,使用姿势

  • Python3日期与时间戳转换的几种方法详解

    日期和时间的相互转换可以利用Python内置模块 time 和 datetime 完成,且有多种方法供我们选择,当然转换时我们可以直接利用当前时间或指定的字符串格式的时间格式. 获取当前时间转换 我们可以利用内置模块 datetime 获取当前时间,然后将其转换为对应的时间戳. import datetime import time # 获取当前时间 dtime = datetime.datetime.now() un_time = time.mktime(dtime.timetuple())

  • JS合并两个数组的3种方法详解

    这篇文章主要介绍了JS合并两个数组的3种方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 需要将两个数组合并成为一个的情况.比如: var a = [1,2,3]; var b = [4,5,6]; 有两个数组a.b,需求是将两个数组合并成一个.方法如下: 1.concat js的Array对象提供了一个叫concat()方法,连接两个或更多的数组,并返回结果. var c = a.concat(b); //c=[1,2,3,4,5,6]

  • SpringBoot处理接口幂等性的两种方法详解

    目录 1. 接口幂等性实现方案梳理 1.1 基于 Token 1.2 基于请求参数校验 2. 基于请求参数的校验 在上周发布的 TienChin 项目视频中,我和大家一共梳理了六种幂等性解决方案,接口幂等性处理算是一个非常常见的需求了,我们在很多项目中其实都会遇到.今天我们来看看两种比较简单的实现思路. 1. 接口幂等性实现方案梳理 其实接口幂等性的实现方案还是蛮多的,我这里和小伙伴们分享两种比较常见的方案. 1.1 基于 Token 基于 Token 这种方案的实现思路很简单,整个流程分两步:

  • Python比较两个日期的两种方法详解

    目录 datetime strptime 之前我们曾经分享过:Python获取某一日期是“星期几”的6种方法!实际上,在我们使用Python处理日期/时间的时候,经常会遇到各种各样的问题.今天我们就来探讨另一个问题,如何用Python比较两个日期? datetime 如果需要用Python处理日期和时间,大家肯定会先想到datetime.time.calendar等模块.在这其中,datetime模块主要是用来表示日期时间的,就是我们常说的年月日/时分秒. datetime模块中常用的类: 类名

  • JavaScript中数组去重常用的五种方法详解

    目录 1.对象属性(indexof) 2.new Set(数组) 3.new Map() 4.filter() + indexof 5.reduce() + includes 补充 原数组 const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}]; 1.对象属性(indexof) 利用对象属性key排除重复项 遍历数组,每次判断新数组中是否存在该属性,不存在就存储在新数组中 并把数组元素作为key,最后返

随机推荐