公共模块是所有框架内部组件依赖的项目,其中包括一些基础实现和小工具。
首先是一个Bootstrapper的理念:
1、在需要的时候,我们可以把实现和接口进行分离,实现使用依赖注入(不一定要项目引用,只需要文件夹下有实现的DLL)。那么,我们就需要在网站应用程序或是其它应用程序启动的时候,把这些实现注入进来。这里需要提一点的是,正因为我们把实现和接口进行分离,使得我们可以让两个组件进行相互的引用,比如配置服务的实现可以调用信息中心的接口来记录日志,而信息中心的实现可以调用配置服务的接口来存储配置。
2、然后,我们需要针对每一个组件进行一些初始化工作,比如进行数据初始化,这步工作需要在1之后。
当然,还可以有3、4、5,这些工作我们称为BootstrapperTask,在应用程序启动的时候,只要程序集中有BootstrapperTask,Bootstrapper就会自动找到这些Task并且按照Order分组并行调用,在应用程序结束的时候,Bootstrapper也会依次调用所有的Dispose来清理。
因此,我们要求类似这样,调用框架的启动和结束方法:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AdhesiveFramework.Start(); } protected void Application_End() { AdhesiveFramework.End(); } }
然后是一些本地的组件,之所以有本地组件,是因为在某些情况下我们不能使用完整的非本地组件:
1、比如配置服务本身作为一个最底层的服务,如果使用信息中心模块记录错误日志的话,就产生了循环依赖的问题,因为信息中心模块在初始化的时候需要用到配置服务,如果配置服务在启动的时候发生异常记录一条日志,那么此时由于配置服务本身没启动,信息中心模块不可能启动成功,此时可以使用本地的日志服务。
2、有的时候我们需要进行测试或调试,不希望使用正式的基于数据库的配置服务(需要连接远程的配置服务服务端,如果需要修改数据的话需要到后台修改),而是希望使用简单的本地配置文件,那么可以使用本地的配置服务。
这些组件包括:
1、LocalConfigService:通过XML序列化把实体保存到config文件中,实现文件依赖方式的缓存,并且为了解决某些类型不能序列化的问题实现了SerializableDictionary以及SerializableTimeSpan等,使用方式如下:
var config = LocalConfigService.GetConfig(new AdhesiveConfig { ApplicationName = "", ConfigServiceAddress = "192.168.129.143:18989/ConfigService", WcfConfigServiceAddress = "192.168.129.143:18888/WcfConfigService", LocalLoggingServiceLevel = LogLevel.Debug, StateServiceDefaultReportStateIntervalMilliSeconds = 1000 * 10, });
2、LocalLoggingService:以一小时为一个文件记录本地日志;并且对于DEBUG方式的编译,为了方便查看日志自动向控制台输出日志(使用不同颜色区分不同等级);另外还为日志附加了调用方法名、时间等信息,使用方式:
LocalLoggingService.Error("AdhesiveFramework.LocalServiceLocator 不能解析 '{0}',异常信息:{1}", typeof(T).FullName, ex.ToString());
在1中我们看到这是Adhesive框架最基础的配置,这是通过本地配置服务进行的,其中有一个就是本地日志服务的级别,如果配置了Warning,那么只有Warning和Error的日志才会记录到本地日志中。一般本地日志服务仅仅用于框架初始化时部分代码的日志,之后的日志记录都应该通过集中化的信息中心模块。
记录的日志差不多这样:
[Info] @17:48:34_1674 #8 Adhesive.Common.AdhesiveFramework Start() - AdhesiveFramework开始启动... [Debug] @17:48:34_6604 #8 Adhesive.Common.Bootstrapper+<>c__DisplayClasscb__8() - AdhesiveFramework.Bootstrapper 开始执行 'Adhesive.AppInfoCenter.Imp.RegisterServiceTask' (注册日志、异常、性能、状态服务) [Debug] @17:48:34_6674 #8 Adhesive.Common.Bootstrapper+<>c__DisplayClassc b__8() - AdhesiveFramework.Bootstrapper 开始执行 'Adhesive.Config.Imp.RegisterServiceTask' () [Debug] @17:48:34_6804 #8 Adhesive.Common.Bootstrapper+<>c__DisplayClassc b__8() - AdhesiveFramework.Bootstrapper 开始执行 'Adhesive.DistributedService.RegisterServiceTask' (注册Wcf定位服务) [Debug] @17:48:34_7014 #8 Adhesive.Common.Bootstrapper+<>c__DisplayClassc b__8() - AdhesiveFramework.Bootstrapper 开始执行 'Adhesive.MemoryQueue.Imp.RegisterServiceTask' () [Debug] @17:48:34_7074 #8 Adhesive.Common.Bootstrapper+<>c__DisplayClassc b__8() - AdhesiveFramework.Bootstrapper 开始执行 'Adhesive.Mongodb.Imp.RegisterServiceTask' () [Debug] @17:48:34_7144 #8 Adhesive.Common.Bootstrapper+<>c__DisplayClassc b__8() - AdhesiveFramework.Bootstrapper 开始执行 'Adhesive.Mongodb.Server.Imp.RegisterServiceTask' () [Debug] @17:48:34_7214 #8 Adhesive.Common.Bootstrapper+<>c__DisplayClassc b__8() - AdhesiveFramework.Bootstrapper 开始执行 'Adhesive.Mongodb.Server.Imp.InitServerTask' (初始化Mongodb服务端、进行第一次数据库元数据维护)
3、LocalServiceLocator:通过Unity提供接口到实现的解析。
除了这些之外,公共组件还放一些公共类库和扩展方法:
1、公共类库:比如序列化、加密等等。
2、公共扩展方法:比如Unity注册和解析的扩展方法、类型的扩展、枚举扩展方法、Json的扩展方法等等。
在开发的过程中遇到几个很诡异的问题,在此希望分享一下。
1、有一次在调试网站项目的时候发现,同样一个静态字段居然在框架启动的时候和页面请求的时候是两个不同对象,也就是对象的HashCode不一致,我们戏称“平行宇宙”问题。后来发现,我们为了解决没有引用的程序集中的BootstrapperTask也能执行,动态加载所有bin目录下的程序集,这就导致在启动的时候加载的程序集所在的应用程序域和网站的应用程序域不同。解决办法是对于网站使用:
BuildManager.GetReferencedAssemblies().Cast().Where(assembly => !assembly.GlobalAssemblyCache).ToList()
来获得所有的程序集。
2、在写配置服务的时候发现配置服务的实现死活不能解析成功,但查看Unity发现确实已经注册进去了,后来发现如果接口和实现位于同一个程序集中,Unity居然不能成功解析,把接口和实现分离到两个不同的程序集之后问题解决。