视频讲解
面向切面编程AOP的对面向对象编程OOP的一个补充,它的特点是将系统逻辑和业务逻辑采取《非侵入式》分离。我们把系统封装成一个一个的切面(单一职责)进行顺意编排组合,插入(织入)到业务逻辑的执行过程(织入点)。
系统逻辑:异常处理,身份认证,授权,mvc,数据校验,事务处理。
业务逻辑:就是我们的业务Service。
切面:用于封装系统逻辑,比如身份认证filter,或者中间件
切入点:就是管道的位置。名词
织入:就是插入到管道的切入点的过程。动词
AOP的特点:
1.非侵入式
2.低耦合
3.代码服用
4.单一职责
5.可插拔
实现方式:
1.管道链,比如aspnetcore的中间件,mvc中的Filter
2.静态代理:思考如何加强一个List,使得在插入时打印日志?
3.动态代理:Emit
4.三种模式都需要通过一些技术进行串联,实现链式调用,构成管道。静态代理通过接口进行串联,动态代理通过反射进行串联。管道通过接口获取委托进行串联。委托本质也是接口。
代理:就是增强,代理对象必须尽量实现目标对象的功能,在此基础上进行加强。比如vpn,你的电脑的网络就是目标对象,vpn就是代理服务,代理服务起码得实现联网功能吧,然后对网络进行加强,访问到一些你的本机网络访问不到的东西。
掌握了AOP技术我们可以实现很多好处,做到非侵入式的增强业务逻辑。
//侵入式方案,把这个看懂。后面就是围绕这个开展,高出非侵入式
public static void A()
{
Console.WriteLine("A:开始");
B();//A,B的调用关系强行绑定,有侵入性
Console.WriteLine("A:结束");
}
public static void B()
{
Console.WriteLine("B:开始");
C();
Console.WriteLine("B:结束");
}
public static void C()
{
Console.WriteLine("Hello World");
}
public static void Dobasic()
{
A();
}
1.代理就是实现目标对象的标准(接口),在目标方法执行之前和之后进行逻辑织入的过程。代理的目的就是为了加强。代理不负责实现接口,一般通过target来实现接口。即代理除了可以增强之外还能简化接口的实现。
2.静态代理就是在代理之前就已经确定了代理关系。需要自己实现标准并编写代理类。代理类中的逻辑只能代理一些标准(实现多个接口)。无法代理所有标准。
3.静态代理可以实现不改变目标对象的源码的情况下进行加强,完成目标对象的能力,并且在此基础之上进行加强。
4.可以简化实现的成本,不改变业务代码,只需要编写额外的增强逻辑。不需要关系具体的业务实现。
5.代理和代理直接通过接口可以进行互相代理,链式调用,顺意编排组合,实现系统的多样化。
/// <summary>
/// 定义标准1
/// </summary>
public interface IPhoneService
{
string Mobile { get; set; }
string Message { get; set; }
void Send();
}
//实现标准-不是代理模式
public class PhoneService : IPhoneService
{
public string Mobile { get; set; }
public string Message { get; set; }
public PhoneService(string mobile, string message)
{
Mobile = mobile;
Message = message;
}
public virtual void Send()
{
Console.WriteLine($"已发送短信:{Message}到{Mobile}");
}
}
//代理模式:
//1.实现目标对象的标准
//2.依赖目标对象(被代理对象)
//3.业务织入
public class PhoneServiceProxy : IPhoneService//实现标准
{
private readonly IPhoneService _target;
public PhoneServiceProxy1(IPhoneService target)
{
_target = target;
}
public string Mobile { get => _target.Mobile; set => _target.Mobile = value; }
public string Message { get => _target.Message; set => _target.Message = value; }
/// <summary>
/// 子类重写父类方法
/// </summary>
public void Send()
{
Console.WriteLine("Proxy1:已对手机号进行验证");
_target.Send();
Console.WriteLine("Proxy1:已确认对方已经收到");
}
}
/// <summary>
/// 定义标准1
/// </summary>
public interface IPhoneService
{
string Mobile { get; set; }
string Message { get; set; }
void Send();
}
/// <summary>
/// 定义标准2
/// </summary>
public interface IEmailService
{
string Email { get; set; }
string Message { get; set; }
void Send();
}
/// <summary>
/// 业务逻辑1
/// </summary>
public class PhoneService : IPhoneService
{
public string Mobile { get; set; }
public string Message { get; set; }
public PhoneService(string mobile, string message)
{
Mobile = mobile;
Message = message;
}
public virtual void Send()
{
Console.WriteLine($"已发送短信:{Message}到{Mobile}");
}
}
/// <summary>
/// 业务逻辑2
/// </summary>
public class EmailService : IEmailService
{
public string Email { get; set; }
public string Message { get; set; }
public EmailService(string email, string message)
{
Email = email;
Message = message;
}
public virtual void Send()
{
Console.WriteLine($"已发送邮件:{Message}到{Email}");
}
}
/// <summary>
/// 切面1:校验能力(系统逻辑)
/// taget方式
/// </summary>
public class PhoneServiceProxy1
: IPhoneService//实现标准1
{
private readonly IPhoneService _target;
public PhoneServiceProxy1(IPhoneService target)
{
_target = target;
}
/// <summary>
/// 子类重写父类方法
/// </summary>
public void Send()
{
Console.WriteLine("Proxy1:已对手机号进行验证");
_target.Send();
Console.WriteLine("Proxy1:已确认对方已经收到");
}
}
/// <summary>
/// 切面2:加速能力(系统逻辑)
/// </summary>
public class PhoneServiceProxy2
: IPhoneService//实现标准1
{
private readonly IPhoneService _target;
public PhoneServiceProxy2(IPhoneService target)
{
_target = target;
}
/// <summary>
/// 子类重写父类方法
/// </summary>
public void Send()
{
Console.WriteLine("Proxy2:已开启加速通道");
_target.Send();
Console.WriteLine("Proxy2:已关闭加速通道");
}
}
//test
public static void TestStaticProxy()
{
//目标对象
IPhoneService target = new PhoneService("10088", "你好啊!");
//切面1:验证,对target进行代理
IPhoneService proxy1 = new PhoneServiceProxy1(target);
//切面2:加速,对proxy1进行代理
IPhoneService proxy2 = new PhoneServiceProxy2(proxy1);
//执行
proxy2.Send();
//思考如果要实现IEmailService标准,是不是要重写实现类了?
}
动态代理和静态代理的区别就是,代理类由工具生成,需要在运行时确认代理类已经代理关系。代理类中的逻辑写到拦截器里面,可以进行复用。缺点是性能差。里面涉及到大量反射技术。
Castle.Core:原理就是通过子类继承父类或者实现父类标准,通过Castle.Core自动帮你生成代理类,通过一个叫拦截器的东西编写代理类要执行的业务逻辑。Castle.Core会帮你生成代理类,并将拦截器织入到代理类中。
动态代理通过invocation进行串联,本质是反射。
/// <summary>
/// 定义标准1
/// </summary>
public interface IPhoneService
{
string Mobile { get; set; }
string Message { get; set; }
void Send();
}
/// <summary>
/// 定义标准2
/// </summary>
public interface IEmailService
{
string Email { get; set; }
string Message { get; set; }
void Send();
}
/// <summary>
/// 业务逻辑1
/// </summary>
public class PhoneService : IPhoneService
{
public string Mobile { get; set; }
public string Message { get; set; }
public PhoneService(string mobile, string message)
{
Mobile = mobile;
Message = message;
}
public virtual void Send()
{
Console.WriteLine($"已发送短信:{Message}到{Mobile}");
}
}
/// <summary>
/// 业务逻辑2
/// </summary>
public class EmailService : IEmailService
{
public string Email { get; set; }
public string Message { get; set; }
public EmailService(string email, string message)
{
Email = email;
Message = message;
}
public virtual void Send()
{
Console.WriteLine($"已发送邮件:{Message}到{Email}");
}
}
/// <summary>
/// 代理1:任意标准
/// </summary>
public class ShareInterceptor1 : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Proxy1:已对接收方进行验证");
invocation.Proceed();//执行下一个拦截器或者目标方法
Console.WriteLine("Proxy1:已确认对方已经收到");
}
}
/// <summary>
/// 代理2:任意标准
/// </summary>
public class ShareInterceptor2 : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Proxy2:已开启加速通道");
invocation.Proceed();//执行下一个拦截器或者目标方法
Console.WriteLine("Proxy2:已关闭加速通道");
}
}
//通过Castel生成代理类
public static void TestDynamicProxy1()
{
//创建代理生成器
var generator = new ProxyGenerator();
var target1 = new PhoneService("10088", "你好啊!");
var target2 = new EmailService("1123@116.com", "你好啊!");
var interceptor1 = new ShareInterceptor1();//代理1,拦截器1,不需要去实现指定的标准
var interceptor2 = new ShareInterceptor2();//代理2,拦截器2,不需要去实现指定的标准
//使用代理1和代理2去代理手机的标准
IPhoneService dynamicProxy1 = generator.CreateInterfaceProxyWithTarget<IPhoneService>(target1, interceptor1, interceptor2);
dynamicProxy1.Send();
//代理邮件的标准
IEmailService dynamicProxy2 = generator.CreateInterfaceProxyWithTarget<IEmailService>(target2, interceptor1, interceptor2);
dynamicProxy2.Send();
}
思考:
generator创建的是什么类型的实列?显然不可能是已有的类型。因为它把拦截器织入进去了。而且没有修改我们的代码,站在面向对象的角度来看只能是实现了我们的接口,Emit动态实现了下面的代码
多个拦截器和目标对象(被代理者)通过Invocation进行串联。Invocation中的Arguments完成链式调用。
手动通过Invocation进行串联
//假设有三个拦截器
//第一个拦截器invocation1:Proxy=interceptor2,Method=Intercept,argument=invocation2
//第二个拦截器invocation2:Proxy=interceptor3,Method=Intercept,argument=invocation3
//第三个拦截器invocation2:Proxy=target,Method=method,argument=arguments
//手动实现
public IInvocation GetInvocation(Stack<IInterceptor> stack, object target, Method method, objuect arguments)
{
var invocation1 = new Invocation()
{
Proxy = interceptor2,
Method = typeof(IInterceptor).GetMethod(nameof(IInterceptor.Intercept)),
Arguments = new object[]
{
new Invocation()
{
Proxy = interceptor3,
Method = typeof(IInterceptor).GetMethod(nameof(IInterceptor.Intercept)),
Arguments = new object[]
{
new Invocation()
{
Proxy = target,
Method = method,
Arguments = arguments
}
}
}
}
}
}
//递归实现
public IInvocation GetInvocation(Stack<IInterceptor> stack, object target, Method method, objuect arguments)
{
if(stack.Any())
{
var proxy = stack.Pop();
return new Invocation()
{
Proxy = proxy,
Method = typeof(IInterceptor).GetMethod(nameof(IInterceptor.Intercept)),
Agrumtns = GetInvocation(stack,method,argumtns)
};
}
else
{
return new Invocation()
{
Proxy = target,
Method = method,
Agrumtns = arguments
};
}
}
//Castel.Core自动帮我们生成了下面这个类
public class CastelPhoneServiceProxy : IPhoneService
{
private IPhoneService _taget;
private IInterceptor[] _interceptors;
public CastelPhoneServiceProxy(IPhoneService taget, IInterceptor[] interceptors)
{
_taget = taget;
_interceptors = interceptors;
}
public string Mobile { get => _taget.Mobile; set => _taget.Mobile = value; }
public string Message { get => _taget.Message; set => _taget.Message = value; }
public void Send()
{
var stack = new Stack<IInterceptor>(_interceptors.Reverse());
if (stack.Any())
{
var item = stack.Pop();
var invocation = GetNextInvocation(stack);
item.Intercept(invocation);
}
else
{
_taget.Send();
}
}
/// <summary>
/// 递归获取Invocaltion
/// </summary>
/// <param name="stack"></param>
/// <returns></returns>
private IInvocation GetNextInvocation(Stack<IInterceptor> stack)
{
if (stack.Any())
{
var next = stack.Pop();
return new Invocaltion
{
Arguments = new object[]
{
//递归
GetNextInvocation(stack)
},
Proxy = next,
Method = typeof(IInterceptor).GetMethod(nameof(IInterceptor.Intercept)) ?? throw new NullReferenceException()
};
}
else
{
return new Invocaltion
{
Arguments = new object[]
{
},
Proxy = _taget,
Method = _taget.GetType().GetMethod(nameof(IPhoneService.Send)) ?? throw new NullReferenceException()
};
}
}
}
//实现一些castle.core的接口
public class Invocaltion : IInvocation
{
public object[] Arguments { get; set; }
public Type[] GenericArguments { get; set; }
public object InvocationTarget { get; set; }
public MethodInfo Method { get; set; }
public MethodInfo MethodInvocationTarget { get; set; }
public object Proxy { get; set; }
public object ReturnValue { get; set; }
public Type TargetType { get; set; }
public IInvocationProceedInfo CaptureProceedInfo()
{
throw new NotImplementedException();
}
public object GetArgumentValue(int index)
{
throw new NotImplementedException();
}
public MethodInfo GetConcreteMethod()
{
throw new NotImplementedException();
}
public MethodInfo GetConcreteMethodInvocationTarget()
{
throw new NotImplementedException();
}
public void Proceed()
{
Method.Invoke(Proxy, Arguments);
}
public void SetArgumentValue(int index, object value)
{
throw new NotImplementedException();
}
}
//链路器
public class EmitInvocation
{
private object? proxy;
private MethodInfo? method;
private object[]? arguments;
public EmitInvocation(object? proxy, MethodInfo? method, object[]? arguments)
{
this.proxy = proxy;
this.method = method;
this.arguments = arguments;
}
public void Proceed()
{
method?.Invoke(proxy, arguments);
}
}
//拦截器
public interface IEmitInteceptor
{
void Intercept(EmitInvocation invocation);
}
//实现拦截器1
public class EmitInteceptor1 : IEmitInteceptor
{
public void Intercept(EmitInvocation invocation)
{
Console.WriteLine("prox1:start");
invocation.Proceed();
Console.WriteLine("prox1:end");
}
}
//实现拦截器1
public class EmitInteceptor2 : IEmitInteceptor
{
public void Intercept(EmitInvocation invocation)
{
Console.WriteLine("prox2:start");
invocation.Proceed();
Console.WriteLine("prox2:end");
}
}
//该工具类帮助我们少写emit代码
public static class EmitProxyInvoker
{
public static EmitInvocation GetNextInvocation(Stack<IEmitInteceptor> stack, MethodInfo method, object target, object[] arguments)
{
if (stack.Any())
{
var next = stack.Pop();
arguments = new object[]
{
//递归
GetNextInvocation(stack, method, target, arguments)
};
return new EmitInvocation(next, typeof(IEmitInteceptor).GetMethod(nameof(IEmitInteceptor.Intercept)), arguments);
}
else
{
return new EmitInvocation(target, method, arguments);
}
}
public static void Invoke(IEmitInteceptor[] interceptors, MethodInfo method, object target, object[] arguments)
{
var stack = new Stack<IEmitInteceptor>(interceptors.Reverse());
if (stack.Any())
{
var item = stack.Pop();
var invocation = GetNextInvocation(stack, method, target, arguments);
item.Intercept(invocation);
}
else
{
method.Invoke(target, arguments);
}
}
}
//业务接口
public interface IEmitService
{
void Send();
}
//将来要生成的代理类
public class EmitServiceProxy : IEmitService
{
private object _target;
private IEmitInteceptor[] _inteceptors;
public EmitService()
{
}
public void Send()
{
var method = _target.GetType().GetMethod(nameof(EmitService.Send));
var arguments = new object[] { };
EmitProxyInvoker.Invoke(_inteceptors, method, _target, new object[] { });
}
}
public static class EmitProxyGenerator
{
static AssemblyBuilder _assemblyBuilder;
static ModuleBuilder _moduleBuilder;
static EmitProxyGenerator()
{
//创建一个程序集
var assemblyName = new AssemblyName("DynamicProxies");
_assemblyBuilder = AssemblyBuilder
.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
//创建一个模块
_moduleBuilder = _assemblyBuilder.DefineDynamicModule("Proxies");
}
public static TInterface Create<TInterface>(object target, params IEmitInteceptor[] inteceptor)
where TInterface : class
{
#region 定义类型
//定义一个class,如果这个类型已定义直接返回,缓存
var typeName = $"{target.GetType().Name}EmitProxy";
var typeBuilder = _moduleBuilder.DefineType(
typeName,
TypeAttributes.Public,typeof(object),
new Type[]
{
typeof(TInterface)
});
#endregion
#region 定义字段
//定义字段
var targetFieldBuilder = typeBuilder.DefineField("target", typeof(object), FieldAttributes.Private);
var inteceptorFieldBuilder = typeBuilder.DefineField("inteceptor", typeof(IEmitInteceptor[]), FieldAttributes.Private);
#endregion
#region 定义构造器
//定义构造器
var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.ExplicitThis, new Type[]
{
typeof(object),
typeof(IEmitInteceptor[])
});
//获取IL编辑器
var generator = constructorBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);//加载this
generator.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException());
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Nop);
// this.age = age;
generator.Emit(OpCodes.Ldarg_0);//加载this
generator.Emit(OpCodes.Ldarg_1);//加载target参数
generator.Emit(OpCodes.Stfld, targetFieldBuilder);//加载target字段
// this.name = name;
generator.Emit(OpCodes.Ldarg_0);//加载this
generator.Emit(OpCodes.Ldarg_2);//加载inteceptor参数
generator.Emit(OpCodes.Stfld, inteceptorFieldBuilder);//加载inteceptor字段
generator.Emit(OpCodes.Ret);
#endregion
#region 实现接口
var methods = typeof(TInterface).GetMethods();
foreach (var item in methods)
{
var parameterTypes = item.GetParameters().Select(s => s.ParameterType).ToArray();
var methodBuilder = typeBuilder.DefineMethod(item.Name,
MethodAttributes.Public| MethodAttributes.Final |MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig,
CallingConventions.Standard|CallingConventions.HasThis,
item.ReturnType,
parameterTypes);
var generator1 = methodBuilder.GetILGenerator();
//init
var methodInfoLocal = generator1.DeclareLocal(typeof(MethodInfo));
var argumentLocal = generator1.DeclareLocal(typeof(object[]));
generator1.Emit(OpCodes.Nop);
generator1.Emit(OpCodes.Ldarg_0);
generator1.Emit(OpCodes.Ldfld, targetFieldBuilder);
generator1.Emit(OpCodes.Callvirt, typeof(Type).GetMethod(nameof(Type.GetType),Type.EmptyTypes));
generator1.Emit(OpCodes.Ldstr, item.Name);
generator1.Emit(OpCodes.Callvirt, typeof(Type).GetMethod(nameof(Type.GetMethod), new Type[] { typeof(string) }));
generator1.Emit(OpCodes.Stloc, methodInfoLocal);
generator1.Emit(OpCodes.Ldc_I4_0);
generator1.Emit(OpCodes.Newarr, typeof(object));
generator1.Emit(OpCodes.Stloc, argumentLocal);
generator1.Emit(OpCodes.Ldarg_0);
generator1.Emit(OpCodes.Ldfld, inteceptorFieldBuilder);
generator1.Emit(OpCodes.Ldloc_0);
generator1.Emit(OpCodes.Ldarg_0);
generator1.Emit(OpCodes.Ldfld, targetFieldBuilder);
generator1.Emit(OpCodes.Ldc_I4_0);
generator1.Emit(OpCodes.Newarr, typeof(object));
generator1.Emit(OpCodes.Call, typeof(EmitProxyInvoker).GetMethod(nameof(EmitProxyUtil.Invoke)));
generator1.Emit(OpCodes.Nop);
generator1.Emit(OpCodes.Ret);
}
#endregion
//创建:这个type可以用一个线程安全的字典缓存起来,第二次需要这个代理类的时候,就不需要在生成一次emit代码了。
var type = typeBuilder.CreateType() ?? throw new ArgumentException();
var instance = Activator.CreateInstance(type, target, inteceptor);
return (TInterface)instance;
}
}
public class DbContext
{
}
public class AService
{
public DbContext DbContext { get; }
public AService(DbContext context)
{
DbContext = context;
}
}
public static void Test()
{
var services = new ServiceCollection();
services.AddScoped<DbContext>();
var generator = new ProxyGenerator();
//泛型-不支持动态注入
services.AddScoped(sp =>
{
//通过容器解析依赖
var target = ActivatorUtilities.CreateInstance<AService>(sp);
return generator.CreateClassProxyWithTarget(target);
});
//反射-可以扫描批量注入
services.AddScoped(typeof(AService), sp =>
{
//通过容器解析依赖
var target = ActivatorUtilities.CreateInstance(sp, typeof(AService));
return generator.CreateClassProxyWithTarget(target);
});
}
1.通过委托构建管道
public delegate Task RequestDelegate(HttpContext context);
public class HttpContext
{
}
public class ApplicationBuilder
{
private readonly List<Func<RequestDelegate, RequestDelegate>> _componen
public void Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
}
public void Use(Func<HttpContext, Func<Task>, Task> middleware)
{
_components.Add((next) =>
{
return async c =>
{
await middleware(c, () => next(c));
};
});
}
public void Use(Func<HttpContext, RequestDelegate, Task> middleware)
{
_components.Add((next) =>
{
return async c =>
{
await middleware(c, next);
};
});
}
public void Run(RequestDelegate handler)
{
_components.Add((next) =>
{
return async c =>
{
await handler(c);
};
});
}
//构建管道
public RequestDelegate Build()
{
RequestDelegate app = c =>
{
throw new InvalidOperationException("无效的管道");
};
for (int i = _components.Count - 1; i > -1; i--)
{
app = _components[i](app);
}
return app;
}
}
2.通过接口构建管道
有点类型动态代理,动态代理是通过Invocation进行反射,而下面的方式是通过接口的方式。反射更加灵活,性能不行。
public interface IChain
{
Task NextAsync();
}
public class FilterChain : IChain
{
private readonly IFilter _filter;
private readonly HttpContext _context;
private readonly IChain _next;
public FilterChain(IFilter filter, HttpContext context, IChain next)
{
_filter = filter;
_context = context;
_next = next;
}
public async Task NextAsync()
{
await _filter.InvokeAsync(_context, _next);
}
}
public class ServletChain : IChain
{
private readonly IServlet _servlet;
private readonly HttpContext _context;
public ServletChain(IServlet servlet, HttpContext context)
{
_servlet = servlet;
_context = context;
}
public async Task NextAsync()
{
await _servlet.DoPostAsync(_context);
}
}
public interface IFilter
{
Task InvokeAsync(HttpContext context, IChain chain);
}
public class Filter1 : IFilter
{
public async Task InvokeAsync(HttpContext context, IChain chain)
{
Console.WriteLine("身份认证开始");
await chain.NextAsync();
Console.WriteLine("身份认证结束");
}
}
public class Filter2 : IFilter
{
public async Task InvokeAsync(HttpContext context, IChain chain)
{
Console.WriteLine("授权认证开始");
await chain.NextAsync();
Console.WriteLine("授权认证结束");
}
}
public interface IServlet
{
Task DoPostAsync(HttpContext context);
}
public class HelloServlet : IServlet
{
public Task DoPostAsync(HttpContext context)
{
Console.WriteLine("Hello World");
return Task.CompletedTask;
}
}
public class WebHost
{
private readonly List<IFilter> _filters = new List<IFilter>();
public void AddFilter(IFilter filter)
{
_filters.Add(filter);
}
public void Exeucte(HttpContext context, IServlet servlet)
{
//自行处理filter为空的情况,就是直接执行serlvet就好了
var stack = new Stack<IFilter>(_filters);
var filter = stack.Pop();
var chain = GetFilterChain(context, servlet,stack);
filter.InvokeAsync(context, chain);
}
private IChain GetFilterChain(HttpContext context, IServlet servlet, Stack<IFilter> filters)
{
if (filters.Any())
{
var filter = filters.Pop();
var chain = GetFilterChain(context, servlet, filters);
return new FilterChain(filter, context, chain);
}
else
{
return new ServletChain(servlet, context);
}
}
}
1.代理分为静态代理和动态代理,静态代理需要自己编写代理类,动态代理由框架生成代理类。
2.代理和管道都需要通过接口(委托)进行链接,串联,形成链式调用。
3.动态代理慎用,因为涉及到反射技术,而且对异步支持不友好。
4.静态代理常用于加强已有类型,比如接口要求一个IList,我们已经拥有了一个list实列,我们需要在在list.Add方法时打印日志,此时我们可以不改变原有的list,通过静态代理实现IList接口来进行对原有的list加强。这个方法在更改框架的时候很有用。我们可以对原有的HttpContext,进行加强。
我得到像这样的数字2.363636363636364.5675631.23456646544846510.5857447736我如何让Ruby将这些数字向上(或向下)舍入到最接近的0.05? 最佳答案 [2.36363636363636,4.567563,1.23456646544846,10.5857447736].mapdo|x|(x*20).round/20.0end#=>[2.35,4.55,1.25,10.6] 关于ruby-将rubyfloat向上或向下舍入到最接近的0.05
目录1、进入AT模式和连接前注意事项2、实现两个蓝牙完美配对3、HC-05_1初始化配置4、HC-05_2初始化配置5、HC-05_1与HC-05_2绑定6、设置模块通信波特率&通信验证1、进入AT模式和连接前注意事项USB转TTL和蓝牙模块连接好后(VCC-VCCGND-GNDRXD-TXDTXD-RXD),插上电脑进入AT模式的两种方法。方法一:先按住按键不放,再给模块上电。此时LED2S闪一次,进入AT模式。波特率固定为38400。方法二:模块直接上电,此时LED灯快闪(1s两次)。再按下按键,模块也会进入AT指令,此时LED还是快闪。这个时候的波特率和自己设置的一样,默认为9600。1
文章目录一、序列帧动画二、骨骼动画——2DAnimation三、反向动力学IK四、换装五、骨骼动画——Spine一、序列帧动画(一)什么是序列帧动画我们最常见的序列帧动画就是我们看的日本动画片,以固定时间间隔按序列切换图片,就是序列帧动画的本质当固定时间间隔足够短时,我们肉眼就会认为图片是连续动态的,进而形成动画(会动的画面)它的本质和游戏的帧率概念有点类似,原理就是在一个循环中按一定时间间隔不停的切换显示的图片(二)制作序列帧动画方法一:创建一个空物体创建一个动画直接将某一个动作的序列帧拖入窗口中方法二:直接将图片拖入Hierarchy层级窗口中注意:可以修改动画帧率,来控制动
我正在尝试将日期转换为日期数字,后跟“st”、“nd”、“rd”或“th,具体取决于日期。我是javascript的新手,所以不知道从哪里开始。例如05/01/2011=1st2011年5月2日=第2次2011年5月3日=第3次2011年5月12日=12日2011年5月22日=22日谢谢 最佳答案 首先,获取日期:vardate=myval.getDate();然后找到后缀:functionget_nth_suffix(date){switch(date){case1:case21:case31:return'st';case2:c
文章目录前言:fac是什么?“人生苦短,我用Python;Web开发,首选Feffery!”↓↓↓今日笔记↓↓↓五、fac反馈:AntdNotification通知提醒框5.1语法与参数5.1.1语法5.1.2主要参数说明5.2使用示例5.2.1基础使用5.2.2不同的状态5.2.3不同的弹出位置5.2.4持续显示时长的设置前言:fac是什么?feffery-antd-components(简称fac),是国内大佬费弗里(Feffery)老师基于著名的Rea
我正在尝试使用Spring'sSchemaBasedAOPSupport在Eclipse中,尝试在Tomcat中加载配置时出现错误。Eclipse中没有错误,并且自动完成对于aop命名空间可以正常工作,但是当我尝试将项目加载到eclipse中时,出现此错误:09:17:59,515WARNXmlBeanDefinitionReader:47-IgnoredXMLvalidationwarningorg.xml.sax.SAXParseException:schema_reference.4:Failedtoreadschemadocument'http://www.springfram
我有一个要序列化为xml文档的集合。该类是:publicclassContacts{publicListcontacts{get;set;}}我的主要问题是现在我的xml看起来问题是,我想看起来像这样:有办法吗? 最佳答案 [XmlRoot("contacts")]publicclassContacts{[XmlElement("contact")]publicListcontacts{get;set;}}应该给你:.........(XmlRootAttribute将Contacts重命名为contacts;XmlElementA
参考书目:深入浅出Python量化交易实战在机器学习里面的X叫做特征变量,在统计学里面叫做协变量也叫自变量,在量化投资里面则叫做因子,所谓多因子就是有很多的特征变量。本次带来的就是多因子模型,并且使用的是机器学习的强大的非线性模型,集成学习里面的随机森林和LGBM模型,带来因子的选择策略和股票的选择策略。由于股票数据的获取都需要第三方库或者是专业的量化投资框架,很多第三方库某些功能需要收费(Tushare),而免费的一些库(证券宝)获取的数据特征变量又没那么多。所以这里是用聚宽量化投资框架,是可以免费使用一些功能的(只需要注册一个账号)。这里获取数据就采用聚宽平台的功能了。数据获取本次使用
我正在尝试设置我的api,以便它根据Acceptheader使用XML或JSON进行响应。我正在学习ShawnW的教程:https://wildermuth.com/2016/03/16/Content_Negotiation_in_ASP_NET_Core它说要添加一个包到:"Microsoft.AspNet.Mvc.Formatters.Xml":"6.0.0-rc1-final"但我找不到它,所以安装了:Microsoft.AspNetCore.Mvc.Formatters.Xml他说要将它添加到Startup的配置服务部分://Addframeworkservices.serv
重新排序问题描述给定一个数组A和一些查询Li,Ri求数组中第Li至第Ri个元素之和。小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。小蓝想知道相比原数组,所有查询结果的总和最多可以增加多少?输入格式输入第一行包含一个整数n。第二行包含n个整数A1,A2,⋯ ,An相邻两个整数之间用一个空格分隔。第三行包含一个整数m表示查询的数目。接下来m行,每行包含两个整数Li、Ri相邻两个整数之间用一个空格分隔。输出格式输出一行包含一个整数表示答案。样例输入51234521325样例输出4样例说明原来的和为6+14=206+14=20,重新排列为(1,4,5,2,3)(