最近做系统重构,计划将多个系统的公共部分抽取出来作为一项公共服务,为以后项目维护和横向扩展奠定基础。
常用的服务发布方式有RMI / HTTPInvoker / Hessian / Burlap,关于这几类java远程服务的性能比较和优缺点大家可参考:http://www.cnblogs.com/jifeng/archive/2011/07/20/2111183.html ,本文着重讲解怎样使用自定的注解标签,结合SPRING将服务方便的发布出去。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>3.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.1.1.RELEASE</version> </dependency> <dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.38</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-webapp</artifactId> <version>8.1.3.v20120416</version> </dependency>
package org.springframework.remoting; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.stereotype.Component; @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface RemoteService { ServiceType serviceType() default ServiceType.HTTP; Class<?> serviceInterface(); }
RemoteService辅助标签RmiServiceProperty,在发布RMI服务时,用来指定RMI服务发布的端口。
package org.springframework.remoting; import org.springframework.stereotype.Component; import java.lang.annotation.*; import java.rmi.registry.Registry; /** * Created by Administrator on 2014/12/8. */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface RmiServiceProperty { int registryPort() default Registry.REGISTRY_PORT; }
ServiceType,指定发布服务的类型
package org.springframework.remoting; public enum ServiceType { HTTP, BURLAP, HESSIAN, RMI }
public class ServiceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements PriorityOrdered { private int order = Ordered.LOWEST_PRECEDENCE - 1; @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { RemoteService service = AnnotationUtils.findAnnotation(bean.getClass(), RemoteService.class); Object resultBean = bean; if (null != service) { if (ServiceType.HTTP == service.serviceType()) { if(!beanName.startsWith("/")){ throw new FatalBeanException("Exception initializing HttpInvokerService for "+beanName+",beanName should bean start with \"/\"."); } HttpInvokerServiceExporter httpInvokerServiceExporter = new HttpInvokerServiceExporter(); httpInvokerServiceExporter.setServiceInterface(service.serviceInterface()); httpInvokerServiceExporter.setService(bean); httpInvokerServiceExporter.afterPropertiesSet(); resultBean = httpInvokerServiceExporter; } else if (ServiceType.HESSIAN == service.serviceType()) { if(!beanName.startsWith("/")){ throw new FatalBeanException("Exception initializing HessianService for "+beanName+",beanName should bean start with \"/\"."); } HessianServiceExporter hessianServiceExporter = new HessianServiceExporter(); hessianServiceExporter.setServiceInterface(service.serviceInterface()); hessianServiceExporter.setService(bean); hessianServiceExporter.afterPropertiesSet(); resultBean = hessianServiceExporter; } else if (ServiceType.BURLAP == service.serviceType()) { if(!beanName.startsWith("/")){ throw new FatalBeanException("Exception initializing BurlapService for "+beanName+",beanName should bean start with \"/\"."); } BurlapServiceExporter burlapServiceExporter = new BurlapServiceExporter(); burlapServiceExporter.setServiceInterface(service.serviceInterface()); burlapServiceExporter.setService(bean); burlapServiceExporter.afterPropertiesSet(); resultBean = burlapServiceExporter; } else if (ServiceType.RMI == service.serviceType()) { RmiServiceExporter rmiServiceExporter = new RmiServiceExporter(); rmiServiceExporter.setServiceInterface(service.serviceInterface()); rmiServiceExporter.setService(bean); RmiServiceProperty rmiServiceProperty = bean.getClass().getAnnotation(RmiServiceProperty.class); if(rmiServiceProperty!=null){ rmiServiceExporter.setRegistryPort(rmiServiceProperty.registryPort()); } String serviceName = beanName; if(serviceName.startsWith("/")){ serviceName = serviceName.substring(1); } rmiServiceExporter.setServiceName(serviceName); try { rmiServiceExporter.afterPropertiesSet(); } catch (RemoteException remoteException) { throw new FatalBeanException("Exception initializing RmiServiceExporter", remoteException); } resultBean = rmiServiceExporter; } } return resultBean; } /****** 参考附件 ******/ }
请注意package和Class Name必须一致
package org.springframework.context.annotation; public class AnnotationConfigUtils { /****** 参考附件 ******/ /** * Register all relevant annotation post processors in the given registry. * @param registry the registry to operate on * @param source the configuration source element (already extracted) * that this registration was triggered from. May be <code>null</code>. * @return a Set of BeanDefinitionHolders, containing all bean definitions * that have actually been registered by this call */ public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, Object source) { Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4); if (!registry.containsBeanDefinition(SERVICE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ServiceAnnotationBeanPostProcessor.class); def.setSource(source); def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); beanDefs.add(registerPostProcessor(registry, def, SERVICE_ANNOTATION_PROCESSOR_BEAN_NAME)); } /******* 参考附件 *******/ return beanDefs; } /******* 参考附件 *******/ }
public interface HttpDateService { public Date getDate(); }
@RemoteService(serviceInterface = HttpDateService.class, serviceType = ServiceType.HTTP) public class HttpDateServiceImpl implements HttpDateService { @Override public Date getDate() { return new Date(); } }SPRING基础配置
<?xml version="1.0" encoding="ISO-8859-1"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <context:annotation-config /> <!-- id的内容对应下面服务地址 --> <bean id="/remote/HttpDateService.service" class="me.bbvip.springremoting.http.impl.HttpDateServiceImpl"/> </beans>
其他类型的服务发布请参考附件。
本次测试使用Junit4结合Spring容器测试服务。
Client Spring配置
<?xml version="1.0" encoding="ISO-8859-1"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!--httpInvoker client,serviceUrl对应上面HttpDateServiceImpl的ID --> <bean id="httpDateService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <property name="serviceUrl" value="http://localhost:8080/remoting/remote/HttpDateService.service" /> <property name="serviceInterface" value="me.bbvip.springremoting.http.HttpDateService" /> </bean> </beans>服务测试基础类,使用注解(RunWith/ ContextConfiguration)初始化Spring容器,测试具体的逻辑前,先将项目发布到Jetty容器中。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations ={"classpath:DateServiceTest-context.xml"}) public class ServerRunner { private static Server server; @BeforeClass public static void startWebapp() throws Exception { server = new Server(); Connector connector = new SelectChannelConnector(); connector.setPort(8080); server.addConnector(connector); WebAppContext webAppContext = new WebAppContext(); webAppContext.setContextPath("/remoting"); webAppContext.setWar("src/main/webapp"); server.setHandler(webAppContext); server.start(); System.out.println("syetem start sucess."); } @AfterClass public static void stopWebapp() throws Exception { server.stop(); } }
服务接口测试
public class HttpDateServiceTest extends ServerRunner { @Resource(name = "httpDateService") private HttpDateService httpDateService; @Test public void getGetDate() { System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(httpDateService.getDate())); } }
测试结果:
syetem start sucess. 2014-12-09 10:43:34
源码地址:https://git.oschina.net/damivip/spring-remoting-annotation.git