DynamicProxy(以下称为动态代理)起作用主要是为我们的类生成一个代理类,这个代理类可以在我们调用原本类的方法之前,调用拦截器以实现AOP。那么动态代理是怎么实现的呢,这里简单一下提一下,这里主要是用了emit技术动态生成IL,相当于在内存中用IL给我们编写了一个Class。
通过静态代理实现AOP
我们新建一个类Cat
,并实现ICat
接口
public interface ICat { void Eat(); } public class Cat:ICat { public void Eat() { Console.WriteLine("猫在吃东西"); } }
然然后我们为其创建一个代理类,CatProxy
public class CatProxy:ICat { private readonly ICat _cat; public CatProxy(ICat cat) { _cat = cat; } public void Eat() { Console.WriteLine("猫吃东西之前"); _cat.Eat(); Console.WriteLine("猫吃东西之后"); } }
可以看见,我们已经成功的通过代理实现在猫吃东西之前和之后执行我们定义的代码,这就是一个简单的AOP,这个称之为静态代理,需要我们手动编写代理类,这个是十分耗费时间的,那么有什么方法帮我们自动生成代理呢,当然有了,接下来介绍我们的动态代理。
动态代理(DynamicProxy)实现AOP
我在前言中已经简单提了下动态代理的实现原理,我们这里就只说说怎么用。我们这里使用Autofac的DynamicProxy。
我们依然使用前一章节所用的控制台项目,通过nuget安装两个Package:Autofac
、Autofac.Extras.DynamicProxy
首先我们需要定义一个拦截器:
public class CatInterceptor:IInterceptor { public void Intercept(IInvocation invocation) { Console.WriteLine("猫吃东西之前"); invocation.Proceed(); Console.WriteLine("猫吃东西之后"); } }
然后在Autofac容器中注册我们的拦截器和类型:
static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<CatInterceptor>();//注册拦截器 builder.RegisterType<Cat>().As<ICat>().InterceptedBy(typeof(CatInterceptor)).EnableInterfaceInterceptors();//注册Cat并为其添加拦截器 var container = builder.Build(); var cat = container.Resolve<ICat>(); cat.Eat(); Console.Read(); }
通过运行我们可以看出,和上一章节的效果一样,但是我们并不需要取手动定义我们的代理类,而是通过组件动态生成了。
关于这个拦截器,我们还可以通过Attribute的方式绑定到我们的具体类型,而不需要在注册到容器的时候动态指定。
[Intercept(typeof(CatInterceptor))] public class Cat:ICat { public void Eat() { Console.WriteLine("猫在吃东西"); } }
注册的代码可改为:
builder.RegisterType<Cat>().As<ICat>().EnableInterfaceInterceptors();
IInvocation
对象:
1、
invocation.Arguments
: 方法执行被拦截时的方法参数数组
2、invocation.GenericArguments
: 被拦截方法的泛型参数类型数组,如果没有就是null
3、invocation.InvocationTarget
: 获取当前执行的目标对象,例如:如果是class代理就是YourClassProxy,接口代理就是实现接口的对象实例,如果没有则为null,也就是当使用xxxWithoutTarget的时候
4、invocation.Method
:获取代理对象的方法信息,例如:如果是class代理的时候就是YourClass.YourMethod对象的MethodInfo且这个时候invocation.Method == invocation.MethodInvocationTarget
;如果是interface代理就是接口对象的方法信息,
例如:ICall.Call 这个方法的MethodInfo信息且这个时候invocation.Method != invocation.MethodInvocationTarget
,因为invocation.MethodInvocationTarget
是接口对应实现的目标对象的方法信息,
也就是例如:MyCall.Call 方法对应上面的 ICall 接口来说,当然也可以使用 WithoutTarget方式,这样就会导致invocation.MethodInvocationTarget==null
的情况
5、invocation.MethodInvocationTarget
: 指向真正的目标对象的方法信息MethodInfo,大致可以根据第四点给出了说明
6、invocation.Proxy
: 获取拦截时的代理对象,例如:YourClassProxy(类代理) 或者 ICallProxy(接口代理) 对象
7、invocation.ResultValue
: 获取或者设置代理方法的返回值
8、invocation.TargetType
: 获取真实目标对象的类型
9、invocation.GetArgumentValue(int index);
: 通过index获取参数值
10、invocation.GetConcreteMethod();
: 同理第四点
11、invocation.GetConcreteMethodInvocationTarget();
: 同理第五点
12、invocation.Proceed();
: 调用下一个拦截器直到目标方法
13、invocation.SetArgumentValue(int index, object value);
: 设置更改参数值通过下标
案例:DAL拦截器,记录调用日志
public class DALInterceptor : IInterceptor { /// <summary> /// Account_AdminDAL 日志对象 /// </summary> private ILogger<DALInterceptor> Logger { get; } public DALInterceptor(ILogger<DALInterceptor> logger) { this.Logger = logger; } public void Intercept(IInvocation invocation) { string methodName = invocation.TargetType.FullName + "." + invocation.Method.Name; var args = string.Join("; ", invocation.Arguments.Select(arg => { string result = null; if (arg == null) { result = string.Empty; } else if (arg is string) {//字符串类型 result = arg as string; } else if (arg is IEnumerable<KeyValuePair<string, object>>) {//字典类型<string, object> result = string.Join(", ", (arg as IEnumerable<KeyValuePair<string, object>>).Select(pair => pair.Key + "=" + pair.Value?.ToString())); } else if (arg is IEnumerable) {//集合类型 result = string.Join(", ", (arg as IEnumerable).Cast<object>().Select(item => item?.ToString())); } else {//其它类型 result = arg.ToString(); } return result; }).ToArray()); string[] logInfos = new string[2]; logInfos[0] = string.Format("calling method=>"{1}" {0} arguments=>[{2}]" , Environment.NewLine , methodName , args); Stopwatch stopwatch = Stopwatch.StartNew(); try { invocation.Proceed(); } catch (Exception ex) { throw ex; } finally { stopwatch.Stop(); string useTime = stopwatch.Elapsed.ToString(@"s.fff"); object returnValue = invocation.ReturnValue; if (returnValue is ICollection) { returnValue = "ICollection.Count=" + (returnValue as ICollection).Count; } logInfos[1] = string.Format(" called method=>"{1}",use=>{2} seconds {0} result=>{3}" , Environment.NewLine , methodName , useTime , returnValue); this.Logger?.LogInformation(string.Join(Environment.NewLine, logInfos)); } } }
=================================?
动态代理的高级用法
我们前面说了,动态代理是动态生成一个代理类,那么我们可以动态的为这个代理类添加一个接口吗,答案当然是可以。
现在我们定义一个铲屎官
类:
public class CatOwner
{
}
可以看出我们的铲屎官
类什么都没有,如果我们的铲屎官想喂猫吃东西怎么办,按照我们传统的思维当然是实例化一个cat传入我们的CatOwner
,但是我们可以用我们的DynamicProxy动态生成。
var builder = new ContainerBuilder();
builder.RegisterType<CatInterceptor>();//注册拦截器
builder.RegisterType<Cat>().As<ICat>();//注册Cat
builder.RegisterType<CatOwner>().InterceptedBy(typeof(CatInterceptor))
.EnableClassInterceptors(ProxyGenerationOptions.Default, additionalInterfaces: typeof(ICat));//注册CatOwner并为其添加拦截器和接口
var container = builder.Build();
var cat = container.Resolve<CatOwner>();//获取CatOwner的代理类
cat.GetType().GetMethod("Eat").Invoke(cat, null);//因为我们的代理类添加了ICat接口,所以我们可以通过反射获取代理类的Eat方法来执行
Console.Read();
我们上面的代码是肯定不能运行的,因为我们的代理类虽然添加了ICat接口,但是却没有具体实现它,所以抛出为卫视现异常:
我们可以使用AOP在我们执行代理类的Eat方法之前去调用我们的具体实现Cat
的Eat方法,我们修改一下拦截器。
public class CatInterceptor:IInterceptor
{
private readonly ICat _cat;
/// <summary>
/// 通过依赖注入 注入ICat的具体实现
/// </summary>
/// <param name="cat"></param>
public CatInterceptor(ICat cat)
{
_cat = cat;
}
public void Intercept(IInvocation invocation)
{
Console.WriteLine("喂猫吃东西");
invocation.Method.Invoke(_cat, invocation.Arguments);//调用Cat的指定方法
}
}
我们看一下运行效果:
可以看见我们从一个什么都没有的CatOwner
类,来为其调用了一个具体的猫吃东西的行为,是不是感觉很神奇!
有人可能会说,一个铲屎官为什么要去实现一个ICat接口。我想说纯属胡编乱造,只是想阐明这个用法,这个意思。
应用场景
用过ABP框架的人都应该知道其有个技术名为DynamicWebapi,非常方便可以动态帮我们的应用逻辑层生成webapi,而不需要我们手动去编写webapi来发布。这里据用到了上面所说的技术,动态生成Wabpi Controller,然后为其添加应用逻辑接口,在调用具体的应用逻辑方法时(Action)通过AOP拦截调用具体应用逻辑实现来完成。
Demo:https://github.com/stulzq/BlogDemos/tree/master/AutofacDynamicProxyTest