一.什么是AOP
AOP为Aspect Oriented Programming的缩写,意为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
二.为什么使用AOP
1.模块化:AOP能够将应用程序中的交叉关注点(cross-cutting concerns)模块化。这些关注点通常是那些跨越多个模块或类的功能,例如日志记录、事务管理、安全检查等。通过将这些关注点封装在单独的切面(Aspect)中,可以使主业务逻辑与这些辅助功能分离,提高代码的可读性和可维护性。
2.代码复用:AOP允许开发者在不同的模块或类之间复用切面。这意味着一旦编写了一个切面,就可以将其应用到多个地方,而无需在每个模块或类中重复相同的代码。这大大提高了代码复用性和开发效率。
3.减少冗余代码:在没有AOP的情况下,开发者可能需要在每个方法或类的开始和结束时编写相同的代码(例如日志记录)。这种冗余代码不仅增加了维护成本,还可能引入错误。AOP通过集中管理这些代码,避免了这种情况。
4.关注点分离:AOP允许开发者将关注点从主业务逻辑中分离出来。这样,当关注点需要更改时,只需要修改相应的切面,而无需修改主业务逻辑。这有助于保持代码的清晰度和可维护性。
5.减少耦合:通过使用AOP,开发者可以减少模块之间的耦合度。这是因为切面可以在不修改现有代码的情况下添加到应用程序中,从而避免了模块之间的紧密依赖。
三.利用Castle实现日志切面功能(AOP)
因为我这里使用的是.net 6.0,先在nuget上安装Castle.Core安装包。
实现对增删改查的某些方法进行日志记录,这里以Add前,Update后增加日志为例。
1.操作的实体对象Customer;
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
2.增删改查的仓储对象Repository;
public class Repository<T> : BaseRepository<T>
{
public override void Add(T entity)
{
Console.WriteLine("Adding {0}", entity);
}
public Task DeleteAsync(T entity)
{
return Task.Run(() => Console.WriteLine("Deleting {0}", entity));
}
public override void Update(T entity)
{
Console.WriteLine("Updating {0}", entity);
}
public IEnumerable<T> GetAll()
{
Console.WriteLine("Getting entities");
return null;
}
public T GetById(int id)
{
Console.WriteLine("Getting entity {0}", id);
return default(T);
}
}
public abstract class BaseRepository<T>
{
[Log(LogStaus = LogStaus.Before)]
public abstract void Add(T entity);
[Log(LogStaus = LogStaus.Behind)]
public abstract void Update(T entity);
}
public class LogAttribute : Attribute
{
public LogStaus LogStaus { get; set; }
}
public enum LogStaus
{
Before =1 ,
Behind =2 ,
}
}
3.拦截器对象实现拦截器接口IInterceptor,因为这里以日志举例,创建接口ILogInterceptor并实现方法前和方法和两个拦截对象LogBeforeInterceptor和LogBehindInterceptor。再对拦截器进行过滤,这里根据特性过滤。代码如下:
public interface ILogInterceptor : IInterceptor
{
public LogStaus LogStaus { get; set; }
}
public class LogBeforeInterceptor<T> : ILogInterceptor
{
public LogStaus LogStaus { get; set; } = LogStaus.Before;
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public void Intercept(IInvocation invocation)
{
try
{
Log("In Dynamic Proxy - Before executing '{0}'", invocation.Method.Name);
invocation.Proceed();
}
catch (Exception)
{
throw;
}
}
}
public class LogBehindInterceptor<T> : ILogInterceptor
{
public LogStaus LogStaus { get; set; } = LogStaus.Behind;
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public void Intercept(IInvocation invocation)
{
try
{
invocation.Proceed();
Log("In Dynamic Proxy - End executed '{0}'", invocation.Method.Name);
}
catch (Exception)
{
throw;
}
}
}
/// <summary>
/// 过滤器
/// </summary>
public class InterceptorSelector : IInterceptorSelector
{
/// <summary>
///
/// </summary>
public InterceptorSelector() { }
/// <summary>
/// 只需要处理public的方法,其他类型的不处理
/// </summary>
public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
{
if (method.IsPublic)
{
if (method.GetCustomAttributes<LogAttribute>().Any(o => o.LogStaus is LogStaus.Before))
return interceptors.Where(o => o is ILogInterceptor x && x.LogStaus is LogStaus.Before).ToArray();
else if (method.GetCustomAttributes<LogAttribute>().Any(o => o.LogStaus is LogStaus.Behind))
return interceptors.Where(o => o is ILogInterceptor x && x.LogStaus is LogStaus.Behind).ToArray();
else
throw new NotImplementedException();
}
else
{
return null;
}
}
}
四.测试效果
Console.WriteLine("Hello, World!");
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
ProxyGenerator ProxyGenerator = new ProxyGenerator();
var result = ProxyGenerator.CreateClassProxy<Repository<Customer>>(new ProxyGenerationOptions() { Selector = new InterceptorSelector()},
new ILogInterceptor[] { new LogBeforeInterceptor<Repository<Customer>>(), new LogBehindInterceptor<Repository<Customer>>() });
result.Add(customer);//add前记录
result.Update(customer);//update后记录
await result.DeleteAsync(customer);//不做拦截,没有记录
Console.ReadLine();
运行效果如下:与我们预期一致。
五.注意事项
拦截器对方法进行拦截时,这个方法必须要重写,可以是抽象方法也可以是虚方法,但是必需要被重写才能被拦截器拦截。
通过这种方式,我们只需要关注日志本身的功能实现,而不需要考虑多个地方的调用,耦合性低,独立性高,更方便统一维护。
缺点是:需要继承父类对象,调用的方法必须被重写。
最后感谢各位大佬的关注和支持,欢迎交流。