维基百科解释以下:html
面向切面的程序设计(Aspect-oriented programming,AOP,又译做面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提升程序代码的模块化程度。经过在现有代码基础上增长额外的通知(Advice)机制,可以对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对全部方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员可以将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不下降业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。java
面向切面的程序设计将代码逻辑切分为不一样的模块(即关注点(Concern),一段特定的逻辑功能)。几乎全部的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点“横切”程序代码中的数个模块,即在多个模块中都有出现,它们即被称做“横切关注点(Cross-cutting concerns, Horizontal concerns)”。node
日志功能便是横切关注点的一个典型案例,由于日志功能每每横跨系统中的每一个业务模块,即“横切”全部有日志需求的类及方法体。而对于一个信用卡应用程序来讲,存款、取款、账单管理是它的核心关注点,日志和持久化将成为横切整个对象结构的横切关注点。git
参见: https://zh.wikipedia.org/wiki...github
编程
简单来讲,就是功能上咱们要加其余感受和本来功能无关的逻辑,好比性能日志,代码混在一块儿,看着不爽,影响咱们理解。安全
举个例子, 以下代码咱们要多花几眼时间才能看明白:架构
public int doAMethod(int n) { int sum = 0; for (int i = 1; i <= n; i++) { if (n % i == 0) { sum += 1; } } if (sum == 2) { return sum; } else { return -1; } }
而后咱们须要记录一系列日志,就会变成这样子:框架
public int doAMethod(int n,Logger logger, HttpContext c, .....) { log.LogInfo($" n is {n}."); log.LogInfo($" who call {c.RequestUrl}."); log.LogInfo($" QueryString {c.QueryString}."); log.LogInfo($" Ip {c.Ip}."); log.LogInfo($" start {Datetime.Now}."); int sum = 0; for (int i = 1; i <= n; i++) { if (n % i == 0) { sum += 1; } } if (sum == 2) { return sum; } else { return -1; } log.LogInfo($" end {Datetime.Now}."); }
一会儿这个方法就复杂多了,至少调用它还得找一堆貌似和方法无关的参数编程语言
AOP 的想法就是把上述方法拆分开, 让log之类的方法不在咱们眼中:
public int doAMethod(int n) { int sum = 0; for (int i = 1; i <= n; i++) { if (n % i == 0) { sum += 1; } } if (sum == 2) { return sum; } else { return -1; } }
AOP 让看着只调用的 doAMethod 方法实际为:
public int doAMethodWithAOP(int n,Logger logger, HttpContext c, .....) { log.LogInfo($" n is {n}."); log.LogInfo($" who call {c.RequestUrl}."); log.LogInfo($" QueryString {c.QueryString}."); log.LogInfo($" Ip {c.Ip}."); log.LogInfo($" start {Datetime.Now}."); return doAMethod(n); log.LogInfo($" end {Datetime.Now}."); }
因此AOP 实际就是干这个事情,
不管语言,
不管实现,
其实只要干这个事不就是AOP吗?
达到AOP要作的这种事情有不少种方法,下面来作个简单分类,不必定很全面哦
不少语言都有内置相似这样一些“加强代码”的功能,
通常来讲,从安全性和编译问题等角度考虑,大多数元编程都只容许新增代码,不容许修改。
这种都是编译器必须有才能作到。(没有的,你也能够本身写个编译器,只要你作的到)
固然元编程的概念不只仅能够用来作相似AOP的事情,
还能够作各类你想作的事情,(只要在限制范围内能作的)
如下的例子就是生成一些新的方法。
例如 Rust / C++ 等等都具备这样的功能
例如 Rust 的文档:https://doc.rust-lang.org/sta...
use hello_macro::HelloMacro; use hello_macro_derive::HelloMacro; #[derive(HelloMacro)] struct Pancakes; fn main() { Pancakes::hello_macro(); }
宏实现
extern crate proc_macro; use crate::proc_macro::TokenStream; use quote::quote; use syn; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_hello_macro(&ast) }
新的实验特性,还在设计修改变化中
官方文档: https://github.com/dotnet/ros...
public partial class ExampleViewModel { [AutoNotify] private string _text = "private field text"; [AutoNotify(PropertyName = "Count")] private int _amount = 5; }
生成器实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Analyzer1 { [Generator] public class AutoNotifyGenerator : ISourceGenerator { private const string attributeText = @" using System; namespace AutoNotify { [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] sealed class AutoNotifyAttribute : Attribute { public AutoNotifyAttribute() { } public string PropertyName { get; set; } } } "; public void Initialize(InitializationContext context) { // Register a syntax receiver that will be created for each generation pass context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); } public void Execute(SourceGeneratorContext context) { // add the attribute text context.AddSource("AutoNotifyAttribute", SourceText.From(attributeText, Encoding.UTF8)); // retreive the populated receiver if (!(context.SyntaxReceiver is SyntaxReceiver receiver)) return; // we're going to create a new compilation that contains the attribute. // TODO: we should allow source generators to provide source during initialize, so that this step isn't required. CSharpParseOptions options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions; Compilation compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options)); // get the newly bound attribute, and INotifyPropertyChanged INamedTypeSymbol attributeSymbol = compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); INamedTypeSymbol notifySymbol = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); // loop over the candidate fields, and keep the ones that are actually annotated List<IFieldSymbol> fieldSymbols = new List<IFieldSymbol>(); foreach (FieldDeclarationSyntax field in receiver.CandidateFields) { SemanticModel model = compilation.GetSemanticModel(field.SyntaxTree); foreach (VariableDeclaratorSyntax variable in field.Declaration.Variables) { // Get the symbol being decleared by the field, and keep it if its annotated IFieldSymbol fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol; if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default))) { fieldSymbols.Add(fieldSymbol); } } } // group the fields by class, and generate the source foreach (IGrouping<INamedTypeSymbol, IFieldSymbol> group in fieldSymbols.GroupBy(f => f.ContainingType)) { string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); } } private string ProcessClass(INamedTypeSymbol classSymbol, List<IFieldSymbol> fields, ISymbol attributeSymbol, ISymbol notifySymbol, SourceGeneratorContext context) { if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) { return null; //TODO: issue a diagnostic that it must be top level } string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); // begin building the generated source StringBuilder source = new StringBuilder($@" namespace {namespaceName} {{ public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} {{ "); // if the class doesn't implement INotifyPropertyChanged already, add it if (!classSymbol.Interfaces.Contains(notifySymbol)) { source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); } // create properties for each field foreach (IFieldSymbol fieldSymbol in fields) { ProcessField(source, fieldSymbol, attributeSymbol); } source.Append("} }"); return source.ToString(); } private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol) { // get the name and type of the field string fieldName = fieldSymbol.Name; ITypeSymbol fieldType = fieldSymbol.Type; // get the AutoNotify attribute from the field, and any associated data AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value; string propertyName = chooseName(fieldName, overridenNameOpt); if (propertyName.Length == 0 || propertyName == fieldName) { //TODO: issue a diagnostic that we can't process this field return; } source.Append($@" public {fieldType} {propertyName} {{ get {{ return this.{fieldName}; }} set {{ this.{fieldName} = value; this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof({propertyName}))); }} }} "); string chooseName(string fieldName, TypedConstant overridenNameOpt) { if (!overridenNameOpt.IsNull) { return overridenNameOpt.Value.ToString(); } fieldName = fieldName.TrimStart('_'); if (fieldName.Length == 0) return string.Empty; if (fieldName.Length == 1) return fieldName.ToUpper(); return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1); } } /// <summary> /// Created on demand before each generation pass /// </summary> class SyntaxReceiver : ISyntaxReceiver { public List<FieldDeclarationSyntax> CandidateFields { get; } = new List<FieldDeclarationSyntax>(); /// <summary> /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation /// </summary> public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { // any field with at least one attribute is a candidate for property generation if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax && fieldDeclarationSyntax.AttributeLists.Count > 0) { CandidateFields.Add(fieldDeclarationSyntax); } } } } }
通常来讲,不多有这样实现的,代码文件都改了,咱们码农还怎么写bug呀。
有不少语言编译的结果并非直接的机器码,而是优化后的一个接近底层的中间层语言,方便扩展支持不一样cpu,不一样机器架构。
好比 dotnet 的 IL
.class private auto ansi '<Module>' { } // end of class <Module> .class public auto ansi beforefieldinit C extends [mscorlib]System.Object { // Fields .field private initonly int32 '<x>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Methods .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x2050 // Code size 21 (0x15) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.5 IL_0002: stfld int32 C::'<x>k__BackingField' IL_0007: ldarg.0 IL_0008: call instance void [mscorlib]System.Object::.ctor() IL_000d: ldarg.0 IL_000e: ldc.i4.4 IL_000f: stfld int32 C::'<x>k__BackingField' IL_0014: ret } // end of method C::.ctor .method public hidebysig specialname instance int32 get_x () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2066 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld int32 C::'<x>k__BackingField' IL_0006: ret } // end of method C::get_x // Properties .property instance int32 x() { .get instance int32 C::get_x() } } // end of class C
好比 java 的字节码 (反编译的结果)
Classfile /E:/JavaCode/TestProj/out/production/TestProj/com/rhythm7/Main.class Last modified 2018-4-7; size 362 bytes MD5 checksum 4aed8540b098992663b7ba08c65312de Compiled from "Main.java" public class com.rhythm7.Main minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#18 // java/lang/Object."<init>":()V #2 = Fieldref #3.#19 // com/rhythm7/Main.m:I #3 = Class #20 // com/rhythm7/Main #4 = Class #21 // java/lang/Object #5 = Utf8 m #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/rhythm7/Main; #14 = Utf8 inc #15 = Utf8 ()I #16 = Utf8 SourceFile #17 = Utf8 Main.java #18 = NameAndType #7:#8 // "<init>":()V #19 = NameAndType #5:#6 // m:I #20 = Utf8 com/rhythm7/Main #21 = Utf8 java/lang/Object { private int m; descriptor: I flags: ACC_PRIVATE public com.rhythm7.Main(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/rhythm7/Main; public int inc(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field m:I 4: iconst_1 5: iadd 6: ireturn LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/rhythm7/Main; } SourceFile: "Main.java"
它们也是编程语言的一种,也是能够写的,因此咱们能够用来把别人方法体改了。
固然怎么改,怎么改得各类各样方法都兼容,作的人简直👍
不修改原来的代码文件,新增代理代码实现
不修改编译好的IL 或 字节码等,往里面添加IL或字节码等形式代理代码
通常来讲,也是利用编译器自身提供得扩展功能作扩展
java的 AspectJ 好像就能够利用了ajc编译器作事情
理论上 dotnet 也能够实现CLR Profiling API 在JIT编译时修改method body。实现真正无任何限制的运行时静态AOP (不过貌似得用C++才能作CLR Profiling API,文档少,兼容貌似也挺难作的)
好比
根据编译好的东西(dotnet的dll或者其余语言的东西)利用反射,解析等技术生成代理实现,而后塞进去
严格来讲,运行时也是编译后
不过不是再编织一次,而是每次运行都编织
而且没有什么 前中后了,
都是程序启动后,在具体类执行以前,把这个类编织了
好比java 的 类加载器:在目标类被装载到JVM时,经过一个特殊的类加载器,对目标类的字节码从新“加强。
具备aop功能的各种 IOC 容器在生成实例前建立代理实例
其实也能够在注册IOC容器时替换为代理类型
这里单独再说一下代理是什么,
毕竟不少AOP框架或者其余框架都有利用代理的思想,
为何都要这样玩呢?
很简单,代理就是帮你作相同事情,而且能够比你作的更多,还一点儿都不动到你原来的代码。
好比以下 真实的class 和代理class 看起来如出一辙
但二者的真实的代码多是这样子的
RealClass: public class RealClass { public virtual int Add(int i, int j) { return i + j; } } ProxyClass: public class ProxyClass : RealClass { public override int Add(int i, int j) { int r = 0; i += 7; j -= 7; r = base.Add(i, j); r += 55; return r; } }
因此咱们调用的时候会是这样