Aop介绍及几种实现方式
目录
1.Aop介绍
2.Aop的基本概念
3.Aop的织入方式
4.Aop之静态织入
5.Aop之动态织入
Aop介绍
我们先看一下wiki百科的介绍
Traditional software development focuses on decomposing systems into
units of primary functionality, while recognizing that there are other
issues of concern that do not fit well into the primary decomposition.
The traditional development process leaves it to the programmers to code
modules corresponding to the primary functionality and to make sure
that all other issues of concern are addressed in the code wherever
appropriate. Programmers need to keep in mind all the things that need
to be done, how to deal with each issue, the problems associated with
the possible interactions, and the execution of the right behavior at
the right time. These concerns span multiple primary functional units
within the application, and often result in serious problems faced
during application development and maintenance. The distribution of the
code for realizing a concern becomes especially critical as the
requirements for that concern evolve – a system maintainer must find and
correctly update a variety of situations.
Aspect-oriented software development focuses on the identification,
specification and representation of cross-cutting concerns and their
modularization into separate functional units as well as their automated
composition into a working system.
传统的软件开发关注将系统分解成为一个主要的功能单元,然而却发现一些问题并不适合分解方式。这种传统的开发过程让编码人员合作编写主要的功能模块,以此保证那些主要的关注点能够正确的被编码。编码人员需要记住所有需要被完成的事情,如何处理每个问题,问题可能的关联关系,以此确定代码在正确的时候以正确的方式被执行。这些关注点在应用里面跨域了多个主要供单元,这经常在开发和维护时引发一些严重的问题。这些分布式代码导致的问题变得越来越迫切得需要得到解决-一个系统维护人员必须解决这种问题。
面向切面软件开发需要关注这些识别的,详细的,具有代表性的切面问题,将其模块化到功能捣衣并且自动将这些代码组合到一个工作中的系统。
英语比较蹩脚,翻译比较涩,总结起来的意思就是,Aop是将一些已经识别的切面关注的功能封装,并能自动将该功能组合到需要的地方。
我对Aop的理解就是,一些被封装好的,通用的业务单元,真正的编程人员不需要关注的部分,并能动态或静态的将这部分功能组合到业务中去。举个简单的例子,我们在代码中,经常要判断是否用户登录,如果未登录,就需要跳转到指定的页面,伪代码如下:
public string GetNews(){
/*判断是否登录,如果已经登录,则执行后面的业务代码
如果没有登录,则跳转到登录页面*/ //业务代码
}
我们可以来看一下简单的流程图
从图中我们可以将代码中的登录的判断业务分解成一个单独的业务单元,在需要的地方打上一个标签,告诉系统这里需要执行,那么其他编码人员就不需要再写重复类似的代码了。这就是Aop解决的问题。
Aop的基本概念
在介绍Aop的实现方式前,我们先了解一下Aop的几个知识点,这有助于我们理解Aop的实际技术。
8)weaving(插入):是指应用aspects到一个target对象创建proxy对象的过程:complie time,classload time,runtime
Aop的俩种织入方式
目前在.NET平台中,支持的织入方式有俩中,一种是静态织入,即编译时织入,另外一个是动态织入,即运行时织入。俩中方式各有优缺点,使用静态织入,可以不破坏代码结构,这里的破坏代码结构是你需要使用多余配置,写一些多余的代码,或必须依赖某种方式(这里大家也许也还不太明白,可以看完后面俩种方式的具体代码比较,再回头来看,会比较好理解)。使用动态织入的优点就是可以动态调试。俩中织入方式是互补的,即动态织入的优点也是静态织入的缺点,同理,静态织入的优点亦是动态织入的缺点。大家在做技术选型时可以根据自己的实际情况进行选择。
Aop之静态织入
目前成熟的框架有PostSharp,这个框架是商业框架,意思就是需要付费,这里就不具体介绍了,需要了解的土豪请到官网查看,具体如何使用请查阅文档。
BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费),实现前后Aop切面和INotifyPropertyChanged注入方式。其原理是在编译生成IL后,借助Mono.Cecil的AssemblyDefinition读取程序集,并检测需要注入的点,并将指定的代码注入到程序集中。有想具体深入研究的同学,可以到BSF.Aop中下载源码进行研究。遗憾的是这个只实现了俩个切入点,并没有在异常时提供切入点。
我们模拟一个日志记录的例子,我们先建一个项目。
1. 在项目中引用BSF.Aop.dll,Mono.Cecil.dll,Mono.Cecil.Pdb.dll,Microsoft.Build.dll;
2. 添加一个类LogAttribute并继承Aop.Attributes.Around.AroundAopAttribute(切面);
3. 重写AroundAopAttribute的Before和After方法,并写入逻辑代码;
4. 新建一个测试类LogTest,并添加Execute方法,并在Execute方法上面添加LogAttribute标签;
5. 我们在main里面new一个LogTest对象并调用看看输出结果;
具体的代码如下:
public class LogTest
{
[LogAttribute]
public void Execute(int a)
{
a = a * 100;
System.Console.WriteLine("Hello world!" + a);
}
} public class LogAttribute : AroundAopAttribute
{
public virtual void Before(AroundInfo info)
{
System.Console.WriteLine("Log before executed value is" + info.Params["a"]);
} public virtual void After(AroundInfo info)
{
System.Console.WriteLine("Log after executed value is" + info.Params["a"]);
}
}
static void Main(string[] args)
{
Aop.AopStartLoader.Start(null); new LogTest().Execute(2); Console.ReadLine();
}
执行代码输出:
上例代码中
- aspect 日志
- join point 即AroundAopAttribute中的Before和After,即方法执行前和方法执行后
- advice 即日志的逻辑部分
- pointcut 即我们LogAttribute中的Before(上面的例子故意没有重写After,是因为怕大家误解)
- target 这里我们是对方法进行切入的,即Execute方法
- weaving 这个例子中我们采用的是编译时的织入
Aop之动态织入
使用.NET提供的远程代理,即RealProxies来实现。
1.先建一个Aop代理类AopClassAttribute继承于ProxyAttribute,这个标签会告诉代理,这个类需要被代理创建调用;
/// <summary>
/// 标记一个类为Aop类,表示该类可以被代理注入
/// </summary>
public class AopClassAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
AopProxy realProxy = new AopProxy(serverType);
return realProxy.GetTransparentProxy() as MarshalByRefObject;
}
}
2.定义Aop的属性,并定义织入点
/// <summary>
/// Attribute基类,通过实现该类来实现切面的处理工作
/// </summary>
public abstract class AopAttribute : Attribute
{
/// <summary>
/// 调用之前会调用的方法
/// 1.如果不需要修改输出结果,请返回null
/// 2.如果返回值不为null,则不会再调用原始方法执行,而是直接将返回的参数作为结果
/// </summary>
/// <param name="args">方法的输入参数列表</param>
/// <param name="resultType">方法的返回值类型</param>
public abstract object PreCall(object[] args, Type resultType);
/// <summary>
/// 调用之后会调用的方法
/// </summary>
/// <param name="resultValue">方法的返回值</param>
/// <param name="args">方法的输入参数列表</param>
public abstract void Called(object resultValue, object[] args);
/// <summary>
/// 调用出现异常时会调用的方法
/// </summary>
/// <param name="e">异常值</param>
/// <param name="args">方法的输入参数列表</param>
public abstract void OnException(Exception e, object[] args);
}
3.定义代理的逻辑过程,这里我对returnvalue做了判断,是为了实现缓存更新和添加的切面代码做的,在这里我实现了三个切入点的调用,具体可看注释部分
/// <summary>
/// 主要代理处理类
/// </summary>
internal class AopProxy : RealProxy
{
public AopProxy(Type serverType)
: base(serverType)
{
} public override IMessage Invoke(IMessage msg)
{
if (msg is IConstructionCallMessage) return InvokeConstruction(msg);
else return InvokeMethod(msg);
} private IMessage InvokeMethod(IMessage msg)
{
IMethodCallMessage callMsg = msg as IMethodCallMessage;
IMessage returnMessage;
object[] args = callMsg.Args;
var returnType = (callMsg.MethodBase as System.Reflection.MethodInfo).ReturnType;//方法返回类型
object returnValue = null;//方法返回值
AopAttribute[] attributes = callMsg.MethodBase.GetCustomAttributes(typeof(AopAttribute), false) as AopAttribute[];
try
{
if (attributes == null || attributes.Length == ) return InvokeActualMethod(callMsg);
//前切点
foreach (AopAttribute attribute in attributes)
returnValue = attribute.PreCall(args, returnType);
//如果以前切面属性都没有返回值,则调用原始的方法;否则不调用
//主要是做缓存类似的业务
if (returnValue == null)
{
returnMessage = InvokeActualMethod(callMsg);
returnValue = (returnMessage as ReturnMessage).ReturnValue;
}
else returnMessage = new ReturnMessage(returnValue, args, args.Length, callMsg.LogicalCallContext, callMsg);
//后切点
foreach (AopAttribute attribute in attributes)
attribute.Called(returnValue,args);
}
catch (Exception e)
{
//异常切入点
foreach (AopAttribute attribute in attributes)
attribute.OnException(e, args); returnMessage = new ReturnMessage(e, callMsg);
}
return returnMessage;
} private IMessage InvokeActualMethod(IMessage msg)
{
IMethodCallMessage callMsg = msg as IMethodCallMessage;
object[] args = callMsg.Args;
object o = callMsg.MethodBase.Invoke(GetUnwrappedServer(), args);
return new ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg);
} private IMessage InvokeConstruction(IMessage msg)
{
IConstructionCallMessage constructCallMsg = msg as IConstructionCallMessage;
IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)msg);
RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue);
return constructionReturnMessage;
}
}
4.定义上下文边界对象,想要使用Aop的类需要继承此类(这个是这种Aop方式破坏性最大的地方,因为需要继承一个类,而面向对象单继承的特性导致了业务类不能再继承其他的类。可以想象一下你有一个查询基类,然后另一个查询类想要继承查询基类,而又想使用Aop,这时就尴尬了);
/// <summary>
/// Aop基类,需要注入的类需要继承该类
/// 对代码继承有要求,后续可以改进一下
/// 注意,需要记录的不支持上下文绑定,如果需要记录,使用代理模式解决
/// </summary>
public abstract class BaseAopObject : ContextBoundObject
{
}
5.定义Advice部分,即实际的业务逻辑,继承于AopAttribute
public class IncreaseAttribute : AopAttribute
{
private int Max = ;
public IncreaseAttribute(int max)
{
Max = max;
}
public override object PreCall(object[] args, Type resultType)
{
if (args == null || args.Count() == || !(args[] is ExampleData)) return null; var data = args[] as ExampleData;
string numString = args[].ToString();
data.Num = data.Num * ;
Console.WriteLine(data.Num);
return null;
} public override void Called(object resultValue, object[] args)
{
if (args == null || args.Count() == || !(args[] is ExampleData)) return; var data = args[] as ExampleData;
string numString = args[].ToString();
data.Num = data.Num * ;
Console.WriteLine(data.Num);
} public override void OnException(Exception e, object[] args)
{ }
} public class ExampleData
{
public int Num { get; set; }
}
6.完成了上面的部分,我们就可以来使用Aop了,定义一个需要使用Aop的类,继承于BaseAopObject,并在类上面加上[AopClass],在需要切入的方法上加上刚才定义的[IncreaseAttribute]
[AopClass]
public class Example : BaseAopObject
{
[IncreaseAttribute()]
public static void Do(ExampleData data)
{
Add(data);
} [IncreaseAttribute()]
public static ExampleData Add(ExampleData data)
{
return new ExampleData { Num = ++data.Num };
}
}
可以看到,使用上面这种织入方式,对代码的侵入性太大,会限制代码的可扩展性。所以我比较不建议使用。
另一种方式是借助Ioc的代理来做Aop切面注入,这里我们以Unity作为Ioc容器,以之前写的关于Unity Ioc中的例子来介绍Aop。
1.添加AopAttribute(定义连接点),这里有个循环引用,就是AopHandler和AopAttribute之间,不过并不影响使用,如有需要大家可以自己解决一下;
/// <summary>
/// 标记一个类或方法为代理,表示该类或方法可以被代理
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public abstract class AopAttribute : HandlerAttribute
{
/// <summary>
/// 请勿重写该方法
/// </summary>
/// <param name="container"></param>
/// <returns></returns>
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new AopHandler();
}
/// <summary>
/// 调用之前会调用的方法
/// 1.如果不需要修改输出结果,请返回null,ouputs返回new object[0]
/// 2.如果返回值不为null,则不会再调用原始方法执行,而是直接将返回的参数作为结果
/// </summary>
/// <param name="inputArgs">方法的输入参数列表</param>
/// <param name="outputs">方法中的out值,如果没有请返回null</param>
/// <returns>返回值</returns>
public abstract object PreCall(object[] inputArgs, out object[] outputs);
/// <summary>
/// 调用之后会调用的方法
/// </summary>
/// <param name="resultValue">方法的返回值</param>
/// <param name="inputArgs">方法的输入参数列表</param>
/// <param name="outputs">方法中的out值,如果没有则该参数值为null</param>
public abstract void Called(object resultValue, object[] inputArgs, object[] outputs);
/// <summary>
/// 调用出现异常时会调用的方法
/// </summary>
/// <param name="e">异常值</param>
/// <param name="inputArgs">方法的输入参数列表,键为参数名,值为参数值</param>
public abstract void OnException(Exception e, Dictionary<string, object> inputArgs);
}
2.添加AopHandler(代理类);
/// <summary>
/// 主要代理处理类
/// </summary>
internal class AopHandler : ICallHandler
{
public int Order { get; set; } = ; public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
IMethodReturn returnValue = null;
object attrReturnValue = null;
object[] outputs = null;
Dictionary<string, object> inputs = new Dictionary<string, object>();
//假如有忽略特性,直接忽略,不进行AOP代理
IgnoreAttribute[] ignoreAttributes = input.MethodBase.GetCustomAttributes(typeof(IgnoreAttribute), true) as IgnoreAttribute[];
if (ignoreAttributes != null && ignoreAttributes.Length > ) return input.CreateMethodReturn(attrReturnValue, outputs); AopAttribute[] attributes = input.MethodBase.GetCustomAttributes(typeof(AopAttribute), true) as AopAttribute[];
try
{
if (attributes == null || attributes.Length == ) return getNext()(input, getNext); for (var i = ; i < input.Arguments.Count; i++) inputs.Add(input.Inputs.ParameterName(i), input.Inputs[i]); foreach (AopAttribute attribute in attributes)
attrReturnValue = attribute.PreCall(inputs.Values.ToArray(), out outputs);
//如果以前切面属性都没有返回值,则调用原始的方法;否则不调用
//主要是做缓存类似的业务
if (attrReturnValue == null)
{
returnValue = getNext()(input, getNext);
outputs = new object[returnValue.Outputs.Count];
for (var i = ; i < returnValue.Outputs.Count; i++) outputs[i] = returnValue.Outputs[i];
}
else returnValue = input.CreateMethodReturn(attrReturnValue, outputs); if (returnValue.Exception != null) throw returnValue.Exception; foreach (AopAttribute attribute in attributes)
attribute.Called(returnValue.ReturnValue, inputs.Values.ToArray(), outputs);
}
catch (Exception e)
{
foreach (AopAttribute attribute in attributes)
attribute.OnException(e, inputs);
returnValue = input.CreateExceptionMethodReturn(e);
}
return returnValue;
}
}
3..定义一个我们自己的功能块(业务逻辑),这里还是以日志为例;
public class LogAttribute : AopAttribute
{
public override void Called(object resultValue, object[] inputArgs, object[] outputs)
{
Console.WriteLine("Called");
} public override void OnException(Exception e, Dictionary<string, object> inputArgs)
{
Console.WriteLine("exception:" + e.Message);
} public override object PreCall(object[] inputArgs, out object[] outputs)
{
Console.WriteLine("PreCall");
outputs = new object[0];
return null;
}
}
5.接下来我们稍微改造一下我们的印钞机;
/// <summary>
/// 印钞机
/// </summary>
public class CashMachine
{
public CashMachine() { } public void Print(ICashTemplate template)
{
string templateContent = template.GetTemplate("人民币"); System.Console.WriteLine(templateContent);
}
}
/// <summary>
/// 印钞模块
/// </summary>
public interface ICashTemplate
{
/// <summary>
/// 获取钞票模板
/// </summary>
/// <returns></returns>
[Log]
string GetTemplate(string flag);
} /// <summary>
/// 人民币钞票模板
/// </summary>
public class CNYCashTemplate : ICashTemplate
{
public CNYCashTemplate() { }
public string GetTemplate(string flag)
{
return "这是人民币模板!" + flag + " 这是返回值。";
}
}
/// <summary>
/// 美钞钞票模板
/// </summary>
public class USDCashTemplate : ICashTemplate
{
public USDCashTemplate() { } public string GetTemplate(string flag)
{
throw new Exception("哎呀,美钞模板有问题呀!");
}
}
6.然后我们在命令行的Main里改造一下;
static void Main(string[] args)
{
try
{
ICashTemplate usdTemplate = new USDCashTemplate();
ICashTemplate rmbTemplate = new CNYCashTemplate();
new CashMachine().Print(rmbTemplate);
new CashMachine().Print(usdTemplate);
}
catch (Exception)
{
}
Console.ReadLine();
}
7.启动一下看看结果
8.可以看到,只输出了GetTemplate方法的输出,并没有输出日志,我们要使用Ioc来注册对象才能使用,继续改造Main方法;
static void Main(string[] args)
{
UnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>().RegisterType<ICashTemplate, CNYCashTemplate>("cny");
container.Configure<Interception>().SetInterceptorFor<ICashTemplate>("cny", new InterfaceInterceptor());
container.AddNewExtension<Interception>().RegisterType<ICashTemplate, USDCashTemplate>("usd");
container.Configure<Interception>().SetInterceptorFor<ICashTemplate>("usd", new InterfaceInterceptor()); try
{new CashMachine().Print(container.Resolve<ICashTemplate>("cny"));
new CashMachine().Print(container.Resolve<ICashTemplate>("usd"));
}
catch (Exception)
{
}
Console.ReadLine();
}
9.启动运行,看一下结果;
可以看到,三个方法都执行了,而在抛出异常时是不会执行Called的方法的;
10.上面我们是直接使用了UnityContainer来注册对象,而没有使用我们之前封装的Ioc,我们还有更简单的方式,就是采用配置的方式来注册对象和拦截器实现Aop。在实际,使用一个单独的文件来配置ioc会更易于维护。我们先添加一个unity.config文件;
<?xml version="1.0" encoding="utf-8" ?>
<unity xmlns= "http://schemas.microsoft.com/practices/2010/unity ">
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
<!--注入对象-->
<typeAliases>
<!--表示单例-->
<typeAlias alias="singleton" type="Unity.Lifetime.ContainerControlledLifetimeManager,Unity.Abstractions" />
<!--表示每次使用都进行创建-->
<typeAlias alias="transient" type="Unity.Lifetime.TransientLifetimeManager,Unity.Abstractions" />
</typeAliases>
<container name= "Default">
<extension type="Interception"/>
<!--type表示接口 格式为 带命名空间的接口,程序集名 mapTo表示需要注入的实体类 name表示注入实体的name-->
<register type= "IocWithUnity.ICashTemplate,IocWithUnity" mapTo= "IocWithUnity.CNYCashTemplate,IocWithUnity" name="cny">
<!--定义拦截器-->
<interceptor type="InterfaceInterceptor"/>
<policyInjection/>
<!--定义对象生命周期-->
<lifetime type="singleton" />
</register>
<!--type表示接口 格式为 带命名空间的接口,程序集名 mapTo表示需要注入的实体类 name表示注入实体的name-->
<register type= "IocWithUnity.ICashTemplate,IocWithUnity" mapTo= "IocWithUnity.USDCashTemplate,IocWithUnity" name="usd">
<!--定义拦截器-->
<interceptor type="InterfaceInterceptor"/>
<policyInjection/>
<!--定义对象生命周期-->
<lifetime type="singleton" />
</register>
</container>
</unity>
11.再配置app.config(WEB项目应该是web.config);
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
</configSections>
<unity configSource="unity.config"/>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
12.将我们之前写的IocContainer修改一下读取配置;
public static class IocContainer
{
private static IUnityContainer _container = null;
static IocContainer()
{
_container = new UnityContainer(); object unitySection = ConfigurationManager.GetSection("unity");
if (unitySection == null) return; UnityConfigurationSection section = (UnityConfigurationSection)unitySection;
section.Configure(_container, "Default");
}
/// <summary>
/// 注册一个实例作为T的类型
/// </summary>
/// <typeparam name="T">需要注册的类型</typeparam>
/// <param name="instance">需要注册的实例</param>
public static void Register<T>(T instance)
{
_container.RegisterInstance<T>(instance);
}
/// <summary>
/// 注册一个名为name的T类型的实例
/// </summary>
/// <typeparam name="T">需要注册的类型</typeparam>
/// <param name="name">关键字名称</param>
/// <param name="instance">实例</param>
public static void Register<T>(string name, T instance)
{
_container.RegisterInstance(name, instance);
}
/// <summary>
/// 将类型TFrom注册为类型TTo
/// </summary>
/// <typeparam name="TFrom"></typeparam>
/// <typeparam name="TTo"></typeparam>
public static void Register<TFrom, TTo>() where TTo : TFrom
{
_container.RegisterType<TFrom, TTo>();
}
/// <summary>
/// 将类型TFrom注册为类型TTo
/// </summary>
/// <typeparam name="TFrom"></typeparam>
/// <typeparam name="TTo"></typeparam>
/// <typeparam name="lifetime"></typeparam>
public static void Register<TFrom, TTo>(LifetimeManager lifetime) where TTo : TFrom
{
_container.RegisterType<TFrom, TTo>(lifetime);
}
/// <summary>
/// 将类型TFrom注册名为name类型TTo
/// </summary>
/// <typeparam name="TFrom"></typeparam>
/// <typeparam name="TTo"></typeparam>
public static void Register<TFrom, TTo>(string name) where TTo : TFrom
{
_container.RegisterType<TFrom, TTo>(name);
}
/// <summary>
/// 通过关键字name来获取一个实例对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
public static T Resolve<T>(string name)
{
return _container.Resolve<T>(name);
}
/// <summary>
/// 获取一个为T类型的对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Resolve<T>()
{
return _container.Resolve<T>();
}
/// <summary>
/// 获取所有注册类型为T的对象实例
/// </summary>
/// <typeparam name="T">需要获取的类型的对象</typeparam>
/// <returns></returns>
public static IEnumerable<T> ResolveAll<T>()
{
return _container.ResolveAll<T>();
}
}
注意:配置时有一个坑 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> 这句话在5.0版本后dll的名称改了,以前是Microsoft.Practices.Unity.Configuration,现在是Unity.Configuration,如果你在运行时碰到找不到文件或程序集xxx时,可以注意看一下你的具体的dll的文件名。包括后面的unity.config里面的lifetime的配置也是,大家需要注意一下自己的版本,然后找到对应的命名空间和dll文件进行配置。
13.接下来我们运行一下看看结果如何;
总结:可以看到,静态织入方式相对较简单,对代码破坏性近乎于0,其原理大致是在编译前,将需要的代码添加到我们添加了Attribute的地方,如果用反编译工具反编译生成的dll就可以看到实际编译后的代码。这种织入方式的缺点是不易于调试工作,因为生成的pdb文件与我们的源代码文件实际上是不一样的。而采用真实代理的方式进行织入,这种方式比较原生,但对代码侵入性较大,而且效率也较低。使用ioc框架的拦截器进行拦截织入的方式,是当下比较好的一种方式,但是也是有一个约束,就是对象必须经过ioc容器来委托创建。基于这些比较,各位看官可以选择适合自己的织入方式。
本文原创,如有转载,请注明出处。
Aop介绍及几种实现方式的更多相关文章
- spring aop提供了两种实现方式jdk和cglib
Spring AOP使用了两种代理机制:一种是基于JDK的动态代理:另一种是基于CGLib的动态代理.之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理. Sprin ...
- 介绍HTML5几种存储方式
总体情况 h5之前,存储主要是用cookies.cookies缺点有在请求头上带着数据,大小是4k之内.主Domain污染. 主要应用:购物车.客户登录 对于IE浏览器有UserData,大小是64k ...
- Eclipse SVN 冲突的 介绍 及 四种解决方式
https://blog.csdn.net/diyu122222/article/details/79879376
- http协议(三)几种数据传输方式
说说http协议的一些特点: 1)无状态 http协议是一种自身不对请求和响应之间的通信状态进行保存的协议,即无状态协议. 这种设置的好处是:更快的处理更多的请求事务,确保协议的可伸缩性 不过随着we ...
- Android中几种定位 方式
介绍的几种定位方式 http://www.cnblogs.com/cuihongyu3503319/p/3863867.html 百度地图api: http://lbsyun.baidu.com/in ...
- C++中的4种类型转换方式
类型转换有c风格的,当然还有c++风格的.c风格的转换的格式很简单(TYPE)EXPRESSION,但是c风格的类型转换有不少的缺点,有的时候用c风格的转换是不合适的,因为它可以在任意类型之间转换,比 ...
- Java 定时任务的几种实现方式
JAVA实现定时任务的几种方式 @(JAVA)[spring|quartz|定时器] 近期项目开发中需要动态的添加定时任务,比如在某个活动结束时,自动生成获奖名单,导出excel等,此类任务由于活动 ...
- 精读JavaScript模式(四),数组,对象与函数的几种创建方式
一.前言 放了个元旦,休息了三天,加上春运抢票一系列事情的冲击,我感觉我的心已经飞了.确实应该收收心,之前计划的学习任务也严重脱节了:我恨不得打死我自己. 在上篇博客中,笔记记录到了关于构造函数方面的 ...
- http协议基础(三)几种数据传输方式
说说http协议的一些特点: 1)无状态 http协议是一种自身不对请求和响应之间的通信状态进行保存的协议,即无状态协议. 这种设置的好处是:更快的处理更多的请求事务,确保协议的可伸缩性 不过随着we ...
随机推荐
- 设计模式的征途—21.迭代器(Iterator)模式
我们都用过电视机遥控器,通过它我们可以进行开机.关机.换台.改变音量等操作.我们可以将电视机看做一个存储电视频道的集合对象,通过遥控器可以对电视机中的频道集合进行操作,例如返回上一个频道.跳转到下一个 ...
- JavaScript学习笔记(二)——字符串
在学习廖雪峰前辈的JavaScript教程中,遇到了一些需要注意的点,因此作为学习笔记列出来,提醒自己注意! 如果大家有需要,欢迎访问前辈的博客https://www.liaoxuefeng.com/ ...
- javaBean实体包区分
随着软件工程结构越来越复杂,单一的entity实体包无法满足业务需要,可以采取PO,BO,DTO结构来分类实体. PO (Persistent Object):与数据库表数据关联的实体. BO(Bus ...
- PHP Xdebug安装及配置
1.首先在官方网站下载dll文件; Xdebug官方网站 2.将php_xdebug.dll文件放入php/ext文件夹下; 3.编辑php.ini,在文件最后加入如下代码: ; Xdebug zen ...
- .NET之RabbitMQ学习笔记(二)-安装
安装 1.安装erlang语言环境 因为rabbitmq是基于erlang进行开发,所以需要安装相应的依赖环境,学习中用到的erlang包下载地址:http://www.erlang.org/down ...
- RocketMQ快速入门
前面几篇文章介绍了为什么选择RocketMQ,以及与kafka的一些对比: 阿里 RocketMQ 优势对比,方便大家对于RocketMQ有一个简单的整体了解,之后介绍了:MQ 应用场景,让我们知道M ...
- Openning
In order to imporve my english writing skill and enhance my understanding of programming ,I'm setti ...
- iOS上new Date异常解决办法
最近有一个项目要实现使用Angluar写一个简历模板, 用户输入姓名/生日/简介...等内容, 然后生成一份在线的简历 后来测试时遇到简历模板在Android手机跟Google浏览器上根据生日计算得出 ...
- SQL---索引---创建索引
CREATE INDEX 语句用于在表中创建索引. 在不读取整个表的情况下,索引使数据库应用程序可以更快地查找数据. 索引 您可以在表中创建索引,以便更加快速高效地查询数据. 用户无法看到索引,它们只 ...
- Jrebel热部署配置完整教程(IntelliJ IDEA、Jrebel、spring boot、springboot、eclipse、Tomcat)
标签:IntelliJ IDEA.Jrebel.spring boot.springboot.eclipse.Tomcat1.安装插件并激活插件安装参考:http://blog.csdn.net/u0 ...