在上一篇文章中,我们解决了子系统之间的通讯问题,并跑起来了一个模型项目。这里我们要详细实现服务端程序。
我们在服务端运行Spring,利用Spring的IoC容器来管理所有的Service组件,然后根据接收到的JMS消息通过反射动态调用Service方法。
首先要先设计一下协议:
public class MessageProtocol implements Serializable { /** * 要调用的接口全限定名 */ private String interfaceName; /** * 要调用的方法名 */ private String methodName; /** * 方法参数列表 */ private List<Object> argList; // setter and setters ... }
由上协议类可以看出,我们是通过接口名、方法名和参数列表来定位服务端组件的。假如在服务端我们有一个AccountService
接口的实现类DefaultAccountService
:
public interface AccountService { void registerMember(MemberDto dto); void sayHello(String name); }
我们想要在客户端调用其registerMember()
方法,那么就应该这样封装MessageProtocol
类:
interfaceName = "com.fh.common.service.AccountService"; methodName = "registerMember"; argList = Arrays.asList(new MemberDto());
然后将这个对象以ObjectMessage
payload的格式通过JMS发送给服务端,服务端要做的工作是:
- 通过反射得到
interfaceName
对应的Class
对象 - 通过反射得到该
Class
对像中的methodName
方法 - 调用
method.invoke()
方法,将argList
传递进去 - 将返回值通过JMS返回给客户端
由于Java的反射机制中调用Class#getDeclaredMethods()
方法开销比较大,所以我们应该缓存得到的Method
对象。思想是,当客户端第一次请求该方法时,先调用getDeclaredMethods()
方法,将返回的结果保存到一个Map<String, List<Method> >
数据结构中,即:
/** * K: 接口的全限定名 * <p> V: 这个接口实现类的所有{@code Method}对象 */ public static Map<String, List<Method>> methodCache = new HashMap<>();
然后,当下一次客户端要请求这个组件的方法时,直接从methodCache
中查找而不是调用getDeclaredMethods()
方法,这样能极大提高运行效率。 消息处理方法如下:
public Object onMessage(MessageProtocol protocol) { System.out.println("message received"); // 取出请求信息 // 接口名 String interfaceName = protocol.getInterfaceName(); // 参数列表 List<Object> argList = protocol.getArgList(); // 方法名 String methodName = protocol.getMethodName(); Object returnVal = null; try { Class clazz = Class.forName(interfaceName); Object service = BeanUtil.getBeanByInterface(clazz); // 反射调用 returnVal = BeanUtil.invokeMethod(clazz, service, methodName, argList.toArray()); } catch (// ... ...) { // ... ... } return returnVal; }
BeanUtil
工具类的完整实现如下:
public class BeanUtil { private BeanUtil() { } /** * 通过反射调用业务逻辑方法 * @param clazz * @param methodName * @param <T> */ public static <T> Object invokeMethod(Class<T> clazz, Object component, String methodName, Object[] args) throws NoSuchMethodException, NoSuchClassException, InvocationFailedException { Method method = getMethodInCache(clazz.getName(), methodName); Object returnValue = null; try { returnValue = method.invoke(component, args); } catch (IllegalAccessException e) { throw new InvocationFailedException("invoke " + methodName + " failed"); } catch (InvocationTargetException e) { throw new InvocationFailedException("invoke " + methodName + " failed"); } return returnValue; } /** * 通过接口Class对象,从Spring容器中得到对应的实现组件 */ public static <T> T getBeanByInterface(Class<T> clazz) { T result = null; Map<String, T> map = ContextHolder.ctx.getBeansOfType(clazz); // 返回第一个bean Set<Map.Entry<String, T>> set = map.entrySet(); for (Map.Entry<String, T> entry : set) { result = entry.getValue(); break; } cacheMethod(clazz); return result; } /** * 如果是第一次调用,缓存Class的Method对象 * @param clazz * @param <T> */ private static <T> void cacheMethod(Class<T> clazz) { if (false == isAlreadyCached(clazz.getName())) { Method[] methods = clazz.getDeclaredMethods(); ContextHolder.methodCache.put(clazz.getName(), Arrays.asList(methods)); } } private static boolean isAlreadyCached(String className) { return ContextHolder.methodCache.containsKey(className); } /** * 从Cache中得到Method对象 */ private static Method getMethodInCache(String className, String methodName) throws NoSuchClassException, NoSuchMethodException { List<Method> methodList = ContextHolder.methodCache.get(className); if (null == methodList) { throw new NoSuchClassException(className); } Optional<Method> optMethod = methodList.stream().filter( (meth) -> { return meth.getName().equals(methodName); }).findAny(); if (false == optMethod.isPresent()) { throw new NoSuchMethodException(methodName); } return optMethod.get(); } }
至此,一个完整可用的分布式网站后端就能够运行起来了。当然这只是一个粗犷的实现,还有很多可以进行性能优化的地方。