C# 实现AOP 的几种常见方式
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的中统一处理业务逻辑的一种技术,比较常见的场景是:日志记录,错误捕获、性能监控等
AOP的本质是通过代理对象来间接执行真实对象,在代理类中往往会添加装饰一些额外的业务代码,比如如下代码:
class RealA
{
public virtual string Pro { get; set; } public virtual void ShowHello(string name)
{
Console.WriteLine($"Hello!{name},Welcome!");
}
} //调用: var a = new RealA();
a.Pro = "测试";
a.ShowHello("梦在旅途");
这段代码很简单,只是NEW一个对象,然后设置属性及调用方法,但如果我想在设置属性前后及调用方法前后或报错都能收集日志信息,该如何做呢?可能大家会想到,在设置属性及调用方法前后都加上记录日志的代码不就可以了,虽然这样是可以,但如果很多地方都要用到这个类的时候,那重复的代码是否太多了一些吧,所以我们应该使用代理模式或装饰模式,将原有的真实类RealA委托给代理类ProxyRealA来执行,代理类中在设置属性及调用方法时,再添加记录日志的代码就可以了,这样可以保证代码的干净整洁,也便于代码的后期维护。(注意,在C#中若需被子类重写,父类必需是虚方法或虚属性virtual)
如下代码:
class ProxyRealA : RealA
{ public override string Pro
{
get
{
return base.Pro;
}
set
{
ShowLog("设置Pro属性前日志信息");
base.Pro = value;
ShowLog($"设置Pro属性后日志信息:{value}");
}
} public override void ShowHello(string name)
{
try
{
ShowLog("ShowHello执行前日志信息");
base.ShowHello(name);
ShowLog("ShowHello执行后日志信息");
}
catch(Exception ex)
{
ShowLog($"ShowHello执行出错日志信息:{ex.Message}");
}
} private void ShowLog(string log)
{
Console.WriteLine($"{DateTime.Now.ToString()}-{log}");
}
} //调用:
var aa = new ProxyRealA();
aa.Pro = "测试2";
aa.ShowHello("zuowenjun.cn");
这段代码同样很简单,就是ProxyRealA继承自RealA类,即可看成是ProxyRealA代理RealA,由ProxyRealA提供各种属性及方法调用。这样在ProxyRealA类内部属性及方法执行前后都有统一记录日志的代码,不论在哪里用这个RealA类,都可以直接用ProxyRealA类代替,因为里氏替换原则,父类可以被子类替换,而且后续若想更改日志记录代码方式,只需要在ProxyRealA中更改就行了,这样所有用到的ProxyRealA类的日志都会改变,是不是很爽。上述执行结果如下图示:
以上通过定义代理类的方式能够实现在方法中统一进行各种执行点的拦截代码逻辑处理,拦截点(或者称为:横切面,切面点)一般主要为:执行前,执行后,发生错误,虽然解决了之前直接调用真实类RealA时,需要重复增加各种逻辑代码的问题,但随之而来的新问题又来了,那就是当一个系统中的类非常多的时候,如果我们针对每个类都定义一个代理类,那么系统的类的个数会成倍增加,而且不同的代理类中可能某些拦截业务逻辑代码都是相同的,这种情况同样是不能允许的,那有没有什么好的办法呢?答案是肯定的,以下是我结合网上资源及个人总结的如下几种常见的实现AOP的方式,各位可以参考学习。
第一种:静态织入,即:在编译时,就将各种涉及AOP拦截的代码注入到符合一定规则的类中,编译后的代码与我们直接在RealA调用属性或方法前后增加代码是相同的,只是这个工作交由编译器来完成。
PostSharp:PostSharp的Aspect是使用Attribute实现的,我们只需事先通过继承自OnMethodBoundaryAspect,然后重写几个常见的方法即可,如:OnEntry,OnExit等,最后只需要在需要进行AOP拦截的属性或方法上加上AOP拦截特性类即可。由于PostSharp是静态织入的,所以相比其它的通过反射或EMIT反射来说效率是最高的,但PostSharp是收费版本的,而且网上的教程比较多,我就不在此重复说明了,大家可以参见:使用PostSharp在.NET平台上实现AOP
第二种:EMIT反射,即:通过Emit反射动态生成代理类,如下Castle.DynamicProxy的AOP实现方式,代码也还是比较简单的,效率相对第一种要慢一点,但对于普通的反射来说又高一些,代码实现如下:
using Castle.Core.Interceptor;
using Castle.DynamicProxy;
using NLog;
using NLog.Config;
using NLog.Win32.Targets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
ProxyGenerator generator = new ProxyGenerator();
var test = generator.CreateClassProxy<TestA>(new TestInterceptor());
Console.WriteLine($"GetResult:{test.GetResult(Console.ReadLine())}");
test.GetResult2("test");
Console.ReadKey();
}
} public class TestInterceptor : StandardInterceptor
{
private static NLog.Logger logger; protected override void PreProceed(IInvocation invocation)
{
Console.WriteLine(invocation.Method.Name + "执行前,入参:" + string.Join(",", invocation.Arguments));
} protected override void PerformProceed(IInvocation invocation)
{
Console.WriteLine(invocation.Method.Name + "执行中");
try
{
base.PerformProceed(invocation);
}
catch (Exception ex)
{
HandleException(ex);
}
} protected override void PostProceed(IInvocation invocation)
{
Console.WriteLine(invocation.Method.Name + "执行后,返回值:" + invocation.ReturnValue);
} private void HandleException(Exception ex)
{
if (logger == null)
{
LoggingConfiguration config = new LoggingConfiguration(); ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget();
consoleTarget.Layout = "${date:format=HH\\:MM\\:ss} ${logger} ${message}";
config.AddTarget("console", consoleTarget); LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget);
config.LoggingRules.Add(rule1);
LogManager.Configuration = config; logger = LogManager.GetCurrentClassLogger(); //new NLog.LogFactory().GetCurrentClassLogger();
}
logger.ErrorException("error",ex);
}
} public class TestA
{
public virtual string GetResult(string msg)
{
string str = $"{DateTime.Now.ToString("yyyy-mm-dd HH:mm:ss")}---{msg}";
return str;
} public virtual string GetResult2(string msg)
{
throw new Exception("throw Exception!");
}
}
}
简要说明一下代码原理,先创建ProxyGenerator类实例,从名字就看得出来,是代理类生成器,然后实例化一个基于继承自StandardInterceptor的TestInterceptor,这个TestInterceptor是一个自定义的拦截器,最后通过generator.CreateClassProxy<TestA>(new TestInterceptor())动态创建了一个继承自TestA的动态代理类,这个代理类只有在运行时才会生成的,后面就可以如代码所示,直接用动态代理类对象实例Test操作TestA的所有属性与方法,当然这里需要注意,若需要被动态代理类所代理并拦截,则父类的属性或方法必需是virtual,这点与我上面说的直接写一个代理类相同。
上述代码运行效果如下:
第三种:普通反射+利用Remoting的远程访问对象时的直实代理类来实现,代码如下,这个可能相比以上两种稍微复杂一点:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{ var A = new AopClass();
A.Hello(); var aop = new AopClassSub("梦在旅途");
aop.Pro = "test";
aop.Output("hlf");
aop.ShowMsg();
Console.ReadKey(); }
} [AopAttribute]
public class AopClass : ContextBoundObject
{
public string Hello()
{
return "welcome";
} } public class AopClassSub : AopClass
{
public string Pro = null;
private string Msg = null; public AopClassSub(string msg)
{
Msg = msg;
} public void Output(string name)
{
Console.WriteLine(name + ",你好!-->P:" + Pro);
} public void ShowMsg()
{
Console.WriteLine($"构造函数传的Msg参数内容是:{Msg}");
}
} public class AopAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
AopProxy realProxy = new AopProxy(serverType);
return realProxy.GetTransparentProxy() as MarshalByRefObject;
}
} public class AopProxy : RealProxy
{
public AopProxy(Type serverType)
: base(serverType) { } public override IMessage Invoke(IMessage msg)
{
if (msg is IConstructionCallMessage)
{
IConstructionCallMessage constructCallMsg = msg as IConstructionCallMessage;
IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)msg);
RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue);
Console.WriteLine("Call constructor");
return constructionReturnMessage;
}
else
{
IMethodCallMessage callMsg = msg as IMethodCallMessage;
IMessage message;
try
{
Console.WriteLine(callMsg.MethodName + "执行前。。。");
object[] args = callMsg.Args;
object o = callMsg.MethodBase.Invoke(GetUnwrappedServer(), args);
Console.WriteLine(callMsg.MethodName + "执行后。。。");
message = new ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg);
}
catch (Exception e)
{
message = new ReturnMessage(e, callMsg);
}
Console.WriteLine(message.Properties["__Return"]);
return message;
}
}
} }
以上代码实现步骤说明:
1.这里定义的一个真实类AopClass必需继承自ContextBoundObject类,而ContextBoundObject类又直接继承自MarshalByRefObject类,表明该类是上下文绑定对象,允许在支持远程处理的应用程序中跨应用程序域边界访问对象,说白了就是可以获取这个真实类的所有信息,以便可以被生成动态代理。
2.定义继承自ProxyAttribute的代理特性标识类AopAttribute,以表明哪些类可以被代理,同时注意重写CreateInstance方法,在CreateInstance方法里实现通过委托与生成透明代理类的过程,realProxy.GetTransparentProxy() 非常重要,目的就是根据定义的AopProxy代理类获取生成透明代理类对象实例。
3.实现通用的AopProxy代理类,代理类必需继承自RealProxy类,在这个代理类里面重写Invoke方法,该方法是统一执行被代理的真实类的所有方法、属性、字段的出入口,我们只需要在该方法中根据传入的IMessage进行判断并实现相应的拦截代码即可。
4.最后在需要进行Aop拦截的类上标注AopAttribute即可(注意:被标识的类必需是如第1条说明的继承自ContextBoundObject类),在实际调用的过程中是感知不到任何的变化。且AopAttribute可以被子类继承,也就意味着所有子类都可以被代理并拦截。
如上代码运行效果如下:
这里顺便分享微软官方如果利用RealProxy类实现AOP的,详见地址:https://msdn.microsoft.com/zh-cn/library/dn574804.aspx
第四种:反射+ 通过定义统一的出入口,并运用一些特性实现AOP的效果,比如:常见的MVC、WEB API中的过滤器特性 ,我这里根据MVC的思路,实现了类似的MVC过滤器的AOP效果,只是中间用到了反射,可能性能不佳,但效果还是成功实现了各种拦截,正如MVC一样,既支持过滤器特性,也支持Controller中的Action执行前,执行后,错误等方法实现拦截
实现思路如下:
A.过滤器及Controller特定方法拦截实现原理:
1.获取程序集中所有继承自Controller的类型;
2.根据Controller的名称找到第1步中的对应的Controller的类型:FindControllerType
3.根据找到的Controller类型及Action的名称找到对应的方法:FindAction
4.创建Controller类型的实例;
5.根据Action方法找到定义在方法上的所有过滤器特性(包含:执行前、执行后、错误)
6.执行Controller中的OnActionExecuting方法,随后执行执行前的过滤器特性列表,如:ActionExecutingFilter
7.执行Action方法,获得结果;
8.执行Controller中的OnActionExecuted方法,随后执行执行后的过滤器特性列表,如:ActionExecutedFilter
9.通过try catch在catch中执行Controller中的OnActionError方法,随后执行错误过滤器特性列表,如:ActionErrorFilter
10.最后返回结果;
B.实现执行路由配置效果原理:
1.增加可设置路由模板列表方法:AddExecRouteTemplate,在方法中验证controller、action,并获取模板中的占位符数组,最后保存到类全局对象中routeTemplates;
2.增加根据执行路由执行对应的Controller中的Action方法的效果: Run,在该方法中主要遍历所有路由模板,然后与实行执行的请求路由信息通过正则匹配,若匹配OK,并能正确找到Controller及Action,则说明正确,并最终统一调用:Process方法,执行A中的所有步骤最终返回结果。
需要说明该模拟MVC方案并没有实现Action方法参数的的绑定功能,因为ModelBinding本身就是比较复杂的机制,所以这里只是为了搞清楚AOP的实现原理,故不作这方面的研究,大家如果有空可以实现,最终实现MVC不仅是ASP.NET MVC,还可以是 Console MVC,甚至是Winform MVC等。
以下是实现的全部代码,代码中我已进行了一些基本的优化,可以直接使用:
public abstract class Controller
{
public virtual void OnActionExecuting(MethodInfo action)
{ } public virtual void OnActionExecuted(MethodInfo action)
{ } public virtual void OnActionError(MethodInfo action, Exception ex)
{ } } public abstract class FilterAttribute : Attribute
{
public abstract string FilterType { get; }
public abstract void Execute(Controller ctrller, object extData);
} public class ActionExecutingFilter : FilterAttribute
{
public override string FilterType => "BEFORE"; public override void Execute(Controller ctrller, object extData)
{
Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutingFilter中拦截发出的消息!-{DateTime.Now.ToString()}");
}
} public class ActionExecutedFilter : FilterAttribute
{
public override string FilterType => "AFTER"; public override void Execute(Controller ctrller, object extData)
{
Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutedFilter中拦截发出的消息!-{DateTime.Now.ToString()}");
}
} public class ActionErrorFilter : FilterAttribute
{
public override string FilterType => "EXCEPTION"; public override void Execute(Controller ctrller, object extData)
{
Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionErrorFilter中拦截发出的消息!-{DateTime.Now.ToString()}-Error Msg:{(extData as Exception).Message}");
}
} public class AppContext
{
private static readonly Type ControllerType = typeof(Controller);
private static readonly Dictionary<string, Type> matchedControllerTypes = new Dictionary<string, Type>();
private static readonly Dictionary<string, MethodInfo> matchedControllerActions = new Dictionary<string, MethodInfo>();
private Dictionary<string,string[]> routeTemplates = new Dictionary<string, string[]>(); public void AddExecRouteTemplate(string execRouteTemplate)
{
if (!Regex.IsMatch(execRouteTemplate, "{controller}", RegexOptions.IgnoreCase))
{
throw new ArgumentException("执行路由模板不正确,缺少{controller}");
} if (!Regex.IsMatch(execRouteTemplate, "{action}", RegexOptions.IgnoreCase))
{
throw new ArgumentException("执行路由模板不正确,缺少{action}");
} string[] keys = Regex.Matches(execRouteTemplate, @"(?<={)\w+(?=})", RegexOptions.IgnoreCase).Cast<Match>().Select(c => c.Value.ToLower()).ToArray(); routeTemplates.Add(execRouteTemplate,keys);
} public object Run(string execRoute)
{
//{controller}/{action}/{id}
string ctrller = null;
string actionName = null;
ArrayList args = null;
Type controllerType = null;
bool findResult = false; foreach (var r in routeTemplates)
{
string[] keys = r.Value;
string execRoutePattern = Regex.Replace(r.Key, @"{(?<key>\w+)}", (m) => string.Format(@"(?<{0}>.[^/\\]+)", m.Groups["key"].Value.ToLower()), RegexOptions.IgnoreCase); args = new ArrayList();
if (Regex.IsMatch(execRoute, execRoutePattern))
{
var match = Regex.Match(execRoute, execRoutePattern);
for (int i = 0; i < keys.Length; i++)
{
if ("controller".Equals(keys[i], StringComparison.OrdinalIgnoreCase))
{
ctrller = match.Groups["controller"].Value;
}
else if ("action".Equals(keys[i], StringComparison.OrdinalIgnoreCase))
{
actionName = match.Groups["action"].Value;
}
else
{
args.Add(match.Groups[keys[i]].Value);
}
} if ((controllerType = FindControllerType(ctrller)) != null && FindAction(controllerType, actionName, args.ToArray()) != null)
{
findResult = true;
break;
}
}
} if (findResult)
{
return Process(ctrller, actionName, args.ToArray());
}
else
{
throw new Exception($"在已配置的路由模板列表中未找到与该执行路由相匹配的路由信息:{execRoute}");
}
} public object Process(string ctrller, string actionName, params object[] args)
{
Type matchedControllerType = FindControllerType(ctrller); if (matchedControllerType == null)
{
throw new ArgumentException($"未找到类型为{ctrller}的Controller类型");
} object execResult = null;
if (matchedControllerType != null)
{
var matchedController = (Controller)Activator.CreateInstance(matchedControllerType);
MethodInfo action = FindAction(matchedControllerType, actionName, args);
if (action == null)
{
throw new ArgumentException($"在{matchedControllerType.FullName}中未找到与方法名:{actionName}及参数个数:{args.Count()}相匹配的方法");
} var filters = action.GetCustomAttributes<FilterAttribute>(true);
List<FilterAttribute> execBeforeFilters = new List<FilterAttribute>();
List<FilterAttribute> execAfterFilters = new List<FilterAttribute>();
List<FilterAttribute> exceptionFilters = new List<FilterAttribute>(); if (filters != null && filters.Count() > 0)
{
execBeforeFilters = filters.Where(f => f.FilterType == "BEFORE").ToList();
execAfterFilters = filters.Where(f => f.FilterType == "AFTER").ToList();
exceptionFilters = filters.Where(f => f.FilterType == "EXCEPTION").ToList();
} try
{
matchedController.OnActionExecuting(action); if (execBeforeFilters != null && execBeforeFilters.Count > 0)
{
execBeforeFilters.ForEach(f => f.Execute(matchedController, null));
} var mParams = action.GetParameters();
object[] newArgs = new object[args.Length];
for (int i = 0; i < mParams.Length; i++)
{
newArgs[i] = Convert.ChangeType(args[i], mParams[i].ParameterType);
} execResult = action.Invoke(matchedController, newArgs); matchedController.OnActionExecuted(action); if (execBeforeFilters != null && execBeforeFilters.Count > 0)
{
execAfterFilters.ForEach(f => f.Execute(matchedController, null));
} }
catch (Exception ex)
{
matchedController.OnActionError(action, ex); if (exceptionFilters != null && exceptionFilters.Count > 0)
{
exceptionFilters.ForEach(f => f.Execute(matchedController, ex));
}
} } return execResult; } private Type FindControllerType(string ctrller)
{
Type matchedControllerType = null;
if (!matchedControllerTypes.ContainsKey(ctrller))
{
var assy = Assembly.GetAssembly(typeof(Controller)); foreach (var m in assy.GetModules(false))
{
foreach (var t in m.GetTypes())
{
if (ControllerType.IsAssignableFrom(t) && !t.IsAbstract)
{
if (t.Name.Equals(ctrller, StringComparison.OrdinalIgnoreCase) || t.Name.Equals($"{ctrller}Controller", StringComparison.OrdinalIgnoreCase))
{
matchedControllerType = t;
matchedControllerTypes[ctrller] = matchedControllerType;
break;
}
}
}
}
}
else
{
matchedControllerType = matchedControllerTypes[ctrller];
} return matchedControllerType;
} private MethodInfo FindAction(Type matchedControllerType, string actionName, object[] args)
{
string ctrlerWithActionKey = $"{matchedControllerType.FullName}.{actionName}";
MethodInfo action = null;
if (!matchedControllerActions.ContainsKey(ctrlerWithActionKey))
{
if (args == null) args = new object[0];
foreach (var m in matchedControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
if (m.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase) && m.GetParameters().Length == args.Length)
{
action = m;
matchedControllerActions[ctrlerWithActionKey] = action;
break;
}
}
}
else
{
action = matchedControllerActions[ctrlerWithActionKey];
} return action;
}
}
使用前,先定义一个继承自Controller的类,如:TestController,并重写相应的方法,或在指定的方法上加上所需的过滤器特性,如下代码所示:
public class TestController : Controller
{
public override void OnActionExecuting(MethodInfo action)
{
Console.WriteLine($"{action.Name}执行前,OnActionExecuting---{DateTime.Now.ToString()}");
} public override void OnActionExecuted(MethodInfo action)
{
Console.WriteLine($"{action.Name}执行后,OnActionExecuted--{DateTime.Now.ToString()}");
} public override void OnActionError(MethodInfo action, Exception ex)
{
Console.WriteLine($"{action.Name}执行,OnActionError--{DateTime.Now.ToString()}:{ex.Message}");
} [ActionExecutingFilter]
[ActionExecutedFilter]
public string HelloWorld(string name)
{
return ($"Hello World!->{name}");
} [ActionExecutingFilter]
[ActionExecutedFilter]
[ActionErrorFilter]
public string TestError(string name)
{
throw new Exception("这是测试抛出的错误信息!");
} [ActionExecutingFilter]
[ActionExecutedFilter]
public int Add(int a, int b)
{
return a + b;
}
}
最后前端实际调用就非常简单了,代码如下:
class MVCProgram
{
static void Main(string[] args)
{
try
{
var appContext = new AppContext();
object rs = appContext.Process("Test", "HelloWorld", "梦在旅途");
Console.WriteLine($"Process执行的结果1:{rs}"); Console.WriteLine("=".PadRight(50, '=')); appContext.AddExecRouteTemplate("{controller}/{action}/{name}");
appContext.AddExecRouteTemplate("{action}/{controller}/{name}"); object result1 = appContext.Run("HelloWorld/Test/梦在旅途-zuowenjun.cn");
Console.WriteLine($"执行的结果1:{result1}"); Console.WriteLine("=".PadRight(50, '=')); object result2 = appContext.Run("Test/HelloWorld/梦在旅途-zuowenjun.cn");
Console.WriteLine($"执行的结果2:{result2}"); Console.WriteLine("=".PadRight(50, '=')); appContext.AddExecRouteTemplate("{action}/{controller}/{a}/{b}");
object result3 = appContext.Run("Add/Test/500/20");
Console.WriteLine($"执行的结果3:{result3}"); object result4 = appContext.Run("Test/TestError/梦在旅途-zuowenjun.cn");
Console.WriteLine($"执行的结果4:{result4}");
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"发生错误:{ex.Message}");
Console.ResetColor();
} Console.ReadKey();
}
}
可以看到,与ASP.NET MVC有点类似,只是ASP.NET MVC是通过URL访问,而这里是通过AppContext.Run 执行路由URL 或Process方法,直接指定Controller、Action、参数来执行。
通过以上调用代码可以看出路由配置还是比较灵活的,当然参数配置除外。如果大家有更好的想法也可以在下方评论交流,谢谢!
MVC代码执行效果如下:
C# 实现AOP 的几种常见方式的更多相关文章
- 适用于app.config与web.config的ConfigUtil读写工具类 基于MongoDb官方C#驱动封装MongoDbCsharpHelper类(CRUD类) 基于ASP.NET WEB API实现分布式数据访问中间层(提供对数据库的CRUD) C# 实现AOP 的几种常见方式
适用于app.config与web.config的ConfigUtil读写工具类 之前文章:<两种读写配置文件的方案(app.config与web.config通用)>,现在重新整理一 ...
- C# DataGridView绑定数据源的几种常见方式
开始以前,先认识一下WinForm控件数据绑定的两种形式,简单数据绑定和复杂数据绑定. 1. 简单的数据绑定 例1 using (SqlConnection conn = new SqlConnect ...
- jedis操作redis的几种常见方式总结
Redis是一个著名的key-value存储系统,也是nosql中的最常见的一种,这篇文章主要给大家总结了关于在java中jedis操作redis的几种常见方式,文中给出了详细的示例代码供大家参考学习 ...
- Tomcat 部署项目的几种常见方式
转自:https://www.cnblogs.com/yuht/p/5714624.html https://www.cnblogs.com/ysocean/p/6893446.html Tomcat ...
- JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解
在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...
- 恶意软件开发——shellcode执行的几种常见方式
一.什么是shellcode? shellcode是一小段代码,用于利用软件漏洞作为有效载荷.它之所以被称为"shellcode",是因为它通常启动一个命令shell,攻击者可以从 ...
- AOP的两种实现方式
技术交流群 :233513714 AOP,面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. Aspect Oriented Progr ...
- Azure登陆的两种常见方式(user 和 service principal登陆)
通过Powershell 登陆Azure(Azure MoonCake为例)一般常见的有两种方式 1. 用户交互式登陆 前提条件:有一个AAD account 此种登陆方式会弹出一个登陆框,让你输入一 ...
- Spring中AOP的两种代理方式(Java动态代理和CGLIB代理)
第一种代理即Java的动态代理方式上一篇已经分析,在这里不再介绍,现在我们先来了解下GCLIB代理是什么?它又是怎样实现的?和Java动态代理有什么区别? cglib(Code Generation ...
随机推荐
- ETL作业调度软件TASKCTL4.1集群部署
熟悉TASKCTL4.1一段时间后,觉得它的调度逻辑什么的都还不错,但是感觉单机部署不太够用.想实现跨机调度作业,就要会TASKCTL的集群部署.下面就是我在网上找到的相关资料,非原创. 单机部署成功 ...
- 浅谈layer.open的弹出层中的富文本编辑器为何不起作用!
很多童鞋都喜欢用贤心的layui框架.是的,我也喜欢用,方便,简单.但是呢,有时候项目中的需求会不一样,导致我们用的时候,显示效果可能会不一样,好吧.这样的话,个别遇到的问题总是解决不好,但是呢还是那 ...
- JUnit4总结
JUnit4使用要求: 测试方法必须使用@Test进行修饰 测试方法必须使用public void 进行修饰,不能带任何的参数 新建一个源代码目录来存放我们的测试代码 测试类的包应该和被测试类保持一致 ...
- [js高手之路] es6系列教程 - var, let, const详解
function show( flag ){ console.log( a ); if( flag ){ var a = 'ghostwu'; return a; } else { console.l ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(二)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 ...
- Sangmado 公共基础类库
Sangmado 涵盖了支撑 .NET/C# 项目开发的最基础的公共类库,为团队在不断的系统开发和演进过程中发现和积累的最公共的代码可复用单元. Sangmado 公共类库设计原则: 独立性:不与任何 ...
- NYOJ 25 A Famous Music Composer
A Famous Music Composer 时间限制:1000 ms | 内存限制:65535 KB 难度:1 描述 Mr. B is a famous music composer. O ...
- 不安分的this
不安分的this 前言:关于javascript中的this,上网一搜一大片的文章.惊! 而我个人认为要想分清this,就有必要先搞清楚“对象”. 目录: 一.函数对象的认识 二.this 一.函数对 ...
- 手工释放linux内存——/proc/sys/vm/drop_caches
--手工释放linux内存——/proc/sys/vm/drop_caches 总有很多朋友对于Linux的内存管理有疑问,之前一篇日志似乎也没能清除大家的疑虑.而在新版核心中,似乎对这个问题提供了新 ...
- WEB的进击之路-第一章 HTML基本标签(1)
一.HTML简介 超文本标记语言,标准通用标记语言下的一个应用. "超文本"就是指页面内可以包含图片.链接,甚至音乐.程序等非文字元素. 超文本标记语言的结构包括"头&q ...