项目中用到了dubbo,默认当然都是同步调用的。某一天想到,既然是调用远程服务,那应该是可以异步调用的,这样说不定可以增加并发性,减少时间。于是上网查了一下,果然可以。配置远程服务为异步之后,像如下调用:
//调用后立即返回null Person person=demoServer2.getPerson("www", 13); System.err.println("立即返回的为null:"+person); //拿到调用的Future引用,当结果返回后,会被通知和设置到此Future。 Future<Person> pFuture = RpcContext.getContext().getFuture(); //如果Person已返回,直接拿到返回值,否则线程wait,等待Person返回后,线程会被notify唤醒。 person = pFuture.get(); System.out.println("返回的有值"+person); System.out.println(person);
见:https://blog.csdn.net/qh_java/article/details/53055421
但感觉这样使用也不太方便,每次调用完都得去拿一个future。然后想到了hibernate好像有一种代理模式,就是查询到的对象其实是一个代理对象,当调用这个代理对象的方法的时候,才会真正去执行sql查询到对象。
于是想到可以仿照这种思路,每次异步调用之后返回一个代理结果,当调用代理结果的方法时,才去执行future.get()拿到返回结果。代码如下:
1.先写一个远程服务的代理类。
package com.csair.csm.web.proxy; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.concurrent.Future; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import com.alibaba.dubbo.rpc.RpcContext; /** * 服务代理类,用于对异步服务提供代理 * <br>!!!使用代理服务调用的返回对象不为null,如果实际为null,则调用返回对象的任何方法都将返回null * @author abo */ public class ProxyService implements MethodInterceptor { /** * 服务者 */ private Object service; private ProxyService(Object service) { this.service = service; } /** * 创建一个代理服务 * @param service * @return */ @SuppressWarnings("unchecked") public static <S> S create(S service) { // 1.工具类 Enhancer en = new Enhancer(); // 2.设置父类 en.setSuperclass(service.getClass()); // 3.设置回调函数 en.setCallback(new ProxyService(service)); // 4.创建子类(代理对象) return (S) en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 执行服务者方法 Object result = method.invoke(service, args); // 获取返回 Future<Object> future = RpcContext.getContext().getFuture(); if (future == null) { // future为null,说明是同步调用,直接返回 return result; } // 返回类型 Class<?> returnClass = method.getReturnType(); if (Modifier.isFinal(returnClass.getModifiers())) { // final类直接返回 return future.get(); } // 返回代理结果 return ProxyServiceResult.create(future, returnClass); } }
这个类用来给远程的服务提供代理,调用这个代理服务的方法时,先是调用原本的异步服务的方法,然后向调用者返回一个代理结果。
不过这样子也有一些不足,就是如果异步服务的返回是null的话,因为在创建代理结果的当时并不知道实际结果,所以代理结果都不会为null。另外就是对于final类无法代理,只能立即等待并返回实际结果。要解决这个问题,目前想到的方法是可以给异步服务的结果都用一个Result类包装起来。
2.然后是一个服务结果的代理类。
package com.csair.csm.web.proxy; import java.lang.reflect.Method; import java.util.concurrent.Future; import org.slf4j.Logger; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import com.csair.csm.servicelog.LoggerUtils; /** * 异步服务返回值的代理 * <br>!!!代理对象不为null,如果实际为null,则调用其任何方法都将返回null * @author abo */ public class ProxyServiceResult implements MethodInterceptor { /** * 异步调用的返回 */ private Future<Object> resultFuture; /** * 日志 */ private static Logger logger = LoggerUtils.getCommonLogger(); private ProxyServiceResult(Future<Object> resultFuture) { this.resultFuture = resultFuture; } /** * 创建一个代理结果 * @param resultFuture * @param resultClass * @return */ public static Object create(Future<Object> resultFuture, Class<?> resultClass) { // 1.工具类 Enhancer en = new Enhancer(); // 2.设置父类 en.setSuperclass(resultClass); // 3.设置回调函数 en.setCallback(new ProxyServiceResult(resultFuture)); // 4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 从future获取返回值 Object result = resultFuture.get(); if (result == null) { // 结果为null,调用会出异常,直接返回null logger.warn("远程异步服务的实际返回结果为null,代理结果不为null。"); return null; } // 再去调用 return method.invoke(result, args); } }
这个用来给调用异步服务的返回结果提供代理,在调用代理结果的方法时,会从future中get到实际结果,然后去调用实际结果上的方法。
这样就大功告成了,使用时只需要这样子:
/** * 日志服务 */ @GuardedBy("itself") @Reference(timeout = 60000, async = true) // 异步服务 private LoggerService loggerService; /** * 初始化之后执行,替换异步服务为代理服务 */ @PostConstruct public void init() { this.loggerService = ProxyService.create(this.loggerService); }
先是配置远程服务为异步(async = true),然后在@PostConstruct中替换原来的服务为代理服务。然后就可以像使用同步服务那样正常的使用了。
因为对cglib代理也不熟悉,代理类只是仿照网上的例子改写的,如果有写得不好的地方,还希望有人批评指正。