[preface]
AOP is the abbreviation of Aspect Oriented Programming, which means Aspect Oriented Programming technology.
What is section?
A code segment without any coupling with the business, such as calling log, sending mail, and even routing distribution. All codes that can be owned by the code and can be fully decoupled from the code can be regarded as the aspect of a business code.
Why do we need AOP?
Let's start with an example:
If we want to collect user operation behavior, we need to master the information of each interface called by the user. What are we going to do at this time?
If AOP technology is not adopted, it is also the simplest. In the first sentence of all method bodies, call a log interface to transfer the method information to the record.
What's the problem?
There is no problem in realizing the business, but the code is bloated and difficult to adjust and maintain.
If we adopt AOP technology, we can inject all classes to collect logs at the start of the system. Before each method call, the AOP framework will automatically call our log code.
Does it save a lot of redundant and useless work? The code will also become very easy to maintain (no need one day, just comment out the relevant code)
Next, let's take a look at the working principle and real process of AOP framework.
[implementation ideas]
AOP framework is generally implemented through static agent and dynamic agent.
What is a static proxy?
Static proxy, also known as compile time proxy, is the way that proxy classes already exist at compile time and are called directly at run time. The popular point is to write code manually to implement the proxy class.
Let's show the implementation process of static agent through an example:
We have a business class with the method Test(). We need to output the log before and after the Test call.
Since we want to take Log as a perspective, we must not move the original business code, which also violates the opening and closing principle of object-oriented design.
So what are we going to do? We define a new class BusinessProxy to wrap this class. In order to distinguish and identify multiple methods, the method is also called Test ()
In this way, if we want to add Log to the methods in all Business classes, we will add corresponding methods to wrap in the BusinessProxy proxy class. It does not destroy the original logic, but also realizes the function of front and back logs.
Of course, we can have a more elegant implementation:
We can define proxy classes that inherit from business classes. Define the methods in the business class as virtual methods. Then we can override the method of the parent class and call the original method of the parent class after adding the log.
Of course, we have more elegant implementation methods:
We can use reflection technology to write a general Invoke method, through which all methods can be called.
In this way, we implement a static proxy.
Now that we have static agents, why do we have dynamic agents?
We carefully review the implementation process of static proxy. To add facets to all methods, we need to override all business methods in the proxy class. What's more, we have N business classes, so we need to define N agent classes. This is a huge workload.
This is the background of the emergence of dynamic agent. You can guess that dynamic agent is to automate this series of cumbersome steps and let the program automatically generate agent classes for us.
What is dynamic agent?
Dynamic agent, also known as runtime agent. During the running of the program, the code of the proxy class is called, and the agent class of the business class is automatically generated. We don't need to write it together, which greatly improves the work efficiency and adjusts the mentality of programmers.
Needless to say, the principle is to dynamically generate the code of static agent. What we need to do is to choose a way to generate code.
Today, I share a simple AOP framework. The code is generated by Emit. Of course, the writing method of Emit code is not the main content to be talked about today. We need to learn it in advance.
Let's start with the effect:
Define an Action attribute class, ActionAttribute, which inherits from ActionBaseAttribute and outputs two logs in the Before and After methods;
Define an Action attribute class, InterceptorAttribute, which inherits from InterceptorBaseAttribute, which captures method call exceptions and outputs logs before and after execution;
Then define a business class, which implements the IBusinessClass interface and defines various types of methods
The redundant method is not mapped.
We put the method call aspect label defined above on the business class, indicating that all methods under this class perform exception filtering;
We put the Action feature on the Test method, indicating that the log should be recorded during the Before and After calls of the Test () method;
We define test classes:
Try calling:
It can be seen that the Interceptor of the full class method tag has printed the corresponding log before and after the Test and GetInt method calls;
The Action method tag is only marked on the Test method, so the log is printed when the Test method Before and After are executed;
[implementation process]
The implementation idea has been explained in detail above. You can refer to the implementation idea of static agent.
We define a dynamic proxy generation class DynamicProxy, which is used to scan the original business code and generate the proxy code;
Define two filter tags, ActionBaseAttribute, and provide "Before" and "After" aspect methods; InterceptorBaseAttribute, which provides the slice method of Invoke "full call" packaging;
Before can get the method and parameter list of the current call, and After can get the result after the current method invocation.
Invoke can get the object and method name and parameter list of the currently called object. Reflection dynamic calls are made here.
1 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 2 public class ActionBaseAttribute : Attribute 3 { 4 public virtual void Before(string @method, object[] parameters) { } 5 6 public virtual object After(string @method, object result) { return result; } 7 }
1 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 2 public class InterceptorBaseAttribute : Attribute 3 { 4 public virtual object Invoke(object @object, string @method, object[] parameters) 5 { 6 return @object.GetType().GetMethod(@method).Invoke(@object, parameters); 7 } 8 }
The agent generation class generates runtime IL code in the way of Emit.
Put the code here first:
1 public class DynamicProxy 2 { 3 public static TInterface CreateProxyOfRealize<TInterface, TImp>() where TImp : class, new() where TInterface : class 4 { 5 return Invoke<TInterface, TImp>(); 6 } 7 8 public static TProxyClass CreateProxyOfInherit<TProxyClass>() where TProxyClass : class, new() 9 { 10 return Invoke<TProxyClass, TProxyClass>(true); 11 } 12 13 private static TInterface Invoke<TInterface, TImp>(bool inheritMode = false) where TImp : class, new() where TInterface : class 14 { 15 var impType = typeof(TImp); 16 17 string nameOfAssembly = impType.Name + "ProxyAssembly"; 18 string nameOfModule = impType.Name + "ProxyModule"; 19 string nameOfType = impType.Name + "Proxy"; 20 21 var assemblyName = new AssemblyName(nameOfAssembly); 22 23 var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); 24 var moduleBuilder = assembly.DefineDynamicModule(nameOfModule); 25 26 //var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); 27 //var moduleBuilder = assembly.DefineDynamicModule(nameOfModule, nameOfAssembly + ".dll"); 28 29 TypeBuilder typeBuilder; 30 if (inheritMode) 31 typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, impType); 32 else 33 typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, null, new[] { typeof(TInterface) }); 34 35 InjectInterceptor<TImp>(typeBuilder, impType.GetCustomAttribute(typeof(InterceptorBaseAttribute))?.GetType(), inheritMode); 36 37 var t = typeBuilder.CreateType(); 38 39 //assembly.Save(nameOfAssembly + ".dll"); 40 41 return Activator.CreateInstance(t) as TInterface; 42 } 43 44 private static void InjectInterceptor<TImp>(TypeBuilder typeBuilder, Type interceptorAttributeType, bool inheritMode = false) 45 { 46 var impType = typeof(TImp); 47 // ---- define fields ---- 48 FieldBuilder fieldInterceptor = null; 49 if (interceptorAttributeType != null) 50 { 51 fieldInterceptor = typeBuilder.DefineField("_interceptor", interceptorAttributeType, FieldAttributes.Private); 52 } 53 // ---- define costructors ---- 54 if (interceptorAttributeType != null) 55 { 56 var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null); 57 var ilOfCtor = constructorBuilder.GetILGenerator(); 58 59 ilOfCtor.Emit(OpCodes.Ldarg_0); 60 ilOfCtor.Emit(OpCodes.Newobj, interceptorAttributeType.GetConstructor(new Type[0])); 61 ilOfCtor.Emit(OpCodes.Stfld, fieldInterceptor); 62 ilOfCtor.Emit(OpCodes.Ret); 63 } 64 65 // ---- define methods ---- 66 67 var methodsOfType = impType.GetMethods(BindingFlags.Public | BindingFlags.Instance); 68 69 string[] ignoreMethodName = new[] { "GetType", "ToString", "GetHashCode", "Equals" }; 70 71 foreach (var method in methodsOfType) 72 { 73 //ignore method 74 if (ignoreMethodName.Contains(method.Name)) 75 return; 76 77 var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); 78 79 MethodAttributes methodAttributes; 80 81 if (inheritMode) 82 methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual; 83 else 84 methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final; 85 86 var methodBuilder = typeBuilder.DefineMethod(method.Name, methodAttributes, CallingConventions.Standard, method.ReturnType, methodParameterTypes); 87 88 var ilMethod = methodBuilder.GetILGenerator(); 89 90 // set local field 91 var impObj = ilMethod.DeclareLocal(impType); //instance of imp object 92 var methodName = ilMethod.DeclareLocal(typeof(string)); //instance of method name 93 var parameters = ilMethod.DeclareLocal(typeof(object[])); //instance of parameters 94 var result = ilMethod.DeclareLocal(typeof(object)); //instance of result 95 LocalBuilder actionAttributeObj = null; 96 97 //attribute init 98 Type actionAttributeType = null; 99 if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null || impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null) 100 { 101 //method can override class attrubute 102 if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null) 103 { 104 actionAttributeType = method.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType(); 105 } 106 else if (impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null) 107 { 108 actionAttributeType = impType.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType(); 109 } 110 111 actionAttributeObj = ilMethod.DeclareLocal(actionAttributeType); 112 ilMethod.Emit(OpCodes.Newobj, actionAttributeType.GetConstructor(new Type[0])); 113 ilMethod.Emit(OpCodes.Stloc, actionAttributeObj); 114 } 115 116 //instance imp 117 ilMethod.Emit(OpCodes.Newobj, impType.GetConstructor(new Type[0])); 118 ilMethod.Emit(OpCodes.Stloc, impObj); 119 120 //if no attribute 121 if (fieldInterceptor != null || actionAttributeObj != null) 122 { 123 ilMethod.Emit(OpCodes.Ldstr, method.Name); 124 ilMethod.Emit(OpCodes.Stloc, methodName); 125 126 ilMethod.Emit(OpCodes.Ldc_I4, methodParameterTypes.Length); 127 ilMethod.Emit(OpCodes.Newarr, typeof(object)); 128 ilMethod.Emit(OpCodes.Stloc, parameters); 129 130 // build the method parameters 131 for (var j = 0; j < methodParameterTypes.Length; j++) 132 { 133 ilMethod.Emit(OpCodes.Ldloc, parameters); 134 ilMethod.Emit(OpCodes.Ldc_I4, j); 135 ilMethod.Emit(OpCodes.Ldarg, j + 1); 136 //box 137 ilMethod.Emit(OpCodes.Box, methodParameterTypes[j]); 138 ilMethod.Emit(OpCodes.Stelem_Ref); 139 } 140 } 141 142 //dynamic proxy action before 143 if (actionAttributeType != null) 144 { 145 //load arguments 146 ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj); 147 ilMethod.Emit(OpCodes.Ldloc, methodName); 148 ilMethod.Emit(OpCodes.Ldloc, parameters); 149 ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("Before")); 150 } 151 152 if (interceptorAttributeType != null) 153 { 154 //load arguments 155 ilMethod.Emit(OpCodes.Ldarg_0);//this 156 ilMethod.Emit(OpCodes.Ldfld, fieldInterceptor); 157 ilMethod.Emit(OpCodes.Ldloc, impObj); 158 ilMethod.Emit(OpCodes.Ldloc, methodName); 159 ilMethod.Emit(OpCodes.Ldloc, parameters); 160 // call Invoke() method of Interceptor 161 ilMethod.Emit(OpCodes.Callvirt, interceptorAttributeType.GetMethod("Invoke")); 162 } 163 else 164 { 165 //direct call method 166 if (method.ReturnType == typeof(void) && actionAttributeType == null) 167 { 168 ilMethod.Emit(OpCodes.Ldnull); 169 } 170 171 ilMethod.Emit(OpCodes.Ldloc, impObj); 172 for (var j = 0; j < methodParameterTypes.Length; j++) 173 { 174 ilMethod.Emit(OpCodes.Ldarg, j + 1); 175 } 176 ilMethod.Emit(OpCodes.Callvirt, impType.GetMethod(method.Name)); 177 //box 178 if (actionAttributeType != null) 179 { 180 if (method.ReturnType != typeof(void)) 181 ilMethod.Emit(OpCodes.Box, method.ReturnType); 182 else 183 ilMethod.Emit(OpCodes.Ldnull); 184 } 185 } 186 187 //dynamic proxy action after 188 if (actionAttributeType != null) 189 { 190 ilMethod.Emit(OpCodes.Stloc, result); 191 //load arguments 192 ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj); 193 ilMethod.Emit(OpCodes.Ldloc, methodName); 194 ilMethod.Emit(OpCodes.Ldloc, result); 195 ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("After")); 196 } 197 198 // pop the stack if return void 199 if (method.ReturnType == typeof(void)) 200 { 201 ilMethod.Emit(OpCodes.Pop); 202 } 203 else 204 { 205 //unbox,if direct invoke,no box 206 if (fieldInterceptor != null || actionAttributeObj != null) 207 { 208 if (method.ReturnType.IsValueType) 209 ilMethod.Emit(OpCodes.Unbox_Any, method.ReturnType); 210 else 211 ilMethod.Emit(OpCodes.Castclass, method.ReturnType); 212 } 213 } 214 // complete 215 ilMethod.Emit(OpCodes.Ret); 216 } 217 } 218 }
There are two proxy methods, one is interface oriented implementation, the other is inheritance and rewriting.
However, in the way of inheritance rewriting, all methods of the business class need to be written as virtual virtual methods, and the dynamic class will override this method.
We get the proxy class dll generated at runtime from the Demo in the previous section and decompile it with ILSpy to view the source code:
You can see that our proxy class calls the methods in our feature tag respectively.
Core code analysis (the source code has been pasted on the folding part above):
Explanation: if the method has an action tag, load the action tag, instantiate the object, load the parameters, and execute the Before method; If the method has an Interceptor tag, use the class field this_ The Interceptor calls the Invoke method of the tag.
Explanation: if the Interceptor property label of the face does not exist, the parameters corresponding to the currently scanned method will be loaded and the method will be called directly; If the Action tag exists, wrap the result just called as an object object and pass it to the After method.
Here, if the target parameter is the object type and the actual parameter is the explicit value type returned by the direct call, the boxing operation is required. Otherwise, the call memory error exception will be reported when running.
Explanation: if the return value is of void type, it ends directly and returns the result; If the return value is a value type, you need to unpack it manually. If it is a reference type, you need to convert it.
The details of IL implementation are not discussed here.
[system test]
1. Interface implementation mode, Api test (different types of method calls corresponding to various tag usage modes):
Conclusion: for the above exhaustive types, various tag use methods have successfully logged;
2. Inheritance mode, Api test (different types of method calls corresponding to various tag usage modes):
Conclusion: the effect of inheritance mode and interface implementation mode is the same, but the method needs different implementation adjustment;
3. Performance results of directly calling three methods millions of times:
Conclusion: it takes 58ms to directly call the three methods one million times
4. Using the implementation interface mode, the results of three methods are called millions of times
Conclusion: the results are shown in the figure above. It should be noted that the three methods have been called millions of times, that is, 300w method calls
5. Using the inheritance method, the results of three methods are called millions of times
Conclusion: the results are shown in the figure above. It should be noted that the three methods have been called millions of times, that is, 300w method calls
Facts have proved that the implementation of IL Emit has high performance.
Comprehensive analysis:
Through various call analysis, we can see where the performance loss is compared with the native method call after using the proxy. The implementation method with the biggest performance gap and the most time-consuming is to add a full class method proxy and use Invoke for full method faceting. This method is time-consuming because it uses the method of reflecting Invoke.
The method of directly adding Action proxy classes to implement Before and After is not far from the original method, and the main loss is on the disassembly box when After is triggered.
To sum up, when we use it, we try to inject AOP into a method as targeted as possible, but try not to inject AOP into all kinds of methods.
[summary]
By implementing an AOP dynamic injection framework, I have a deeper understanding of Emit. Most importantly, I have a certain understanding of the execution process of CLR IL code and benefit a lot.
Problems were also found in the process of using this method. For example, there will be problems when there are ref and out parameters, which need to be improved in the future
The source code of this article has been hosted on GitHub and can be taken by yourself (Star ~): https://github.com/sevenTiny/CodeArts (you can access the link below to get the. Net Standard cross platform version code)
Latest seventiny Bantina. AOP component code (cross platform): https://github.com/sevenTiny/SevenTiny.Bantina
The location of the code is in codearts Under CSharp partition
After VS is opened, it can be found under the EmitDynamicProxy partition; All the test items of this blog can be found in the project.
Put the source code address again for the reference of friends studying together. I hope it can help you: https://github.com/sevenTiny/CodeArts (you can access the link below to get the. Net Standard cross platform version code)
Latest seventiny Bantina. AOP component code (cross platform): https://github.com/sevenTiny/SevenTiny.Bantina
source: https://www.cnblogs.com/7tiny/p/9657451.html