JDK动态代理之个推源码解析

219 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

一 背景

项目要集成个推能力,于是查阅了个推的文档,个推有提供sdk,集成后只需要调用sdk中的api接口就可以了,还是很方便的,有趣的是个推的sdk项目代码也是开源的,于是出于好奇,开始分析了下他的源码结构。

二 开扒

  1. 集成

    引入maven依赖

    <dependency>
    
        <groupId>com.getui.push</groupId>
    
        <artifactId>restful-sdk</artifactId>
    
        <version>1.0.0.7</version>
    
    </dependency>
    
    
  2. demo

    public class TestCreatApi {
    
       public void test() {
    
       // 设置httpClient最大连接数,当并发较大时建议调大此参数。或者启动参数加上 -Dhttp.maxConnections=200
    
       System.setProperty("http.maxConnections", "200");
       GtApiConfiguration apiConfiguration = new GtApiConfiguration(); 
       //填写应用配置 apiConfiguration.setAppId("xxx"); apiConfiguration.setAppKey("xxx");
       apiConfiguration.setMasterSecret("xxx"); 
       // 接口调用前缀,请查看文档: 接口调用规范 -> 接口前缀, 可不填写appId apiConfiguration.setDomain("https://restapi.getui.com/v2/");
       // 实例化ApiHelper对象,用于创建接口对象
       ApiHelper apiHelper = ApiHelper.build(apiConfiguration); 
       // 创建对象,建议复用。目前有PushApi、StatisticApi、UserApi
       PushApi pushApi = apiHelper.creatApi(PushApi.class); 
       } }
    

3 解析

实际上 sdk就是对api的封装,我们可以看他们别人的封装 思考自己封装接口的暴露

1)对象

  • GtApiConfiguration 对象 保存个推配置
  • GtApiProxyFactory 管理创建代理工厂类
  • ApiHelper api助手类
  • DefaultApiClient
      1. 管理token
      1. 解析请求参数
      1. 发送HTTP请求
      1. 解析请求结果
  1. 调用流程 首先,构造GtApiConfiguration类,传入密钥等 其次,构建ApiHelper类,这个类相当一个核心类,里面包含了很多功能,代码其实比较简单

    
         private static final Map<String, ApiHelper> apiHelperCache = new HashMap<String, ApiHelper>(4);
    
         /**
         * @param configuration 配置信息类
         * 创建api助手
         * @return
         */
         public static ApiHelper build(GtApiConfiguration configuration) {
             return build(configuration, new DefaultJson());
         }
         /**
         * @param configuration 配置信息类
         * @return
         */
         public static ApiHelper build(GtApiConfiguration configuration, IJson json) {
         Assert.notNull(configuration, "configuration");
         // 校验配置
         configuration.check();
         // 生成缓存key
         String key = configuration.keyOfCache();
         // 从缓存中获取apiHelper 注意apiHelperCache是 不可变得常量 且 静态的
         ApiHelper apiHelper = apiHelperCache.get(key);
         if (apiHelper != null) {
             return apiHelper;
         }
         // 如果没有传入 就用个推自己的DefaultJson ,因为接口参数是IJson,所以给外部开发者留了口子
         if (json == null) {
             json = new DefaultJson();
         }
         // 锁在静态常量上,锁的是整个类对象
         synchronized (BUILD_LOCK) {
             // 所有缓存都是map中获取
             apiHelper = apiHelperCache.get(key);
             if (apiHelper != null) {
                 return apiHelper;
             }
             // 构建DefaultApiClient
             final DefaultApiClient defaultApiClient = DefaultApiClient.build(configuration, json);
             GtApiProxyFactory factory = GtApiProxyFactory.build(defaultApiClient);
             // 创建代理对象 代理AuthApi
             final AuthApi authApi = factory.createProxy(AuthApi.class);
             // 这里面执行了获取token的方法将token保存在了DefaultApiClient中
             defaultApiClient.setAuthApiAndAuth(authApi);
             // DefaultApiClient又在GtApiProxyFactory中,最终放到了ApiHelper中
             apiHelper = new ApiHelper(factory);
             apiHelperCache.put(key, apiHelper);
             return apiHelper;
         }
     }
     
     
     /**
     * 生成缓存的key
     *
     * @return 缓存key
     */
     public String keyOfCache() {
         check();
         return String.format("%s|%s|%s", this.getAppId(), this.getAppKey(), this.getMasterSecret());
     }
     
    
    
     private DefaultApiClient(GtApiConfiguration apiConfiguration, IJson json) {
         if (apiConfiguration == null) {
             throw new ApiException("apiConfiguration cannot be null.", true);
         }
         this.json = json;
         apiConfiguration.check();
         this.apiConfiguration = apiConfiguration;
         // 创建http连接
         this.httpManager = new HttpManager(apiConfiguration.getConnectTimeout(),
                 apiConfiguration.getSoTimeout(), apiConfiguration.getConnectionRequestTimeout(),
                 apiConfiguration.getMaxHttpTryTime(), apiConfiguration.getKeepAliveSeconds(),
                 apiConfiguration.getProxyConfig(), apiConfiguration.isTrustSSL());
    
         this.hostManager = new HostManager(apiConfiguration, this.httpManager);
         // 分析最稳定域名
         initAnalyseDomainThread();
         // 数据上报
         initReportDataThread();
         if (apiConfiguration.isOpenCheckHealthDataSwitch()
                 || apiConfiguration.isOpenAnalyseStableDomainSwitch()) {
             defaultGtInterceptor = new DefaultGtInterceptor(hostManager, reportDataQueue, apiConfiguration);
             // 创建拦截器 这里面其实也是开了一个口子 如果我们想增加拦截 可以在这里实现接口就行了
             this.interceptorList.add(defaultGtInterceptor);
         }
     }
    
    

3)分析

  • 校验配置
  • 从缓存中取配置
  • 处理具体的json实现
  • 赋值token url等配置
  • 创建代理类

这里面有比较有趣的是创建代理类,我们先看一下被代理的类,像不像feign的类?

   ```
    @GtApi
    public interface AuthApi {

        /**
         * 获取鉴权token接口
         *
         * @param authDTO
         * @return
         */
        @GtPost(uri = "/auth", needToken = false)
        ApiResult<TokenDTO> auth(@GtBodyParam AuthDTO authDTO);

        /**
         * 关闭鉴权token
         *
         * @param token
         * @return
         */
        @GtDelete(uri = "/auth")
        ApiResult<Void> close(@GtPathParam String token);

    }
    ```

创建jdk动态代理对象过程

    ```

    /**
     * 创建代理对象
     *
     * @param apiService
     * @param <T>
     * @return
     */
    public <T> T createProxy(Class<T> apiService) {
        return (T) Proxy.newProxyInstance(apiService.getClassLoader(), new Class[]{apiService}, new ApiProxyHandler());
    }

    class ApiProxyHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            try {
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                }
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
            final BaseParam baseParam = gtApiRegistry.get(method);
            ApiParam apiParam = new ApiParam(baseParam);
            // 解析参数 -> HTTP参数
            handleApiParam(method, args, apiParam);
            return defaultApiClient.execute(apiParam);
        }
    }
    ```

当执行authApi.auth()获取token时,实际上执行的是代理类方法,我们看下代理后执行的方法 1) 判断如果被代理类不是接口 那么久直接返回了,因为个推这边被代理类必须是接口,所以算是一种校验吧

    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
    } catch (Throwable t) {
        throw new RuntimeException(t);
    }

    

2)代理类方法中心

    private ApiResult<?> doExecute(GtApiProxyFactory.ApiParam apiParam, TokenDTO token) {
        Map<String, Object> header = new HashMap<String, Object>(4);
        if (apiParam.getNeedToken()) {
            if (token == null) {
                header.put("token", refreshTokenAndGet(token));
            } else {
                header.put("token", token.getToken());
            }
        }
        String body = null;
        if (apiParam.getBody() != null) {
            body = json.toJson(apiParam.getBody());
        }
        String result = null;
        String fullUrl = genFullUrl(apiParam.getUri(), apiParam.getPathParams(), apiParam.getQueryParams());
        try {
            // 处理header
            handleHeader(header);
            beforeExecute(apiParam, header, body);
            result = httpManager.syncHttps(fullUrl, apiParam.getMethod(), header, body, CONTENT_TYPE);
            postExecute(apiParam, header, body, result);
        } catch (ApiException e) {
            handleException(apiParam, header, body, e);
            return ApiResult.fail(e.getMessage(), e.getCode());
        } finally {
            afterDoExecute(apiParam, header, body, result);
        }
        if (result == null) {
            throw new ApiException(String.format("请求失败,返回值为空。url:%s, body: %s.", fullUrl, body));
        }
  
    }
    
    

逻辑上处理上比较简单就是获取被代理类的注解,参数,请求url等信息,然后通过http发送请求,其中再增加拦截器对请求时间进行记录等。

三 结语

个推的sdk使用非常典型的jdk动态代理,算是一次jdk动态代理的实战,对他的源码查看主要能够让我们以后封装接口时多一些思考,多一些选择,还有他对缓存的使用其实就是使用map,然后用静态变量修饰,也很简单,而且当我们要扩展时也可以自己写一个api类和个推api类格式保持一致就行了,非常容易拓展,总的来说,还是很有趣的。