依赖注入(Dependency Injection,简称DI)是一种设计模式,用于解耦组件(服务)之间的依赖关系。它通过将依赖关系的创建和管理交给外部容器来实现,而不是在组件(服务)内部直接创建依赖对象。
咱就是通过 IServiceCollection
和 IServiceProvider
来实现的,他们直接被收入到了runtime libraries,在整个.NET平台下通用!
3.1 ServiceCollection
IServiceCollection
本质是一个 ServiceDescriptor
而 ServiceDescriptor
则是用于描述服务类型,实现和生命周期
public interface IServiceCollection :
IList,
ICollection,
IEnumerable,
IEnumerable;
官方提供一些列拓展帮助我们向服务容器中添加服务描述,具体在 ServiceCollectionServiceExtensions
builder.Services.AddTransient();
builder.Services.AddKeyedTransient("a");
builder.Services.AddKeyedTransient("b");
builder.Services.AddTransient();
builder.Services.AddScoped();
builder.Services.AddSingleton();
3.2 ServiceProvider
IServiceProvider
定义了一个方法 GetService
,帮助我们通过给定的服务类型,获取其服务实例
public interface IServiceProvider
{
object? GetService(Type serviceType);
}
下面是 GetService
的默认实现(如果不给定engine scope,则默认是root)
public object? GetService(Type serviceType) => GetService(ServiceIdentifier.FromServiceType(serviceType), Root);
也就是
internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
// 获取服务标识符对应的服务访问器
ServiceAccessor serviceAccessor = _serviceAccessors.GetOrAdd(serviceIdentifier, _createServiceAccessor);
// 执行解析时的hock
OnResolve(serviceAccessor.CallSite, serviceProviderEngineScope);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
// 通过服务访问器提供的解析服务,得到服务实例
object? result = serviceAccessor.RealizedService?.Invoke(serviceProviderEngineScope);
System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceIdentifier));
return result;
}
其中,服务标识符 ServiceIdentifier
其实就是包了一下服务类型,和服务Key(为了.NET8的键化服务)
internal readonly struct ServiceIdentifier : IEquatable
{
public object? ServiceKey { get; }
public Type ServiceType { get; }
}
显而易见的,我们的服务解析是由 serviceAccessor.RealizedService
提供,而创建服务访问器 serviceAccessor
只有一个实现 CreateServiceAccessor
private ServiceAccessor CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
{
// 通过 CallSiteFactory 获取服务的调用点(CallSite),这是服务解析的一个表示形式。
ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceIdentifier, new CallSiteChain());
// 如果调用站点不为空,则继续构建服务访问器。
if (callSite != null)
{
DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceIdentifier.ServiceType, callSite);
// 触发创建调用站点的相关事件。
OnCreate(callSite);
// 如果调用站点的缓存位置是根(Root),即表示这是一个单例服务。
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
{
// 直接拿缓存内容
object? value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
return new ServiceAccessor { CallSite = callSite, RealizedService = scope => value };
}
// 通过引擎解析
Func realizedService = _engine.RealizeService(callSite);
return new ServiceAccessor { CallSite = callSite, RealizedService = realizedService };
}
// 如果调用点为空,则它的实现服务函数总是返回 null。
return new ServiceAccessor { CallSite = callSite, RealizedService = _ => null };
}
3.2.1 ServiceProviderEngine
ServiceProviderEngine
是服务商解析服务的执行引擎,它在服务商被初始化时建立。有两种引擎,分别是动态引擎 和运行时引擎 ,在 NETFRAMEWORK || NETSTANDARD2_0 默认使用动态引擎。
private ServiceProviderEngine GetEngine()
{
ServiceProviderEngine engine;
#if NETFRAMEWORK || NETSTANDARD2_0
engine = CreateDynamicEngine();
#else
if (RuntimeFeature.IsDynamicCodeCompiled && !DisableDynamicEngine)
{
engine = CreateDynamicEngine();
}
else
{
// Don't try to compile Expressions/IL if they are going to get interpreted
engine = RuntimeServiceProviderEngine.Instance;
}
#endif
return engine;
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "CreateDynamicEngine won't be called when using NativeAOT.")] // see also https://github.com/dotnet/linker/issues/2715
ServiceProviderEngine CreateDynamicEngine() => new DynamicServiceProviderEngine(this);
}
由于.NET Aot技术与dynamic技术冲突,因此Aot下只能使用运行时引擎,但动态引擎在大多情况下仍然是默认的。
动态引擎使用了emit技术,这是一个动态编译技术,而aot的所有代码都需要在部署前编译好,因此运行时无法生成新的代码。那运行时引擎主要使用反射,目标是在不牺牲太多性能的情况下,提供一个在aot环境中可行的解决方案。
我们展开动态引擎来看看它是如何解析服务的。
public override Func RealizeService(ServiceCallSite callSite)
{
// 定义一个局部变量来跟踪委托被调用的次数
int callCount = 0;
return scope =>
{
// 当委托被调用时,先使用CallSiteRuntimeResolver.Instance.Resolve方法来解析服务。这是一个同步操作,确保在编译优化之前,服务可以被正常解析。
var result = CallSiteRuntimeResolver.Instance.Resolve(callSite, scope);
// 委托第二次被调用,通过UnsafeQueueUserWorkItem在后台线程上启动编译优化
if (Interlocked.Increment(ref callCount) == 2)
{
// 将一个工作项排队到线程池,但不捕获当前的执行上下文。
_ = ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
try
{
// 用编译优化后的委托替换当前的服务访问器,主要用到emit/expression技术
_serviceProvider.ReplaceServiceAccessor(callSite, base.RealizeService(callSite));
}
catch (Exception ex)
{
DependencyInjectionEventSource.Log.ServiceRealizationFailed(ex, _serviceProvider.GetHashCode());
Debug.Fail($"We should never get exceptions from the background compilation.{Environment.NewLine}{ex}");
}
},
null);
}
return result;
};
}
这个实现的关键思想是,第一次解析服务时使用一个简单的运行时解析器,这样可以快速返回服务实例。然后,当服务再被解析,它会在后台线程上启动一个编译过程,生成一个更高效的服务解析委托。一旦编译完成,新的委托会替换掉原来的委托,以后的服务解析将使用这个新的、更高效的委托。这种方法可以在不影响应用程序启动时间的情况下,逐渐优化服务解析的性能。
3.2.2 ServiceProviderEngineScope
ServiceProviderEngineScope
闪亮登场,他是我们服务商的代言人,从定义不难看出他对外提供了服务商所具备的一切能力
internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IKeyedServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
// this scope中所有实现IDisposable or IAsyncDisposable的服务
private List? _disposables;
// 解析过的服务缓存(其实就是scope生命周期的服务缓存,singleton生命周期的服务缓存都直接挂在调用点上了)
internal Dictionary ResolvedServices { get; }
// 实锤服务商代言人
public IServiceProvider ServiceProvider => this;
// 没错啦,通过root scope我们又能继续创建无数个scope,他们彼此独立
public IServiceScope CreateScope() => RootProvider.CreateScope();
}
我们来观察他获取服务的逻辑,可以发现他就是很朴实无华的用着我们根服务商 ServiceProvider
,去解析服务,那 engine scope 呢,就是 this。现在我们已经隐约可以知道engine scope,就是为了满足scope生命周期而生。而 ResolvedServices
中存的呢,就是该scope中的所有scope生命周期服务实例啦,在这个scope中他们是唯一的。
public object? GetService(Type serviceType)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
return RootProvider.GetService(ServiceIdentifier.FromServiceType(serviceType), this);
}
再来看另一个核心的方法:CaptureDisposable
,实现disposable的服务会被添加到 _disposables。
internal object? CaptureDisposable(object? service)
{
// 如果服务没有实现 IDisposable or IAsyncDisposable,那么不需要捕获,直接原路返回
if (ReferenceEquals(this, service) || !(service is IDisposable || service is IAsyncDisposable))
{
return service;
}
bool disposed = false;
lock (Sync)
{
if (_disposed) // 如果scope已经销毁则进入销毁流程
{
disposed = true;
}
else
{
_disposables ??= new List();
_disposables.Add(service);
}
}
// Don't run customer code under the lock
if (disposed) // 这表示我们在试图捕获可销毁服务时,scope就已经被销毁
{
if (service is IDisposable disposable)
{
disposable.Dispose();
}
else
{
// sync over async, for the rare case that an object only implements IAsyncDisposable and may end up starving the thread pool.
object? localService = service; // copy to avoid closure on other paths
Task.Run(() => ((IAsyncDisposable)localService).DisposeAsync().AsTask()).GetAwaiter().GetResult();
}
// 这种case会抛出一个ObjectDisposedException
ThrowHelper.ThrowObjectDisposedException();
}
return service;
}
在engine scope销毁时,其作用域中所有scope生命周期且实现了disposable的服务(其实就是_disposable)呢,也会被一同的销毁。
public ValueTask DisposeAsync()
{
List? toDispose = BeginDispose(); // 获取_disposable
if (toDispose != null)
{
// 从后往前,依次销毁服务
}
}
那么有同学可能就要问了:单例实例既然不存在root scope中,而是单独丢到了调用点上,那他是咋销毁的?压根没看到啊,那不得泄露了?
其实呀,同学们并不用担心这个问题。首先,单例服务的实例确实是缓存在调用点上,但 disable 服务仍然会被 scope 捕获呀(在下文会详细介绍)。在 BeginDispose 中的,我们会去判断,如果是 singleton case,且root scope 没有被销毁过,我们会主动去销毁喔~
if (IsRootScope && !RootProvider.IsDisposed()) RootProvider.Dispose();
3.3 ServiceCallSite
ServiceCallSite
的主要职责是封装服务解析的逻辑,它可以代表一个构造函数调用、属性注入、工厂方法调用等。DI系统使用这个抽象来表示服务的各种解析策略,并且可以通过它来生成服务实例。
internal abstract class ServiceCallSite
{
protected ServiceCallSite(ResultCache cache)
{
Cache = cache;
}
public abstract Type ServiceType { get; }
public abstract Type? ImplementationType { get; }
public abstract CallSiteKind Kind { get; }
public ResultCache Cache { get; }
public object? Value { get; set; }
public object? Key { get; set; }
public bool CaptureDisposable => ImplementationType == null ||
typeof(IDisposable).IsAssignableFrom(ImplementationType) ||
typeof(IAsyncDisposable).IsAssignableFrom(ImplementationType);
}
3.3.1 ResultCache
其中 ResultCache
定义了我们如何缓存解析后的结果
public CallSiteResultCacheLocation Location { get; set; } // 缓存位置
public ServiceCacheKey Key { get; set; } // 服务key(键化服务用的)
CallSiteResultCacheLocation
是一个枚举,定义了几个值
Root
:表示服务实例应该在根级别的 IServiceProvider
中缓存。这通常意味着服务实例是单例的(Singleton),在整个应用程序的生命周期内只会创建一次,并且在所有请求中共享。
Scope
:表示服务实例应该在当前作用域(Scope)中缓存。对于作用域服务(Scoped),实例会在每个作用域中创建一次,并在该作用域内的所有请求中共享。
Dispose
:尽管这个名称可能会让人误解,但在 ResultCache
的上下文中,Dispose
表示着服务是瞬态的(每次请求都创建新实例)。
None
:表示没有缓存服务实例。
ServiceCacheKey
结构体就是包了一下服务标识符和一个slot,用于适配多实现的
internal readonly struct ServiceCacheKey : IEquatable
{
public ServiceIdentifier ServiceIdentifier { get; }
public int Slot { get; } // 那最后一个实现的slot是0
}
3.3.2 CallSiteFactory.GetCallSite
那我们来看看调用点是怎么创建的吧,其实上面已经出现过一次了:
private ServiceCallSite? CreateCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
{
if (!_stackGuard.TryEnterOnCurrentStack()) // 防止栈溢出
{
return _stackGuard.RunOnEmptyStack(CreateCallSite, serviceIdentifier, callSiteChain);
}
// 获取服务标识符对应的锁,以确保在创建调用点时的线程安全。
// 是为了保证并行解析下的调用点也只会被创建一次,例如:
// C -> D -> A
// E -> D -> A
var callsiteLock = _callSiteLocks.GetOrAdd(serviceIdentifier, static _ => new object());
lock (callsiteLock)
{
// 检查当前服务标识符是否会导致循环依赖
callSiteChain.CheckCircularDependency(serviceIdentifier);
// 首先尝试创建精确匹配的服务调用站点,如果失败,则尝试创建开放泛型服务调用站点,如果还是失败,则尝试创建枚举服务调用站点。如果所有尝试都失败了,callSite将为null。
ServiceCallSite? callSite = TryCreateExact(serviceIdentifier, callSiteChain) ??
TryCreateOpenGeneric(serviceIdentifier, callSiteChain) ??
TryCreateEnumerable(serviceIdentifier, callSiteChain);
return callSite;
}
}
那服务点的创建过程我就简单概述一下啦
查找调用点缓存,存在就直接返回啦
服务标识符会被转成服务描述符 ServiceDescriptor
(key化服务不指定key默认取last)
计算ServiceCallSite
,依次是:
计算 ResultCache
如果已经有实现实例了,则返回 ConstantCallSite
:表示直接返回已经创建的实例的调用点。
如果有实现工厂,则返回 FactoryCallSite
:表示通过工厂方法创建服务实例的调用点。
如果有实现类型,则返回 ConstructorCallSite
:表示通过构造函数创建服务实例的调用点。
根据泛型定义获取服务描述符 ServiceDescriptor
计算 ResultCache
使用服务标识符中的具体泛型参数来构造实现的闭合类型
AOT兼容性测试(因为不能保证值类型泛型的代码已经生成)
如果成功闭合,则返回 ConstructorCallSite
:表示通过构造函数创建服务实例的调用点。
确定类型是 IEnumerable
AOT兼容性测试(因为不能保证值类型数组的代码已经生成)
如果 T
不是泛型类型,并且可以找到对应的服务描述符集合,则循环 TryCreateExact
否则,方向循环 TryCreateExact,然后方向循环 TryCreateOpenGeneric
3.4 CallSiteVisitor
好了,有了上面的了解我们可以开始探索服务解析的内幕了。服务解析说白了就是引擎围着 CallSiteVisitor
转圈圈,所谓的解析服务,其实就是访问调用点了。
protected virtual TResult VisitCallSite(ServiceCallSite callSite, TArgument argument)
{
if (!_stackGuard.TryEnterOnCurrentStack()) // 一些校验,分栈啥的
{
return _stackGuard.RunOnEmptyStack(VisitCallSite, callSite, argument);
}
switch (callSite.Cache.Location)
{
case CallSiteResultCacheLocation.Root: // 单例
return VisitRootCache(callSite, argument);
case CallSiteResultCacheLocation.Scope: // 作用域
return VisitScopeCache(callSite, argument);
case CallSiteResultCacheLocation.Dispose: // 瞬态
return VisitDisposeCache(callSite, argument);
case CallSiteResultCacheLocation.None: // 不缓存(ConstantCallSite)
return VisitNoCache(callSite, argument);
default:
throw new ArgumentOutOfRangeException();
}
}
为了方便展示,我们这里的解析器都拿运行时来说,因为内部是反射,而emit、expression实在是难以观看。
3.4.1 VisitRootCache
那我们来看看单例的情况下,是如何访问的:
protected override object? VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
if (callSite.Value is object value)
{
// Value already calculated, return it directly
return value;
}
var lockType = RuntimeResolverLock.Root;
// 单例都是直接放根作用域的
ServiceProviderEngineScope serviceProviderEngine = context.Scope.RootProvider.Root;
lock (callSite)
{
// 这里搞了个双检锁来确保在多线程环境中,同一时间只有一个线程可以执行接下来的代码块。
// Lock the callsite and check if another thread already cached the value
if (callSite.Value is object callSiteValue)
{
return callSiteValue;
}
object? resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
// 捕获可销毁的服务
serviceProviderEngine.CaptureDisposable(resolved);
// 缓存解析结果到调用点上
callSite.Value = resolved;
return resolved;
}
}
好,可以看到真正解析调用点的主角出来了 VisitCallSiteMain
,那这里的 CallSiteKind
上面计算 ServiceCallSite
时呢已经讲的很清楚啦,咱对号入座就行了
protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
{
switch (callSite.Kind)
{
case CallSiteKind.Factory:
return VisitFactory((FactoryCallSite)callSite, argument);
case CallSiteKind.IEnumerable:
return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
case CallSiteKind.Constructor:
return VisitConstructor((ConstructorCallSite)callSite, argument);
case CallSiteKind.Constant:
return VisitConstant((ConstantCallSite)callSite, argument);
case CallSiteKind.ServiceProvider:
return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
default:
throw new NotSupportedException(SR.Format(SR.CallSiteTypeNotSupported, callSite.GetType()));
}
}
我们就看看最经典的通过构造函数创建服务实例的调用点 ConstructorCallSite
,很显然就是new嘛,只不过可能构造中依赖其它服务,那就递归创建呗。easy,其它几种太简单了大家自己去看看吧。
protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
{
object?[] parameterValues;
if (constructorCallSite.ParameterCallSites.Length == 0)
{
parameterValues = Array.Empty();
}
else
{
parameterValues = new object?[constructorCallSite.ParameterCallSites.Length];
for (int index = 0; index < parameterValues.Length; index++)
{
// 递归构建依赖的服务
parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
}
}
// new (xxx)
return constructorCallSite.ConstructorInfo.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameterValues, culture: null);
}
3.4.2 VisitScopeCache
在访问单例缓存的时候呢,仅仅通过了一个double check lock就搞定了,因为人家全局的嘛,咱再来看看访问作用域缓存,会不会有什么不一样
protected override object? VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
// Check if we are in the situation where scoped service was promoted to singleton
// and we need to lock the root
return context.Scope.IsRootScope ?
VisitRootCache(callSite, context) :
VisitCache(callSite, context, context.Scope, RuntimeResolverLock.Scope);
}
哈哈,它果然很不一般啊,上来就来检查我们是否是 root scope。如果是这种case呢,则走 VisitRootCache
。但是奇怪啊,为什么访问 scope cache,所在 engine scope 能是 root scope?
还记得 ServiceProvider
获取的服务实例的核心方法吗?engine scope 他是传进来的,如果我们给一个 root scope,自然就出现的这种case,只是这种 case 特别罕见。
internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
VisitCache
的同步模型写的实在是酷,我们看 RuntimeResolverLock
的枚举就两个:Scope = 1
和 Root = 2
至此我们应该不难看出这个设计的精妙之处,即在非 root scope(scope生命周期)中,scope之间是互相隔离的,没有必要像 root scope(singleton生命周期)那样,在所有scope中独占服务点。
private object? VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine
{
bool lockTaken = false;
object sync = serviceProviderEngine.Sync;
Dictionary resolvedServices = serviceProviderEngine.ResolvedServices;
if ((context.AcquiredLocks & lockType) == 0)
{
Monitor.Enter(sync, ref lockTaken);
}
try
{
// Note: This method has already taken lock by the caller for resolution and access synchronization.
// For scoped: takes a dictionary as both a resolution lock and a dictionary access lock.
if (resolvedServices.TryGetValue(callSite.Cache.Key, out object? resolved))
{
return resolved;
}
// scope服务的解析结果是放在engine scope的ResolvedServices上的,而非调用点
resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
serviceProviderEngine.CaptureDisposable(resolved);
resolvedServices.Add(callSite.Cache.Key, resolved);
return resolved;
}
finally
{
if (lockTaken)
{
Monitor.Exit(sync);
}
}
}
3.4.3 VisitDisposeCache
我们看最后一个,也就是 Transient
case
protected override object? VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
{
return context.Scope.CaptureDisposable(VisitCallSiteMain(transientCallSite, context));
}
异常的简单,我们沿用了scope的设计,但是我们没有进行任何缓存行为。即,每次都去访问调用点。
文章转载自:xiaolipro
原文链接:https://www.cnblogs.com/xiaolipro/p/17873575.html
你可能感兴趣的:(.net,java,spring)
CSE 231 Computer Python program
后端
CSE231Spring2025ComputerProject#4LearningobjectivesThisassignmentfocusesonthedesign,implementationandtestingofaPythonprogramthatusescharacterstringsforlookingattheDNAsequencesforkeyproteinsandseeingho
【Java】代理模式
非 白
代理模式 java 开发语言
代理模式代理模式是指给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问代理模式是一种结构型设计模式背景如果不采用代理,对一个类的多个方法进行监控时,重复的代码总是重复出现,不但破坏了原方法,如果要实现多个监控,将会对代码造成大量冗余。同时,还导致业务代码,与非业务的监控代码掺杂在一起,不利于扩展和维护。代理类在无限制膨胀,就需要无限的修改业务代码。而采用代理后,原方法不需要做任何改动,操
责任链模式原理详解和源码实例以及Spring AOP拦截器链的执行源码如何使用责任链模式?
一个儒雅随和的男子
spring 设计模式 责任链模式 spring java
前言 本文首先介绍了责任链的基本原理,并附带一个例子说明责任链模式,确保能够理解责任链的前提下,在进行SpringAOP执行责任链的源码分析。责任链模式允许将多个处理对象连接成链,请求沿着链传递,直到被处理或结束。每个处理者可以选择处理请求或传递给下一个。 SpringAOP的拦截器链,拦截器或者过滤器链,都是典型的责任链应用。比如,当一个方法被调用时,多个拦截器按顺序执行,每个拦截器可以决定
技术分享:MyBatis SQL 日志解析脚本
£漫步 云端彡
运维趣分享 sql java mybatis 日志解析
技术分享:MyBatisSQL日志解析脚本1.脚本功能概述2.实现细节2.1HTML结构2.2JavaScript逻辑3.脚本代码4.使用方法4.1示例5.总结在日常开发中,使用MyBatis作为持久层框架时,我们经常需要查看SQL日志以调试和优化查询。然而,MyBatis的日志输出通常包含占位符和参数信息,这使得直接执行这些SQL语句变得困难。为了解决这个问题,我们开发了一个简单的HTML和Ja
Spring Bean 生命周期详解
黑风风
java 多线程 spring java 数据库
SpringBean生命周期详解在Spring框架中,Bean的生命周期由Spring容器全权管理。了解和掌握Bean的生命周期对于使用Spring开发稳定且高效的应用程序至关重要。本文将详细介绍SpringBean生命周期的五个主要阶段:实例化、属性注入、初始化、使用和销毁,并涵盖各个阶段的关键步骤和扩展点。1.实例化(Instantiation)实例化阶段包括以下关键步骤:BeanNameAw
Spring Bean 生命周期的执行流程
涛粒子
spring 数据库 java
1.Bean定义阶段在Spring应用启动时,会读取配置文件(如XML配置、Java注解配置等)或者扫描带有特定注解(如@Component、@Service、@Repository等)的类,将这些Bean的定义信息加载到Spring的BeanFactory或ApplicationContext中。这些定义信息包括Bean的类名、作用域、依赖关系等。2.Bean实例化阶段调用构造函数:Spring
Spring Bean 生命周期
CT随
spring java 后端
SpringBean生命周期是Spring框架中一个非常重要的概念,它描述了一个Bean从创建到销毁的完整过程。这个生命周期可以分为五个主要阶段:创建前准备阶段、创建实例阶段、依赖注入阶段、容器缓存阶段和销毁实例阶段。下面我们将详细介绍每个阶段的作用,并通过生活中的例子来帮助理解。创建前准备阶段定义与作用:在这一阶段,Spring容器会解析配置文件或注解,查找并加载需要被管理的Bean的相关信息。
“深入浅出”系列之QT:(10)Qt接入Deepseek
我真不会起名字啊
qt 开发语言
项目配置:在.pro文件中添加网络模块:QT+=corenetworkAPI配置:将apiUrl替换为实际的DeepSeekAPI端点将apiKey替换为你的有效API密钥根据API文档调整请求参数(模型名称、温度值等)功能说明:使用QNetworkAccessManager处理HTTP请求自动处理JSON序列化/反序列化支持异步请求处理包含基本的错误处理扩展建议:添加更完善的错误处理(HTTP状
Spring Bean 生命周期的执行流程
涛粒子
spring java 后端
1.Bean定义阶段解析配置元数据:Spring容器会读取配置信息,这些配置信息可以是XML文件、Java注解或者Java配置类。容器根据这些配置信息解析出Bean的定义,包括Bean的类名、作用域、依赖关系等。注册Bean定义:解析完成后,Spring会将Bean定义信息注册到BeanDefinitionRegistry中,BeanDefinitionRegistry是一个存储Bean定义的注册
零基础学会asp.net做AI大模型网站/小程序十六:专栏总结
借雨醉东风
asp.net 小程序 后端
本专栏以实战为主,轻理论。如果哪里有不太懂的,可关注博主后加个人微信(平台规定文章中不能贴联系方式,需先关注博主,再加微信),后续一起交流学习。-------------------------------------正文----------------------------------------目录本专栏总结后续方向项目简介项目结构使用方法项目地址关键特点LLaMA机器学习简介使用LLaMA
使用Druid连接池优化Spring Boot应用中的数据库连接
和烨
其它 spring boot 数据库 后端
使用Druid连接池优化SpringBoot应用中的数据库连接使用Druid连接池优化SpringBoot应用中的数据库连接1.什么是Druid连接池?2.在SpringBoot中配置Druid连接池2.1添加依赖2.2配置Druid连接池2.3配置参数详解3.启用Druid监控4.总结使用Druid连接池优化SpringBoot应用中的数据库连接在现代的Java应用中,数据库连接管理是一个非常重
新电脑配置安装下载
今天吃了嘛o
前端
1、谷歌浏览器地址https://www.google.cn/chrome/下载安装即可。2、nvm下载下载地址:地址https://nvm.uihtm.com/#google_vignettenvminstall相对应的node版本//安装nvmlist可以查看已下载的node版本//查看nvmuse相对应的node版本号//使用nvmuninstall对应版本号//卸载3、git下载官网地址h
jvm虚拟机详解(一)-----jvm概述
Mir Su
JVM由浅至深 jvm java
写在前面本篇文章是再下人生中的第一次发布关于技术相关的文章。从事开发工作这么多年来,也算是对自己过往的工作的一个总结,对人生的一次重装再出发。从jvm谈起,然后是关于mysql、redis、消息中间件、微服务等最后在归纳一些常见的java面试方面的高频问题。这是开始我的一个写博计划,希望感兴趣的朋友加个关注一起探讨,有什么不做的地方也请欢迎指教。为什么要先说jvm呢?因为jvm是java程序蜕变的
vue中使用ueditor上传到服务器_vue+Ueditor集成 [前后端分离项目][图片、文件上传][富文本编辑]...
小西超人
写在最前面的话:鉴于近期很多的博友讨论,说我按照文章的一步一步来,弄好之后,怎么会提示后端配置项http错误,文件上传会提示上传错误。这里提别申明一点,ueditor在前端配置好后,需要与后端部分配合进行,后端部分的项目代码git地址:https://github.com/coderliguoqing/UeditorSpringboot,然后将配置ueditor.config.js里的server
java新技术
计算机毕业设计系统
转载:http://lj6684.iteye.com/blog/895010最近在网上查资料碰到好多没接触过的技术,先汇总在这里备用,以后慢慢吸收1.JNAJNI的替代品,调用方式比JNI更直接,不再需要JNI那层中间接口,几乎达到Java直接调用动态库2.SmallSQL基于JDBC3.0转为Desktop应用设计的嵌入式数据库,纯Java,本地访问,不支持网络但目前好像不太活跃,最新版本是0.
Java 与设计模式(15):模板方法模式
暗星涌动
设计模式 java 设计模式 模板方法模式 spring boot
一、定义模板方法模式是一种行为设计模式,它定义了一个操作中的算法的骨架(也就是大致的步骤和流程),而将一些具体步骤的实现延迟到子类中。这样,子类可以不改变算法的结构即可重新定义算法的某些特定步骤。二、Java示例举个简单的例子:假设我们要泡一杯茶和一杯咖啡,这两者的制作过程有一些共同的步骤,比如烧水、倒水、搅拌等,但也有不同的地方,比如茶需要放茶叶,而咖啡需要放咖啡粉。泡茶的过程:烧水、放茶叶、倒
js的垃圾回收机制
www.www
JavaScript 相关 javascript 前端 开发语言
js中的垃圾回收机制JavaScript作为一种高级语言,开发者不需要手动管理内存的分配和释放。垃圾回收机制是JavaScript引擎中的一部分,负责自动回收那些不再被使用的内存,确保内存资源得到有效利用,避免内存泄漏。垃圾回收机制主要有两种算法:引用计数和标记清除引用计数基本原理:每个对象都有一个引用计数器,当有一个引用指向该对象时,计数器+1,当一个引用不再指向该对象时,计数器-1。如果某个对
若依前后端分离集成CAS详细教程
Roc-xb
单点登录 前后端分离 CAS
目录一、后端配置1、添加cas依赖2、修改配置文件3、修改LoginUser.java4、修改Constants.java5、添加CasProperties.java6、添加CasUserDetailsService.java7、添加CasAuthenticationSuccessHandler.java8、修改SecurityConfig9、启动后端二、前端配置1、修改settings.js2、
基于Transformer的YOLOv8检测头架构改进:提升目标检测精度的全新突破(YOLOv8)
步入烟尘
transformer YOLO 目标检测
本专栏专为AI视觉领域的爱好者和从业者打造。涵盖分类、检测、分割、追踪等多项技术,带你从入门到精通!后续更有实战项目,助你轻松应对面试挑战!立即订阅,开启你的YOLOv8之旅!专栏订阅地址:https://blog.csdn.net/mrdeam/category_12804295.html文章目录基于Transformer的YOLOv8检测头架构改进:提升目标检测精度的全新突破什么是DAtten
Python爬虫TLS
dme.
Python爬虫零基础入门 爬虫 python
TLS指纹校验原理和绕过浏览器可以正常访问,但是用requests发送请求失败。后端是如何监测得呢?为什么浏览器可以返回结果,而requests模块不行呢?https://cn.investing.com/equities/amazon-com-inc-historical-data1.指纹校验案例1.1案例:ascii2dhttps://ascii2d.net/importrequestsres
前后端分离跨域问题解决方案
慕容屠苏
大前端爬坑之路 前后端分离 跨域问题解决方案
前后端分离跨域问题解决方案现在的web开发中经常会用到前后分离技术,前后端分解技术,都会涉及到跨域问题。解决跨域问题的方法:第一种解决方案jsonp(不推荐使用)这种方案其实我是不赞同的,第一,在编码上jsonp会单独因为回调的关系,在传入传出还有定义回调函数上都会有编码的”不整洁”.简单阐述jsonp能够跨域是因为javascript的script标签,通过服务器返回script标签的code,
lombok 不生效
howeres
Maven maven
Lombok不生效0现象在build/rebuild时,提示Lombok不生效:java:Youaren’tusingacompilersupportedbylombok,solombokwillnotworkandhasbeendisabled.或java:JPSincrementalannotationprocessingisdisabled.Compilationresultsonparti
深入浅出:基于SpringBoot和JWT的后端鉴权系统设计与实现
Vcats
spring boot 后端 java
文章目录什么是鉴权系统定义与作用主要组成部分工作原理常用技术和框架基于SpringBoot+JWT的鉴权系统设计与实现指南前言技术对比令牌技术JWT令牌实现全流程1.**依赖引入**2.**JWT工具类**3.**JWT拦截器(Interceptor)**4.**拦截器注册**5.**登录接口**什么是鉴权系统后端开发鉴权系统是一种用于验证和授权用户访问后端资源的系统,在保障系统安全和资源合理访问
CSS flex布局 列表单个元素点击 本行下插入详情独占一行
Cxiaomu
CSS3 UI设计 css 前端
技术栈:Vue2+javaScript简介在实际开发过程中有遇到一个场景:一个list,每行个数固定,点击单个元素后,在当前行与下一行之间插入一行元素详情,便于更直观的查看到对应的数据详情。这种情形,在移动端比较常见,比如用户列表,点击单个列表展示详情,可以考虑flex布局+positionrelative定位。实现思路对于需求重点和实现拆解列表元素:for遍历每行固定(3)个元素:flex布局、
20个高级Java开发面试题及答案!
Java进阶八股文
java jvm 开发语言 spring 面试 spring boot
1、java中都有哪些引用类型?(1)强引用Java中默认声明的就是强引用,比如:Objectobj=newObject();obj=null;只要强引用存在,垃圾回收器将永远不会回收被引用的对象。如果想被回收,可以将对象置为null;(2)软引用(SoftReference)在内存足够的时候,软引用不会被回收,只有在内存不足时,系统才会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,
同城拼车打车约车系统:Java源码全开源构建与优化
狂团商城小师妹
博纳miui52086 微信小程序 小程序 微信公众平台
同城拼车系统是一个复杂且功能全面的软件系统,它巧妙地运用互联网技术,将具有相同出行需求的乘客与车主进行精准匹配,旨在实现资源的最大化共享、显著降低出行成本、有效缓解交通拥堵问题,并大幅提升出行效率。Java,作为一种功能强大、应用广泛的编程语言,凭借其出色的跨平台性、丰富的API库以及强大的性能,成为开发此类系统的理想选择。一、Java源码构建系统架构MVC架构:同城拼车系统采用MVC(Model
活动报名系统源码:JAVA同城服务系统活动报名同城圈子商家商城城市代理躲猫猫
狂团商城小师妹
博纳miui52086 java 人工智能 大数据 微信公众平台 微信小程序
JAVA同城服务系统:打造多元化社交与娱乐新体验在数字化时代,同城服务系统已成为连接城市生活的重要桥梁。我们精心打造的JAVA同城服务系统,不仅融合了活动报名、同城圈子、商家商城、城市代理等多重功能,还特别加入了创新的“躲猫猫”游戏模块,旨在为用户提供一个集社交、娱乐、消费于一体的综合性平台。以下是对该系统功能的详细介绍及技术栈分析。功能介绍活动报名用户可以通过系统轻松浏览并参与同城各类精彩活动,
Java如何调用构造函数和方法以及使用
WZMeiei
java 开发语言
调用构造函数的格式构造函数在创建新对象时被调用。调用格式如下:ClassNameobjectName=newClassName(parameters);ClassName:你需要创建其实例的类的名称。objectName:你将创建的对象的名称。parameters:如果你使用的是带有参数的构造函数,这里需要传递相应的参数。示例:Personperson=newPerson("John",25);调
Java中的static关键字
WZMeiei
Java java 开发语言
static是Java中的一个关键字,主要用于修饰类成员(变量和方法),以表示这个成员属于类本身,而不是类的实例1.静态变量(StaticVariables)类级属性:静态变量也称为类变量或静态属性,它们在类加载时初始化,并且只有一份拷贝,被所有该类的对象共享。这意味着无论创建多少个对象,静态变量的内存空间只有一处。生命周期长:静态变量的生命周期与类相同,只要应用运行,它们就存在。访问方式:可以直
AJAX使用和固定格式
乐多_L
ajax 前端 javascript
ajax的全称AsynchronousJavaScriptandXML(异步JavaScript和XML)。ajax是一种创建交互式网页应用的网页开发技术。其中最核心的依赖是浏览器提供的XMLHttpRequest对象,是这个对象使得浏览器可以发出HTTP请求与接收HTTP响应。实现了在页面不刷新的情况下和服务器进行交互。方法描述newXMLHttpRequest()生成一个XMLHttpRequ
书其实只有三类
西蜀石兰
类
一个人一辈子其实只读三种书,知识类、技能类、修心类。
知识类的书可以让我们活得更明白。类似十万个为什么这种书籍,我一直不太乐意去读,因为单纯的知识是没法做事的,就像知道地球转速是多少一样(我肯定不知道),这种所谓的知识,除非用到,普通人掌握了完全是一种负担,维基百科能找到的东西,为什么去记忆?
知识类的书,每个方面都涉及些,让自己显得不那么没文化,仅此而已。社会认为的学识渊博,肯定不是站在
《TCP/IP 详解,卷1:协议》学习笔记、吐槽及其他
bylijinnan
tcp
《TCP/IP 详解,卷1:协议》是经典,但不适合初学者。它更像是一本字典,适合学过网络的人温习和查阅一些记不清的概念。
这本书,我看的版本是机械工业出版社、范建华等译的。这本书在我看来,翻译得一般,甚至有明显的错误。如果英文熟练,看原版更好:
http://pcvr.nl/tcpip/
下面是我的一些笔记,包括我看书时有疑问的地方,也有对该书的吐槽,有不对的地方请指正:
1.
Linux—— 静态IP跟动态IP设置
eksliang
linux IP
一.在终端输入
vi /etc/sysconfig/network-scripts/ifcfg-eth0
静态ip模板如下:
DEVICE="eth0" #网卡名称
BOOTPROTO="static" #静态IP(必须)
HWADDR="00:0C:29:B5:65:CA" #网卡mac地址
IPV6INIT=&q
Informatica update strategy transformation
18289753290
更新策略组件: 标记你的数据进入target里面做什么操作,一般会和lookup配合使用,有时候用0,1,1代表 forward rejected rows被选中,rejected row是输出在错误文件里,不想看到reject输出,将错误输出到文件,因为有时候数据库原因导致某些column不能update,reject就会output到错误文件里面供查看,在workflow的
使用Scrapy时出现虽然队列里有很多Request但是却不下载,造成假死状态
酷的飞上天空
request
现象就是:
程序运行一段时间,可能是几十分钟或者几个小时,然后后台日志里面就不出现下载页面的信息,一直显示上一分钟抓取了0个网页的信息。
刚开始已经猜到是某些下载线程没有正常执行回调方法引起程序一直以为线程还未下载完成,但是水平有限研究源码未果。
经过不停的google终于发现一个有价值的信息,是给twisted提出的一个bugfix
连接地址如下http://twistedmatrix.
利用预测分析技术来进行辅助医疗
蓝儿唯美
医疗
2014年,克利夫兰诊所(Cleveland Clinic)想要更有效地控制其手术中心做膝关节置换手术的费用。整个系统每年大约进行2600例此类手术,所以,即使降低很少一部分成本,都可以为诊 所和病人节约大量的资金。为了找到适合的解决方案,供应商将视野投向了预测分析技术和工具,但其分析团队还必须花时间向医生解释基于数据的治疗方案意味着 什么。
克利夫兰诊所负责企业信息管理和分析的医疗
java 线程(一):基础篇
DavidIsOK
java 多线程 线程
&nbs
Tomcat服务器框架之Servlet开发分析
aijuans
servlet
最近使用Tomcat做web服务器,使用Servlet技术做开发时,对Tomcat的框架的简易分析:
疑问: 为什么我们在继承HttpServlet类之后,覆盖doGet(HttpServletRequest req, HttpServetResponse rep)方法后,该方法会自动被Tomcat服务器调用,doGet方法的参数有谁传递过来?怎样传递?
分析之我见: doGet方法的
揭秘玖富的粉丝营销之谜 与小米粉丝社区类似
aoyouzi
揭秘玖富的粉丝营销之谜
玖富旗下悟空理财凭借着一个微信公众号上线当天成交量即破百万,第七天成交量单日破了1000万;第23天时,累计成交量超1个亿……至今成立不到10个月,粉丝已经超过500万,月交易额突破10亿,而玖富平台目前的总用户数也已经超过了1800万,位居P2P平台第一位。很多互联网金融创业者慕名前来学习效仿,但是却鲜有成功者,玖富的粉丝营销对外至今仍然是个谜。
近日,一直坚持微信粉丝营销
Java web的会话跟踪技术
百合不是茶
url会话 Cookie会话 Seession会话 Java Web 隐藏域会话
会话跟踪主要是用在用户页面点击不同的页面时,需要用到的技术点
会话:多次请求与响应的过程
1,url地址传递参数,实现页面跟踪技术
格式:传一个参数的
url?名=值
传两个参数的
url?名=值 &名=值
关键代码
web.xml之Servlet配置
bijian1013
java web.xml Servlet配置
定义:
<servlet>
<servlet-name>myservlet</servlet-name>
<servlet-class>com.myapp.controller.MyFirstServlet</servlet-class>
<init-param>
<param-name>
利用svnsync实现SVN同步备份
sunjing
SVN 同步 E000022 svnsync 镜像
1. 在备份SVN服务器上建立版本库
svnadmin create test
2. 创建pre-revprop-change文件
cd test/hooks/
cp pre-revprop-change.tmpl pre-revprop-change
3. 修改pre-revprop-
【分布式数据一致性三】MongoDB读写一致性
bit1129
mongodb
本系列文章结合MongoDB,探讨分布式数据库的数据一致性,这个系列文章包括:
数据一致性概述与CAP
最终一致性(Eventually Consistency)
网络分裂(Network Partition)问题
多数据中心(Multi Data Center)
多个写者(Multi Writer)最终一致性
一致性图表(Consistency Chart)
数据
Anychart图表组件-Flash图转IMG普通图的方法
白糖_
Flash
问题背景:项目使用的是Anychart图表组件,渲染出来的图是Flash的,往往一个页面有时候会有多个flash图,而需求是让我们做一个打印预览和打印功能,让多个Flash图在一个页面上打印出来。
那么我们打印预览的思路是获取页面的body元素,然后在打印预览界面通过$("body").append(html)的形式显示预览效果,结果让人大跌眼镜:Flash是
Window 80端口被占用 WHY?
bozch
端口占用 window
平时在启动一些可能使用80端口软件的时候,会提示80端口已经被其他软件占用,那一般又会有那些软件占用这些端口呢?
下面坐下总结:
1、web服务器是最经常见的占用80端口的,例如:tomcat , apache , IIS , Php等等;
2
编程之美-数组的最大值和最小值-分治法(两种形式)
bylijinnan
编程之美
import java.util.Arrays;
public class MinMaxInArray {
/**
* 编程之美 数组的最大值和最小值 分治法
* 两种形式
*/
public static void main(String[] args) {
int[] t={11,23,34,4,6,7,8,1,2,23};
int[]
Perl正则表达式
chenbowen00
正则表达式 perl
首先我们应该知道 Perl 程序中,正则表达式有三种存在形式,他们分别是:
匹配:m/<regexp>;/ (还可以简写为 /<regexp>;/ ,略去 m)
替换:s/<pattern>;/<replacement>;/
转化:tr/<pattern>;/<replacemnt>;
[宇宙与天文]行星议会是否具有本行星大气层以外的权力呢?
comsci
举个例子: 地球,地球上由200多个国家选举出一个代表地球联合体的议会,那么现在地球联合体遇到一个问题,地球这颗星球上面的矿产资源快要采掘完了....那么地球议会全体投票,一致通过一项带有法律性质的议案,既批准地球上的国家用各种技术手段在地球以外开采矿产资源和其它资源........
&
Oracle Profile 使用详解
daizj
oracle profile 资源限制
Oracle Profile 使用详解 转
一、目的:
Oracle系统中的profile可以用来对用户所能使用的数据库资源进行限制,使用Create Profile命令创建一个Profile,用它来实现对数据库资源的限制使用,如果把该profile分配给用户,则该用户所能使用的数据库资源都在该profile的限制之内。
二、条件:
创建profile必须要有CREATE PROFIL
How HipChat Stores And Indexes Billions Of Messages Using ElasticSearch & Redis
dengkane
elasticsearch Lucene
This article is from an interview with Zuhaib Siddique, a production engineer at HipChat, makers of group chat and IM for teams.
HipChat started in an unusual space, one you might not
循环小示例,菲波拉契序列,循环解一元二次方程以及switch示例程序
dcj3sjt126com
c 算法
# include <stdio.h>
int main(void)
{
int n;
int i;
int f1, f2, f3;
f1 = 1;
f2 = 1;
printf("请输入您需要求的想的序列:");
scanf("%d", &n);
for (i=3; i<n; i
macbook的lamp环境
dcj3sjt126com
lamp
sudo vim /etc/apache2/httpd.conf
/Library/WebServer/Documents
是默认的网站根目录
重启Mac上的Apache服务
这个命令很早以前就查过了,但是每次使用的时候还是要在网上查:
停止服务:sudo /usr/sbin/apachectl stop
开启服务:s
java ArrayList源码 下
shuizhaosi888
ArrayList源码
版本 jdk-7u71-windows-x64
JavaSE7 ArrayList源码上:http://flyouwith.iteye.com/blog/2166890
/**
* 从这个列表中移除所有c中包含元素
*/
public boolean removeAll(Collection<?> c) {
Spring Security(08)——intercept-url配置
234390216
Spring Security intercept-url 访问权限 访问协议 请求方法
intercept-url配置
目录
1.1 指定拦截的url
1.2 指定访问权限
1.3 指定访问协议
1.4 指定请求方法
1.1 &n
Linux环境下的oracle安装
jayung
oracle
linux系统下的oracle安装
本文档是Linux(redhat6.x、centos6.x、redhat7.x) 64位操作系统安装Oracle 11g(Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production),本文基于各种网络资料精心整理而成,共享给有需要的朋友。如有问题可联系:QQ:52-7
hotspot虚拟机
leichenlei
java HotSpot jvm 虚拟机 文档
JVM参数
http://docs.oracle.com/javase/6/docs/technotes/guides/vm/index.html
JVM工具
http://docs.oracle.com/javase/6/docs/technotes/tools/index.html
JVM垃圾回收
http://www.oracle.com
读《Node.js项目实践:构建可扩展的Web应用》 ——引编程慢慢变成系统化的“砌砖活”
noaighost
Web node.js
读《Node.js项目实践:构建可扩展的Web应用》
——引编程慢慢变成系统化的“砌砖活”
眼里的Node.JS
初初接触node是一年前的事,那时候年少不更事。还在纠结什么语言可以编写出牛逼的程序,想必每个码农都会经历这个月经性的问题:微信用什么语言写的?facebook为什么推荐系统这么智能,用什么语言写的?dota2的外挂这么牛逼,用什么语言写的?……用什么语言写这句话,困扰人也是阻碍
快速开发Android应用
rensanning
android
Android应用开发过程中,经常会遇到很多常见的类似问题,解决这些问题需要花时间,其实很多问题已经有了成熟的解决方案,比如很多第三方的开源lib,参考
Android Libraries 和
Android UI/UX Libraries。
编码越少,Bug越少,效率自然会高。
但可能由于 根本没听说过、听说过但没用过、特殊原因不能用、自己已经有了解决方案等等原因,这些成熟的解决
理解Java中的弱引用
tomcat_oracle
java 工作 面试
不久之前,我
面试了一些求职Java高级开发工程师的应聘者。我常常会面试他们说,“你能给我介绍一些Java中得弱引用吗?”,如果面试者这样说,“嗯,是不是垃圾回收有关的?”,我就会基本满意了,我并不期待回答是一篇诘究本末的论文描述。 然而事与愿违,我很吃惊的发现,在将近20多个有着平均5年开发经验和高学历背景的应聘者中,居然只有两个人知道弱引用的存在,但是在这两个人之中只有一个人真正了
标签输出html标签" target="_blank">关于标签输出html标签
xshdch
jsp
http://back-888888.iteye.com/blog/1181202
关于<c:out value=""/>标签的使用,其中有一个属性是escapeXml默认是true(将html标签当做转移字符,直接显示不在浏览器上面进行解析),当设置escapeXml属性值为false的时候就是不过滤xml,这样就能在浏览器上解析html标签,
&nb