接上篇:MES項目簡單總結(技術篇)
開篇說明
MES產品框架采用WCF通信,服務端Service的更新 以及 客制化方案都以dll的方式實現,並且對Service的更新要在服務Runing的情況下進行(即“熱替換”)。
采用動態加載/卸載dll的方式實現。
動態加載dll的兩種方式
C#中實現動態加載dll有兩種方式:Assembly和AppDomain。
1、 Assembly
如果直接使用Assembly.LoadFrom(fileName)的方式加載,在主程序運行過程中,無法更新dll文件。
所以,一般的做法時,在加載的時候,先加載到內存中,然后再從內存中加載。
示例代碼:
1 byte[] bytes = File.ReadAllBytes("dll全路徑"); 2 3 Assembly assembly = Assembly.Load(bytes); 4 5 類型名稱 instance = (類型名稱)assembly.CreateInstance("類型全名稱");
2、 AppDomain
關於AppDomain的使用,可以參考MSDN:使用應用程序域
動態替換程序集的方法主要使用AppDomain的影像復制功能,可以通過設置AppDomainSetup的兩個屬性實現 :ShadowCopyFiles和ShadowCopyDirectories。參考MSDN: 影像復制
示例代碼:
1 AppDomainSetup setup = new AppDomainSetup(); 2 setup.ApplicationName = "ApplicationLoader"; 3 setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; 4 setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "private"); 5 setup.CachePath = setup.ApplicationBase; 6 setup.ShadowCopyFiles = "true"; //啟用影像復制程序集 7 setup.ShadowCopyDirectories = setup.ApplicationBase; 8 AppDomain.CurrentDomain.SetShadowCopyFiles();
在線更新(熱替換)
這里采用 AppDomain的方式。
1、建立一個調用相關信息類InvokerInfo,用來保存調用的相關信息。
代碼:
1 public class InvokerInfo 2 { 3 //待調用的服務類型 4 /// <summary> 5 /// 待調用的服務類型 6 /// </summary> 7 public string TypeName { get; set; } 8 9 //服務所在應用程序域 10 /// <summary> 11 /// 服務所在應用程序域 12 /// </summary> 13 public AppDomain Domain { get; set; } 14 15 //服務調用器 16 /// <summary> 17 /// 服務調用器 18 /// </summary> 19 public MethodInvoker Invoker { get; set; } 20 21 //dll文件最后修改時間 22 /// <summary> 23 /// dll文件最后修改時間 24 /// </summary> 25 public DateTime LastWriteTime { get; set; } 26 27 //引用數 28 /// <summary> 29 /// 引用數 30 /// </summary> 31 public int Ref { get; set; } 32 }
LastWriteTime:影像復制的文件夾中dll的最后寫入時間,用來判斷是否更新,以便刷新緩存;
Ref:每次調用前Ref++,調用后Ref--。在卸載dll的時候,判斷Ref是否等於0。如果Ref大於0,則不可以卸載。
2、建立方法調用器MethodInvoker,負責緩存指定dll中的指定類型中的所有方法,以及對方法的調用。該類需要繼承自:MarshalByRefObject以實現跨程序域的操作。
代碼:
1 //方法調用器 2 /// <summary> 3 /// 方法調用器 4 /// </summary> 5 public class MethodInvoker : MarshalByRefObject,IDisposable 6 { 7 //程序集名稱 8 private readonly string _DllName; 9 //程序集中的類類型全名 10 private readonly string _TypeName; 11 //方法信息緩存列表 12 private readonly Dictionary<string, MethodInfo> _Methods = 13 new Dictionary<string, MethodInfo>(); 14 //程序集中的類類型實例 15 private object _TypeInstance; 16 17 //構造方法 18 /// <summary> 19 /// 構造方法 20 /// </summary> 21 /// <param name="dllName"></param> 22 /// <param name="typeName"></param> 23 public MethodInvoker(string dllName, string typeName) 24 { 25 this._DllName = dllName; 26 this._TypeName = typeName; 27 } 28 29 //加載程序集中的所有方法 30 /// <summary> 31 /// 加載程序集中的所有方法 32 /// </summary> 33 public void LoadAllMethods() 34 { 35 Assembly assembly = Assembly.LoadFrom(_DllName); 36 if (assembly == null) 37 throw new Exception("Can't find " + _DllName); 38 Type tp = assembly.GetType(_TypeName); 39 if (tp == null) 40 throw new Exception("Can't get type " + _TypeName + " from " + _DllName); 41 _TypeInstance = Activator.CreateInstance(tp); 42 if (_TypeInstance == null) 43 throw new Exception("Can't construct type " + _TypeName + " from " + _DllName); 44 45 MethodInfo[] typeMethod; 46 47 if (_Methods.Count == 0) 48 { 49 typeMethod = tp.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance); 50 51 for (int i = 0; i < typeMethod.Length; i++) 52 { 53 if (typeMethod[i].DeclaringType != typeof(object)) 54 { 55 MethodInfo method; 56 if (!_Methods.TryGetValue(typeMethod[i].Name, out method)) 57 _Methods.Add(typeMethod[i].Name, typeMethod[i]); 58 } 59 } 60 } 61 } 62 63 //調用程序集中的指定方法 64 /// <summary> 65 /// 調用程序集中的指定方法 66 /// </summary> 67 /// <param name="methodName">方法名稱</param> 68 /// <param name="methodParams">參數數組</param> 69 /// <returns></returns> 70 public object InvokeMethod(string methodName, object[] methodParams) 71 { 72 MethodInfo method; 73 if (string.IsNullOrEmpty(methodName)) 74 throw new Exception("Method Name IsNullOrEmpty"); 75 76 _Methods.TryGetValue(methodName, out method); 77 78 if (method == null) 79 throw new Exception("Method can not be found"); 80 81 object result = method.Invoke(_TypeInstance, methodParams); 82 //這里可以對result進行包裝 83 return result; 84 } 85 86 87 #region IDisposable 成員 88 89 public void Dispose() 90 { 91 _TypeInstance = null; 92 GC.Collect(); 93 GC.WaitForPendingFinalizers(); 94 GC.Collect(0); 95 } 96 #endregion 97 }
3、建立程序集加載器AssemblyLoader,負責所有dll的加載/卸載,並負責這些dll的緩存及刷新。
代碼:
1 //程序集加載器 2 /// <summary> 3 /// 程序集加載器 4 /// </summary> 5 public class AssemblyLoader : IDisposable 6 { 7 private readonly object _lockThis = new object(); 8 private Dictionary<string, Queue<InvokerInfo>> _caches = 9 new Dictionary<string, Queue<InvokerInfo>>(); 10 11 //加載所有可用的程序集 12 /// <summary> 13 /// 加載所有可用的程序集 14 /// </summary> 15 /// <param name="dlls"></param> 16 public void LoadAssemblys(Dictionary<string, string> dlls) 17 { 18 foreach (KeyValuePair<string, string> kvp in dlls) 19 LoadAssembly(kvp.Key, kvp.Value); 20 } 21 //卸載程序集 22 /// <summary> 23 /// 卸載程序集 24 /// </summary> 25 /// <param name="dllName"></param> 26 public void Unload(string dllName) 27 { 28 InvokerInfo info = _caches[dllName].Dequeue(); 29 AppDomain.Unload(info.Domain); 30 } 31 //調用指定程序集中的指定方法 32 /// <summary> 33 /// 調用指定程序集中指定的方法 34 /// </summary> 35 /// <param name="dllName">程序集名稱</param> 36 /// <param name="methodName">方法名稱</param> 37 /// <param name="methodParams">參數數組</param> 38 /// <returns></returns> 39 public object InvokeMethod(string dllName, string methodName, object[] methodParams) 40 { 41 object result; 42 InvokerInfo info; 43 lock (_lockThis) 44 { 45 info = GetInvoker(dllName); 46 info.Ref++; 47 } 48 49 result = info.Invoker.InvokeMethod(methodName, methodParams); 50 51 lock (_lockThis) 52 { 53 info.Ref--; 54 TryToUnLoad(dllName, info); 55 } 56 57 return result; 58 } 59 60 //加載指定的程序集 61 /// <summary> 62 /// 加載指定的程序集 63 /// </summary> 64 /// <param name="dllName"></param> 65 /// <param name="typeName"></param> 66 private void LoadAssembly(string dllName, string typeName) 67 { 68 //Get object from cache 69 Queue<InvokerInfo> result; 70 _caches.TryGetValue(dllName, out result); 71 72 if (result == null || result.Count == 0) 73 { 74 //Get TimeStamp of file 75 FileInfo info = 76 new FileInfo( 77 AppDomain.CurrentDomain.BaseDirectory + @"ServiceDlls\" + dllName); 78 79 if (!info.Exists) 80 throw new Exception(AppDomain.CurrentDomain.BaseDirectory 81 + @"ServiceDlls\" + dllName + " not exist"); 82 83 CacheMethodInvoker(dllName, typeName, info.LastWriteTime); 84 } 85 } 86 //緩存指定的方法調用信息 87 /// <summary> 88 /// 緩存指定的方法調用信息 89 /// </summary> 90 /// <param name="dllName"></param> 91 /// <param name="typeName"></param> 92 /// <param name="lastWriteTime"></param> 93 /// <returns></returns> 94 private InvokerInfo CacheMethodInvoker(string dllName, string typeName, DateTime lastWriteTime) 95 { 96 MethodInvoker invoker; 97 98 var invokerInfo = new InvokerInfo(); 99 100 var setup = new AppDomainSetup 101 { 102 ShadowCopyFiles = "true", 103 ShadowCopyDirectories = AppDomain.CurrentDomain.BaseDirectory + @"ServiceDlls\", 104 ConfigurationFile = "DynamicLoadAssembly.exe.config", 105 ApplicationBase = AppDomain.CurrentDomain.BaseDirectory 106 }; 107 108 AppDomain domain = AppDomain.CreateDomain(dllName, null, setup); 109 110 domain.DoCallBack(delegate { LifetimeServices.LeaseTime = TimeSpan.Zero; }); 111 112 invokerInfo.Domain = domain; 113 invokerInfo.LastWriteTime = lastWriteTime; 114 invokerInfo.TypeName = typeName; 115 116 BindingFlags bindings = 117 BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public; 118 object[] para = new object[] { setup.ShadowCopyDirectories + @"\" + dllName, typeName }; 119 try 120 { 121 invoker = (MethodInvoker)domain.CreateInstanceFromAndUnwrap( 122 Assembly.GetExecutingAssembly().CodeBase.Substring(8), 123 typeof(MethodInvoker).FullName, 124 true, bindings, null, para, null, 125 null, null); 126 } 127 catch (Exception ex) 128 { 129 throw new Exception( 130 "Can't create object which type is " + typeof(MethodInvoker).FullName + " from assembly " + 131 Assembly.GetExecutingAssembly().CodeBase + ",Error Message: " + ex.Message); 132 } 133 134 if (invoker == null) 135 throw new Exception( 136 "Can't find type " + typeof(MethodInvoker).FullName + " from " + 137 Assembly.GetExecutingAssembly().CodeBase); 138 139 try 140 { 141 invoker.LoadAllMethods(); 142 } 143 catch (Exception ex) 144 { 145 throw new Exception("Can't initialize object which type is " + typeof(MethodInvoker).FullName + 146 " from " + 147 Assembly.GetExecutingAssembly().CodeBase + ",Error Message: " + ex.Message); 148 } 149 150 invokerInfo.Invoker = invoker; 151 invokerInfo.Ref = 0; 152 153 if (_caches.Keys.Contains(dllName)) 154 { 155 _caches[dllName].Enqueue(invokerInfo); 156 } 157 else 158 { 159 Queue<InvokerInfo> queue = new Queue<InvokerInfo>(); 160 queue.Enqueue(invokerInfo); 161 _caches[dllName] = queue; 162 } 163 164 return invokerInfo; 165 } 166 //嘗試卸載程序集 167 /// <summary> 168 /// 嘗試卸載程序集 169 /// </summary> 170 /// <param name="dllName"></param> 171 /// <param name="currentInfo"></param> 172 private void TryToUnLoad(string dllName, InvokerInfo currentInfo) 173 { 174 InvokerInfo info = _caches[dllName].Peek(); 175 176 if (info == currentInfo) 177 return; 178 179 if (info.Ref == 0) 180 { 181 Unload(dllName); 182 } 183 } 184 //獲取指定程序集的調用信息 185 /// <summary> 186 /// 獲取指定程序集的調用信息 187 /// </summary> 188 /// <param name="dllName"></param> 189 /// <returns></returns> 190 private InvokerInfo GetInvoker(string dllName) 191 { 192 //Get object from cache 193 Queue<InvokerInfo> result; 194 _caches.TryGetValue(dllName, out result); 195 196 if (result == null) 197 { 198 throw new Exception(dllName + " not loaded"); 199 } 200 201 //Get TimeStamp of file 202 FileInfo info = 203 new FileInfo(AppDomain.CurrentDomain.BaseDirectory + @"ServiceDlls\" + dllName); 204 205 if (!info.Exists) 206 { 207 return result.ToArray()[result.Count - 1]; 208 } 209 210 if (info.LastWriteTime > result.ToArray()[result.Count - 1].LastWriteTime) 211 { 212 return CacheMethodInvoker(dllName, result.Peek().TypeName, info.LastWriteTime); 213 } 214 215 return result.ToArray()[result.Count - 1]; 216 } 217 218 #region IDisposable 成員 219 220 public void Dispose() 221 { 222 _caches.Clear(); 223 224 foreach (var o in _caches.Keys) 225 { 226 Unload(o); 227 } 228 } 229 230 #endregion 231 }
4、使用說明
a) 在主應用程序域通過單態模式定義一個AssemblyLoader,並通過該AssemblyLoader的LoadAssemblys()方法加載所有dll
b) 在LoadAssemblys()方法內部會調用LoadAssembly()方法逐個加載,真正使用AppDomain加載dll的方法是 CacheMethodInvoker()。
c) 在CacheMethodInvoker()方法中,聲明AppDomainSetup,並設置其屬性:
ShadowCopyFiles = "true";
ShadowCopyDirectories = AppDomain.CurrentDomain.BaseDirectory + @"ServiceDlls\"; //注:文件夾路徑要以反斜杠結束
並使用AppDomain的CreateInstanceFromAndUnwrap方法創建MethodInvoker實例,並緩存下來
d) 外部通過AssemblyLoader中的InvokeMethod()方法進行調用。InvokeMethod()方法中會通過GetInvoker()方法獲取一個InvokerInfo信息,首先會從緩存中獲取,獲取之后,比較其LastWriteTime和物理位置中的dll文件的LastWriteTime,如果有更新,就通過CacheMethodInvoker()方法刷新緩存,並返回,對新獲取的InvokerInfo的Ref++;然后調用InvokerInfo中的MethodInvoker實例的InvokeMethod方法進行實際的方法調用,完成之后Ref--,並通過TryUnload()方法嘗試卸載。
客制化支持
在以上方案的基礎上實現客制化的支持。
1、定義請求類,分為基本請求類和需要流程支持的請求類:BaseRequest和WorkFlowRequest,WorkFlowRequest從BaseRequest繼承。
2、 針對請求類,定義響應類,同樣分為基本響應類和需要流程支持的響應類:BaseResponse和WorkFlowResponse。
3、根據模塊定義不同的AssemblyLoader容器類,比如:WIPService。
4、在WIPService類中定義對兩種請求進行處理的方法:
public static BaseResponse ExecuteBaseRequest(BaseRequest)
public static WorkFlowResponse ExecuteWorkFlowRequest(WorkFlowRequest)
5、在WCF的服務啟動時加載指定名稱的可以用來客制化的dll
6、客制化的dll中,通過執行WIPService.ExecuteBaseRequest()和WIPService.ExecuteWorkFlowRequest()方法完成操作。
最后,和本篇主題無關的是,目前的架構中,使用了NHibernate的Session來保證事務,即只支持單一數據庫,且客制化代碼中對數據庫的操作也要使用框架中提供的數據操作方法進行操作。
測試源碼下載
點擊-->下載源碼