- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
依赖注入 (DI) 是.NET中一个非常重要的软件设计模式,它可以帮助我们更好地管理和组织组件,提高代码的可读性,扩展性和可测试性。在日常工作中,我们一定遇见过这些问题或者疑惑.
虽然我们可能已经知道了答案,但本文将通过阅读CLR源码的方式来学习DI实现机制,同时也更加深入地理解上述问题。如果您不想阅读源码,可以直接跳至文末的解决方案.
理论篇可以先看一下,防止在下文代码不知道这些对象的作用。如果有些概念不是很清晰可以先记着,带入下文源码,应该就可以理解 。
ServiceProvider: ServiceProvider(依赖注入容器)不仅对外提供GetService()、GetRequiredService()方法,还可以方便地注册和管理应用程序需要的各种服务。 通过创建ServiceProvider的方式,我们可以更好地理解管理和控制服务实例的生命周期和依赖关系.
应用程序级别的根级ServiceProvider .NET Core应用程序通常会使用一个应用程序级别的根级ServiceProvider,它是全局唯一的,并且负责维护所有单例服务的实例。这个实例通常是由WebHostBuilder、HostBuilder或ServiceCollection等类创建和配置的,可以通过IServiceProvider接口来访问.
每个请求的作用域级别的ServiceProvider 除了根级ServiceProvider之外,在.NET Core中还可以创建每个请求的作用域级别的ServiceProvider,它通常用于管理Scoped和Transient服务的生命周期和依赖关系。每个作用域级别的ServiceProvider都有自己独立的作用域,可以通过IServiceScopeFactory创建,同时也继承了根级ServiceProvider中注册的所有单例服务的实例.
自定义级别的ServiceProvider 在某些情况下,我们可能需要自定义级别的ServiceProvider来满足特定的业务需求,例如,将多个ServiceProvider组合起来以提供更高级别的服务解析和管理功能。此时,我们可以通过实现IServiceProviderFactory接口和IServiceProviderBuilder接口来创建和配置自定义级别的ServiceProvider,从而实现更灵活、可扩展的依赖注入框架.
生命周期管理: 我们可以将依赖注入容器看作一个树形结构,其中root节点的子节点是Scoped节点,每个Scoped节点的子节点是Transient节点(如果存在)。在容器初始化时,会在root节点下创建和缓存所有单例服务的实例,以及创建第一个Scoped节点。每个Scoped节点下都有一个独立的作用域,用于管理Scoped服务的生命周期和依赖关系,同时还继承了父级节点(即root或其他Scoped节点)的所有单例服务的实例。 在处理每个新的请求时,依赖注入容器会创建一个新的Scoped节点,并在该节点下创建和缓存该请求所需的所有Scoped服务的实例。在完成请求处理后,该Scoped节点及其下属的服务实例也将被销毁,从而确保Scoped服务实例的生命周期与请求的作用域相对应.
重要对象 。
IServiceCollection: 用于注册应用程序所需的服务实例,并将其添加到依赖注入容器中.
IServiceScopeFactory: 用于创建依赖注入作用域(IServiceScope)的工厂类。每个IServiceScope都可以独立地管理Scoped和Transient类型的服务实例,并在作用域结束时释放所有资源。IServiceScope通过ServiceProvider属性来访问该作用域内的服务实例 。
ServiceProvider: 可以看作是一个服务容器,它可以方便地注册、提供和管理应用程序需要的各种服务。还支持创建依赖注入作用域(IServiceScope),可以更好地管理和控制服务实例的生命周期和依赖关系 。
IServiceProviderFactory: 创建最终的依赖注入容器(IServiceProvider),提供默认的DefaultServiceProviderFactory(也就是官方自带的IOC),也支持自定义的,比如autofac的AutofacServiceProviderFactory工厂.
ServiceProviderEngineScope: 实现了IServiceProvider和IDisposable接口,用于创建和管理依赖注入作用域(Scope)。通过使用ServiceProviderEngineScope,我们可以访问依赖注入作用域中的服务实例,并实现Scoped和Transient类型的服务实例的生命周期管理。作用域机制可以帮助我们更好地管理和控制应用程序的各个组件之间的依赖关系 。
CallSiteFactory: 通常由依赖注入容器(如ServiceProvider)在服务解析过程中使用。当容器需要解析某个服务时,它会创建一个CallSiteFactory对象,并使用其中的静态方法来创建对应的ServiceCallSite对象。然后,容器会将这些ServiceCallSite对象组合成一个树形结构,最终构建出整个服务实例的解析树.
ServiceCallSite: 表示服务的解析过程。它包含了服务类型、服务的生命周期、以及从容器中获取服务实例的方法等信息 。
CallSiteVisitor: 通常由依赖注入容器(如ServiceProvider)在服务解析过程中使用。当容器需要解析某个服务时,它会创建一个ServiceCallSite的对象图,并将其传递给CallSiteVisitor进行遍历和访问。CallSiteVisitor通过调用不同节点的虚拟方法,将每个节点的信息收集起来,并最终构建出服务实例的解析树.
CallSiteValidator 通常由依赖注入容器(如ServiceProvider)在服务解析过程中使用,用于验证ServiceCallSite对象图的正确性。它提供了一组检查方法,可以检测ServiceCallSite对象图中可能存在的循环依赖、未注册的服务类型和生命周期问题等.
以下是源代码的部分删减和修改,以便于更好地理解 。
为了更好地理解依赖注入的整个流程,可以根据依赖注入容器将其简单理解为以下两个模块:
配置ConfigureServices,将服务对象(ServiceDescriptor)注册到了IServiceCollection集合,构建Host主机的时候,会调用BuildServiceProvide()方法创建IServiceProvider,并获取相关服务.
public IWebHost Build()
{
var hostingServices = BuildCommonServices(out var hostingStartupErrors);// 构建WebHost通用服务
var hostingServiceProvider = GetProviderFromFactory(hostingServices);
// 获取ServiceProvider
IServiceProvider GetProviderFromFactory(IServiceCollection collection)
{
// 构建IServiceProvider对象
var provider = collection.BuildServiceProvider();
// 获取服务
var factory = provider.GetService<IServiceProviderFactory<IServiceCollection>>();
// 是否使用默认的DefaultServiceProviderFactory类
if (factory != null && !(factory is DefaultServiceProviderFactory))
{
using (provider)
{
return factory.CreateServiceProvider(factory.CreateBuilder(collection));
}
}
return provider;
}
}
BuildServiceProvider是IServiceCollection接口的扩展方法。该方法用于创建一个IServiceProvider接口实例,并将已注册到IServiceCollection容器中的服务对象注入到该实例中.
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
{
// 生成ServiceProvider对象
return new ServiceProvider(services, options);
}
ServiceProvider类的构造函数,创建依赖注入容器,并将服务描述信息加载到容器中 。
internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
// 创建一个根级别的服务引擎作用域
Root = new ServiceProviderEngineScope(this, isRootScope: true);
// 获取服务引擎用于解析依赖关系
_engine = GetEngine();
// 访问器,动态创建服务
_createServiceAccessor = CreateServiceAccessor;
// 缓存已经解析出来的服务实例(线程安全)
_realizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object?>>();
// CallSiteFactory用于创建和缓存服务的调用站点(ServiceCallSite)
CallSiteFactory = new CallSiteFactory(serviceDescriptors);
// 添加内置的服务
CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
CallSiteFactory.Add(typeof(IServiceScopeFactory), new ConstantCallSite(typeof(IServiceScopeFactory), Root));
CallSiteFactory.Add(typeof(IServiceProviderIsService), new ConstantCallSite(typeof(IServiceProviderIsService), CallSiteFactory));
// ValidateScopes属性为true,表示需要验证服务范围
if (options.ValidateScopes)
{
_callSiteValidator = new CallSiteValidator();
}
// ValidateOnBuild属性为true,需要检查所有服务是否能够成功创建
if (options.ValidateOnBuild)
{
List<Exception>? exceptions = null;
foreach (ServiceDescriptor serviceDescriptor in serviceDescriptors)
{
ValidateService(serviceDescriptor);
}
}
}
当ValidateOnBuild属性为true时,进入到ValidateService方法中。ValidateService方法使用CallSiteFactory对象的GetCallSite方法来获取对应的ServiceCallSite对象,并将其保存到callSite变量中。如果callSite不为null,表示该服务可以被成功创建,则调用OnCreate方法.
此时我们需要知道并理解ServiceCallSite 对象 。
ServiceCallSite记录着从根调用站点到当前服务实例的一条依赖链。在DI容器中,每一个已注册的服务都对应一个ServiceCallSite,而所有的CallSite又组成了整个 DI 系统的拓扑结构。在 DI 系统初始化时,容器会通过递归调用ServiceCallSite上的信息,来完成整个DI容器的配置和初始化.
internal abstract class ServiceCallSite
{
protected ServiceCallSite(ResultCache cache)
{
Cache = cache;
}
// 服务类型
public abstract Type ServiceType { get; }
// 实现类型
public abstract Type ImplementationType { get; }
// 调用链类型(Scope、Singleton、Factory、Constructor、CreateInstance等)
public abstract CallSiteKind Kind { get; }
public ResultCache Cache { get; }
// 是否需要捕获可释放资源,类似IDisposable接口
public bool CaptureDisposable =>
ImplementationType == null ||
typeof(IDisposable).IsAssignableFrom(ImplementationType) ||
typeof(IAsyncDisposable).IsAssignableFrom(ImplementationType);
}
有没有发现,ServiceCallSite和ServiceDescriptor有几分相似。那么他们有什么关系和区别呢?
ServiceDescriptor用于描述一个服务实例的信息,包括服务类型、实现类型、生命周期等。在容器注册服务时使用的.
ServiceCallSite则表示服务调用链节点,是ServiceDescriptor的运行时表示形式,即在Resolve服务时,ServiceDescriptor会被转换为相应的ServiceCallSite。ServiceCallSite包含了解析服务所需要的全部信息,包括服务类型、实现工厂、参数列表等,它能够通过递归访问自己的子节点来构建出完整的服务调用链.
ValidateService方法验证服务是否能够正常创建 。
private void ValidateService(ServiceDescriptor descriptor)
{
// 这个方法中出现了循环依赖和多构造函数
ServiceCallSite callSite = CallSiteFactory.GetCallSite(descriptor, new CallSiteChain());
if (callSite != null)
{
// 这个方法中进行依赖校验
OnCreate(callSite);
}
}
我们先看GetCallSite方法,GetCallSite尝试从缓存中获取,如果缓存中不存在,则创建CreateCallSite.
internal ServiceCallSite GetCallSite(Type serviceType, CallSiteChain callSiteChain) =>
_callSiteCache.TryGetValue(new ServiceCacheKey(serviceType, DefaultSlot), out ServiceCallSite site) ? site :
CreateCallSite(serviceType, callSiteChain);
CreateCallSite是非常重要的方法,它负责创建和缓存CallSite对象,并为整个依赖注入容器的服务解析提供了基础支持 。
private ServiceCallSite CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
{
var callsiteLock = _callSiteLocks.GetOrAdd(serviceType, static _ => new object());
// 保证对CallSite缓存的线程安全。由于多个服务之间可能存在依赖关系,因此需要确保同一时间只有一个服务的CallSite被创建和缓存
lock (callsiteLock)
{
// 哦吼,出现了 检查是否存在循环依赖关系,以避免产生无限递归调用
callSiteChain.CheckCircularDependency(serviceType);
// 依次尝试创建精确类型、开放泛型类型和IEnumerable类型的CallSite对象,并返回第一个成功创建的对象
ServiceCallSite callSite = TryCreateExact(serviceType, callSiteChain) ??
TryCreateOpenGeneric(serviceType, callSiteChain) ??
TryCreateEnumerable(serviceType, callSiteChain);
return callSite;
}
}
此时,我们发现了判断循环依赖的方法,他是如何实现的呢?我们就要看一下callSiteChain对象了。callSiteChain用于描述服务调用站点(CallSite)之间的依赖关系。callSiteChain使用DIctionary容器存储当前链路上的CallSite。如果容器存在当前服务,说明存在循环依赖。 举个栗子: A->B B->A 。
public CallSiteChain()
{
_callSiteChain = new Dictionary<Type, ChainItemInfo>();
}
public void CheckCircularDependency(Type serviceType)
{
if (_callSiteChain.ContainsKey(serviceType))
{
throw new InvalidOperationException(CreateCircularDependencyExceptionMessage(serviceType));
}
}
我们选择TryCreateExact方法,进入CreateConstructorCallSite方法,该方法创建和缓存ConstructorCallSite对象。此时您就看见多个构造参数是如何进行选择的啦!如果存在多个构造函数,但其中某个构造函数的参数类型是其他构造函数的子集,则返回该构造函数对应的ConstructorCallSite对象 。
private ServiceCallSite CreateConstructorCallSite(
ResultCache lifetime,
Type serviceType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
CallSiteChain callSiteChain)
{
try
{
// 将当前服务加入callSiteChain,以便后续的依赖解析过程中使用
callSiteChain.Add(serviceType, implementationType);
// 获取所有构造函数
ConstructorInfo[] constructors = implementationType.GetConstructors();
ServiceCallSite[] parameterCallSites = null;
// 0个构造函数
if (constructors.Length == 0)
{
throw new InvalidOperationException(SR.Format(SR.NoConstructorMatch, implementationType));
}
// 1个构造函数
else if (constructors.Length == 1)
{
ConstructorInfo constructor = constructors[0];
ParameterInfo[] parameters = constructor.GetParameters();
if (parameters.Length == 0)
{
return new ConstructorCallSite(lifetime, serviceType, constructor);
}
parameterCallSites = CreateArgumentCallSites(
implementationType,
callSiteChain,
parameters,
throwIfCallSiteNotFound: true);
return new ConstructorCallSite(lifetime, serviceType, constructor, parameterCallSites);
}
// 多个构造函数如何选择,终于等到你,还好我没放弃0.0
Array.Sort(constructors,
(a, b) => b.GetParameters().Length.CompareTo(a.GetParameters().Length));
ConstructorInfo bestConstructor = null;
HashSet<Type> bestConstructorParameterTypes = null;
for (int i = 0; i < constructors.Length; i++)
{
ParameterInfo[] parameters = constructors[i].GetParameters();
ServiceCallSite[] currentParameterCallSites = CreateArgumentCallSites(
implementationType,
callSiteChain,
parameters,
throwIfCallSiteNotFound: false);
if (currentParameterCallSites != null)
{
if (bestConstructor == null)
{
bestConstructor = constructors[i];
parameterCallSites = currentParameterCallSites;
}
else
{
// Since we're visiting constructors in decreasing order of number of parameters,
// we'll only see ambiguities or supersets once we've seen a 'bestConstructor'.
//由于我们以参数数量递减的顺序访问构造函数,
//只有在看到“最佳构造函数”后,我们才会看到歧义或超集。
if (bestConstructorParameterTypes == null)
{
bestConstructorParameterTypes = new HashSet<Type>();
foreach (ParameterInfo p in bestConstructor.GetParameters())
{
bestConstructorParameterTypes.Add(p.ParameterType);
}
}
foreach (ParameterInfo p in parameters)
{
if (!bestConstructorParameterTypes.Contains(p.ParameterType))
{
// Ambiguous match exception
throw new InvalidOperationException(string.Join(
Environment.NewLine,
SR.Format(SR.AmbiguousConstructorException, implementationType),
bestConstructor,
constructors[i]));
}
}
}
}
}
if (bestConstructor == null)
{
throw new InvalidOperationException(
SR.Format(SR.UnableToActivateTypeException, implementationType));
}
else
{
Debug.Assert(parameterCallSites != null);
return new ConstructorCallSite(lifetime, serviceType, bestConstructor, parameterCallSites);
}
}
finally
{
callSiteChain.Remove(serviceType);
}
}
看到这里,我们已经解决了两个问题:
Singleton服务不能依赖Scoped服务,是如何校验的?我们回到刚才OnCreate的地方继续阅读.
private void OnCreate(ServiceCallSite callSite)
{
_callSiteValidator?.ValidateCallSite(callSite);
}
ValidateCallSite方法,用于验证指定的ServiceCallSite对象是否正确,并将其中包含的作用域服务添加到_scopedServices字典中.
在ValidateCallSite方法中,我们首先使用VisitCallSite方法遍历整个ServiceCallSite对象,并返回其中所包含的作用域服务类型。如果ServiceCallSite对象中存在作用域服务,则将其添加到_scopedServices字典中,以便后续的依赖解析过程中使用.
public void ValidateCallSite(ServiceCallSite callSite)
{
Type scoped = VisitCallSite(callSite, default);
if (scoped != null)
{
_scopedServices[callSite.ServiceType] = scoped;
}
}
ValidateCallSite存在VisitScopeCache方法,该方法首先判断当前ServiceCallSite对象是否是IServiceScopeFactory类型,如果是,则直接返回null。否则,我们检查state.Singleton属性是否为null,如果不为null,则说明当前ServiceCallSite对象属于单例服务,并且其中包含作用域服务的注入,此时将抛出InvalidOperationException异常,提示用户检查服务依赖关系是否正确;否则,我们继续递归遍历ServiceCallSite对象图.
protected override Type VisitScopeCache(ServiceCallSite scopedCallSite, CallSiteValidatorState state)
{
// We are fine with having ServiceScopeService requested by singletons
if (scopedCallSite.ServiceType == typeof(IServiceScopeFactory))
{
return null;
}
// ScopedInSingletonException异常!
if (state.Singleton != null)
{
throw new InvalidOperationException(SR.Format(SR.ScopedInSingletonException,
scopedCallSite.ServiceType,
state.Singleton.ServiceType,
nameof(ServiceLifetime.Scoped).ToLowerInvariant(),
nameof(ServiceLifetime.Singleton).ToLowerInvariant()
));
}
VisitCallSiteMain(scopedCallSite, state);
return scopedCallSite.ServiceType;
}
获取服务 。
以上我们可以归纳为构建IServiceProvider,然后我们通过GetService()方法,看下如何获取服务.
从缓存中获取指定类型的服务,如果缓存中不存在,则调用_createServiceAccessor委托创建一个新的实例,并将其添加到缓存中 。
internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
{
// 使用了ConcurrentDictionary来缓存已经解析的服务
Func<ServiceProviderEngineScope, object> realizedService = _realizedServices.GetOrAdd(serviceType, _createServiceAccessor);
OnResolve(serviceType, serviceProviderEngineScope);
// 服务的实际实现
var result = realizedService.Invoke(serviceProviderEngineScope);
return result;
}
当缓存中不存在的时候,我们使用_createServiceAccessor创建一个新的实例(和上文获取callSite流程一致).
private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType)
{
// 取给定服务类型的CallSite对象
ServiceCallSite callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
if (callSite != null)
{
OnCreate(callSite);
// 服务具有Singleton生命周期,可以优化处理,避免每次获取服务实例时都需要重新创建
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
{
object value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
return scope => value;
}
// 服务具有Transient或Scoped生命周期,需要创建并返回一个新的服务实例访问器
return _engine.RealizeService(callSite);
}
return _ => null;
}
创建一个生命周期为单例的SingletonService和另一个生命周期为作用域的ScopedService,SingletonService服务依赖ScopedService服务。就会报错:
Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: MyTest.Service.ISingletonService Lifetime: Singleton ImplementationType: MyTest.Service.SingletonService': Cannot consume scoped service 'MyTest.Service.IScopedService' from singleton 'MyTest.Service.ISingletonService'.) 。
解决方法 。
public class SingletonService : ISingletonService
{
private readonly IScopedService _scopedService;
public SingletonService(IServiceScopeFactory serviceScopeFactory)
{
_scopedService = serviceScopeFactory.CreateScope().ServiceProvider.GetRequiredService<IScopedService>();
}
}
public class SingletonService : ISingletonService
{
private readonly IScopedService _scopedService;
public SingletonService(IServiceProvider serviceProvider)
{
_scopedService = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IScopedService>();
}
}
如果实现类没有构造函数,则抛出NoConstructorMatch异常.
如果实现类只有一个构造函数,判断该构造函数是否无参。如果是无参构造函数,则直接返回ConstructorCallSite;否则,对构造函数的参数创建对应的parameterCallSites,并返回ConstructorCallSite.
多个构造函数的逻辑 。
如果不是很理解选择逻辑,可以结合上文中的CreateConstructorCallSite方法,观看代码会更加直接,便于理解.
参考:
http://misko.hevery.com/2008/08/01/circular-dependency-in-constructors-and-dependency-injection/ 。
https://thomaslevesque.com/2020/03/18/lazily-resolving-services-to-fix-circular-dependencies-in-net-core/ 。
我认为出现循环依赖,是我们代码结构设计有问题,根本解决方案是将依赖关系分解成更小的部分,从而避免出现循环依赖的情况,同时使个代码结构更加清晰、简单.
在这种情况下,真实原因是两个对象中的一个隐藏了另一个对象 C。A 包含 C 或 B 包含 C。我们假设B包含了C.
class A {
final B b;
A(B b){
this.b = b;
}
}
class B {
final A a;
B(A a){
this.a = a;
}
}
+---------+ +---------+
| A |<-----| B |
| | | | +-+ |
| | | +->|C| |
| |------+---->| | |
| | | +-+ |
+---------+ +---------+
我们将C单独抽出来,作为一个服务,让A和B都依赖于C,这样就可以解决循环依赖的问题.
+---------+
+---------+ | B |
| A |<-------------| |
| | | |
| | +---+ | |
| |--->| C |<----| |
| | +---+ +---------+
+---------+
class C {
C(){
}
}
class A {
final C c;
A(C c){
this.c = c;
}
}
class B {
final A a;
final C c;
B(A a, C c){
this.a = a;
this.c = c;
}
}
class C : IC
{
private readonly IServiceProvider _services;
public C(IServiceProvider services)
{
_services = services;
}
public void Bar()
{
...
var a = _services.GetRequiredService<IA>();
a.Foo();
...
}
}
public static IServiceCollection AddLazyResolution(this IServiceCollection services)
{
return services.AddTransient(
typeof(Lazy<>),
typeof(LazilyResolved<>));
}
private class LazilyResolved<T> : Lazy<T>
{
public LazilyResolved(IServiceProvider serviceProvider)
: base(serviceProvider.GetRequiredService<T>)
{
}
}
然后再 Startup.cs 中的 ConfigureServices 方法中这样写 。
services.AddLazyResolution(),
在依赖的类中IA,注入Lazy,当您需要使用时IA,只需访问lazy的值 Value 即可:
class C : IC
{
private readonly Lazy<IA> _a;
public C(Lazy<IA> a)
{
_a = a;
}
public void Bar()
{
...
_a.Value.Foo();
...
}
}
注意:不要访问构造函数中的值,保存Lazy即可 ,在构造函数中访问该值,这将导致我们试图解决的相同问题.
这个解决方案不是完美的,但是它解决了最初的问题却没有太多麻烦,并且依赖项仍然在构造函数中明确声明,我可以看到类之间的依赖关系.
如果您觉得这篇文章有所收获,还请点个赞并关注。如果您有任何建议或意见,欢迎在评论区留言,非常感谢您的支持和指导! 。
最后此篇关于.NET通过源码深究依赖注入原理的文章就讲到这里了,如果你想了解更多关于.NET通过源码深究依赖注入原理的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
ACO.Visualization项目 本项目演示蚁群算法求解旅行商问题的可视化过程,包括路径上的信息素浓度、蚁群的运动过程等。项目相关的代码:https://github.com/anycad/A
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
我需要用Sql数据库制作并包含的PHP票务系统源码用户客户端和管理员。我需要个人 CMS 的这个来源。谢谢你帮助我。 最佳答案 我在不同的情况下使用了 osticket。 这里: http://ost
我的场景:我想在日志文件中写入发生异常的部分代码(例如,发生异常的行前 5 行和行后 5 行 - 或者至少是该方法的所有代码)。 我的想法是用 C# 代码反编译 pdb 文件,并从该反编译文件中找到一
RocketMQ设定了延迟级别可以让消息延迟消费,延迟消息会使用 SCHEDULE_TOPIC_XXXX 这个主题,每个延迟等级对应一个消息队列,并且与普通消息一样,会保存每个消息队列的消费进度
先附上Hystrix源码图 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和
此篇博客学习的api如标题,分别是: current_url 获取当前页面的url; page_source 获取当前页面的源码; title 获取当前页面的titl
? 1 2
1、前言 作为一个数据库爱好者,自己动手写过简单的sql解析器以及存储引擎,但感觉还是不够过瘾。<<事务处理-概念与技术>>诚然讲的非常透彻,但只能提纲挈领,不能让你
gory"> 目录 运行时信号量机制 semaphore 前言 作用是什么 几个主要的方法 如何实现
自己写的一个评论系统源码分享给大家,包括有表情,还有评论机制。用户名是随机的 针对某一篇文章进行评论 function subcomment() {
一、概述 StringBuilder是一个可变的字符串序列,这个类被设计去兼容StringBuffer类的API,但不保证线程安全性,是StringBuffer单线程情况下的一个替代实现。在可能的情
一、概述 System是用的非常多的一个final类。它不能被实例化。System类提供了标准的输入输出和错误输出流;访问外部定义的属性和环境变量;加载文件和库的方法;以及高效的拷贝数组中一部分元素
在JDK中,String的使用频率和被研究的程度都非常高,所以接下来我只说一些比较重要的内容。 一、String类的概述 String类的声明如下: public final class Str
一、概述 Class的实例代表着正在运行的Java应用程序的类和接口。枚举是一种类,而直接是一种接口。每一个数组也属于一个类,这个类b被反射为具有相同元素类型和维数的所有数组共享的类对象。八大基本树
一、概述 Compiler这个类被用于支持Java到本地代码编译器和相关服务。在设计上,这个类啥也不做,他充当JIT编译器实现的占位符。 放JVM虚拟机首次启动时,他确定系统属性java.comp
一、概述 StringBuffer是一个线程安全的、可变的字符序列,跟String类似,但它能被修改。StringBuffer在多线程环境下可以很安全地被使用,因为它的方法都是通过synchroni
一、概述 Enum是所有Jav中枚举类的基类。详细的介绍在Java语言规范中有说明。 值得注意的是,java.util.EnumSet和java.util.EnumMap是Enum的两个高效实现,
一、概述 此线程指的是执行程序中的线程。 Java虚拟机允许应用程序同时执行多个执行线程。 每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守
一、抽象类Number 类继承关系 这里面的原子类、BigDecimal后面都会详细介绍。 属性和抽象方法 二、概述 所有的属性,最小-128,最大127,SIZE和BYTES代码比
我是一名优秀的程序员,十分优秀!