要重构事务管理代码,这次不使用OnMethodBoundaryAspect ,而是使用MethodInterceptionAspect ,它不是在方法的边界插入代码,而是会拦截任何该方法的调用。拦截切面会在拦截到方法调用时执行切面代码,之后再执行拦截到的方法;而边界切面会在方法执行前后运行切面代码。
[Serializable]
public class TransactionManagement : MethodInterceptionAspect
{
public override void OnInvoke(MethodInterceptionArgs args)
{
using (var ts = new TransactionScope())
{
var retries = 3;//重试3次
var succeeded = false;
while (!succeeded)
{
try
{
args.Proceed();//继续执行拦截的方法
ts.Complete();//事务完成
succeeded = true;
}
catch (Exception ex)
{
if (retries >= 0)
retries--;
else
throw ex;
}
}
}
}
}
这个切面例子的代码和业务逻辑中的代码基本一样,除了使用args.Proceed() 方法替换了业务逻辑代码。Proceed() 方法意思就是继续执行拦截到的方法。通过上面的代码,我们的代码又简化了,下面记得给服务方法添加特性,并将业务代码从事务中移除:
[LoggingAspect]
[DefensiveProgramming]
[TransactionManagement]
public void Accrue(RentalAgreement agreement)
{
//...略
}
[LoggingAspect]
[DefensiveProgramming]
[TransactionManagement]
public void Redeem(Invoice invoice, int numberOfDays)
{
//...
}
为了说明事务切面能正常工作,可以在OnInvoke 内部前后添加Console.WriteLine("{0}方法开始/结束:{1}", args.Method.Name,DateTime.Now); ,打印出来看一下。
重构异常处理切面
异常处理切面需要使用OnMethodBoundaryAspect ,或者可以使用OnExceptionAspect ,无论使用哪一种,样子都是差不多的。
[Serializable]
public class MyExceptionAspect:OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
if (ExceptionHelper.Handle(args.Exception))
{
args.FlowBehavior=FlowBehavior.Continue;
}
}
}
ExceptionHelper 是我自己定义的异常处理静态类,这里出现了一个新玩意FlowBehavior ,它指定了当切面执行完之后,接下来怎么办!这里设置了Continue ,也就是说,如果异常处理完了,程序继续执行,否则,默认的FlowBehavior 是 RethrowException ,这样的话,切面就没效果了,异常又再次抛出来了。
移除异常处理的代码,加上异常处理切面特性,至此,所有的横切关注点就重构完了。下面完整地看一下成品:
[LoggingAspect]
[DefensiveProgramming]
[TransactionManagement]
[MyExceptionAspect]
public void Accrue(RentalAgreement agreement)
{
var rentalTime = agreement.EndDate.Subtract(agreement.StartDate);
var days = (int) Math.Floor(rentalTime.TotalDays);
var pointsPerDay = 1;
if (agreement.Vehicle.Size>=Size.Luxury)
{
pointsPerDay = 2;
}
var totalPoints = days*pointsPerDay;
_loyaltyDataService.AddPoints(agreement.Customer.Id,totalPoints);
}
[LoggingAspect]
[DefensiveProgramming]
[TransactionManagement]
[MyExceptionAspect]
public void Redeem(Invoice invoice, int numberOfDays)
{
var pointsPerday = 10;
if (invoice.Vehicle.Size>=Size.Luxury)
{
pointsPerday = 15;
}
var totalPoints = numberOfDays*pointsPerday;
_loyaltyDataService.SubstractPoints(invoice.Customer.Id,totalPoints);
invoice.Discount = numberOfDays*invoice.CostPerDay;
}
可以看到,这样的代码看着很不错吧?又回到了之前最开始的代码,只有业务逻辑的单一职责状态,所有的横切关注点都放到了它们各自的类中去了。代码非常容易阅读。
再来看看使用AOP的优点:
- 更改方便。如果更改了方法的方法名或参数名,切面会自动处理。切面不会关心业务逻辑是否发生变化(比如每天积分的变化),业务逻辑也不会关心你是否从
Console 切换到了log4Net或NLog ,除非你想使用TransactionScope 之外的东西处理事务或者需要改变重试次数的最大值。
- 可以将这些切面重复给每个服务的各个方法使用,而不是不使用AOP时,每次都要复制粘贴相似的代码。
- 可以在整个类、命名空间或程序集使用多广播切面,而不用在每个方法上这样写。
小结 (编辑:成都站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|