[Abp 源码分析]六、工作单元的实现
0.简介
在 Abp 框架内部实现了工作单元,在这里讲解一下,什么是工作单元?
Unit Of Work(工作单元)模式用来维护一个由已经被业务事物修改(增加、删除或更新)的业务对象组成的列表。Unit Of Work模式负责协调这些修改的持久化工作以及所有标记的并发问题。在数据访问层中采用Unit Of Work模式带来的好处是能够确保数据完整性。如果在持久化一系列业务对象(他们属于同一个事物)的过程中出现问题,那么应该将所有的修改回滚,以确保数据始终处于有效状态。
而在 Abp 的内部则是结合 Castle 的 Dynamic Proxy 拦截 UnitOfwork Attribute 来进行动态代理注入,实现了当执行标注了 [UnitOfwork] 方法时能够通过 UnitOfworkManager 来进行事务控制。
其大概流程如下:

1 启动流程
首先我们来看一下 Abp 内部是什么时候注入 UOW 相关的代码的,翻阅源码,在 AbpBootstrapper 内部我们就可以看到 Abp 作者为 UOW 写了一个拦截器,并且在 Abp 框架初始化的时候就通过 AddInterceptorRegistrars() 方法来监听 IocManager 的组件注册事件,当触发事件的时候就来判断是否满足条件,如果满足则将拦截器与该类型进行一个绑定。
public class AbpBootstrapper : IDisposable
{
private AbpBootstrapper([NotNull] Type startupModule, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
{
// 其他代码
if (!options.DisableAllInterceptors)
{
// 添加拦截器
AddInterceptorRegistrars();
}
}
private void AddInterceptorRegistrars()
{
ValidationInterceptorRegistrar.Initialize(IocManager);
AuditingInterceptorRegistrar.Initialize(IocManager);
EntityHistoryInterceptorRegistrar.Initialize(IocManager);
UnitOfWorkRegistrar.Initialize(IocManager);
AuthorizationInterceptorRegistrar.Initialize(IocManager);
}
}
internal static class UnitOfWorkRegistrar
{
public static void Initialize(IIocManager iocManager)
{
// 监听组件注册事件
iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
{
var implementationType = handler.ComponentModel.Implementation.GetTypeInfo();
// 按 UOW 特性注册
HandleTypesWithUnitOfWorkAttribute(implementationType, handler);
// 按规约注册
HandleConventionalUnitOfWorkTypes(iocManager, implementationType, handler);
};
}
private static void HandleTypesWithUnitOfWorkAttribute(TypeInfo implementationType, IHandler handler)
{
if (IsUnitOfWorkType(implementationType) || AnyMethodHasUnitOfWork(implementationType))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
}
private static void HandleConventionalUnitOfWorkTypes(IIocManager iocManager, TypeInfo implementationType, IHandler handler)
{
if (!iocManager.IsRegistered<IUnitOfWorkDefaultOptions>())
{
return;
}
var uowOptions = iocManager.Resolve<IUnitOfWorkDefaultOptions>();
if (uowOptions.IsConventionalUowClass(implementationType.AsType()))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
}
private static bool IsUnitOfWorkType(TypeInfo implementationType)
{
return UnitOfWorkHelper.HasUnitOfWorkAttribute(implementationType);
}
private static bool AnyMethodHasUnitOfWork(TypeInfo implementationType)
{
return implementationType
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Any(UnitOfWorkHelper.HasUnitOfWorkAttribute);
}
}
可以看到在这个 Registrar 里面他拥有两种注册方式,第一种很简单,就是判断注册的组件类型是否拥有 UOW 标签,第二种则是通过规约来注入拦截器。
Abp 默认针对仓储与应用服务会自动将拦截器挂载到这两个类型以及他的所有子类的。这里的 UnitOfWorkDefaultOptionsExtensions.IsConventionalUowClass() 方法就是用来判断传入的 Type 是否属于规约的 Type。
public static bool IsConventionalUowClass(this IUnitOfWorkDefaultOptions unitOfWorkDefaultOptions, Type type)
{
return unitOfWorkDefaultOptions.ConventionalUowSelectors.Any(selector => selector(type));
}
又牵扯到了一个 IUnitOfWorkDefaultOptions ,看一下他的默认实现 UnitOfWorkDefaultOptions 就会发现这样的代码:
public UnitOfWorkDefaultOptions()
{
_filters = new List<DataFilterConfiguration>();
IsTransactional = true;
Scope = TransactionScopeOption.Required;
IsTransactionScopeAvailable = true;
// 默认类型
ConventionalUowSelectors = new List<Func<Type, bool>>
{
type => typeof(IRepository).IsAssignableFrom(type) ||
typeof(IApplicationService).IsAssignableFrom(type)
};
}
2. 实现原理
2.1 工作单元拦截器
在上一步我们通过两种注入方式将拦截器注入到需要应用工作单元特性的类型里面,那么我们程序在执行的时候就会使用 Dyncmic Proxy 来拦截包裹这些方法。
下面我们就来看一下刚刚注入的拦截器:
internal class UnitOfWorkInterceptor : IInterceptor
{
// ...
// 其他代码
public void Intercept(IInvocation invocation)
{
MethodInfo method;
try
{
method = invocation.MethodInvocationTarget;
}
catch
{
method = invocation.GetConcreteMethod();
}
// 判断当前进入的方法是否带有 UnitOfWork 特性
var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
{
// 没有则直接执行该方法
invocation.Proceed();
return;
}
PerformUow(invocation, unitOfWorkAttr.CreateOptions());
}
// ...
// 其他代码
}
拦截器内部方法很简单,如果是 UOW 方法则执行 PerformUow() 即可,在该方法内部则对方法类型进行了不同的判断,同步与异步的处理方法是不一样的。
// ...
// 其他代码
private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
{
// 判断方法是同步还是异步方法,不同则执行不同的处理操作
if (invocation.Method.IsAsync())
{
PerformAsyncUow(invocation, options);
}
else
{
PerformSyncUow(invocation, options);
}
}
// ...
// 其他代码
那么我们就先来看一下同步方法:
// ...
// 其他代码
private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
using (var uow = _unitOfWorkManager.Begin(options))
{
// 继续执行
invocation.Proceed();
uow.Complete();
}
}
// ...
// 其他代码
同步方法针对 UOW 的操作十分简单,直接使用 UnitOfWorkManager.Begin() 方法开启一个事务,然后在内部执行原有方法的代码,执行完成之后调用 Complete() 完成此次调用。
假如我拥有两个应用服务类,他们都拥有 UnitOfWork 特性,然后我再一个 A 方法调用他们两个 B 类的 Run() 方法,而 B类的内部也调用了C 的 Run() 方法,大体如下:
public class A
{
private readonly B B;
public A(B b)
{
B = b;
}
public void TestMethod()
{
B.Run();
}
}
internal class B
{
private readonly C C;
public B(C c)
{
C = c;
}
[UnitOfWork]
public void Run()
{
// 数据库操作
C.Run();
Console.WriteLine("B 的 Run 方法被调用.");
}
}
internal class C
{
[UnitOfWork]
public void Run()
{
Console.WriteLine("C 的 Run 方法被调用.");
}
}
然后在拦截器内部的执行过程就类似于下面这种:
internal class UnitOfWorkInterceptor
{
public void TestMethod()
{
using (var uow = _unitOfWorkManager.Begin(options))
{
using(var uow2 = _unitOfWorkManager.Begin(options))
{
// C 方法的代码
Console.WriteLine("C 的 Run 方法被调用.");
uow2.Complete();
}
// B 方法的代码
Console.WriteLine("B 的 Run 方法被调用.");
uow.Complete();
}
}
}
两个工作单元之间的调用会被嵌套在一个 using 语句块之中,一旦任何代码抛出了异常,都会导致最外层的 uow.Complete() 不会被执行,而 Complete() 方法没有执行,则会导致 uow 对象被释放的时候,uow.Dispose() 内部检测到 Complete() 没有被调用,Abp 框架也会自己抛出异常。
所以 Abp 巧妙结合 Castle Dynamic 实现了 UOW 模式。
下面我们继续看一下他是如何处理异步 UOW 方法的。
private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
var uow = _unitOfWorkManager.Begin(options);
try
{
invocation.Proceed();
}
catch
{
uow.Dispose();
throw;
}
// 如果是无返回值的异步方法
if (invocation.Method.ReturnType == typeof(Task))
{
invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
(Task) invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
// 有返回值的异步方法处理
else
{
invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
invocation.Method.ReturnType.GenericTypeArguments[0],
invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
}
相比而言,针对拦截到的异步方法处理起来更加复杂一点,但是总体思路仍然是一样的,将这些工作单元的方法一层层地嵌套起来,依次执行就是核心。而在上面代码里面,一样的首先使用 UnitOfManager.Begin() 获得了一个新的工作单元之后,继续执行原有的操作,下面则主要是通过内部的 InternalAsyncHelper 封装的两个辅助方法来确保等待原有任务执行完成之后,再执行 CompleteAsync() 方法。
我们可以来看一下这个内部类的实现:
// 异步无返回值处理
public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
try
{
// 等待原有任务执行完成
await actualReturnValue;
// 执行 CompleteAsync() 表示本工作单元已经顺利执行
await postAction();
}
// 捕获异常
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
// 不论是否抛出异常,都调用之前传入的 uow.Dispose() 方法
finalAction(exception);
}
}
// 原理基本同上,只是多了一个返回值
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);
}
}
// 异步有返回值处理
public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
{
// 这里通过反射获取到 AwaitTaskWithPostActionAndFinallyAndGetResult 方法,并调用。
return typeof (InternalAsyncHelper)
.GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(taskReturnType)
.Invoke(null, new object[] { actualReturnValue, action, finalAction });
}
并不复杂,以上即是拦截器所做的操作。
2.2 工作单元管理器
通过上文我们可以看到一个工作单元是通过 IUnitOfWorkManager.Begin() 拿到的,那 IUnitOfWorkManager 又是个什么东西呢?
根据字面意思我们大概知道应该类似于管理 UOW 的东西,它其实只有两个作用。第一,获取当前处于激活状态的工作单元,什么叫激活状态我们后面再讲。第二个作用就是我们之前看到的,可以通过 Begin() 方法来创建一个新的工作单元。
IUnitOfWorkManager 在 Abp 框架初始化的时候就被注入了,其默认实现为 UnitOfWorkManager ,其核心方法就是 Begin() 方法。
public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
{
// 如果没有传入 UOW 参数,则填充一个默认的参数
options.FillDefaultsForNonProvidedOptions(_defaultOptions);
// 获取当前的外部工作单元
var outerUow = _currentUnitOfWorkProvider.Current;
// 如果已经存在有外部工作单元,则直接构建一个内部工作单元
if (options.Scope == TransactionScopeOption.Required && outerUow != null)
{
return new InnerUnitOfWorkCompleteHandle();
}
// 不存在外部工作单元,则从 IOC 容器当中获取一个新的出来
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);
};
// 设置过滤器
if (outerUow != null)
{
options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList());
}
uow.Begin(options);
// 绑定租户 ID
if (outerUow != null)
{
uow.SetTenantId(outerUow.GetTenantId(), false);
}
// 设置当前的外部工作单元为刚刚初始化的工作单元
_currentUnitOfWorkProvider.Current = uow;
return uow;
}
可以看到 Begin() 方法返回的是一个类型为 IUnitOfWorkCompleteHandle 的东西,转到其定义:
/// <summary>
/// Used to complete a unit of work.
/// This interface can not be injected or directly used.
/// Use <see cref="IUnitOfWorkManager"/> instead.
/// </summary>
public interface IUnitOfWorkCompleteHandle : IDisposable
{
/// <summary>
/// Completes this unit of work.
/// It saves all changes and commit transaction if exists.
/// </summary>
void Complete();
/// <summary>
/// Completes this unit of work.
/// It saves all changes and commit transaction if exists.
/// </summary>
Task CompleteAsync();
}
他只有两个方法,都是标识 UOW 处于已经完成的状态。
在方法上面右键查看其实现可以看到有这样一种依赖关系:

可以看到 IUnitOfWorkCompleteHandle 有两个实现,一个是 InnerUnitOfWorkCompleteHandle 还有一个则是 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 Task CompleteAsync()
{
_isCompleteCalled = true;
return Task.FromResult(0);
}
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() != 0;
}
catch (Exception)
{
return false;
}
}
}
代码很简单,调用 Complete()/CompleteAsync() 会将 _isCompleteCalled 置为 true,然后在 Dispose() 方法内会进行检测,为 faslse 的话直接抛出异常。可以看到在 InnerUnitOfWorkCompleteHandle 内部并不会真正地调用 DbContext.SaveChanges() 进行数据保存。
那么谁才是真正进行数据库操作的工作单元呢?
答案就是之前在 IUnitOfWorkManager.Begin() 里面,可以看到在创建 UOW 对象的时候,他在内部进行了一个判断,如果不存在外部工作单元的情况下才会创建 InnerUnitOfWorkCompleteHandle 对象,否则是解析的一个 IUnitOfWork 对象。
也就是说你可以想象有以下代码:
public void TestUowMethod()
{
using(var outerUOW = Manager.Begin()) // 这里返回的是 IOC 解析出的 IUnitOfWork
{
OperationOuter();
using(var innerUOW1 = Manager.Begin()) // 内部 UOW
{
Operation1();
using(var innerUOW2 = Manager.Begin()) // 内部 UOW
{
Operation2();
Complete();
}
Complete();
}
Complete();
}
}
当代码执行的时候,如同俄罗斯套娃,从内部依次到外部执行,内部工作单元仅会在调用 Complete 方法的时候将 completed 标记为 true,但一旦操作抛出异常,Complete() 无法得到执行,则会直接抛出异常,中断外层代码执行。
在 ABP 内部针对 EF Core 框架实现了一套 UOW,其继承自 UnitOfWorkBase,而在 UnitOfWorkBase 内部有部分针对接口 IActiveUnitOfWork 的实现,同时由于 IUnifOfWork 也实现了 IUnitOfWorkCompleteHandle 接口,所以在 Begin() 方法处能够向上转型。
2.3 抽象工作单元
根据上图可以知道 Abp 默认实现了一个 UnitOfWorkBase 作为工作单元的抽象基类,他主要的属性就是 Id 与 Outer 属性。
public abstract class UnitOfWorkBase : IUnitOfWork
{
public string Id { get; }
[DoNotWire]
public IUnitOfWork Outer { get; set; }
}
这里的 Id 是使用的 Guid 生成的,用于标识每个工作单元。
而 Outer 则是当前 UOW 对象的引用对象。
这里重点说一下 Outer 是哪儿来的,Outer 他的值是在之前的 UnitOfWorkManager.Begin() 里面的 _currentUnitOfWorkProvider.Current = uow; 进行设置的,_currentUnitOfWorkProvider 的实现在 AsyncLocalCurrentUnitOfWorkProvider 内部,其作用是维护一个 UOW 链,确保当前的工作单元始终是最新的,这里的代码原本是使用 CallContext 实现的,现在已经换为 AsyncLocal<T> 了。
public class AsyncLocalCurrentUnitOfWorkProvider : ICurrentUnitOfWorkProvider, ITransientDependency
{
/// <inheritdoc />
[DoNotWire]
public IUnitOfWork Current
{
get { return GetCurrentUow(); }
set { SetCurrentUow(value); }
}
public ILogger Logger { get; set; }
private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>();
public AsyncLocalCurrentUnitOfWorkProvider()
{
Logger = NullLogger.Instance;
}
private static IUnitOfWork GetCurrentUow()
{
var uow = AsyncLocalUow.Value?.UnitOfWork;
if (uow == null)
{
return null;
}
if (uow.IsDisposed)
{
AsyncLocalUow.Value = null;
return null;
}
return uow;
}
private static void SetCurrentUow(IUnitOfWork value)
{
lock (AsyncLocalUow)
{
if (value == null)
{
if (AsyncLocalUow.Value == null)
{
return;
}
if (AsyncLocalUow.Value.UnitOfWork?.Outer == null)
{
AsyncLocalUow.Value.UnitOfWork = null;
AsyncLocalUow.Value = null;
return;
}
AsyncLocalUow.Value.UnitOfWork = AsyncLocalUow.Value.UnitOfWork.Outer;
}
else
{
if (AsyncLocalUow.Value?.UnitOfWork == null)
{
if (AsyncLocalUow.Value != null)
{
AsyncLocalUow.Value.UnitOfWork = value;
}
AsyncLocalUow.Value = new LocalUowWrapper(value);
return;
}
value.Outer = AsyncLocalUow.Value.UnitOfWork;
AsyncLocalUow.Value.UnitOfWork = value;
}
}
}
private class LocalUowWrapper
{
public IUnitOfWork UnitOfWork { get; set; }
public LocalUowWrapper(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
}
}
继续往下看,在 UnitOfWorkBase 的里面也是有个 Complete() 与 CompleteAsync() 方法的。
protected abstract void CompleteUow();
/// <inheritdoc/>
public void Complete()
{
// 判断是否重复完成
PreventMultipleComplete();
try
{
CompleteUow();
_succeed = true;
OnCompleted();
}
catch (Exception ex)
{
_exception = ex;
throw;
}
}
这里的 CompleteUow() 仍然只是一个抽象方法,具体的实现在具体的访问层里面。
2.4 EF Core 实际处理
Abp 针对 EF Core 的 UOW 实现是 EfCoreUnitOfWork,代码如下:
protected override void BeginUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.InitOptions(Options);
}
}
public override void SaveChanges()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
SaveChangesInDbContext(dbContext);
}
}
public override async Task SaveChangesAsync()
{
// 遍历所有激活的 DbContext
foreach (var dbContext in GetAllActiveDbContexts())
{
await SaveChangesInDbContextAsync(dbContext);
}
}
protected override void CompleteUow()
{
SaveChanges();
CommitTransaction();
}
protected override async Task CompleteUowAsync()
{
await SaveChangesAsync();
CommitTransaction();
}
private void CommitTransaction()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Commit();
}
}
public IReadOnlyList<DbContext> GetAllActiveDbContexts()
{
return ActiveDbContexts.Values.ToImmutableList();
}
根本就是遍历 DbContext 调用其 SaveChanges() 来提交所有数据库更改。
余下更加详细的东西会放在 《七、仓储与 Entity Framework Core》 当中说明的。
3.常见问题
3.1 待写
4.点此跳转到总目录
[Abp 源码分析]六、工作单元的实现的更多相关文章
- ABP源码分析六:依赖注入的实现
ABP的依赖注入的实现有一个本质两个途径:1.本质上是依赖于Castle这个老牌依赖注入的框架.2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入 ...
- [Abp vNext 源码分析] - 4. 工作单元
一.简要说明 统一工作单元是一个比较重要的基础设施组件,它负责管理整个业务流程当中涉及到的数据库事务,一旦某个环节出现异常自动进行回滚处理. 在 ABP vNext 框架当中,工作单元被独立出来作为一 ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
- ABP源码分析九:后台工作任务
文主要说明ABP中后台工作者模块(BackgroundWorker)的实现方式,和后台工作模块(BackgroundJob).ABP通过BackgroundWorkerManager来管理Backgr ...
- ABP源码分析十六:DTO的设计
IDTO:空接口,用于标注Dto对象. ComboboxItemDto:用于combobox/list中Item的DTO NameValueDto<T>/NameValueDto:用于na ...
- ABP源码分析二十六:核心框架中的一些其他功能
本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSess ...
- ABP源码分析三十六:ABP.Web.Api
这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...
- ABP源码分析四十六:ABP ZERO中的Ldap模块
通过AD作为用户认证的数据源.整个管理用户认证逻辑就在LdapAuthenticationSource类中实现. LdapSettingProvider:定义LDAP的setting和提供Defaut ...
- [Abp 源码分析]零、文章目录
0.系列文章目录 一.Abp 框架启动流程分析 二.模块系统 三.依赖注入 四.模块配置 五.系统设置 六.工作单元的实现 七.仓储与 Entity Framework Core 八.缓存管理 九.事 ...
随机推荐
- 快速从一个空虚拟机,空idea打通提交spark
https://www.cnblogs.com/xxbbtt/p/8143593.html #!/bin/bash # Install Spark on CentOS 7 yum install ja ...
- INotifyPropertyChanged 接口 CallerMemberName属性
调用方信息 使用调用方信息属性,可以获取关于调用方的信息传递给方法. 可以获取源代码.行号在源代码和调用方的成员名称的文件路径. 此信息用于跟踪,调试和创建诊断工具非常有用.若要获取此信息,则使用适用 ...
- 2018-2019-2 网络对抗技术 20165319 Exp2 后门原理与实践
后门的基本概念及基础问题 后门程序就是留在计算机系统中,供某位特殊使用者通过某种特殊方式控制计算机系统的途径. [1] 后门程序,跟我们通常所说的"木马"有联系也有区别.联系在于: ...
- Git帮助之初始化项目设置向导
初始化项目设置向导 Git设置: git config --global user.name "Your Name Here" # 设置Git提交时的默认用户名,推荐使用本站用户名 ...
- 修改或添加HTTP请求头
1.Cookie的修改 (一)cookie长什么样 cookie是一个个键值对(“键=值”的形式)加上分号空格隔开组合而成, 形如: "name1=value1; name2=value2; ...
- 计算机硬件&操作系统
一.计算机的硬件: 控制器:计算机的指挥系统 运算器:数学运算+逻辑运算 存储器I/O设备:存I取O数据 内存(内存条):短期记忆,速度快,但是断电数据会丢失: 外存(硬盘):永久记忆,速度非 ...
- Maven 插件之 docker-maven-plugin 的使用
目录 docker-maven-plugin 介绍环境.软件准备Demo 示例 配置 DOCKER_HOST示例构建镜像 指定构建信息到 POM 中构建使用 Dockerfile 构建使用命令绑定 D ...
- MongoDB 用Robomong可视化工具操作的 一些简单语句
一.数据更新 db.getCollection('表名').update({ "字段":{$in:["值"]} }, //更新条件 {$set:{ " ...
- 真正的ddos防御之道,简单干脆有效!
话说,30G 就各种发博客 BB,唉,坦白说 ,博客园团队真心没见过世面 来 各位 先看图 啥意思呢? 就是哥的 最高防御是 600G. 没错,基本对当时的游戏没啥大的影响,10秒内恢复. 因为时间 ...
- 04.封装ajax
<script> //封装ajax // 函数名 ajax // 函数的参数 // url: 请求的地址 // type: 请求的方式 get /post // data: 要上传的数据 ...