远程方法调用HttpInvoker之利用BeanFactoryPostProcessor减少服务端客户端xml配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/linfujian1999/article/details/86168952

场景
客户端http远程调用服务端的service方法,服务端将结果通过http返回给客户端。
思考
远程方法调用的话有多种方式,这里就不卖弄了,我自己也没怎么用过像rmi之类的。我下边直接拿spring框架集成的httpInvoker来实现远程方法调用。用过的童鞋们可能知道少不了服务端HttpInvokerServiceExporter和客户端HttpInvokerProxyFactoryBean的xml配置,先把配置文件贴出来:

1、服务端xml

	<bean name="/remote1" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
		<property name="service" ref="remote1Service"></property>
		<property name="serviceInterface" value="com.example.test.service.Remote1"></property>
	</bean>
	
	<bean name="/remote2" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
		<property name="service" ref="remote2Service"></property>
		<property name="serviceInterface" value="com.example.test.service.Remote2"></property>
	</bean>

大概是拦截/remote1和/remote2分别交给相应的exporter bean去处理,bean中有两个属性:service代表了具体调用实现类;serviceInterface代表了实现类的接口。注意这两个bean的class均为httpInvokerServiceExporter。

2、客户端xml

<bean id="remote1" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
		<property name="serviceUrl" value="http://localhost:8080/remote1"></property>
		<property name="serviceInterface" value="com.example.test.service.Remote1"></property>
	</bean>
	
	<bean id="remote2" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
		<property name="serviceUrl" value="http://localhost:8080/remote2"></property>
		<property name="serviceInterface" value="com.example.test.service.Remote2"></property>
	</bean>

客户端有两个对应的bean,class均为HttpInvokerProxyFactoryBean。bean均有两个属性:serviceInterface代表了要远程调用的服务类的接口;serviceUrl代表了远程调用服务类的url地址。一般说来,客户端只需要有业务调用的interface而不需要具体实现类。

上边的具体java实现我就不贴出来了,因为今天想要总结的是如何减少如上的xml配置,你可以设想,假如远程调用服务很多的场景(上千上万?),你要一一在xml中配置出来吗,有没有发现这些bean定义有很多重复的地方?能不能统一处理呢?

假如你觉得一一列举到xml麻烦,那你离成功不远了,仔细想想,这些bean无非就是一些bean defination, 而且组成有一定的规律可以利用(className一致,service属性ref指向的实现类上有@Service注解,serviceInterface属性的值为实现类的interface)…

BeanFactoryPostProcessor是beanFactory后处理器,主要是在bean实例化前修改beandefination的属性;当然也可以生成新的beanDefination并注册到beanFacotry中用于后续的实例化。

能够利用beanFactoryPostProcessor来生成xml中的这些bean定义呢?

答案是可以的:

1、首先替换服务端的xml

@Component
public class BeanDefinationGenerator implements BeanFactoryPostProcessor {
	
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		
		//服务端beanDefination生成
		String[] beanNames = beanFactory.getBeanNamesForAnnotation(Service.class);
		for(String beanName : beanNames) {
			BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
			String className = beanDefinition.getBeanClassName();
			try {
				Class<?> clazz = Class.forName(className);
				Class<?> [] interfaces = clazz.getInterfaces();
				if(interfaces.length == 1) {
					String interfaceName = interfaces[0].getName();
					BeanDefinition beanDefinition2 = null;
					beanDefinition2 = new RootBeanDefinition("org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter");
					beanDefinition2.getPropertyValues().addPropertyValue(new PropertyValue("service", beanDefinition));
					beanDefinition2.getPropertyValues().addPropertyValue(new PropertyValue("serviceInterface", interfaceName));
					String beanname = "/" + interfaceName.substring(interfaceName.lastIndexOf(".")+1).toLowerCase();
					DefaultListableBeanFactory beanFactory2 = (DefaultListableBeanFactory)beanFactory;
					beanFactory2.registerBeanDefinition(beanname, beanDefinition2);
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

2、替换客户端xml

@Component
public class BeanDefinationGenerator implements BeanFactoryPostProcessor {
	
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

		//客户端自定义生成beanDefination
		Map<String, String> prefix2Url = new HashMap<>();
		try {
			Class.forName("com.mysql.jdbc.Driver");
			Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bookstore?serverTimezone=GMT", "root", "root");
			Statement statement = connection.createStatement();
			ResultSet rSet = statement.executeQuery("select service_url.beanName as beanName,  service_url.url as url from service_url");
			
			while(rSet.next()) {
				String beanName = rSet.getString("beanName");
				String url = rSet.getString("url");
				prefix2Url.put(beanName, url);
			}
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		

		Reflections reflections = new Reflections("com.example.test.service", new SubTypesScanner(false));
		
		Set<String> classNames = reflections.getAllTypes();
		for(String className : classNames) {
			try {
				Class<?> clazz = Class.forName(className);
				if(clazz.isInterface()) {
					String interfaceName = className.substring(className.lastIndexOf(".")+1);
					for(String beanName : prefix2Url.keySet()) {
						if(interfaceName.contains(beanName)) {
							BeanDefinition beanDefinition = new RootBeanDefinition(HttpInvokerProxyFactoryBean.class.getName());
							beanDefinition.getPropertyValues().add("serviceUrl", prefix2Url.get(beanName) + "/" + interfaceName.toLowerCase());
							beanDefinition.getPropertyValues().add("serviceInterface", className);
							DefaultListableBeanFactory beanFactory2 = (DefaultListableBeanFactory)beanFactory;
							beanFactory2.registerBeanDefinition(interfaceName.toLowerCase(), beanDefinition);
						}
					}
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

如上我需要操作数据库获取具体远程调用的url地址,我数据库里配置的规则为前缀为Remote的接口调用的url均为http://localhost:8080/,地址随后的字符串为请求接口的name的小写格式。然后服务端的请求接受字符串也为接口的name小写格式。这样自定义一些匹配规则开发起来更加高效。当然我可以根据不同的前缀来将请求转发到不同的服务器ip上去调用。大家可能注意到我用了JDBC的方式来crud数据库,因为beanFactorypostProcessor的postProcessBeanFactory在执行时上下文中的bean还为实例化,当然不能通过autowired 所谓的Dao bean去操作数据库了。

完结。

猜你喜欢

转载自blog.csdn.net/linfujian1999/article/details/86168952
今日推荐