虽然目前.NET对web服务支持的非常好,Spring.NET认为还是有几个方面可以改进。
编辑22.1. 服务端
首先,.NET在.asmx文件中保存Web服务请求和服务对象的关联关系,这些.asmx文件不管有用没用都得放在那儿。
第二,Spring.NET希望能通过IoC容器对web服务进行依赖注入。一般说来web服务总会依赖其它服务对象,所以,如果能用配置方式来选择服务对象,这个功能就相当强大了。
最后,目前在.NET中Web服务的创建完全是一个实现(特定类型)的过程。多数服务(虽不能说是全部)都应实现为使用粗粒度服务接口的普通类型,并且,某个对象能否发布为远程对象、web服务还是企业(COM+)组件应该只与配置有关,而不应该取决于它的实现方式。
顶部编辑21.1.1.消除对.asmx文件的依赖
ASP.NET用.aspx文件来保存表示层代码,用code-behind文件中的类保存应用逻辑,.aspx与类代码文件各有分工;但web服务却不同,Web服务的逻辑完全是在code-behind的类中实现的。.asmx文件并没有什么真正的用途,实际上,这个文件既没有必要存在、也不应该存在。
在将WebServiceFactoryHandler类注册为响应*.asmx请求的HTTP Handler之后,开发人员就可以在IoC容器中用标准的Spring.NET对象定义来发布Web服务。
下面通过一个例子来说明这个过程,首先看下面的web服务...
namespace MyComany.MyApp.Services
{
[WebService(Namespace="http://myCompany/services")]
public class HelloWorldService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World!";
}
}
}
这是一个普通的类型,其中应用了两个.NET特性:方法级的[WebMethod]及类型级的[WebService]。开发人员可以在VS.NET中创建这个服务类。
要想将这个对象发布为web服务,只需依次作以下操作:
1.在web.config文件中,将Spring.Web.Services.WebServiceFactoryHandler注册为响应*.asmx请求的HTTP Handler。
<system.web>
<httpHandlers>
<add verb="*" path="*.asmx" type="Spring.Web.Services.WebServiceHandlerFactory, Spring.Web"/>
</httpHandlers>
</system.web>
当然,也可以为*.asmx请求注册其它的Http Handler来处理意外情况,不过一般没这个必要,因为如果WebServiceFactoryHandler无法在容器中找到与某个请求相匹配的对象定义,就会求助于.NET标准的Http Handler去查找.asmx文件。
2.为web服务创建XML对象定义
<object name="HelloWorld.asmx" type="MyComany.MyApp.Services.HelloWorldService, MyAssembly" abstract="true"/>
注意,为web服务创建的对象定义并不要求一定是抽象的(通过设置abstract="true"),但设为抽象可防止创建不必要的服务实例,因为.NET基础框架会在后台自动为每次请求创建新的服务实例,Spring.NET需要做的只是提供服务的类型名,即便标记为抽象,也能从容器中获取服务对象的实例。所以Spring.NET建议将Web服务的对象定义显示标记为抽象。
现在就能以对象定义中name属性的值为服务名来访问这个Web服务了:
http://localhost/MyWebApp/HelloWorld.asmx
顶部编辑22.1.2.向web服务中注入依赖项
为了方便讨论,我们来修改HelloWorld方法的实现,以便返回一个可配置的消息。
当然了,我们可以用某种消息定位器来返回一条合适的信息,但是这个“消息定位器”也是需要自己实现的。如果我们在整个应用程序中都用依赖注入来配置对象,对web服务反倒去用服务定位器,难免有些古怪。
理想的情况是,我们在web服务类中为消息定义一个属性,然后让Spring.NET注入这个属性的值:
namespace MyApp.Services
{
public interface IHelloWorld
{
string HelloWorld();
}
[WebService(Namespace="http://myCompany/services")]
public class HelloWorldService : IHelloWorld
{
private string message;
public string Message
{
set { message = value; }
}
[WebMethod]
public string HelloWorld()
{
return this.message;
}
}
}
在这里,Spring.NET标准的依赖注入机制遇到了一个问题:Web服务的初始化不受Spring.NET控制,而是由.NET框架在内部进行的,所以很难把握配置对象的时机。
解决的方法是创建一个动态的服务端代理,用以包装和配置web服务。这样,.NET框架从Spring.NET获取的实际上是这个代理类的类型,代理对象初始化后,就会向Spring.NET的应用程序上下文请求真正的服务对象来处理web服务请求。
这种代理方式要求用Spring.Web.Services.WebServiceExporter类显式的导出web服务。对于本例来说,别忘了配置Message属性:
<object id="HelloWorld" type="MyApp.Services.HelloWorldService, MyApp">
<property name="Message" value="Hello, World!"/>
</object>
<object id="HelloWorldExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
<property name="TargetName" value="HelloWorld"/>
</object>
WebServiceExporter类会将服务对象中
WebService和
WebMethod特性的值复制到代理类中。请注意这些值可以被WebServiceExporter类的属性覆盖。
对接口的需求
要实现某些高级的特性,比如将AOP代理发布为web服务(允许向web方法应用AOP通知),Spring.NET需要这些目标对象实现某个(服务)接口。
只有定义在接口中的方法才会被WebServiceExporter类导出。
顶部编辑22.1.3.将PONO导出为web服务
既然我们为web服务创建了服务端代理,就没有必要在web服务类中应用诸如WebMethod之类的特性了。因为此时.NET基础框架是看不到“真正”的服务对象的,.NET真正接触到的是应用在代理类上的特性。
也就是说,我们完全可以从服务类中去掉WebService和WebMethod特性,只留下一个普通的.NET对象(一个PONO)。对于上面的例子来说,去掉这些特性后仍然可以正常工作,因为代理类生成器可以自动的在要导出的接口方法上应用WebMethod特性。
不过,这还不是最理想的解决方案。去掉了WebService和WebMethod特性也就无法再保留它们定义的可选信息,比如服务的命名空间、描述、事务模式等等。但若为这些信息而仍在服务类中应用特性,让代理生成器将这些特性的值复制到代理类中去,又违背了我们的初衷。
更好的方法是在WebServiceExporter的对象定义中设置所有必要的值,如下...
<object id="HelloWorldExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
<property name="TargetName" value="HelloWorld"/>
<property name="Namespace" value="http://myCompany/services"/>
<property name="Description" value="My exported HelloWorld web service"/>
<property name="MemberAttributes">
<dictionary>
<entry key="HelloWorld">
<object type="System.Web.Services.WebMethodAttribute, System.Web.Services">
<property name="Description" value="My Spring-configured HelloWorld method."/>
<property name="MessageName" value="ZdravoSvete"/>
</object>
</entry>
</dictionary>
</property>
</object>
// or, once configuration improvements are implemented...
<web:service targetName="HelloWorld" namespace="http://myCompany/services">
<description>My exported HelloWorld web service.</description>
<methods>
<method name="HelloWorld" messageName="ZdravoSvete">
<description>My Spring-configured HelloWorld method.</description>
</method>
</methods>
</web:service>
根据上面的配置,Spring.NET可以为目标对象实现的所有接口生成web服务代理,并添加必要的特性。同时,将web服务的元数据从类实现移到了配置文件中,并允许将任意类型发布为web服务。
通过设置WebServiceExporter类的Interfaces属性,也可以只发布服务类实现的部分接口...
关于分布式对象
不能因为Spring.NET能够把任意对象发布为web服务,就想把所有对象都发布为web服务。我们仍然要受分布式计算原则的约束,我们要保证所发布的web服务是有意义的,同时web方法的参数和返回值都是可序列化的。
同样,还是要了解一些这方面的基础知识,当需要选择使用web服务(或者更广义的说,远程服务)还是本地服务时,应该能够做出正确的判断。
顶部编辑22.1.4.将AOP代理发布为web服务
很多时候需要将AOP代理发布为web服务。比如说,假设我们有一个被AOP代理包装好的服务类,打算同时通过本地和远程(使用web服务)访问它。本地客户端只需要直接获取AOP代理的引用,而远程客户端获取则是的web服务代理的引用,由它来代理对AOP代理的调用,然后再由AOP代理负责对目标对象的调用。
要将AOP代理发布为web服务实际上是相当简单的;因为AOP代理和其它对象也没什么两样,所要做的无非就是把WebServiceExporter对象定义的TargetName属性值设为AOP代理对象的id(或者name、或者别名)。请看下面的代码...
<object id="DebugAdvice" type="MyApp.AOP.DebugAdvice, MyApp"/>
<object id="TimerAdvice" type="MyApp.AOP.TimerAdvice, MyApp"/>
<object id="MyService" type="MyApp.Services.MyService, MyApp"/>
<object id="MyServiceProxy" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
<property name="TargetName" value="MyService"/>
<property name="IsSingleton" value="true"/>
<property name="InterceptorNames">
<list>
<value>DebugAdvice</value>
<value>TimerAdvice</value>
</list>
</property>
</object>
<object id="MyServiceExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
<property name="TargetName" value="MyServiceProxy"/>
<property name="Name" value="MyService"/>
<property name="Namespace" value="http://myApp/webservices"/>
<property name="Description" value="My web service"/>
</object>
这样,Web服务方法的每次调用都会被AOP代理拦截,然后将配置中的DebugAdvice和TimerAdvice两个通知应用到方法上。
编辑22.1.5.客户端的问题
在客户端,.NET本身最大的缺陷是将客户端代码绑定在了代理类而非服务接口上。除非按照Jubal Lowy的著作《Programming .NET Components》中提到的方法,手工让代理类实现某个服务接口,否则应用程序代码的灵活性会很低,同时,如果需要使用一个新的、改进过的Web服务类,或者打算用一个本地服务替换掉web服务时,相应的更换工作会很复杂。
在Spring.NET的支持下,可以很方便的为web服务创建实现了指定服务接口的客户端代理。
顶部编辑22.2. 客户端
在客户端,.NET本身最大的缺陷是将客户端代码绑定在了代理类而非服务接口上。除非按照Jubal Lowy的著作《Programming .NET Components》中提到的方法,手工让代理类实现某个服务接口,否则应用程序代码的灵活性会很低,同时,如果需要使用一个新的、改进过的Web服务类,或者打算用一个本地服务替换掉web服务时,相应的更换工作会很复杂。
在Spring.NET的支持下,可以很方便的为web服务创建实现了指定服务接口的客户端代理。
顶部编辑22.2.1. WebServiceProxyFactory
如果使用VS.NET或WSDL命令行工具,生成的web服务代理类最大的问题是没有实现任何接口。这种客户代码和Web服务紧耦合的做法使将来不可能在不修改和重新编译客户代码的前提下更换web服务代理类。
Spring.NET有一个很简单的IFactoryObject实现类,可以生成“代理的代理“(乍一听好像挺蠢)。简单来说, Spring.Web.Services.WebServiceProxyFactory类会为VS.NET/WSDL工具生成的代理再创建一个代理,由这个代理去实现指定的服务接口(这就解决了上一段提到的问题)。
现在用一个例子也许更能说明问题;假如我们打算将下面的接口发布为web服务...
namespace MyCompany.Services
{
public interface IHelloWorld
{
string HelloWorld();
}
}
在客户端,为了能通过此接口引用web服务,需要将下面的对象定义添加到应用程序上下文的配置中:
<object id="HelloWorld" type="Spring.Web.Services.WebServiceProxyFactory, Spring.Services">
<property name="ProxyType" value="MyCompany.WebServices.HelloWorld, MyClientApp"/>
<property name="ServiceInterface" value="MyCompany.Services.IHelloWorld, MyServices"/>
</object>
有一点很重要,上面指定的服务接口不必一定是IHelloWorld接口,只要存在签名吻合的方法(一种duck typing(按:“duck typing”是ruby语言专家Dave Thomas对动态类型的描述,意思是说,如果某个东西走起来象鸭子,叫起来也象鸭子,那么它可能就是一个鸭子。反映到语言中,就是如果一个对象的方法名和某个类型匹配,那么这个对象就可以看做是那个类型)),Spring.NET就能正确的创建代理。如果找不到匹配的方法,Spring.NET就会抛出异常。
如果可以同时控制客户端和服务端的设计,那么最好能确保服务端的web服务类实现了某个服务接口,特别是打算用Spring.NET的WebServiceExporter发布web服务时,因为该类要求服务类必须实现一个以上接口。
顶部编辑22.2.2. WebServiceClientFactory
另一个IFactoryObject的实现类WebServiceClientFactory用以动态生成Web服务代理:
<object id="HelloWorld" type="Spring.Web.Services.WebServiceClientFactory, Spring.Services">
<property name="ServiceUrl" value="http://www.MyCompany.com/HelloWorld.asmx"/>
<property name="ServiceInterface" value="MyCompany.Services.IHelloWorld, MyServices"/>
</object>
当通过Web服务处理强类型DataSet的时候,这种代理相当有用。我们且不论这种代理有什么优缺点,先来看.NET自己的代理生成行为:在处理强类型DataSet时,.NET会为其创建包装类型(wrapper types)。并且,会为每个使用强类型DataSet的Web服务都创建包装类型,整个解决方案很快会被许多无关紧要的类搞的一团糟。而 Spring.NET创建的代理则允许开发人员直接引用强类型DataSet,从而避免了这些问题。
顶部