AOP概念
Aspect Oriented Programming,面向切面编程,可以通过预编译方式和运行期动态代理,实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
前提
- 了解Spring.NET IOC
- 熟悉代理模式
下面我们结合一个具体的例子来了解AOP。
基本情景
User类
/// <summary>
/// 用户实体,具有姓名和年龄两个属性
/// </summary>
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
IUserService
public interface IUserService
{
//获得用户信息
void GetUserInfo(User enUser);
}
UserService
public class UserService:IUserService
{
/// <summary>
/// 获得用户的信息
/// </summary>
/// <param name="enUser">用户实体</param>
public void GetUserInfo(User enUser)
{
//输出用户的姓名和年龄
Console.WriteLine("姓名:"+enUser .Name+"年龄:"+enUser .Age );
}
}
客户端(控制台应用程序)Program
- “ new()”出具体的实现——这里我们不采用此方式
class Program
{
static void Main(string[] args)
{
//实例化一个实体
User enUser = new User()
{
Name = "Danny",
Age = 15
};
IUserService userService = new UserService();
userService.GetUserInfo(enUser);
}
}
- 通过Spring IOC获得具体实现(相当于工厂)
//引入Spring.Core.dll
using Spring.Context;
using Spring.Context.Support;
namespace WithoutAOP
{
class Program
{
static void Main(string[] args)
{
//实例化一个实体
User enUser = new User()
{
Name = "Danny",
Age = 15
};
//获得Spring.IOC容器的上下文
IApplicationContext context = ContextRegistry.GetContext();
//根据配置文件中的配置获得业务实现类
IUserService userService = (IUserService)context.GetObject("userService");
//调用业务实现类的方法
userService.GetUserInfo(enUser);
}
}
}
配置文件
<!--Spring配置-->
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects"/>
</context>
<objects xmlns="http://www.springframework.net">
<object id="userService" type="AOPExample.UserService,AOPExample"/>
</objects>
</spring>
现在我们要为已有的业务逻辑增加日志功能。接下来看看AOP如何来实现。
在了解之前,我们先来了解AOP中的一些术语。
AOP术语
- 目标对象(Target),即我们要代理的对象(UserService),也就是我们要为此对象增加日志功能。
- 关注点(Concern):如日志功能。日志功能是系统级别的,不可能只在一个对象中增加日志功能。
- 通知(Advice):在Advice中写日志功能的具体实现。通知类实现不同的接口,如:Before,After……这些决定了日志功能如何添加(切入)到业务逻辑中,是在业务逻辑执行前添加,还是执行后添加。
AOP实现思路
- 写具体的通知类Advice(实现日志功能)
在配置文件中进行配置
- 配置生成代理类的方式
配置目标对象(可以直接配置具体的目标对象,也可以通过名称或正则表达式,程序执行后会在容器中寻找到符合条件的对象作为目标对象)
配置通知类
ProxyFactoryObject 显式创建AOP代理
Advice通知类
//引入System.Aop.dll;
using Spring.Aop; //AOP
using System.Reflection;//反射
namespace AOPExample
{
/// <summary>
/// Advice通知类:实现日志功能
/// </summary>
public class LogBeforeAdvice:IMethodBeforeAdvice
{
/// <summary>
/// 实现接口IMethodBeforeAdvice中的Before方法,在目标方法执行前执行Before()
/// </summary>
/// <param name="method">拦截的方法</param>
/// <param name="args">方法的参数</param>
/// <param name="target">拦截的对象</param>
public void Before(MethodInfo method, object[] args, object target)
{
Console.WriteLine("拦截的方法名—>" + method.Name);
Console.WriteLine("目标对象—>" + target);
Console.WriteLine("参数—>");
//如果参数不为空,遍历所有参数并输出
if (args != null)
{
foreach (object arg in args)
{
Console.WriteLine("\t: " + arg);
}
}
}
}
}
配置文件
<?xml version="1.0"?>
<configuration>
<!--Spring.Context.Support.ContextRegistry 的类型初始值设定项引发异常。-->
<!--注释掉此配置节,否则会报上述异常-->
<!--<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup>-->
<!--Spring配置-->
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects"/>
</context>
<objects xmlns="http://www.springframework.net">
<!--配置通知类LogBeforeAdvice-->
<object id="beforeAdvice" type="AOPExample.LogBeforeAdvice,AOPExample"/>
<!--通过ProxyFactoryObject 显式创建AOP代理-->
<object id="userServiceProxy" type="Spring.Aop.Framework.ProxyFactoryObject">
<!--指定代理的真实对象-->
<property name="Target">
<object type="AOPExample.UserService,AOPExample"/>
</property>
<!--指定具体通知类-->
<property name="InterceptorNames">
<list>
<!--指定具体通知类,和前面配置的id一致-->
<value>beforeAdvice</value>
</list>
</property>
</object>
</objects>
</spring>
</configuration>
客户端
class Program
{
static void Main(string[] args)
{
//实例化用户
User enUser = new User()
{
Name ="Jenny",
Age=15
};
//获得Spring容器上下文
IApplicationContext context = ContextRegistry.GetContext();
//从配置文件获得由ProxyFactoryObject生成的代理类
//需要修改客户端,原来是直接调用真实对象,现在是调用代理
IUserService userServiceProxy = (IUserService)context["userServiceProxy"];
userServiceProxy.GetUserInfo(enUser);
}
}
运行结果
总结
- 优点:通过ProxyFactoryObject 创建代理类,省去自己手动创建代理类;
- 缺点1:如果有多个目标对象,每个都需要在配置文件中配置;
- 缺点2:客户端调用时,由原来调用目标对象,变成调用代理对象,需要修改客户端代码。