前言
ABP
ABP是“ASP.NET Boilerplate Project”的简称。
ABP的官方网站:http://www.aspnetboilerplate.com
ABP在Github上的开源项目:https://github.com/aspnetboilerplate
ABP其他学习博客推荐及介绍:http://www.cnblogs.com/mienreal/p/4528470.html
ABP中Unit of Work概念及使用
如果这是你首次接触ABP框架或ABP的Unit of Work,推荐先看看 ABP使用及框架解析系列-[Unit of Work part.1-概念及使用]
框架实现
温馨提示
1.ABP的Unit of Work相关代码路径为:/Abp/Domain/Uow
2.框架实现中,代码不会贴全部的,但是会说明代码在项目中的位置,并且为了更加直观和缩短篇幅,对代码更细致的注释,直接在代码中,不要忘记看代码注释哈,。
3.博客中的代码,有一些方法是可以点击的!
动态代理/拦截器/AOP
上面讲到Unit of Work有两个默认实现,领域服务和仓储库的每个方法默认就是一个工作单元,这个是如何实现的呢?在方法上添加一个UnitOfWork特性也就让该方法为一个工作单元,这又是如何实现的呢?上面的标题已然暴露了答案——动态代理
在ABP中,使用了Castle的DynamicProxy进行动态代理,在组件注册是进行拦截器的注入,具体代码如下:
internal static class UnitOfWorkRegistrar
{
public static void Initialize(IIocManager iocManager)
{//该方法会在应用程序启动的时候调用,进行事件注册
iocManager.IocContainer.Kernel.ComponentRegistered += ComponentRegistered;
} private static void ComponentRegistered(string key, IHandler handler)
{
if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation))
{//判断类型是否实现了IRepository或IApplicationService,如果是,则为该类型注册拦截器(UnitOfWorkInterceptor)
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute))
{//或者类型中任何一个方法上应用了UnitOfWorkAttribute,同样为类型注册拦截器(UnitOfWorkInterceptor)
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
}
}public static bool IsConventionalUowClass(Type type)
{
return typeof(IRepository).IsAssignableFrom(type) || typeof(IApplicationService).IsAssignableFrom(type);
}public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo)
{
return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true);
}拦截器UnitOfWorkInterceptor实现了IInterceptor接口,在调用注册了拦截器的类的方法时,会被拦截下来,而去执行IInterceptor的Intercept方法,下面是Intercept方法的代码实现:
public void Intercept(IInvocation invocation)
{
if (_unitOfWorkManager.Current != null)
{//如果当前已经在工作单元中,则直接执行被拦截类的方法
invocation.Proceed();
return;
} //获取方法上的UnitOfWorkAttribute,如果没有返回NULL,invocation.MethodInvocationTarget为被拦截类的类型
var unitOfWorkAttr = UnitOfWorkAttribute.GetUnitOfWorkAttributeOrNull(invocation.MethodInvocationTarget);
if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
{//如果当前方法上没有UnitOfWorkAttribute或者是设置为Disabled,则直接调用被拦截类的方法
invocation.Proceed();
return;
} //走到这里就表示是需要将这个方法作为工作单元了,详情点击查看
PerformUow(invocation, unitOfWorkAttr.CreateOptions());
}internal static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(MemberInfo methodInfo)
{
//获取方法上标记的UnitOfWorkAttribute
var attrs = methodInfo.GetCustomAttributes(typeof(UnitOfWorkAttribute), false);
if (attrs.Length > )
{
return (UnitOfWorkAttribute)attrs[];
} if (UnitOfWorkHelper.IsConventionalUowClass(methodInfo.DeclaringType))
{//如果方法上没有标记UnitOfWorkAttribute,但是方法的所属类实现了IRepository或IApplicationService,则返回一个默认UnitOfWorkAttribute
return new UnitOfWorkAttribute();
} return null;
}private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
{
if (AsyncHelper.IsAsyncMethod(invocation.Method))
{//被拦截的方法为异步方法
PerformAsyncUow(invocation, options);
}
else
{//被拦截的方法为同步方法
PerformSyncUow(invocation, options);
}
}private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
//手动创建一个工作单元,将被拦截的方法直接放在工作单元中
using (var uow = _unitOfWorkManager.Begin(options))
{
invocation.Proceed();
uow.Complete();
}
}private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
//异步方法的处理相对麻烦,需要将工作单元的Complete和Dispose放到异步任务中
var uow = _unitOfWorkManager.Begin(options); invocation.Proceed(); if (invocation.Method.ReturnType == typeof(Task))
{//如果是无返回值的异步任务
invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
(Task)invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
else //Task<TResult>
{//如果是有返回值的异步任务
invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
invocation.Method.ReturnType.GenericTypeArguments[],
invocation.ReturnValue,
async () => await uow.CompleteAsync(),
(exception) => uow.Dispose()
);
}
}/// <summary>
/// 修改异步返回结果,并且使用try...catch...finally
/// </summary>
/// <param name="actualReturnValue">方法原始返回结果</param>
/// <param name="postAction">期望返回结果</param>
/// <param name="finalAction">无论是否异常都将执行的代码</param>
/// <returns>新的异步结果</returns>
public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
//在方法被拦截方法执行前调用工作单元的Begin,修改异步结果为调用工作单元的CompleteAsync方法,并保证工作单元会被Dispose掉
try
{
await actualReturnValue;
await postAction();
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
{
//有返回值的异步任务重写更为复杂,需要先通过反射来为泛型传值,然后才可调用泛型方法来重写异步返回值
return typeof (InternalAsyncHelper)
.GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(taskReturnType)
.Invoke(null, new object[] { actualReturnValue, action, finalAction });
}public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
//该方法与之前无返回值的异步任务调用的方法相同,只是多了个泛型
try
{
var result = await actualReturnValue;
await postAction();
return result;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}总结来说,就是通过拦截器在执行方法的时候,先判断是否需要进行工作单元操作。如果需要,则在执行方法前开启工作单元,在执行方法后关闭工作单元。
在上面的代码中,我们可以看到,工作单元都是通过_unitOfWorkManager(IUnitOfWorkManager)这样一个对象进行的,下面我们就来解析这个类到底是如何进行单元控制的。
IUnitOfWorkManager、IUnitOfWorkCompleteHandle
public interface IUnitOfWorkManager
{
IActiveUnitOfWork Current { get; }
IUnitOfWorkCompleteHandle Begin();
IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope);
IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options);
}ABP中,默认将UnitOfWorkManager作为IUnitOfWorkManager作为实现类,其实现中,Current直接取得ICurrentUnitOfWorkProvider对象的Current属性,后续解析ICurrentUnitOfWorkProvider。而IUnitOfWorkManager的三个Begin只是重载,最后都将调用第三个Begin的重载方法。下面是它的代码实现:
public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
{
//为未赋值的参数设置默认值
options.FillDefaultsForNonProvidedOptions(_defaultOptions); if (options.Scope == TransactionScopeOption.Required && _currentUnitOfWorkProvider.Current != null)
{//如果当前Scope的设置为Required(而非RequiredNew),并且当前已存在工作单元,那么久返回下面这样的一个对象
return new InnerUnitOfWorkCompleteHandle();
}
//走到这里,表示需要一个新的工作单元,通过IoC创建IUnitOfWork实现对象,然后开始工作单元,并设置此工作单元为当前工作单元
var uow = _iocResolver.Resolve<IUnitOfWork>(); uow.Completed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
};
uow.Failed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
};
uow.Disposed += (sender, args) =>
{
_iocResolver.Release(uow);
}; uow.Begin(options);
_currentUnitOfWorkProvider.Current = uow; return uow;
}public IActiveUnitOfWork Current
{
get { return _currentUnitOfWorkProvider.Current; }
}internal void FillDefaultsForNonProvidedOptions(IUnitOfWorkDefaultOptions defaultOptions)
{
if (!IsTransactional.HasValue)
{
IsTransactional = defaultOptions.IsTransactional;
} if (!Scope.HasValue)
{
Scope = defaultOptions.Scope;
} if (!Timeout.HasValue && defaultOptions.Timeout.HasValue)
{
Timeout = defaultOptions.Timeout.Value;
} if (!IsolationLevel.HasValue && defaultOptions.IsolationLevel.HasValue)
{
IsolationLevel = defaultOptions.IsolationLevel.Value;
}
}Begin方法最后返回的对象继承自IUnitOfWorkCompleteHandle,让我们看看IUnitOfWorkCompleteHandle的接口声明又是什么样的:
public interface IUnitOfWorkCompleteHandle : IDisposable
{
void Complete(); Task CompleteAsync();
}总共也就两个方法,而且意思相同,都是用来完成当前工作单元的,一个同步一个异步。同时实现了IDisposable接口,结合IUnitOfWorkManager使用Begin的方式便可理解其含义(使用using)。
在之前的Begin实现中,我们看到,其返回路线有两个,一个返回了InnerUnitOfWorkCompleteHandle对象,另一个返回了IUnitOfWork实现对象。IUnitOfWork稍后详细解析,让我们先来解析InnerUnitOfWorkCompleteHandle :
internal class InnerUnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle
{
public const string DidNotCallCompleteMethodExceptionMessage = "Did not call Complete method of a unit of work."; private volatile bool _isCompleteCalled;
private volatile bool _isDisposed; public void Complete()
{
_isCompleteCalled = true;
} public async Task CompleteAsync()
{
_isCompleteCalled = true;
} public void Dispose()
{
if (_isDisposed)
{
return;
} _isDisposed = true; if (!_isCompleteCalled)
{
if (HasException())
{
return;
} throw new AbpException(DidNotCallCompleteMethodExceptionMessage);
}
} private static bool HasException()
{
try
{
return Marshal.GetExceptionCode() != ;
}
catch (Exception)
{
return false;
}
}
}我们可以看到,Complete和CompleteAsync里面,都只是简单的将_isCompleteCalled设置为true,在Dispose方法中,也仅仅只是判断是否已经dispose过、是否完成、是否有异常,没有更多的动作。这样大家会不会有一个疑问“这个工作单元的完成和释放工作里没有具体的完成操作,怎么就算完成工作单元了?”,这时,让我们结合使用到InnerUnitOfWorkCompleteHandle的地方来看:
if (options.Scope == TransactionScopeOption.Required && _currentUnitOfWorkProvider.Current != null)
{
return new InnerUnitOfWorkCompleteHandle();
}这个判断,代表了当前已经存在工作单元,所以这个就是用于工作单元嵌套。内部的工作单元在提交和释放时,不需要做实际的提交和释放,只需要保证没有异常抛出,然后最外层工作单元再进行实际的提交和释放。这也就说明,在Begin方法中的另一条路线,返回IUnitOfWork实现对象才是最外层的事务对象。
IUnitOfWork
public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle
{
//唯一的标识ID
string Id { get; } //外层工作单元
IUnitOfWork Outer { get; set; } //开始工作单元
void Begin(UnitOfWorkOptions options);
}IUnitOfWork除了继承了IUnitOfWorkCompleteHandle接口,拥有了Complete方法外,还继承了IActiveUnitOfWork接口:
public interface IActiveUnitOfWork
{
//三个事件
event EventHandler Completed;
event EventHandler<UnitOfWorkFailedEventArgs> Failed;
event EventHandler Disposed; //工作单元设置
UnitOfWorkOptions Options { get; } //数据过滤配置
IReadOnlyList<DataFilterConfiguration> Filters { get; } bool IsDisposed { get; } void SaveChanges(); Task SaveChangesAsync(); IDisposable DisableFilter(params string[] filterNames);
IDisposable EnableFilter(params string[] filterNames);
bool IsFilterEnabled(string filterName);
IDisposable SetFilterParameter(string filterName, string parameterName, object value);
}这个接口中包含了很多Filter相关的属性与方法,都是些数据过滤相关的,这里不对其进行介绍,所以这个接口里,主要包含了三个事件(Completed、Failed、Disposed),工作单元设置(Options),IsDisposed以及同步异步的SaveChanges。
除了IUnitOfWork接口,ABP还提供了一个实现IUnitOfWork接口的抽象基类UnitOfWorkBase,UnitOfWorkBase的主要目的,是为开发人员处理一些前置、后置工作和异常处理,所以UnitOfWorkBase实现了部分方法,并提供了一些抽象方法,在实现的部分中做好前后置工作,然后调用抽象方法,将主要实现交给子类,并对其进行异常处理,下面以Begin实现与Complete实现为例:
public void Begin(UnitOfWorkOptions options)
{
if (options == null)
{
throw new ArgumentNullException("options");
} //防止Begin被多次调用
PreventMultipleBegin();
Options = options; //过滤配置
SetFilters(options.FilterOverrides); //抽象方法,子类实现
BeginUow();
} public void Complete()
{
//防止Complete被多次调用
PreventMultipleComplete();
try
{
//抽象方法,子类实现
CompleteUow();
_succeed = true;
OnCompleted();
}
catch (Exception ex)
{
_exception = ex;
throw;
}
}private void PreventMultipleBegin()
{
if (_isBeginCalledBefore)
{//如果已经调用过Begin方法,再次调用则抛出异常
throw new AbpException("This unit of work has started before. Can not call Start method more than once.");
} _isBeginCalledBefore = true;
}private void PreventMultipleComplete()
{
if (_isCompleteCalledBefore)
{//如果已经掉用过Complete方法,再次调用则抛出异常
throw new AbpException("Complete is called before!");
} _isCompleteCalledBefore = true;
}UnitOfWorkBase的其他实现与上面的类似(非Unit of Work相关内容略去),便不全贴出。但是上面的代码,大家有发现一个问题吗? 就是那些PreventMultiple…方法的实现,用来防止多次Begin、Complete等。这些方法的实现,在单线程下是没有问题的,但是里面并没有加锁,所以不适用于多线程。这是作者的BUG吗? 然而,这却并不是BUG,而是设计如此,为何呢?因为在设计上,一个线程共用一个工作单元,也就不存在多线程了。关于这部分内容,后续将会介绍。
IUnitOfWorkManager目的是提供一个简洁的IUnitOfWork管理对象,而IUnitOfWork则提供了整个工作单元需要的所有控制(Begin、SaveChanges、Complete、Dispose)。而具体应该如何保证一个线程共用一个工作单元,如何获取当前的工作单元,则由ICurrentUnitOfWorkProvider进行管控,正如在解析UnitOfWorkManager时,说明了它的Current实际上就是调用ICurrentUnitOfWorkProvider实现对象的Current属性。
ICurrentUnitOfWorkProvider
public interface ICurrentUnitOfWorkProvider
{
IUnitOfWork Current { get; set; }
}ICurrentUnitOfWorkProvider仅仅只声明了一个Current属性,那么重点让我们来看看Current在实现类(CallContextCurrentUnitOfWorkProvider)中是如何写的吧:
[DoNotWire]
public IUnitOfWork Current
{
get { return GetCurrentUow(Logger); }
set { SetCurrentUow(value, Logger); }
}上面标注的DoNotWire是为了不让IoC进行属性注入,Current内部分别调用了GetCurrentUow和SetCurrentUow,要取值,先要设值,让我来先看看set吧:
private static void SetCurrentUow(IUnitOfWork value, ILogger logger)
{
if (value == null)
{//如果在set的时候设置为null,便表示要退出当前工作单元
ExitFromCurrentUowScope(logger);
return;
} //获取当前工作单元的key
var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string;
if (unitOfWorkKey != null)
{
IUnitOfWork outer;
if (UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out outer))
{
if (outer == value)
{
logger.Warn("Setting the same UOW to the CallContext, no need to set again!");
return;
}
//到这里也就表示当前存在工作单元,那么再次设置工作单元,不是替换掉当前的工作单元而是将当前工作单元作为本次设置的工作单元的外层工作单元
value.Outer = outer;
}
} unitOfWorkKey = value.Id;
if (!UnitOfWorkDictionary.TryAdd(unitOfWorkKey, value))
{//如果向工作单元中添加工作单元失败,便抛出异常
throw new AbpException("Can not set unit of work! UnitOfWorkDictionary.TryAdd returns false!");
}
//设置当前线程的工作单元key
CallContext.LogicalSetData(ContextKey, unitOfWorkKey);
}private static void ExitFromCurrentUowScope(ILogger logger)
{
//ContextKey为一个常量字符串
//CallContext可以理解为每个线程的独有key,value集合类,每个线程都会有自己的存储区,
// 也就是在线程A中设置一个key为xx的value,在线程B中通过xx取不到,并可以存入相同键的value,但是不会相互覆盖、影响
//根据ContextKey从线程集合中取出当前工作单元key
var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string;
if (unitOfWorkKey == null)
{//没有取到值,表示当前无工作单元
logger.Warn("There is no current UOW to exit!");
return;
} IUnitOfWork unitOfWork;
//UnitOfWorkDictionary类型为ConcurrentDictionary,线程安全字典,用于存储所有工作单元(单线程上最多只能有一个工作单元,但是多线程可能会有多个)
if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork))
{//根据key没有取到value,从线程集合(CallContext)中释放该key
CallContext.FreeNamedDataSlot(ContextKey);
return;
} //从工作单元集合中移除当前工作单元
UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork);
if (unitOfWork.Outer == null)
{//如果当前工作单元没有外层工作单元,则从线程集合(CallContext)中释放该key
CallContext.FreeNamedDataSlot(ContextKey);
return;
} var outerUnitOfWorkKey = unitOfWork.Outer.Id; //这里也就表明了key实际上就是UnitOfWork的Id
if (!UnitOfWorkDictionary.TryGetValue(outerUnitOfWorkKey, out unitOfWork))
{//如果当前工作单元有外层工作单元,但是从工作单元集合中没有取到了外层工作单元,那么同样从线程集合(CallContext)中释放该key
CallContext.FreeNamedDataSlot(ContextKey);
return;
}
//能到这里,就表示当前工作单元有外层工作单元,并且从工作单元集合中获取到了外层工作单元,那么就设外层工作单元为当前工作单元
CallContext.LogicalSetData(ContextKey, outerUnitOfWorkKey);
}看完set,再让我们来看看get吧:
private static IUnitOfWork GetCurrentUow(ILogger logger)
{
//获取当前工作单元key
var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string;
if (unitOfWorkKey == null)
{
return null;
} IUnitOfWork unitOfWork;
if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork))
{//如果根据key获取不到当前工作单元,那么就从当前线程集合(CallContext)中释放key
CallContext.FreeNamedDataSlot(ContextKey);
return null;
} if (unitOfWork.IsDisposed)
{//如果当前工作单元已经dispose,那么就从工作单元集合中移除,并将key从当前线程集合(CallContext)中释放
logger.Warn("There is a unitOfWorkKey in CallContext but the UOW was disposed!");
UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork);
CallContext.FreeNamedDataSlot(ContextKey);
return null;
} return unitOfWork;
}总的说来,所有的工作单元存储在线程安全的字典对象中(ConcurrentDictionary),每个主线程共用一个工作单元的实现,通过线程集合(CallContext)实现。
UnitOfWork实现
从上面的分析可以看出,ABP/Domain/Uow路径下,主要只是提供了一套抽象接口,并没有提供实际的实现,IUnitOfWork最多也只是提供了一个UnitOfWorkBase抽象类,这样的*性非常大,我非常喜欢这种方式。
当然ABP也另起了几个项目来提供一些常用的ORM的Unit of Work封装:
1.Ef: Abp.EntityFramework/EntityFramework/Uow
2.NH: Abp.NHibernate/NHibernate/Uow
3.Mongo: Abp.MongoDB/MongoDb/Uow
4.Memory: Abp.MemoryDb/MemoryDb/Uow
其中Mongo和Memory都没有进行实质性的单元操作,Ef中使用TransactionScope进行单元操作,NH中使用ITransaction来进行单元操作。
ABP/Domain/Uow结构说明
UnitOfWorkRegistrar····································注册拦截器,实现两种默认的UnitOfWork,详见最上面的默认行为
UnitOfWorkInterceptor··································Unit of Work拦截器,实现以AOP的方式进行注入单元控制
IUnitOfWorkManager····································简洁的UnitOfWork管理对象
UnitOfWorkManager··································IUnitOfWorkManager默认实现
ICurrentUnitOfWorkProvider···························当前UnitOfWork管理对象
CallContextCurrentUnitOfWorkProvider············ICurrentUnitOfWorkProvider默认实现
IUnitOfWork···············································工作单元对象(Begin、SaveChanges、Complete、Dispose)
UnitOfWorkBase·······································IUnitOfWork抽象实现类,封装实际操作的前后置操作及异常处理
NullUnitOfWork········································IUnitOfWork空实现
IActiveUnitOfWork·······································IUnitOfWork操作对象,不包含Begin与Complete操作
IUnitOfWorkCompleteHandle··························工作单元完成对象,用于实现继承工作单元功能
InnerUnitOfWorkCompleteHandle··················IUnitOfWorkCompleteHandle实现之一,用于继承外部工作单元
IUnitOfWorkDefaultOptions····························UnitOfWork默认设置
UnitOfWorkDefaultOptions··························IUnitOfWorkDefaultOptions默认实现
UnitOfWorkOptions·····································UnitOfWork配置对象
UnitOfWorkAttribute····································标记工作单元的特性
UnitOfWorkFailedEventArgs··························UnitOfWork的Failed事件参数
UnitOfWorkHelper·····································工具类
AbpDataFilters·········································数据过滤相关
DataFilterConfiguration·······························数据过滤相关
ABP使用及框架解析系列 - [Unit of Work part.2-框架实现]的更多相关文章
-
ABP使用及框架解析系列 - [Unit of Work part.1-概念及使用]
前言 ABP ABP是“ASP.NET Boilerplate Project”的简称. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开 ...
-
深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...
-
【SMB源码解析系列】——003.SMB游戏基本框架
前面有了解到RESET中断相关代码,结尾处通过一句jmp进入了无限循环,之后CPU将会在每一帧PUU进入VBlank状态时,接收NMI中断信号, 跳转至NMI代码处继续执行,直到遇见RTI指令时又返回 ...
-
AF(操作者框架)系列(2)-在Actor框架中派生Actor核心(命令模式)
为了能够提高程序的复用性,我们准备用Actor Framework框架,来演示其满足了这个需求(本章及后面的内容,需要有OOP的基础知识). 首先,简述一下ActorFramework的运行过程: 在 ...
-
Sword框架解析——知识采集流程页面初始化
Sword框架解析——知识采集流程页面初始化 Sword框架解析知识采集流程页面初始化 问题解答流程采集新增页面初始化 1后台t_xt_gnzy表和BLH类 2BLH类的写法前台目录树代码 3登录系统 ...
-
深入理解Java并发框架AQS系列(一):线程
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.概述 1.1.前言 重剑无锋,大巧不工 读j.u.c包下的源码,永远无法绕开的经典 ...
-
深入理解Java并发框架AQS系列(四):共享锁(Shared Lock)
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock) 深入 ...
-
ABP(现代ASP.NET样板开发框架)系列之12、ABP领域层——工作单元(Unit Of work)
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ABP是“ASP.NET Boilerplate Pr ...
-
ABP开发框架前后端开发系列---(1)框架的总体介绍
ABP是ASP.NET Boilerplate的简称,ABP是一个开源且文档友好的应用程序框架.ABP不仅仅是一个框架,它还提供了一个最徍实践的基于领域驱动设计(DDD)的体系结构模型.学习使用ABP ...
随机推荐
-
Hibernate入门案例配置以及增、删、改、查看
享受痛苦就是走向成功的第一步. 一.创建一个项目(lib里面是需要的夹包小奶瓶要导包) 二.书写大配置文件 大配置文件必须放置在项目根目录(专业classpath下):界定:就是src 1名称:hib ...
-
django复习笔记1:环境配置
一.IDE 推荐使用sublime安装djaneiro插件. 1.安装方式 package control中搜索djaneiro 支持补全请参考:Django support for Sublime ...
-
【IOS】2.基础
1.Identifers命名规则 Identifers is combined with letters, underline, dollars, numbers must begin with le ...
-
JavaEE Tutorials (10) - Java持久化查询语言
10.1查询语言术语14010.2使用Java持久化查询语言创建查询141 10.2.1查询中的命名参数142 10.2.2查询中的位置参数14210.3简化的查询语言语法142 10.3.1选择语句 ...
-
设计模式 -->; (14)中介者模式
中介者模式 用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互.中介者模式的例子很多,大到联合国安理会,小到房屋中介,都扮演了 ...
-
python提取网页表格并保存为csv
0. 1.参考 W3C HTML 表格 表格标签 表格 描述 <table> 定义表格 <caption> 定义表格标题. <th> 定义表格的表头. <tr ...
-
python图像处理库PIL的基本概念介绍
PIL中所涉及的基本概念有如下几个:通道(bands).模式(mode).尺寸(size).坐标系统(coordinate system).调色板(palette).信息(info)和滤波器(filt ...
-
dcoker 安装mysql和数据持久化
# mysql 镜像下载 docker pull mysql ( 版本自己选择) # run 服务 docker run --name mysql.5.5 -p 3306:3306 -e MYSQ ...
-
BZOJ2563: 阿狸和桃子的游戏 贪心
Description 阿狸和桃子正在玩一个游戏,游戏是在一个带权图G=(V, E)上进行的,设节点权值为w(v),边权为c(e).游戏规则是这样的: 1. 阿狸和桃子轮流将图中的顶点染色,阿狸会将顶 ...
-
电子设备 Kindle如何删除书籍或漫画
需要在亚马逊的官方设置好国家: 再在这里就可以管理了 要在kindle的上方小齿轮那里设置(飞行模式右边): 同步kindle 这样就能管理了