使用Protocol Buffer打造spring的http請求分發

前言

對於Spring的IoC(控制反轉) 、DI(依賴注入)這兩個概念,使用過spring的都很熟悉,使用容器來控制相關的對象的生命週期和對象間的關係。擴散下思維,可以使用一個容器來存儲相關的http的controller類,根據http的請求參數來遍歷這個容器執行哪個類哪個方法。但是如何是http調用這個容器呢?spring還提供了事件監聽(事件監聽概念這裏不做說明)的實現,實現一個監聽,利用spring-mvc的手動註冊控制器來監聽相關的http請求,調用容器執行相關的類和方法。

目標

先看下spring web的普通的http請求

@RestController
public class HttpController {
    @GetMapping("/http/welcome")
    @ResponseBody
    public string getLeagueById() {
        return "Hello word!";
    }
}

而目標需要改造成這樣

@MessageController
public class HttpController {
    @MessageMapping(messageType = PBMessageType.HTTP_WELCOME_VALUE)
    public PBString adminLogin(PBWelcome req) {
        return PBWelcome.newBuilder().setValue("Hello word!").build();
    }
}

其實可以看出兩者是基本相同的,但下面這個自定義了MessageController和MessageMapping使其支持Protocol Buffer(PB)格式。爲什麼要使用PB數據呢?因爲它很適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式,用於將這些數據結構產生或解析數據流,並且性能比json/xml好。

核心設計

  • MessageController
/**
 * controller註解類,用來spring掃描生成handler消息處理
 * 跟 MessageMapping 一起使用
 *
 * @see MessageMapping
 * @see MessageDispatcher
 * @author fomin
 * @date 2019-12-07
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Component
public @interface MessageController {
}
  • MessageMapping
/**
 * 消息處理器,支持處理指定的消息,需要註解到public方法
 *
 * @author fomin
 * @date 2019-12-07
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@MetaAnnotation(MessageResolverImp.class)
public @interface MessageMapping {
    /**
     * 消息類型
     */
    int messageType();
}
  • MessageDispatcher
/**
 * 消息調度,掃描初始化MessageController註解類
 *
 * @author fomin
 * @date 2019-12-07
 */
public class MessageDispatcher implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(@NotNull ContextRefreshedEvent event) {
        if (initialized.compareAndSet(false, true)) {
            init(event.getApplicationContext());
            registerMapping(event.getApplicationContext());
        }
    }
    
    /**
     * 初始化 MessageDispatcher, 從 ApplicationContext 中掃描消息處理器並註冊
   */
    private void init(ApplicationContext context) {
        // 掃描標註MessageController的類
        for (Object bean : context.getBeansWithAnnotation(MessageController.class).values()) {
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
    
            for (Method method : targetClass.getMethods()) {
                MessageData data = resolveMessageData(method);
                if (data == null) continue;
    
                if (handlers.containsKey(data)) {
                    throw new IllegalStateException("Duplicate handler data: " + data);
                } else {
                    handlers.put(data, new MessageHandler(data, bean, objectStringifier));
                    log.debug("Registered handler: {}", data);
                }
            }
        }
    }
    
    /**
     * 註冊方法映射
     */
    private void registerMapping(ApplicationContext context) {
        try {
    
            RequestMappingInfo mapping = RequestMappingInfo.paths(path).build();
            Method method = this.getClass().getMethod("handleApi", HttpServletRequest.class, HttpServletResponse.class);
    
            RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
            handlerMapping.registerMapping(mapping, this, method);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }
    
    // 執行相關的api請求
    @SuppressWarnings("WeakerAccess")
    public void handleApi(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        try {
            MessageRequest request = messageFactory.parseMessageRequest(httpRequest);
            log.info("[API] Request: {}", request);
    
            ArgumentData argumentData = createArgumentData(httpRequest, httpResponse);
            MessageResponse response = dispatchMessage(request, argumentData);
            log.info("[API] Response: {}", response);
    
            byte[] rawData = response.toByteArray();
            byte[] sentData = gzip(httpRequest, httpResponse, rawData);
            log.info("[API] Total " + sentData.length + " bytes sent, raw " + rawData.length + " bytes.");
    
            httpResponse.setContentLength(sentData.length);
            httpResponse.setContentType(response.getContentType());
            httpResponse.getOutputStream().write(sentData);
            httpResponse.getOutputStream().flush();
    
        } catch (Throwable e) {
            unexpectedException(httpResponse, e);
        }
    }

}

上面只展示部分核心代碼,具體代碼文末會說明,該框架的設計思路其實就是利用註解和反射方式,在spring容器初始化的時候掃描相關注解類,並把該類的相關注解方法掃描保存到Map中,最後定義handlerApi把利用spring-webmvc註冊到registerMapping中。

時序圖

圖片

使用

需要先定義一個PB的請求類型enum文件,並使用pb工具生產響應PB的java文件。

//定義額pb文件
enum PBMessageType {
   DEFAULT_TYPE = 0;
}
// 生成的pb文件
/**
 * Protobuf enum {@code fgame.PBMessageType}
 */
public enum PBMessageType
    implements com.google.protobuf.ProtocolMessageEnum {
  /**
   * <code>DEFAULT_TYPE = 0;</code>
   */
  DEFAULT_TYPE(0),
}

創建一個api-server的model,存放相關的controller文件

@MessageController
public class HttpController {
    @MessageMapping(messageType = PBMessageType.DEFAULT_TYPE_VALUE)
    public PBString adminLogin(PBWelcome req) {
        return PBWelcome.newBuilder().setValue("Hello word!").build();
    }
}

這樣就可以打造一個支持pb的http請求了。github地址:https://github.com/fomin-zhu/Fg-message

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章