之前介绍了Spring.Net的IOC基础用法。在IOC的基础上接着介绍AOP,AOP即面向切面编程,这次用Spring.Net搭建程序的日志服务。包括方法日志,性能日志,异常日志等。
首先Spring.Net公开了拦截器接口(也可叫环绕)“IMethodInterceptor”,基于该接口实现拦截器
业务异常拦截器
using System;
using AopAlliance.Intercept;
namespace MyService
{
///<summary NoteObject="Class">
/// [功能描述: 业务异常拦截器,一方面写日志,另一方面整个业务层的facade层不让抛出非底层异常,即使抛出了其他异常,也转换成业务异常]<br></br>
/// [创建者: 张联珠]<br></br>
/// [创建时间: 2014-7-17]<br></br>
/// <说明>
///
/// </说明>
/// <修改记录>
/// <修改时间></修改时间>
/// <修改内容>
///
/// </修改内容>
/// </修改记录>
/// </summary>
public class BusinessExceptionAdvice : IMethodInterceptor
{
/// <summary>
/// 业务异常拦截调用方法
/// </summary>
/// <param name="invocation">方法信息</param>
/// <returns>返回值</returns>
public object Invoke(IMethodInvocation invocation)
{
try
{
return invocation.Proceed();
}
//可以按照异常类型来分别捕获处理
catch (Exception ex)
{
//写日志
LogUtils.WriteExceptionLog(ex.Message, ex);
//抛出异常
throw ex;
}
}
}
}
性能日志拦截器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using AopAlliance.Intercept;
namespace MyService
{
///<summary NoteObject="Class">
/// [功能描述: 性能拦截器,进行性能方面的日子书写工作]<br></br>
/// [创建者: 张联珠]<br></br>
/// [创建时间: 2014-7-17]<br></br>
/// <说明>
///
/// </说明>
/// <修改记录>
/// <修改时间></修改时间>
/// <修改内容>
///
/// </修改内容>
/// </修改记录>
/// </summary>
public class CapabilityAdvice : IMethodInterceptor
{
/// <summary>
/// 业务异常拦截调用方法
/// </summary>
/// <param name="invocation"></param>
/// <returns></returns>
public object Invoke(IMethodInvocation invocation)
{
Stopwatch watch = new Stopwatch();
watch.Start();
var result = invocation.Proceed();
//写性能日志
LogUtils.WriteCapabilityLog("执行类" + invocation.TargetType.Name + "的方法" + invocation.Method.Name +"耗时:" + watch.Elapsed.TotalSeconds.ToString());
return result;
}
}
}
方法日志拦截器
using AopAlliance.Intercept;
using System.Text;
namespace MyService
{
///<summary NoteObject="Class">
/// [功能描述: 操作日志拦截器,对容器对象的符合条件的方法都书写方法输入输出日志]<br></br>
/// [创建者: 张联珠]<br></br>
/// [创建时间: 2014-7-17]<br></br>
/// <说明>
///
/// </说明>
/// <修改记录>
/// <修改时间></修改时间>
/// <修改内容>
///
/// </修改内容>
/// </修改记录>
/// </summary>
public class OperationLogAdvice : IMethodInterceptor
{
/// <summary>
/// 拦截方法
/// </summary>
/// <param name="invocation">方法实例</param>
/// <returns>方法返回值</returns>
public object Invoke(IMethodInvocation invocation)
{
//输出类名,方法名
string methodString = string.Format("类名:{0}方法名:{1}", invocation.TargetType.Name, invocation.Method.Name);
StringBuilder sb = new StringBuilder();
sb.Append(methodString);
LogUtils.WriteOperationLog(methodString);
//有参数
if (invocation.Arguments != null && invocation.Arguments.Length > 0)
{
foreach (var arg in invocation.Arguments)
{
if (arg != null)
{
sb.Append("参数:" + arg.ToString());
}
else
{
sb.Append("参数:null");
}
}
LogUtils.WriteOperationLog(sb.ToString());
}
//执行方法
var result = invocation.Proceed();
//有结果的话,写出结果到日志
if (result != null)
{
sb.Append("结果:" + result.ToString());
}
LogUtils.WriteOperationLog(sb.ToString());
return result;
}
}
}
写日志工具类(基于Log4Net)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.Logging;
using System.Text.RegularExpressions;
namespace MyService
{
///<summary NoteObject="Class">
/// [功能描述: 日志辅助工具]<br></br>
/// [创建者: 张联珠]<br></br>
/// [创建时间: 2013-9-17]<br></br>
/// <说明>
///
/// </说明>
/// <修改记录>
/// <修改时间></修改时间>
/// <修改内容>
///
/// </修改内容>
/// </修改记录>
/// </summary>
public static class LogUtils
{
#region 静态成员
/// <summary>
/// 书写异常日志
/// </summary>
/// /// <param name="message">日志内容</param>
/// <param name="exception">异常对象</param>
public static void WriteExceptionLog(string message, Exception exception)
{
LogManager.GetLogger("Exception").Error(message, exception);
}
/// <summary>
/// 书写性能日志
/// </summary>
/// <param name="message">日志内容</param>
public static void WriteCapabilityLog(string message)
{
LogManager.GetLogger("Capability").Info(message);
}
/// <summary>
/// 书写安全日志
/// </summary>
/// <param name="message">日志内容</param>
public static void WriteSecurityLog(string message)
{
LogManager.GetLogger("Security").Info(message);
}
/// <summary>
/// 书写操作日志
/// </summary>
/// <param name="message">日志内容</param>
public static void WriteOperationLog(string message)
{
LogManager.GetLogger("Operation").Info(message);
}
/// <summary>
/// 书写调试日志
/// </summary>
/// <param name="message">日志内容</param>
public static void WriteDebugLog(string message)
{
LogManager.GetLogger("Debug").Debug(message);
}
/// <summary>
/// 书写调试日志
/// </summary>
/// <param name="message">日志内容</param>
public static void WriteSqlLog(string message)
{
LogManager.GetLogger("SqlLog").Debug(ReplaceSpaceToOne(message));
}
/// <summary>
/// 把多个空格替换成一个
/// </summary>
/// <param name="str">要处理的串</param>
/// <returns>结果</returns>
private static string ReplaceSpaceToOne(string str)
{
string resultString = string.Empty;
try
{
resultString = Regex.Replace(str, "\\s{2,}", " ");
return resultString;
}
catch
{
return str;
}
}
#endregion
}
}
然后修改app.config的配置(配置拦截器对象和日志)
配置代码
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="spring">
<!---此三项必须配置-->
<!--参数段-->
<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/>
<!--运用程序上下文段-->
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
<!--配置总节点段-->
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
</sectionGroup>
<sectionGroup name="common">
<!--日志段-->
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
</sectionGroup>
<!--日志段-->
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<common>
<logging>
<!--日志适配器-->
<factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net">
<arg key="configType" value="FILE-WATCH"/>
<arg key="configFile" value="~/Conf/Log4Net.xml"/>
</factoryAdapter>
</logging>
</common>
<spring>
<!--当前容器对象节点,最主要的容器对象配置在这里-->
<context>
<resource uri="config://spring/objects"/>
<resource uri="~/Conf/AopConfig.xml"/>
</context>
<objects xmlns="http://www.springframework.net" default-autowire="byType">
<!--系统主数据库基层配置-->
<!--狗实现类配置-->
<object id="objdog" type="MyService.DogService,MyService" singleton="false">
</object>
<!--鸭子实现类配置-->
<object id="objduck" type="MyService.DuckService,MyService" singleton="false">
</object>
<!--接口实现类配置-->
<object type="MyService.InterfaceService,MyService" singleton="false">
</object>
</objects>
</spring>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
拦截器和日志详细配置
后期可以通过修改这个配置来控制给哪些类增加拦截器(开发模式和上线模式)
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net" xmlns:aop="http://www.springframework.net/aop">
<!--必须让Spring.NET容器管理DefaultAdvisorAutoProxyCreator类-->
<object id="ProxyCreator" type="Spring.Aop.Framework.AutoProxy.DefaultAdvisorAutoProxyCreator, Spring.Aop"/>
<!-- 操作方法日志 -->
<object id="OperationLogAdvice" type="MyService.OperationLogAdvice, MyService" />
<!-- 操作方法日志拦截 -->
<object type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop">
<property name="advice" ref="OperationLogAdvice"/>
<property name="patterns">
<list>
<value>.*Service</value>
<value>.*Facade</value>
<value>.*Dal</value>
<value>.*Table</value>
</list>
</property>
</object>
<!-- 异常处理日志拦截 -->
<object id="BllExceptionAdvice" type="MyService.BusinessExceptionAdvice, MyService" />
<!-- 异常处理日志拦截 -->
<object type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop">
<property name="advice" ref="BllExceptionAdvice"/>
<property name="patterns">
<list>
<value>.*Service</value>
<value>.*Facade</value>
<value>.*Dal</value>
<value>.*Table</value>
</list>
</property>
</object>
<!-- 性能日志拦截 -->
<object id="CapabilityAdvice" type="MyService.CapabilityAdvice, MyService" />
<!-- 性能日志拦截 -->
<object type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop">
<property name="advice" ref="CapabilityAdvice"/>
<property name="patterns">
<list>
<value>.*Service</value>
<value>.*Facade</value>
<value>.*Dal</value>
<value>.*Table</value>
</list>
</property>
</object>
</objects>
后面可以通过该配置来控制哪些日志的输出(开发模式和上线模式)
<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="false">
<appender name="ExceptionRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="LockingModel" type="log4net.Appender.FileAppender+MinimalLock" />
<param name="DatePattern" value="yyyy-MM-dd.'log'" />
<file value="Logs/异常日志.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %15logger %appdomain %location -- %message %exception %newline" />
</layout>
</appender>
<appender name="DebugRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="LockingModel" type="log4net.Appender.FileAppender+MinimalLock" />
<param name="DatePattern" value="yyyy-MM-dd.'log'" />
<file value="Logs/调试日志.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date -- %message %exception %newline" />
</layout>
</appender>
<appender name="OperationRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="LockingModel" type="log4net.Appender.FileAppender+MinimalLock" />
<param name="DatePattern" value="yyyy-MM-dd.'log'" />
<file value="Logs/方法日志.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="SqlRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="LockingModel" type="log4net.Appender.FileAppender+MinimalLock" />
<param name="DatePattern" value="yyyy-MM-dd.'log'" />
<file value="Logs/Sql语句日志.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="CapabilityRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="LockingModel" type="log4net.Appender.FileAppender+MinimalLock" />
<param name="DatePattern" value="yyyy-MM-dd.'log'" />
<file value="Logs/性能.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="SecurityRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="LockingModel" type="log4net.Appender.FileAppender+MinimalLock" />
<param name="DatePattern" value="yyyy-MM-dd.'log'" />
<file value="Logs/安全.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG"/>
</root>
<logger name="Exception">
<appender-ref ref="ExceptionRollingLogFileAppender" />
</logger>
<logger name="Operation">
<appender-ref ref="OperationRollingLogFileAppender"/>
</logger>
<logger name="Debug">
<appender-ref ref="DebugRollingLogFileAppender" />
</logger>
<logger name="SqlLog">
<appender-ref ref="SqlRollingLogFileAppender" />
</logger>
<logger name="Capability">
<appender-ref ref="CapabilityRollingLogFileAppender"/>
</logger>
<logger name="Security">
<appender-ref ref="SecurityRollingLogFileAppender"/>
</logger>
</log4net>
以上实现之后程序不用修改就会给实现类对象加上拦截器实现的日志服务,并且是可配置调整的,即所说的切面服务;可以实现统一的参数校验、安全认证、日志服务等
是不是就毫无侵入性的把切面服务加入程序里了_
代码可以到下载里下载名称“SpringAop练习”