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 源码分析]六、工作单元的实现的更多相关文章

  1. ABP源码分析六:依赖注入的实现

    ABP的依赖注入的实现有一个本质两个途径:1.本质上是依赖于Castle这个老牌依赖注入的框架.2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入 ...

  2. [Abp vNext 源码分析] - 4. 工作单元

    一.简要说明 统一工作单元是一个比较重要的基础设施组件,它负责管理整个业务流程当中涉及到的数据库事务,一旦某个环节出现异常自动进行回滚处理. 在 ABP vNext 框架当中,工作单元被独立出来作为一 ...

  3. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  4. ABP源码分析九:后台工作任务

    文主要说明ABP中后台工作者模块(BackgroundWorker)的实现方式,和后台工作模块(BackgroundJob).ABP通过BackgroundWorkerManager来管理Backgr ...

  5. ABP源码分析十六:DTO的设计

    IDTO:空接口,用于标注Dto对象. ComboboxItemDto:用于combobox/list中Item的DTO NameValueDto<T>/NameValueDto:用于na ...

  6. ABP源码分析二十六:核心框架中的一些其他功能

    本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSess ...

  7. ABP源码分析三十六:ABP.Web.Api

    这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...

  8. ABP源码分析四十六:ABP ZERO中的Ldap模块

    通过AD作为用户认证的数据源.整个管理用户认证逻辑就在LdapAuthenticationSource类中实现. LdapSettingProvider:定义LDAP的setting和提供Defaut ...

  9. [Abp 源码分析]零、文章目录

    0.系列文章目录 一.Abp 框架启动流程分析 二.模块系统 三.依赖注入 四.模块配置 五.系统设置 六.工作单元的实现 七.仓储与 Entity Framework Core 八.缓存管理 九.事 ...

随机推荐

  1. sql server 实现split功能 行转列

    --實現split功能的函數create function [dbo].[func_split]( @SourceSql varchar(max), @StrSeprate varchar(10))r ...

  2. GLSL ES 3.0 和 2.0 的区别

    GLSL ES 3.0 和 2.0 的区别 语法区别 attribute和varying. 取而代之的是 in和out 头文件多了个#version 300 es 纹理 texture2D 和 tex ...

  3. shiro(四)项目开发中的配置、

    配置拦截.过滤.验证请求 <!-- shiro --> <!-- 項目自定义的Realm --> <bean id="ShiroRealm" clas ...

  4. 关于<Servlet>定义

    1,百度百科定义:             Servlet,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容 ...

  5. python基础其他

    目录: 一.Python为什么受欢迎的本质,知道的人寥寥无几? 二.PyCharm的模板设置 三.pycharm的一些快捷键 四.PyCharm最新2018激活 五.python虚拟环境--virtu ...

  6. 查看Android应用包名、Activity的几个方法

    一.有源码情况 直接打开AndroidManifest.xml文件,找到包含android.intent.action.MAIN和android.intent.category.LAUNCHER对应的 ...

  7. ORM之SQLALchemy

    今天来聊一聊 Python 的 ORM 框架 SQLAlchemy SQLAlchemy 没有 Django 的 Models 好用!因为models是Django自带的ORM框架,也正是因为是Dja ...

  8. 19.3.5日,报关于表单验证和ui-router

    今天完成的事情: (1)学习angularJS表单验证 可以验证的内容<input type="text" minlength="2" maxlength ...

  9. java从pdf中提取文本

    一(单文件转换):下载pdfbox包,百度搜pdfbox.(fontbox-1.8.16.jar和pdfbox-app-1.8.16.jar) package pdf; import java.io. ...

  10. Conda命令指标

    一.Conda相关指令 # 查看当前环境下已安装的包 conda list # 查看某个指定环境的已安装包 conda list -n tensorflow # 查找package信息 conda s ...