在上一篇文章中,我们解决了子系统之间的通讯问题,并跑起来了一个模型项目。这里我们要详细实现服务端程序。
我们在服务端运行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();
}
}
至此,一个完整可用的分布式网站后端就能够运行起来了。当然这只是一个粗犷的实现,还有很多可以进行性能优化的地方。