Java は Http リクエストを実装するコードを記述せず、インターフェースとアノテーションのみを使用するため、Rest Proxy を見逃すことはできません

レストプロキシとは?

Rest Proxy コンポーネントは、長期にわたるシステム ドッキング作業で抽出した再利用性の高いコンポーネントであり、同僚からは「成果物」として歓迎されています。これは、これまでに使用した中で最もクールな http ドッキング アーティファクトかもしれません。マイクロサービスを行ったことのある学生はFeign Refモジュールを知っているはずです。このコンポーネントはそのようなものです。

具体的には次のとおりです。インターフェイスを作成し、いくつかのメソッドを定義してから、注釈を追加して完了です。、ビジネスでメソッドを直接呼び出すだけで、実装クラスを作成する必要はありません!

REST リクエスト プロキシ (HTTP インターフェイスに基づく)

このコンポーネントは SpringBoot2.7.7 に基づいて構築されており、完全な AutoConfiguraiton をサポートしています。構成を完了するには、最小限の構成のみが必要です。最新バージョンは で1.1.2、標準の REST アノテーションが追加されています。

機能一覧

  • 非知覚自動インジェクション、ドッキングを簡単に完了するためのインターフェイスと注釈のみが必要
  • 洗練された API により、学習の手間が省けます
  • 高パフォーマンスの実装、インテリジェントな結果処理バインディング
  • Retrofit よりも初期化構成が簡単で、Spring とのシームレスな統合
  • センスドッキングなしでファイルのアップロードとダウンロードをサポート
  • 自動アンラップ結果ラッパー クラス
  • パラメータとリクエストボディの自動変換
  • カスタム認証
  • 動的置換の実装 (建設中)

クイックスタート

ソース コード内のモジュールを参照して、テスト ケースを表示し、基本的な使用法を理解することrest-proxy-coreができます。また、プロジェクトを提供しており、その記述方法を直接参照して、関連するロジックを SpringBoot プロジェクトに移行できます。test
example

詳細については、オープン ソース ウェアハウス ( https://git.flyfish.group/flyfish-group/rest-proxy.git ) を確認してから、デモ用のブランチ
に切り替えてください。example

依存関係のインポート

Maven を使用して依存関係をインポートしてください

   <dependency>
        <groupId>group.flyfish</groupId>
        <artifactId>rest-proxy-core</artifactId>
        <version>1.1.2</version>
    </dependency>

1. リクエスト元を定義する

リクエスト ソース(ベース URL) を定義する非常に柔軟な方法を提供します

リクエストソースは、インターフェースが呼び出されるときにプレフィックスとして各インターフェースにスプライシングされます。これは、パスの再利用に便利です。

このコンポーネントは、グローバル リクエスト ソースマルチ リクエスト ソースアノテーション指定のリクエスト ソースの 3 つのメソッドをサポートしており、構成は非常にシンプルです。

1.1 グローバル リクエスト ソース

# application.yml
rest:
  client:
    # 通过配置文件指定全局请求源,所有未指定请求源的RestService都会以该请求开头
    base-url: https://mybase-source.domain

1.2 複数のリクエスト元

# application.yml
rest:
  client:
    urls:
      # 定义多个请求源,每个请求源有一个唯一的key,支持全限定url以及路径
      baidu: https://ug.baidu.com
      yapi: http://yapi.flyfish.group/api

1.3 アノテーションでリクエスト元を特定

Service サービス (クラス) レベルでの要求元と、メソッド (メソッド) レベルでの要求元指定をサポートします。

クラスのリクエスト元:

@RestService(baseUrl = "https://ug.baidu.com")
public interface TestService {
    
    
   
    @GET("/action")
    String getActionString();
}

メソッドリクエスト元

@RestService
public interface TestService {
    
    
    
    @GET(baseUrl = "https://ug.baidu.com", uri = "/action")
    String getActionString();
}

2. RestService リクエスト サービス クラスを定義する

@RestService実装を記述せずにインターフェイスをすばやく定義できるように、一連の HTTP アノテーションを提供しています。
参照用の基本的な例を次に示します。

/**
 * 使用@RestService,如果不指定任何参数,
 * 默认会取得全局请求源,也就是rest.client.baseUrl的值
 */
@RestService
public interface TestService {
    
    

    /**
     * 通过关键字string作为query,查询用户列表
     * @param keyword 关键字,会以?keyword={value}的方式传递
     * @return 结果
     */
    @GET("/users")
    List<User> getUsers(String keyword);

    /**
     * 保存用户
     * @param user 请求体,使用@RestBody标记为请求体后,参数会自动转换
     * @return 保存结果
     */
    @POST("/users")
    User saveUser(@RestBody User user);

    /**
     * 更新用户信息
     * @param id 用户id路径参数。使用@RestPathParam注解结合花括号{}来标记路径参数
     * @param user 用户数据
     * @return 结果
     */
    @PUT("/users/{id}")
    User updateUser(@RestPathParam("id") Long id, @RestBody User user);

    /**
     * 更新用户的名称
     * @param id 用户id路径参数
     * @param name 用户名称,使用 mergeBody选项,将除了指定注解类型的其他参数合并到body中,如{name: ''}
     * @return 结果
     */
    @PATCH(value = "/users/{id}", mergedBody = true)
    User updateUser(@RestPathParam("id") Long id, String name);

    /**
     * 删除用户
     * @param id 用户id
     * @return 结果
     */
    @DELETE("/users/{id}")
    User deleteUser(@RestPathParam("id") Long id);
}

リクエスト ソースを単独で使用する必要がある場合は、以下を参照してください。

# application.yml
rest:
  client:
    urls:
      baidu: https://api.baidu.com
      other: http://other.com
@RestService("baidu")
public interface BaiduService {
    
    
    
    @POST("/id-cards")
    String updateIdCard(@RestBody IdCardDto idCard);
}

さらに、ファイルのアップロードとダウンロードもサポートしています。参照してください。

@RestService(baseUrl = "http://localhost:8999", timeout = 500)
public interface TestRestService {
    
    

    /**
     * 上传一个预定义的文件,使用Multipart包装类,并传入?type=xxx以及一个字符串body:token=xxx
     * @param file 文件信息
     * @param type 文件类型名称
     * @param token 凭据
     * @return 结果
     */
    @POST("/files")
    FileEntity uploadPart(@RestPart Multipart file, String type, @RestPart("token") Long token);

    /**
     * 使用input stream上传一个文件,此外还支持byte[],可以用@RestPart.Filename指定文件名
     * @param file 文件
     * @param name 文件名
     * @return 结果
     */
    @POST("/files")
    FileEntity uploadAnno(@RestPart("fbl") InputStream file, @RestPart.Filename("fbl") String name);

    /**
     * 下载一个文件为byte[]
     * @return 下载结果
     */
    @GET("/files")
    byte[] downloadByte();
}

電話をかけるときは、次の手順のみが必要です

@Service
public class Test {
    
    
    
    @Resource 
    private TestRestService testRestService;
    
    /**
     * 测试入口
     */
    @Test
    public void test() {
    
    
        // 使用file对象包装
        File file = new File("/Users/wangyu/Desktop/2022年终述职报告.docx");
        FileEntity result = testRestService.uploadPart(new Multipart("file", file.getName(), file), "docx", 55L);
        // 使用input stream
        try (InputStream in = Files.newInputStream(file.toPath())) {
    
    
            FileEntity entity = testRestService.uploadAnno(in, file.getName());
            System.out.println(entity);
        } catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }
        // 下载
        testRestService.downloadByte();
    }
}

3. インターフェイス スキャンを有効にする

最後に、スプリング ブート スタートアップ クラスにアノテーションを追加し、それを直接使用できるようにします。

ここでは、スキャンする basePackages を手動で指定する必要があります. コンポーネントは、ユーザーの許可なしに他のパッケージをスキャンしません.

package group.flyfish.demo;

import group.flyfish.rest.annotation.EnableRestApiProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableRestApiProxy(basePackages = "group.flyfish.demo.service")
public class RestProxyDemoApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(RestProxyDemoApplication.class, args);
    }

}

4. ハッピードッキングを始めましょう!

この時点で、クイック スタート チュートリアルは完了です。

結果マップの処理とバインディング

インターフェイスを実装するとき、誰もが暗黙の習慣を持っています。つまり、次のようにインターフェイスの応答にラッパー クラスを追加します。

{
    
    
  "success": true,
  "code": 0,
  "message": "成功",
  "result": {
    
    
    "name": "名称",
    "code": "编码"
  }
}

しかし、Java コードを書くときは、このようには書きません。それ以来、これは関連するロジックをアンパックした結果です。

デフォルトでは、Rest Proxy は結果をアンパックしません。ただし、@AutoMapping注釈を追加して、自動アンラップをマークすることができます。

ここでは、アンパックのデフォルトの動作のためのデフォルトのデータ構造を提供します。特定の形式は、上記の例で定義されているとおりです。
呼び出したいシステムがインターフェース パッケージ形式で返されない場合は、対応するロジックを自分で記述する必要があります。インターフェイスを実装し、メソッドとメソッドをRestResultMapping正しく記述する必要がありますmapresolve

mapこのメソッドは、アンパック ロジックを実装するために使用されます。つまり、上記のフィールドなど、パッケージ化された結果から実際のデータを取得する方法resultです。
また、この方法で異常な結果を自分で処理しcode、積極的に投げることをお勧めしますRestClientException

convert@RestServiceこのメソッドは、定義した戻り値をラップして、method実際の型が確実に返されるようにするために使用されます。
たとえば、メソッド シグネチャは: でUser getUser()、ラッパー クラスは: . この時点で、提供するツール クラスメソッドでラップできるResult戻り値の型が必要です。詳細については、以下の例を参照してください。Result<User>TypeResolveUtils
wrap

以下は、システムのデフォルトの実装ロジックです。参照のみを目的としています。

/**
 * 默认缺省的结果映射
 *
 * @author wangyu
 */
@Slf4j
public class DefaultRestResultMapping implements RestResultMapping {
    
    
    
    /**
     * 模糊的结果映射
     *
     * @param body 结果
     * @return 映射后的结果
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T map(Object body) throws RestClientException {
    
    
        // 多一步类型检查,保证转换的结果类型正确
        if (body instanceof RestResult) {
    
    
            RestResult<?> result = (RestResult<?>) body;
            if (result.isSuccess()) {
    
    
                return (T) result.getResult();
            }
            log.error("【RestProxy】请求发生异常!状态码:{},时间:{},信息:{}", result.getCode(),
                    DateUtils.formatDate(new Date(result.getTimestamp()), "yyyy-MM-dd HH:mm:ss"), result.getMessage());
            throw new RestClientException(result.getMessage());
        }
        return (T) body;
    }

    /**
     * 解析返回类型
     *
     * @param resultType 返回类型
     * @return 结果
     */
    @Override
    public Type resolve(Type resultType) {
    
    
        return TypeResolveUtils.wrap(resultType, RestResult.class);
    }
}

上記の手順を完了すると、@AutoMapping注釈タグを使用して解凍して処理できます。
達成される最終的な効果は、次のように から までTestServiceですTestUnwrapService

@RestService
public interface TestService {
    
    
    
    RestResult<User> getUser();
}
@RestService
@AutoMapping(DefaultRestResultMapping.class)
public interface TestUnwrapService {
    
    

    User getUser();
}

残りのクライアントをカスタマイズする

コンポーネントをより適切に使用できるように、多くの豊富なカスタマイズ オプションを提供しています。

1. 設定ファイルのカスタマイズ

以下は、構成パラメーターの完全な説明です。

# application.yml
rest:
  client:
    # 总是信任ssl证书
    always-trust: true
    # 全局连接超时时间
    connection-timeout: 30s
    # 全局请求源
    base-url: http://22.11.33.22:5001
    # 多请求源配置,key: url
    urls:
      other: https://ug.baidu.com
      yapi: http://yapi.flyfish.group/api

2.構成クラスのカスタマイズ

また、プログラムを詳細にカスタマイズするための多くの構成クラスも提供しています。

2.1 カスタムフックの設定

RestPropertiesModifierインターフェースを実装し、SpringBean としてプログラムに登録することで、実行時に構成を変更できます
一般的な使用シナリオは、マルチリクエスト ソースが他の Bean、データベース、またはキャッシュから読み取られる可能性があるため、次のようにコードで設定する必要があるということです。

public class UrlMappingAutoConfigure {
    
    

    /**
     * 从WorkflowProperties这个bean读取url配置并放入
     * @param workflow 其他bean
     * @return 结果
     */
    @Bean
    public RestPropertiesModifier configureUrls(WorkflowProperties workflow) {
    
    
        return properties -> {
    
    
            // urls请求源map,可以直接操作
            Map<String, String> urls = properties.getUrls();
            String baseUrl = workflow.getEngine().getBaseUrl();
            String businessBaseUrl = workflow.getEngine().getBusinessBaseUrl();
            String controlBaseUrl = workflow.getEngine().getControlBaseUrl();
            // 配置基础路径集合
            FlowableUrlMapping.URLS.forEach((key, url) -> urls.put(key, baseUrl + url));
            FlowableUrlMapping.BUSINESS_URLS.forEach((key, url) -> urls.put(key, businessBaseUrl + url));
            FlowableUrlMapping.CONTROL_URLS.forEach((key, url) -> urls.put(key, controlBaseUrl + url));
        };
    }
}

2.2 構成インジェクションフック

プログラムが一部の Bean に残りのクライアントのパラメーター クラスを注入する必要がある場合はRestClientProperties、 @Resource を直接使用しないでください。
必ずPropertiesConfigurableフックを使用して完了してください。そうしないと、プロジェクト Bean の依存関係の問題が発生します。

フレームワーク内での使用例を次に示します。

package group.flyfish.rest.core.factory;

/**
 * 生产httpClient
 *
 * @author wangyu
 */
@Slf4j
public final class HttpClientFactoryBean implements FactoryBean<CloseableHttpClient>, PropertiesConfigurable {
    
    

    // 使用非公平锁
    private final ReentrantLock lock = new ReentrantLock();
    // 客户端实例,单例
    private volatile CloseableHttpClient client;
    // 配置,配置没进来就不初始化
    private RestClientProperties properties;
    
    // ...代码省略
    
    @Override
    public Class<?> getObjectType() {
    
    
        return CloseableHttpClient.class;
    }

    /**
     * 配置属性,完成初始化
     *
     * @param properties 属性
     */
    @Override
    public void configure(RestClientProperties properties) {
    
    
        this.properties = properties;
    }
}

2.3 インターフェース認証構成の要求

リクエスト認証はインターフェイス ドッキングの非常に重要な部分であり、引き続き柔軟なサポートを提供します

2.3.1 グローバルデフォルト認証設定

シングルトンRestAuthProviderインスタンスを直接宣言して、グローバル認証ロジックを指定できます。
複数の Bean を宣言する場合は、@Primaryどの特定の注入された Bean がどれであるかを伝える必要があります。
次のように:

/**
 * 使用@Component注解注册为bean即可被自动配置
 *
 * @author wangyu
 */
@Component
public class YapiAuthProvider implements RestAuthProvider {
    
    

    /**
     * 通过入侵client提供鉴权
     * yapi是使用query鉴权的,所以增加query即可
     *
     * @param builder rest客户端构建器
     */
    @Override
    public void provide(RestClientBuilder builder) {
    
    
        // 支持添加认证头的方式,在此处也可以调用其他rest服务获取接口
        // builder.addHeader("Authorization", "token")
        builder.addParam("token", token);
    }
}

2.3.2 クラスレベル指定認証

アノテーションでクラスを指定する限り、グローバル認証に加えて、クラス レベルの認証もサポートされます@RestService

/**
 * yapi服务,支持鉴权
 *
 * @author wangyu
 */
@RestService(value = "yapi", authProvider = YapiAuthProvider.class)
public interface YapiService {
    
    
    
}

2.4 クラスレベルおよびメソッドレベルでのクライアント構成

グローバル構成を指定するだけでなく、注釈を使用していくつかの要求構成パラメーターを指定することもできます。以下を参照してください。

package group.flyfish.rest.annotation;

import java.lang.annotation.*;

/**
 * 标记服务为rest proxy
 *
 * @author wangyu
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestService {
    
    

    /**
     * 通过标识符找到地址,不需要#开头
     *
     * @return 结果
     */
    String value() default "";

    /**
     * 服务级别的基本url,字典请使用#开头
     *
     * @return 结果
     */
    String baseUrl() default "";

    /**
     * 超时时间,-1则取默认,0表示无限
     *
     * @return 结果
     */
    int timeout() default -1;

    /**
     * 鉴权提供者类
     *
     * @return 具体实现了RestAuthProvider的类
     */
    Class<?> authProvider() default Object.class;
}

package group.flyfish.rest.annotation;

import group.flyfish.rest.enums.HttpMethod;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

/**
 * 启用Rest请求的方法会自动代理实现,
 * 并封装返回值
 *
 * @author wangyu
 */
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestApi {
    
    

    /**
     * uri的别名
     *
     * @return 结果
     */
    @AliasFor("uri")
    String value() default "";

    /**
     * 请求uri,使用次标注必须指定BaseUrl或者配置(现在还不支持)
     *
     * @return uri
     */
    @AliasFor("value")
    String uri() default "";

    /**
     * 请求方法
     *
     * @return 结果
     */
    HttpMethod method() default HttpMethod.GET;

    /**
     * 多个参数时使用合并的body
     *
     * @return 结果
     */
    boolean mergedBody() default false;

    /**
     * 可选指定的url,不会从默认地址请求
     *
     * @return url
     */
    String url() default "";

    /**
     * 基本路径,包含host
     *
     * @return baseUrl
     */
    String baseUrl() default "";

    /**
     * 是否带上认证token
     *
     * @return 结果
     */
    boolean credentials() default false;
}

スポンサーシップとチップ

私のプロジェクトが役に立ったと思ったら、スターを付けるか飲み物を買ってください☕️、ありがとうございます〜
同時に、すべての友人が P/R を提出し、このプロジェクトを共同で維持することを歓迎します。

おすすめ

転載: blog.csdn.net/wybaby168/article/details/129296785
おすすめ