MES项目简单总结(技术篇)—— “热替换”及客制化支持的实现(动态加载/卸载程序集)

 

接上篇: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来保证事务,即只支持单一数据库,且客制化代码中对数据库的操作也要使用框架中提供的数据操作方法进行操作。


 

测试源码下载

点击-->下载源码

 

posted @ 2012-12-07 16:21  YinFanIT  阅读(3365)  评论(4编辑  收藏  举报