1. 回顾 AOP 是什么?

维基百科解释如下:

面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。

面向切面的程序设计将代码逻辑切分为不同的模块(即关注点(Concern),一段特定的逻辑功能)。几乎所有的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点“横切”程序代码中的数个模块,即在多个模块中都有出现,它们即被称作“横切关注点(Cross-cutting concerns, Horizontal concerns)”。

日志功能即是横切关注点的一个典型案例,因为日志功能往往横跨系统中的每个业务模块,即“横切”所有有日志需求的类及方法体。而对于一个信用卡应用程序来说,存款、取款、帐单管理是它的核心关注点,日志和持久化将成为横切整个对象结构的横切关注点。

参见: https://zh.wikipedia.org/wiki/面向切面的程序设计

简单来说,就是功能上我们要加其他感觉和原本功能无关的逻辑,比如性能日志,代码混在一起,看着不爽,影响我们理解。

举个例子, 如下代码我们要多花几眼时间才能看明白:

  1. public int doAMethod(int n)
  2. {
  3. int sum = 0;
  4. for (int i = 1; i <= n; i++)
  5. {
  6. if (n % i == 0)
  7. {
  8. sum += 1;
  9. }
  10. }
  11. if (sum == 2)
  12. {
  13. return sum;
  14. }
  15. else
  16. {
  17. return -1;
  18. }
  19. }

然后我们需要记录一系列日志,就会变成这样子:

  1. public int doAMethod(int n,Logger logger, HttpContext c, .....)
  2. {
  3. log.LogInfo($" n is {n}.");
  4. log.LogInfo($" who call {c.RequestUrl}.");
  5. log.LogInfo($" QueryString {c.QueryString}.");
  6. log.LogInfo($" Ip {c.Ip}.");
  7. log.LogInfo($" start {Datetime.Now}.");
  8. int sum = 0;
  9. for (int i = 1; i <= n; i++)
  10. {
  11. if (n % i == 0)
  12. {
  13. sum += 1;
  14. }
  15. }
  16. if (sum == 2)
  17. {
  18. return sum;
  19. }
  20. else
  21. {
  22. return -1;
  23. }
  24. log.LogInfo($" end {Datetime.Now}.");
  25. }

一下子这个方法就复杂多了,至少调用它还得找一堆貌似和方法无关的参数

AOP 的想法就是把上述方法拆分开, 让log之类的方法不在我们眼中:

  1. public int doAMethod(int n)
  2. {
  3. int sum = 0;
  4. for (int i = 1; i <= n; i++)
  5. {
  6. if (n % i == 0)
  7. {
  8. sum += 1;
  9. }
  10. }
  11. if (sum == 2)
  12. {
  13. return sum;
  14. }
  15. else
  16. {
  17. return -1;
  18. }
  19. }

AOP 让看着只调用的 doAMethod 方法实际为:

  1. public int doAMethodWithAOP(int n,Logger logger, HttpContext c, .....)
  2. {
  3. log.LogInfo($" n is {n}.");
  4. log.LogInfo($" who call {c.RequestUrl}.");
  5. log.LogInfo($" QueryString {c.QueryString}.");
  6. log.LogInfo($" Ip {c.Ip}.");
  7. log.LogInfo($" start {Datetime.Now}.");
  8. return doAMethod(n);
  9. log.LogInfo($" end {Datetime.Now}.");
  10. }

所以AOP 实际就是干这个事情,

无论语言,

无论实现,

其实只要干这个事不就是AOP吗?

2. 类似AOP想法的实现方式分类

达到AOP要做的这种事情有很多种方法,下面来做个简单分类,不一定很全面哦

2.1 按照方式

2.1.1 元编程

很多语言都有内置类似这样一些“增强代码”的功能,

一般来说,从安全性和编译问题等角度考虑,大多数元编程都只允许新增代码,不允许修改。

这种都是编译器必须有才能做到。(没有的,你也可以自己写个编译器,只要你做的到)

当然元编程的概念不仅仅可以用来做类似AOP的事情,

还可以做各种你想做的事情,(只要在限制范围内能做的)

以下的例子就是生成一些新的方法。

例如 Rust / C++ 等等都具有这样的功能

例如 Rust 的文档:https://doc.rust-lang.org/stable/book/ch19-06-macros.html

  1. use hello_macro::HelloMacro;
  2. use hello_macro_derive::HelloMacro;
  3. #[derive(HelloMacro)]
  4. struct Pancakes;
  5. fn main() {
  6. Pancakes::hello_macro();
  7. }

宏实现

  1. extern crate proc_macro;
  2. use crate::proc_macro::TokenStream;
  3. use quote::quote;
  4. use syn;
  5. #[proc_macro_derive(HelloMacro)]
  6. pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
  7. let ast = syn::parse(input).unwrap();
  8. impl_hello_macro(&ast)
  9. }

csharp 的 Source Generators

新的实验特性,还在设计修改变化中

官方文档: https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md

  1. public partial class ExampleViewModel
  2. {
  3. [AutoNotify]
  4. private string _text = "private field text";
  5. [AutoNotify(PropertyName = "Count")]
  6. private int _amount = 5;
  7. }

生成器实现

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Microsoft.CodeAnalysis;
  6. using Microsoft.CodeAnalysis.CSharp;
  7. using Microsoft.CodeAnalysis.CSharp.Syntax;
  8. using Microsoft.CodeAnalysis.Text;
  9. namespace Analyzer1
  10. {
  11. [Generator]
  12. public class AutoNotifyGenerator : ISourceGenerator
  13. {
  14. private const string attributeText = @"
  15. using System;
  16. namespace AutoNotify
  17. {
  18. [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
  19. sealed class AutoNotifyAttribute : Attribute
  20. {
  21. public AutoNotifyAttribute()
  22. {
  23. }
  24. public string PropertyName { get; set; }
  25. }
  26. }
  27. ";
  28. public void Initialize(InitializationContext context)
  29. {
  30. // Register a syntax receiver that will be created for each generation pass
  31. context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
  32. }
  33. public void Execute(SourceGeneratorContext context)
  34. {
  35. // add the attribute text
  36. context.AddSource("AutoNotifyAttribute", SourceText.From(attributeText, Encoding.UTF8));
  37. // retreive the populated receiver
  38. if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
  39. return;
  40. // we're going to create a new compilation that contains the attribute.
  41. // TODO: we should allow source generators to provide source during initialize, so that this step isn't required.
  42. CSharpParseOptions options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions;
  43. Compilation compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options));
  44. // get the newly bound attribute, and INotifyPropertyChanged
  45. INamedTypeSymbol attributeSymbol = compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute");
  46. INamedTypeSymbol notifySymbol = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged");
  47. // loop over the candidate fields, and keep the ones that are actually annotated
  48. List<IFieldSymbol> fieldSymbols = new List<IFieldSymbol>();
  49. foreach (FieldDeclarationSyntax field in receiver.CandidateFields)
  50. {
  51. SemanticModel model = compilation.GetSemanticModel(field.SyntaxTree);
  52. foreach (VariableDeclaratorSyntax variable in field.Declaration.Variables)
  53. {
  54. // Get the symbol being decleared by the field, and keep it if its annotated
  55. IFieldSymbol fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol;
  56. if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)))
  57. {
  58. fieldSymbols.Add(fieldSymbol);
  59. }
  60. }
  61. }
  62. // group the fields by class, and generate the source
  63. foreach (IGrouping<INamedTypeSymbol, IFieldSymbol> group in fieldSymbols.GroupBy(f => f.ContainingType))
  64. {
  65. string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context);
  66. context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8));
  67. }
  68. }
  69. private string ProcessClass(INamedTypeSymbol classSymbol, List<IFieldSymbol> fields, ISymbol attributeSymbol, ISymbol notifySymbol, SourceGeneratorContext context)
  70. {
  71. if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default))
  72. {
  73. return null; //TODO: issue a diagnostic that it must be top level
  74. }
  75. string namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
  76. // begin building the generated source
  77. StringBuilder source = new StringBuilder($@"
  78. namespace {namespaceName}
  79. {{
  80. public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()}
  81. {{
  82. ");
  83. // if the class doesn't implement INotifyPropertyChanged already, add it
  84. if (!classSymbol.Interfaces.Contains(notifySymbol))
  85. {
  86. source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;");
  87. }
  88. // create properties for each field
  89. foreach (IFieldSymbol fieldSymbol in fields)
  90. {
  91. ProcessField(source, fieldSymbol, attributeSymbol);
  92. }
  93. source.Append("} }");
  94. return source.ToString();
  95. }
  96. private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol)
  97. {
  98. // get the name and type of the field
  99. string fieldName = fieldSymbol.Name;
  100. ITypeSymbol fieldType = fieldSymbol.Type;
  101. // get the AutoNotify attribute from the field, and any associated data
  102. AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
  103. TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value;
  104. string propertyName = chooseName(fieldName, overridenNameOpt);
  105. if (propertyName.Length == 0 || propertyName == fieldName)
  106. {
  107. //TODO: issue a diagnostic that we can't process this field
  108. return;
  109. }
  110. source.Append($@"
  111. public {fieldType} {propertyName}
  112. {{
  113. get
  114. {{
  115. return this.{fieldName};
  116. }}
  117. set
  118. {{
  119. this.{fieldName} = value;
  120. this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof({propertyName})));
  121. }}
  122. }}
  123. ");
  124. string chooseName(string fieldName, TypedConstant overridenNameOpt)
  125. {
  126. if (!overridenNameOpt.IsNull)
  127. {
  128. return overridenNameOpt.Value.ToString();
  129. }
  130. fieldName = fieldName.TrimStart('_');
  131. if (fieldName.Length == 0)
  132. return string.Empty;
  133. if (fieldName.Length == 1)
  134. return fieldName.ToUpper();
  135. return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
  136. }
  137. }
  138. /// <summary>
  139. /// Created on demand before each generation pass
  140. /// </summary>
  141. class SyntaxReceiver : ISyntaxReceiver
  142. {
  143. public List<FieldDeclarationSyntax> CandidateFields { get; } = new List<FieldDeclarationSyntax>();
  144. /// <summary>
  145. /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
  146. /// </summary>
  147. public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
  148. {
  149. // any field with at least one attribute is a candidate for property generation
  150. if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax
  151. && fieldDeclarationSyntax.AttributeLists.Count > 0)
  152. {
  153. CandidateFields.Add(fieldDeclarationSyntax);
  154. }
  155. }
  156. }
  157. }
  158. }

2.1.2 修改代码

代码文件修改

一般来说,很少有这样实现的,代码文件都改了,我们码农还怎么写bug呀。

中间语言修改

有很多语言编译的结果并不是直接的机器码,而是优化后的一个接近底层的中间层语言,方便扩展支持不同cpu,不同机器架构。

比如 dotnet 的 IL

  1. .class private auto ansi '<Module>'
  2. {
  3. } // end of class <Module>
  4. .class public auto ansi beforefieldinit C
  5. extends [mscorlib]System.Object
  6. {
  7. // Fields
  8. .field private initonly int32 '<x>k__BackingField'
  9. .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
  10. 01 00 00 00
  11. )
  12. // Methods
  13. .method public hidebysig specialname rtspecialname
  14. instance void .ctor () cil managed
  15. {
  16. // Method begins at RVA 0x2050
  17. // Code size 21 (0x15)
  18. .maxstack 8
  19. IL_0000: ldarg.0
  20. IL_0001: ldc.i4.5
  21. IL_0002: stfld int32 C::'<x>k__BackingField'
  22. IL_0007: ldarg.0
  23. IL_0008: call instance void [mscorlib]System.Object::.ctor()
  24. IL_000d: ldarg.0
  25. IL_000e: ldc.i4.4
  26. IL_000f: stfld int32 C::'<x>k__BackingField'
  27. IL_0014: ret
  28. } // end of method C::.ctor
  29. .method public hidebysig specialname
  30. instance int32 get_x () cil managed
  31. {
  32. .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
  33. 01 00 00 00
  34. )
  35. // Method begins at RVA 0x2066
  36. // Code size 7 (0x7)
  37. .maxstack 8
  38. IL_0000: ldarg.0
  39. IL_0001: ldfld int32 C::'<x>k__BackingField'
  40. IL_0006: ret
  41. } // end of method C::get_x
  42. // Properties
  43. .property instance int32 x()
  44. {
  45. .get instance int32 C::get_x()
  46. }
  47. } // end of class C

比如 java 的字节码 (反编译的结果)

  1. Classfile /E:/JavaCode/TestProj/out/production/TestProj/com/rhythm7/Main.class
  2. Last modified 2018-4-7; size 362 bytes
  3. MD5 checksum 4aed8540b098992663b7ba08c65312de
  4. Compiled from "Main.java"
  5. public class com.rhythm7.Main
  6. minor version: 0
  7. major version: 52
  8. flags: ACC_PUBLIC, ACC_SUPER
  9. Constant pool:
  10. #1 = Methodref #4.#18 // java/lang/Object."<init>":()V
  11. #2 = Fieldref #3.#19 // com/rhythm7/Main.m:I
  12. #3 = Class #20 // com/rhythm7/Main
  13. #4 = Class #21 // java/lang/Object
  14. #5 = Utf8 m
  15. #6 = Utf8 I
  16. #7 = Utf8 <init>
  17. #8 = Utf8 ()V
  18. #9 = Utf8 Code
  19. #10 = Utf8 LineNumberTable
  20. #11 = Utf8 LocalVariableTable
  21. #12 = Utf8 this
  22. #13 = Utf8 Lcom/rhythm7/Main;
  23. #14 = Utf8 inc
  24. #15 = Utf8 ()I
  25. #16 = Utf8 SourceFile
  26. #17 = Utf8 Main.java
  27. #18 = NameAndType #7:#8 // "<init>":()V
  28. #19 = NameAndType #5:#6 // m:I
  29. #20 = Utf8 com/rhythm7/Main
  30. #21 = Utf8 java/lang/Object
  31. {
  32. private int m;
  33. descriptor: I
  34. flags: ACC_PRIVATE
  35. public com.rhythm7.Main();
  36. descriptor: ()V
  37. flags: ACC_PUBLIC
  38. Code:
  39. stack=1, locals=1, args_size=1
  40. 0: aload_0
  41. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  42. 4: return
  43. LineNumberTable:
  44. line 3: 0
  45. LocalVariableTable:
  46. Start Length Slot Name Signature
  47. 0 5 0 this Lcom/rhythm7/Main;
  48. public int inc();
  49. descriptor: ()I
  50. flags: ACC_PUBLIC
  51. Code:
  52. stack=2, locals=1, args_size=1
  53. 0: aload_0
  54. 1: getfield #2 // Field m:I
  55. 4: iconst_1
  56. 5: iadd
  57. 6: ireturn
  58. LineNumberTable:
  59. line 8: 0
  60. LocalVariableTable:
  61. Start Length Slot Name Signature
  62. 0 7 0 this Lcom/rhythm7/Main;
  63. }
  64. SourceFile: "Main.java"

它们也是编程语言的一种,也是可以写的,所以我们可以用来把别人方法体改了。

当然怎么改,怎么改得各种各样方法都兼容,做的人简直

生成代理代码

不修改原来的代码文件,新增代理代码实现

不修改编译好的IL 或 字节码等,往里面添加IL或字节码等形式代理代码

2.1.3 利用编译器或者运行时的功能

一般来说,也是利用编译器自身提供得扩展功能做扩展

java的 AspectJ 好像就可以利用了ajc编译器做事情

2.1.4 利用运行时功能

理论上 dotnet 也可以实现CLR Profiling API 在JIT编译时修改method body。实现真正无任何限制的运行时静态AOP (不过貌似得用C++才能做CLR Profiling API,文档少,兼容貌似也挺难做的)

2.2 按照编织时机

2.2.1 编译前

比如

  • 修改掉别人的代码文件(找死)
  • 生成新的代码,让编译器编译进去,运行时想办法用新的代码

2.2.2 编译时

  • 元编程
  • 做个编译器

2.2.3 编译后静态编织一次

根据编译好的东西(dotnet的dll或者其他语言的东西)利用反射,解析等技术生成代理实现,然后塞进去

2.2.4 运行时

严格来说,运行时也是编译后

不过不是再编织一次,而是每次运行都编织

并且没有什么 前中后了,

都是程序启动后,在具体类执行之前,把这个类编织了

比如java 的 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强。

具有aop功能的各类 IOC 容器在生成实例前创建代理实例

其实也可以在注册IOC容器时替换为代理类型

3. 代理

这里单独再说一下代理是什么,

毕竟很多AOP框架或者其他框架都有利用代理的思想,

为什么都要这样玩呢?

很简单,代理就是帮你做相同事情,并且可以比你做的更多,还一点儿都不动到你原来的代码。

比如如下 真实的class 和代理class 看起来一模一样

但两者的真实的代码可能是这样子的

  1. RealClass:
  2. public class RealClass
  3. {
  4. public virtual int Add(int i, int j)
  5. {
  6. return i + j;
  7. }
  8. }
  9. ProxyClass:
  10. public class ProxyClass : RealClass
  11. {
  12. public override int Add(int i, int j)
  13. {
  14. int r = 0;
  15. i += 7;
  16. j -= 7;
  17. r = base.Add(i, j);
  18. r += 55;
  19. return r;
  20. }
  21. }

所以我们调用的时候会是这样

AOP 有几种实现方式?的更多相关文章

  1. 适用于app.config与web.config的ConfigUtil读写工具类 基于MongoDb官方C#驱动封装MongoDbCsharpHelper类(CRUD类) 基于ASP.NET WEB API实现分布式数据访问中间层(提供对数据库的CRUD) C# 实现AOP 的几种常见方式

    适用于app.config与web.config的ConfigUtil读写工具类   之前文章:<两种读写配置文件的方案(app.config与web.config通用)>,现在重新整理一 ...

  2. JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解

    在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...

  3. AOP的两种实现方式

    技术交流群 :233513714 AOP,面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.    Aspect Oriented Progr ...

  4. C# 实现AOP 的几种常见方式

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

  5. Spring中AOP的两种代理方式(Java动态代理和CGLIB代理)

    第一种代理即Java的动态代理方式上一篇已经分析,在这里不再介绍,现在我们先来了解下GCLIB代理是什么?它又是怎样实现的?和Java动态代理有什么区别? cglib(Code Generation ...

  6. (一)spring aop的两种配置方式。

    sring aop的方式有两种:(1)xml文件配置方式(2)注解的方式实现,我们可以先通过一个demo认识spring aop的实现,然后再对其进行详细的解释. 一.基于注解的springAop配置 ...

  7. Spring AOP源码分析(二):AOP的三种配置方式与内部解析实现

    AOP配置 在应用代码中,可以通过在spring的XML配置文件applicationContext.xml或者基于注解方式来配置AOP.AOP配置的核心元素为:pointcut,advisor,as ...

  8. spring AOP 的几种实现方式(能测试)

    我们经常会用到的有如下几种 1.基于代理的AOP 2.纯简单Java对象切面 3.@Aspect注解形式的 4.注入形式的Aspcet切面 一.需要的java文件 public class ChenL ...

  9. spring AOP的两种配置方式

    连接点(JoinPoint) ,就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的前.后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点.其他 ...

随机推荐

  1. 本地Git仓库的使用方法

    一.如何将自己的项目上传到本地git仓库以及上传到GitHub上面 上传到本地git仓库步骤: 1.先配置好git:工具-->扩展和更新-->安装GitHbu Extension for ...

  2. ceph 集群快速部署

    1.三台Centos7的主机 [root@ceph-1 ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core)    2.主机 ...

  3. 使用javaxmail发送文字邮件

    package com.rupeng.javaMail; import java.util.Properties; import javax.mail.Authenticator;import jav ...

  4. css万能清除原理

    如果现在能有清理浮动的办法,但不至于在文档中多一个没有用的空标记,这时的效果是最好的!引入:after伪元素选择器,可以在指定的元素的内容添加最后一个子元素 .container:after{ } 如 ...

  5. flink:StreamGraph转换为JobGraph

    1 转换基本流程 2 简单来看可以分为两部分: 第一部分是通过一些util.translator.generator等类将职责进行解耦.托管和分离,期间涉及FlinkPipelineTranslati ...

  6. P2943 [USACO09MAR]Cleaning Up G

    一句话题意:将一个数列分成若干段,每段的不和谐度为该段内不同数字数量的平方,求不和谐度之和的最小值. 令 \(f_i\) 表示前 \(i\) 个数的最小答案,很容易就能写出暴力转移方程:\(f_i=\ ...

  7. C语言精华——内存管理,很多学校学习不到的知识~

    在编写程序时,通常并不知道需要处理的数据量,或者难以评估所需处理数据量的变动程度.在这种情况下,要达到有效的资源利用--使用内存管理,必须在运行时动态地分配所需内存,并在使用完毕后尽早释放不需要的内存 ...

  8. OllyDbg使用入门

    OllyDbg的四个窗口: http://www.360doc.com/content/16/0913/07/16447955_590419156.shtml 反汇编窗口:显示被调试程序的反汇编代码, ...

  9. python模块wifi使用小记

    安装命令 pip install wifi 连接命令 sudo wifi connect --add-hoc ssid,使用该命令会修改/etc/network/interfaces配置文件,导致启动 ...

  10. 基础篇:异步编程不会?我教你啊!CompeletableFuture

    前言 以前需要异步执行一个任务时,一般是用Thread或者线程池Executor去创建.如果需要返回值,则是调用Executor.submit获取Future.但是多个线程存在依赖组合,我们又能怎么办 ...