场景:
客户端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去操作数据库了。
完结。