Project address: Castle Dynamic Proxy
Castle DynamicProxy dynamically generates transparent proxy types. The entity can implement proxy classes without inheriting MarshalByRef and ContextBoundObject.
The transparent proxy function can intercept method calls. For example, NHibernate uses it to implement delayed loading.
The use of DP is very simple and reflection is not used internally. Instead, the proxy type is generated by means of Emit and delegation, and the real class method is called without too much performance loss.
Basic example
Referenced namespace:
using Castle.Core.Interceptor;using Castle.DynamicProxy;
Proxy entity class:
public class SimpleSamepleEntity{ public virtual string Name { get; set; } public virtual int Age { get; set; } public override string ToString() { return string.Format("{{ Name: \"{0}\", Age: {1} }}", this.Name, this.Age); }}
Define an interceptor and use the Interceptor to intercept the method of the proxy class:
public class CallingLogInterceptor : IInterceptor{ private int _indent = 0; private void PreProceed(IInvocation invocation) { if (this._indent > 0) Console.Write(" ".PadRight(this._indent * 4, ' ')); this._indent++; Console.Write("Intercepting: " + invocation.Method.Name + "("); if (invocation.Arguments != null && invocation.Arguments.Length > 0) for (int i = 0; i < invocation.Arguments.Length; i++) { if (i != 0) Console.Write(", "); Console.Write(invocation.Arguments[i] == null ? "null" : invocation.Arguments[i].GetType() == typeof(string) ? "\"" + invocation.Arguments[i].ToString() + "\"" : invocation.Arguments[i].ToString()); } Console.WriteLine(")"); } private void PostProceed(IInvocation invocation) { this._indent--; } public void Intercept(IInvocation invocation) { this.PreProceed(invocation); invocation.Proceed(); this.PostProceed(invocation); }}
Test code:
ProxyGenerator generator = new ProxyGenerator();CallingLogInterceptor interceptor = new CallingLogInterceptor();SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>(interceptor);entity.Name = "Richie";entity.Age = 50;Console.WriteLine("The entity is: " + entity);Console.WriteLine("Type of the entity: " + entity.GetType().FullName);Console.ReadKey();
Running result:
The above example uses the CreateClassProxy method to create a proxy object entity. The output entity type shows that its type is Castle. Proxies. SimpleSampleEntity.
When any method of the proxy object is called, The CallingLogInterceptor interceptor is used for processing, and the call log information is output in CallingLogInterceptor.
In Intercept of the interceptor interface method, you can modify the input parameter to determine whether to call the actual method and modify the returned result.
Methods that can be intercepted:
1. for Class Proxy proxies, because DP uses the inheritance and override virtual methods to implement proxies, the intercepted methods must be virtual, and non-virtual methods cannot be intercepted.
2. The Object inherits the methods of objects such as Object and MarshalByRefObject. If there is no override in the Object class, it cannot be intercepted.
DP caches dynamically created proxy types
Interceptor Pipeline
You can specify multiple interceptors when creating a proxy object. For example, you can simply implement an interceptor:
public class SimpleLogInterceptor : IInterceptor{ public void Intercept(IInvocation invocation) { Console.WriteLine(">>" + invocation.Method.Name); invocation.Proceed(); }}
Modify the test code as follows:
ProxyGenerator generator = new ProxyGenerator();SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>( new SimpleLogInterceptor(), new CallingLogInterceptor());entity.Name = "Richie";entity.Age = 50;Console.WriteLine("The entity is: " + entity);Console.WriteLine("Type of the entity: " + entity.GetType().FullName);Console.ReadKey();
Running result:
Multiple interceptors process the call sequence in the pipeline mode:
In practice, not all methods need to use all the interceptors. There are two methods for selecting the interceptor for method calls. For example:
public class InterceptorSelector : IInterceptorSelector{ public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors) { if (method.Name.StartsWith("set_")) return interceptors; else return interceptors.Where(i => i is CallingLogInterceptor).ToArray<IInterceptor>(); }}public class InterceptorFilter : IProxyGenerationHook{ public bool ShouldInterceptMethod(Type type, MethodInfo memberInfo) { return memberInfo.IsSpecialName && (memberInfo.Name.StartsWith("set_") || memberInfo.Name.StartsWith("get_")); } public void NonVirtualMemberNotification(Type type, MemberInfo memberInfo) { } public void MethodsInspected() { }}
The test code is changed:
ProxyGenerator generator = new ProxyGenerator();var options = new ProxyGenerationOptions(new InterceptorFilter()) { Selector = new InterceptorSelector() };SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>( options, new SimpleLogInterceptor(), new CallingLogInterceptor());entity.Name = "Richie";entity.Age = 50;Console.WriteLine("The entity is: " + entity);Console.WriteLine("Type of the entity: " + entity.GetType().FullName);Console.ReadKey();
Running result:
The IProxyGenerationHook interface determines whether the entire method uses the Interceptor. It is used when the proxy type is dynamically constructed. The IInterceptorSelector interface determines which interceptor should be used for a method, it is executed every time the intercepted method is called.
The preceding example only blocks the setter and getter methods, and only uses the CallingLogInterceptor interceptor for the getter method.
Proxy Types
The proxy types include class proxy, interface proxy with target, interface proxy without target, and interface proxy with interface target.
The above example uses class proxy and is created using the CreateClassProxy method. Some versions of this method require the default constructor. If the default constructor is not used, the constructor must be input. When the proxy object is of the class proxy type, the intercepted method must be virtual, and the non-virtual method cannot be intercepted.
Interface proxy with target is similar to class proxy. When a proxy object is created, the client specifies an interface and provides an object that implements this interface as a real object, DP will create the proxy object for this interface. After the proxy object method is called by the interceptor, the corresponding method of the real object will be called. Unlike class proxy, the method of a real object can be intercepted without the virtual type.
Interface proxy without target is special. You only need to specify an interface when creating a proxy. DP automatically constructs an implemented Class Based on the interface as the proxy object type, however, this proxy class can only be used to intercept objects and cannot call the actual object processing method in the interceptor like the class proxy. For example, when multiple interceptors are provided, the invocation cannot be called in the interface method of the last interceptor. proceed () method, otherwise an exception will be thrown (because the real object does not exist, there is only one fake proxy object)
Interface proxy with interface target is similar to interface proxy with target, but it provides an opportunity to change the proxy object (real object), for example:
Public interface IStorageNode {bool IsDead {get; set;} void Save (string message);} public class StorageNode: IStorageNode {private string _ name; public StorageNode (string name) {this. _ name = name;} public bool IsDead {get; set;} public void Save (string message) {Console. writeLine (string. format ("\" {0} \ "was saved to {1}", message, this. _ name) ;}} public class DualNodeInterceptor: IInterceptor {private IStorageNode _ slave; public DualNodeInterceptor (IStorageNode slave) {this. _ slave = slave;} public void Intercept (IInvocation invocation) {IStorageNode master = invocation. invocationTarget as IStorageNode; if (master. isDead) {IChangeProxyTarget cpt = invocation as IChangeProxyTarget; // Replace the proxy master with slave cpt. changeProxyTarget (this. _ slave); // restore the master state in the test, so that the subsequent call still uses the master effect. isDead = false;} invocation. proceed ();}}
Test code:
ProxyGenerator generator = new ProxyGenerator (); IStorageNode node = generator. createInterfaceProxyWithTargetInterface <IStorageNode> (new StorageNode ("master"), new DualNodeInterceptor (new StorageNode ("slave"), new CallingLogInterceptor (); node. save ("my message"); // The master object node should be called. isDead = true; node. save ("my message"); // call the slave object node. save ("my message"); // call the master object Console. readKey ();
Running result:
The IChangeProxyTarget interface is implemented only when the IInterceptor interface parameter IInvocation object is interface proxy with interface target.
The ChangeInvocationTarget method of IChangeProxyTarget replaces the called object with the proxy object. The ChangeProxyTarget method is replaced by the proxy object permanently, but does not include this call.
Mixins
It is difficult to find a proper word to accurately translate mixin into Chinese. The general idea is to use the "merge" method to modify the object behavior at runtime. For example, if the object obj type is A, all the attributes and methods of type B are "mixed" into the object obj at runtime, so that object obj has the attributes and behaviors of type A and B at the same time, as if obj inherits both A and B. It is easier to achieve this effect in dynamic languages. For example, python supports multi-inheritance and can dynamically modify the base class through _ bases, therefore, it is very simple to use the mixin Technology in python, including javascript. For details about the mixin concept, refer to wikipedia.
Using DP, we can implement mixin in C #, for example:
public interface InterfaceA{ void ActionA();}public class ClassA : InterfaceA{ public void ActionA() { Console.WriteLine("I'm from ClassA"); }}public class ClassB{ public virtual void ActionB() { Console.WriteLine("I'm from ClassB"); }}
Test code:
ProxyGenerator generator = new ProxyGenerator();var options = new ProxyGenerationOptions();options.AddMixinInstance(new ClassA());ClassB objB = generator.CreateClassProxy<ClassB>(options, new CallingLogInterceptor());objB.ActionB();InterfaceA objA = objB as InterfaceA;objA.ActionA();Console.ReadKey();
Running result:
You can see that the proxy object has both ClassA and ClassB behavior.
Export and generate proxy types
Castle Dynamic Proxy allows us to save the dll file generated during runtime to the disk. By loading this dll file at next startup, we can avoid Dynamic generation of the Proxy type.
var scope = new ModuleScope( true, ModuleScope.DEFAULT_ASSEMBLY_NAME, ModuleScope.DEFAULT_FILE_NAME, "DynamicProxyTest.Proxies", "DynamicProxyTest.Proxies.dll");var builder = new DefaultProxyBuilder(scope);var generator = new ProxyGenerator(builder);var options = new ProxyGenerationOptions(new InterceptorFilter()){ Selector = new InterceptorSelector()};SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>( options, new SimpleLogInterceptor(), new CallingLogInterceptor());IStorageNode node = generator.CreateInterfaceProxyWithTargetInterface<IStorageNode>( new StorageNode("master") , new DualNodeInterceptor(new StorageNode("slave")) , new CallingLogInterceptor());options = new ProxyGenerationOptions();options.AddMixinInstance(new ClassA());ClassB objB = generator.CreateClassProxy<ClassB>(options, new CallingLogInterceptor());scope.SaveAssembly(false);
Note: The [Serializable] attribute must be added to the interceptor and other test classes used above.
You can use reflector to view the generated dll to get a general idea of how the proxy object works.
You can use
Scope. loadassembly#cache (assembly );
Load the generated proxy type to the memory, where the assembly needs to be manually loaded
Refer:
Castle Dynamic Proxy Tutorial
Castle's DynamicProxy for. NET