阅读更多
最近在处理web services时用到apache xmlrpc,这里简单总结一下。
1.选用xml rpc
web services有很多种实现,这里选用xml rpc的是因为公司各个项目都支持xml rpc的调用,其他的web services实现并非完全支持;xml rpc与平台无关;xml rpc可以保证项目与项目之间的无侵入性。
2.spring bean
由于目前的业务组件都由spring container去管理、增强,所以理想的实现是在xml rpc的server端作为服务的beans都由spring container提供。但是 xml rpc server端的默认实现中,服务beans都由classloader去加载,代码如下:
//org.apache.xmlrpc.server.PropertyHandlerMapping
public void load(ClassLoader pClassLoader, Map pMap) throws XmlRpcException {
for (Iterator iter = pMap.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry) iter.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
Class c = newHandlerClass(pClassLoader, value);
registerPublicMethods(key, c);
}
}
//org.apache.xmlrpc.metadata.Util
/**
* Creates a new instance of pClass
.
*/
public static Object newInstance(Class pClass) throws XmlRpcException {
try {
return pClass.newInstance();
} catch (InstantiationException e) {
throw new XmlRpcException("Failed to instantiate class " + pClass.getName(), e);
} catch (IllegalAccessException e) {
throw new XmlRpcException("Illegal access when instantiating class " + pClass.getName(), e);
}
}
这样取得一个bean的实例并不能满足我们的要求,例如我们取的bean不能通过依赖注入得到相关属性;不能通过spring中设置的aop切面做动态的增强…………
有鉴于此,我们有必要自己实现一个xml rpc server的factorybean,并将其交由spring container管理。代码如下:
/**
*
xmlrpc server 工厂
*
* @author yangpeng 2008-8-1 上午09:18:34
*/
public class XmlRpcServletServerFactoryBean extends ApplicationObjectSupport
implements FactoryBean, InitializingBean {
private XmlRpcServletServer server;
/** XmlRpcServletServer的属性集合 */
private Map
serverProperties;
/** 是否在父BeanFactory中寻找xml rpc services */
private boolean detectServersInAncestorContexts = false;
private AbstractReflectiveHandlerMapping.AuthenticationHandler authenticationHandler;
private RequestProcessorFactoryFactory requestProcessorFactoryFactory;
private TypeConverterFactory typeConverterFactory;
protected Log log = LogFactory.getLog(XmlRpcServletServerFactoryBean.class);
public Object getObject() throws Exception {
return server;
}
public Class> getObjectType() {
return XmlRpcServletServer.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() throws Exception {
server = new XmlRpcServletServer();
initServerProperties();
server.setHandlerMapping(newXmlRpcHandlerMapping());
}
protected void initServerProperties() {
if (null != serverProperties) {
Set keys = serverProperties.keySet();
for (String key : keys) {
String value = serverProperties.get(key);
try {
if (!ReflectionUtil.setProperty(this, key, value)
&& !ReflectionUtil.setProperty(server, key, value)
&& !ReflectionUtil.setProperty(server.getConfig(),
key, value)) {
throw new BeanInitializationException("key:" + key
+ ";value:" + value + " is wrong property!");
}
}
catch (IllegalAccessException e) {
log.error(e);
throw new BeanInitializationException("key:" + key
+ ";value:" + value + " is wrong property!");
}
catch (InvocationTargetException e) {
log.error(e);
throw new BeanInitializationException("key:" + key
+ ";value:" + value + " is wrong property!");
}
}
}
}
protected XmlRpcHandlerMapping newXmlRpcHandlerMapping()
throws XmlRpcException {
SpringPropertyHandlerMapping mapping = new SpringPropertyHandlerMapping();
mapping.setAuthenticationHandler(authenticationHandler);
if (requestProcessorFactoryFactory != null) {
mapping
.setRequestProcessorFactoryFactory(requestProcessorFactoryFactory);
}
if (typeConverterFactory != null) {
mapping.setTypeConverterFactory(typeConverterFactory);
}
else {
mapping.setTypeConverterFactory(server.getTypeConverterFactory());
}
mapping.setVoidMethodEnabled(server.getConfig()
.isEnabledForExtensions());
mapping.addHandler(detectServersInAncestorContexts,
getApplicationContext());
return mapping;
}
//省略getter、setter
}
SpringPropertyHandlerMapping继承于PropertyHandlerMapping,重载addHandler方法,将ApplicationContext作为参数传入,addHandler中的实现类似于spring2.5 MVC中查找声明@controller的Controller类的实现。为了能获得这样的效果,我们先要定义两个Annotation:
/**
*
xml rpc service 注解
*
* @author yangpeng 2008-8-1 下午03:37:34
*/
@Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface XmlRpcService {
/**
* value为空则xml rpc service的名称默认使用sping bean的id。否则使用value
*/
String value() default "";
/**
* 是否使用方法注解
*
* @return false:默认服务组件中的所有公共方法都作为xml rpc的服务方法
* true:在服务组件
*/
boolean useMethodAnnotation() default false;
}
/**
*
标注此方法会作为xmlrpc server的响应方法
*
* @author yangpeng 2008-8-1 下午03:33:12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlRpcMethod {
String value() default "";
}
接着来看看SpringPropertyHandlerMapping,在这里它是核型:
/**
*
注册spring bean的HandlerMapping
*
* @author yangpeng 2008-8-1 上午10:42:21
*/
public class SpringPropertyHandlerMapping extends PropertyHandlerMapping {
public void addHandler(boolean detectServersInAncestorContexts,
final ApplicationContext context) throws XmlRpcException {
Assert.notNull(context, "context must not be null!");
String[] beanNames = (detectServersInAncestorContexts ? BeanFactoryUtils
.beanNamesForTypeIncludingAncestors(context, Object.class)
: context.getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
registerPublicMethods(beanName, context);
}
}
@SuppressWarnings( { "unchecked", "unchecked" })
protected void registerPublicMethods(String beanName,
final ApplicationContext context) throws XmlRpcException {
Class> serviceType = context.getType(beanName);
XmlRpcService service = AnnotationUtils.findAnnotation(serviceType,
XmlRpcService.class);
if (service == null
&& context instanceof ConfigurableApplicationContext
&& context.containsBeanDefinition(beanName)) {
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
BeanDefinition bd = cac.getBeanFactory().getMergedBeanDefinition(
beanName);
if (bd instanceof AbstractBeanDefinition) {
AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
if (abd.hasBeanClass()) {
Class> beanClass = abd.getBeanClass();
serviceType = beanClass;// 得到被代理对象
service = AnnotationUtils.findAnnotation(beanClass,
XmlRpcService.class);
}
}
}
if (service != null) {
Map map = new HashMap();
Method[] methods = serviceType.getMethods();
for (Method method : methods) {
if (!isHandlerMethod(service.useMethodAnnotation(), method)) {
continue;
}
String serviceName = StringUtils.isEmpty(service.value()) ? beanName
: service.value();
String name = serviceName + "." + method.getName();
Method[] mArray;
Method[] oldMArray = (Method[]) map.get(name);
if (oldMArray == null) {
mArray = new Method[] { method };
}
else {
mArray = new Method[oldMArray.length + 1];
System.arraycopy(oldMArray, 0, mArray, 0, oldMArray.length);
mArray[oldMArray.length] = method;
}
map.put(name, mArray);
}
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
String name = (String) entry.getKey();
Method[] mArray = (Method[]) entry.getValue();
handlerMap.put(name, newXmlRpcHandler(
context.getBean(beanName), mArray));
}
}
}
protected XmlRpcHandler newXmlRpcHandler(final Object bean,
final Method[] pMethods) throws XmlRpcException {
String[][] sig = getSignature(pMethods);
String help = getMethodHelp(bean.getClass(), pMethods);
if (sig == null || help == null) {
return new SpringXmlRpcHandler(this, getTypeConverterFactory(),
bean, pMethods);
}
return new SpringReflectiveXmlRpcMetaDataHandler(this,
getTypeConverterFactory(), bean, pMethods, sig, help);
}
protected boolean isHandlerMethod(boolean useMethodAnnotation, Method method) {
if (useMethodAnnotation) {
XmlRpcMethod xmlRpcMethod = AnnotationUtils.getAnnotation(method,
XmlRpcMethod.class);
if (null == xmlRpcMethod) {
return false;
}
}
return super.isHandlerMethod(method);
}
}
简单解释一下。SpringPropertyHandlerMapping遍历spring container中所有注册的beans,查找使用了@XmlRpcService注解的bean(无论其是否被代理)。对于这样的bean认为提供xml rpc服务,然后查找其声明的xml rpc服务方法。默认情况下,其所有的public、非static、非Object类方法的方法都会被当作xml rpc的服务方法(有点拗口)。如果在XmlRpcService注解中声明useMethodAnnotation为true,则其method除了要满足以上条件外,还要必须声明XmlRpcMethod注解才会被认为是xml rpc的服务方法。
SpringXmlRpcHandler与SpringReflectiveXmlRpcMetaDataHandler都非常简单,类似于默认实现的ReflectiveXmlRpcHandler、ReflectiveXmlRpcMetaDataHandler,这里就不多展开演示了。
3.例子
server1:
使用spring自带的petclinic,为业务实现一个xml rpc的faced:
package org.springframework.samples.petclinic.xmlrpcfaced;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.Clinic;
import org.springframework.samples.petclinic.PetType;
import xmlrpc.annotation.XmlRpcService;
@XmlRpcService
public class PetFaced {
@Autowired
Clinic clinic;
public String getPetTypesName() {
Collection types = clinic.getPetTypes();
String typesName = "";
for (PetType petType : types) {
typesName += petType.getName() + ",";
}
return typesName;
}
}
springcontext:
最后实现一个测试filter:
public class SpringXmlRpcFilter extends OncePerRequestFilter {
public static final String DEFAULT_XML_RPC_SERVIER_NAME = "xmlRpcServer";
private String servierName = DEFAULT_XML_RPC_SERVIER_NAME;
private XmlRpcServletServer server;
/*
* (non-Javadoc)
*
* @see org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
*/
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
server.execute(request, response);
// filterChain.doFilter(request, response);
}
/*
* (non-Javadoc)
*
* @see org.springframework.web.filter.GenericFilterBean#initFilterBean()
*/
@Override
protected void initFilterBean() throws ServletException {
WebApplicationContext wac = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());
server = (XmlRpcServletServer) wac.getBean(servierName,
XmlRpcServletServer.class);
}
public String getServierName() {
return servierName;
}
public void setServierName(String servierName) {
this.servierName = servierName;
}
}
server2:
使用被spring container代理过的service、具有事务属性的bean作为xml rpc的service
@XmlRpcService(useMethodAnnotation = true)
@Transactional
public class HibernateClinic implements Clinic {
@Autowired
private SessionFactory sessionFactory;
@XmlRpcMethod
@Transactional(readOnly = true)
public String getTypeName(int id) throws DataAccessException {
return String.valueOf(sessionFactory.getCurrentSession().createQuery(
"select name from PetType type where id = ?").setInteger(0, id)
.uniqueResult());
}
//其他方法省略
}
其他地方都一样,只是省略了faced。
个人总结
xml rpc的明显局限是对于复杂、用户自定义java bean的支持很弱,只支持一些基本的类型。
性能方面我没有做过测试,不能乱讲。
目前3.1的发布版本的客户端代码的默认实现有线程不安全的bug,这个问题在后来的2007年10月份的版本中才被修复,但是提供给大家下载的版本为8月份的版本,一个有问题的版本,大家注意了!
那个filter有个小问题不知道大家发现了没有,就是如果使用filterChain.doFilter(request, response);这句代码程序会报异常,具体原因我没仔细查,建议大家写个servlet做代替,文档中也是用servlet去做的。
后记
补充一个xml rpc client的包装类,使得客户端也可以通过spring容器管理,注入到需要的service中。详见附件。
- xmlrpc_with_spring_beans.rar (6.5 KB)
- 下载次数: 153
- xml_rpc_spring_client.rar (2 KB)
- 下载次数: 94