一款超级好用的HTTP客户端工具(lucky-httpclient 2.0.0.FINAL)

文章目录

lucky-httpclient

🍀 简介


lucky-httpclient是一个简单易用的HTTP客户端工具,提供了编程式注解式两种编码方式,支持异步调用响应结果选择,并提供了丰富的扩展机制,开发者可以根据自己的需求来定制和扩展。
另外lucky-httpclient还支持与SpringBoot整合开发,如果需要与SpringBoot整合需要导入lucky-httpclient-spring-boot-starter模块

源码:
lucky-httpclient
lucky-httpclient-spring-boot-starter

🥕 单独使用

⚙️ 安装


🪶 Maven
在项目的pom.xmldependencies中加入以下内容:

    <dependency>
        <groupId>io.github.lucklike</groupId>
        <artifactId>lucky-httpclient</artifactId>
        <version>2.0.0.FINAL</version>
    </dependency>

🐘 Gradle

    implementation group: 'io.github.lucklike', name: 'lucky-httpclient', version: '2.0.0.FINAL'

📃 开发文档

💻 编程式开发


编程式开发中主要会涉及到以下几个组件:

  1. Request
    请求信息,用于封装http请求信息如:urlmethodheadersqueryformbodyfile等。

  2. Response
    响应信息,用于封装HTTP响应信息如:响应状态码、响应头、响应体等

  3. HttpExecutor
    HTTP请求执行器,用于发起请求和返回响应结果,内置以下了三种实现:

    • JdkHttpExecutor: 基于JdkHttpURLConnection实现。
    • HttpClientExecutor: 基于Apache HttpClient实现,使用该实现需要导入Apache HttpClient相关的依赖。
    • OkHttpExecutor: 基于OkHttp3实现,使用该实现需要导入OkHttp3相关的依赖。
  4. ResponseProcessor
    响应处理器,通过该接口可以获取原始响应流,做大文件下载时可以使用该接口进行流式处理

  5. SaveResultResponseProcessor
    ResponseProcessor接口的一个重要实现类,用于将原始响应数据转化为Response对象

开发流程如下:

  1. 创建一个用于执行http请求的执行器HttpExecutor
  2. 创建一个具体的请求对象Request
  3. 使用执行器的execute()方法执行请求,并得到一个响应Response
  4. 根据业务需求处理响应结果

👀 代码示例


1️⃣ 【 GET 获取百度首页】
    // 1.创建一个用于执行http请求的请求执行器
    HttpExecutor httpExecutor = new JdkHttpExecutor();

    // 2.创建一个GET请求
    Request req = Request.get("https://www.baidu.com");

    // 3.执行请求返回一个响应
    Response response = httpExecutor.execute(req);
    
    // 4.将相应结果转化为UTF8编码的String类型并打印
    System.out.println(response.getStringResult(StandardCharsets.UTF_8));
2️⃣ 【 POST 表单提交】
    // 1.创建一个用于执行http请求的请求执行器
    HttpExecutor httpExecutor = new JdkHttpExecutor();

    // 2.创建一个POST表单提交请求
    Request req = Request.post("http://127.0.0.1:8080/addUser")
        .addFormParameter("name", "Jack")
        .addFormParameter("sex", "男")
        .addFormParameter("age", "22")
        .addFormParameter("vip", true);

    // 3.执行请求返回一个响应
    Response response = httpExecutor.execute(req);
    System.out.println(response.getStringResult());
3️⃣ 【文件下载 – 内存byte模式
    // 图片地址
    String filePath = "https://ts1.cn.mm.bing.net/th/id/R-C.b49dbddffaa692d75988e0c5882dacca?rik=r6IIYs2muimY7A&riu=http%3a%2f%2fwww.quazero.com%2fuploads%2fallimg%2f140529%2f1-140529145A4.jpg&ehk=Co9XURYRCjJXUTzFG0Mw6lD7olzDKceEgv3slEC8kvQ%3d&risl=&pid=ImgRaw&r=0";
    HttpExecutor httpExecutor = new JdkHttpExecutor();
    Request req = Request.get(filePath);
    Response response = httpExecutor.execute(req);
    
    // 使用Response的getMultipartFile方法获取一个MultipartFile对象
    MultipartFile file = response.getMultipartFile();
    // 将图片保存在D盘
    file.copyToFolder("D:/");
4️⃣ 【大文件下载 – 流式下载
    // 系统镜像地址
    String fileUrl = "https://mirrors.sohu.com/centos/8/isos/x86_64/CentOS-8.5.2111-x86_64-dvd1.iso";
    HttpExecutor httpExecutor = new JdkHttpExecutor();
    Request req = Request.get(fileUrl);
    
    // 利用ResponseProcessor接口获取原始响应流后进行流式处理
    httpExecutor.execute(req, new ResponseProcessor() {
        @Override
        public void process(ResponseMetaData responseMeta) {
            try {
                String savePath = StringUtils.format("D:/{}", responseMeta.getDownloadFilename());
                OutputStream out = new BufferedOutputStream(Files.newOutputStream(Paths.get(savePath)));
                FileCopyUtils.copy(responseMeta.getInputStream(), out);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    });
5️⃣ 【 POST 文件上传】
    Request request = Request.post("http://127.0.0.1:8080/file/upload")
            // 添加本地文件(File)
            .addFiles("file", new File("D:/github-poject/luckliy_v4/LUCKY_V4_TEST/springboot-test/pom.xml"))
            // 添加InputStream
            .addInputStream("file2", "HELP.md", Files.newInputStream(Paths.get("D:/github-poject/luckliy_v4/LUCKY_V4_TEST/springboot-test/HELP.md")))
            // 添加Resource
            .addResources("file3", "classpath:application.properties", "https://ts1.cn.mm.bing.net/th/id/R-C.jpeg");

    HttpExecutor httpExecutor = new JdkHttpExecutor();
    httpExecutor.execute(request);
6️⃣ 【Restful请求】
    // 使用Map封装参数,也可以使用实体类来封装参数
    Map<String, Object> userMap = new HashMap<>();
    userMap.put("id", 123);
    userMap.put("name", "Test User");
    userMap.put("age", 22);
    userMap.put("email", "test@example.com");

    // 使用setJsonBody()方法设置JSON格式的请求体参数
    Request request = Request.put("http://127.0.0.1:8080/putUser")
                             .setJsonBody(userMap);

    HttpExecutor httpExecutor = new JdkHttpExecutor();
    Response response = httpExecutor.execute(request);
    
    // 当明确返回值为JSON格式的字符串时,可以使用jsonStrToEntity()方法将返回会结果直接反序列化为实体对象
    User entity = response.jsonStrToEntity(User.class);

    // 如果不确定返回值格式时可以使用getEntity()方法来反序列化结果,但是这种方法只支持JSON和XML格式的响应格式
    User entity2 = response.getEntity(User.class);
    System.out.println(entity);
7️⃣ 【配置代理】
    Request request = Request.post("url")
              // 代理无需账号密码,可以直接这样设置
             .setProxy("127.0.0.1", 9080)
              // 如果需要自定其他类型代理或更多的项目,可以这样设置
             .setProxy(new Proxy(Proxy.Type.HTTP,
                     new InetSocketAddress(host, port));
8️⃣ 【超时设置】
    Request request = Request.post("url")
            // 设置连接超时时间
            .setConnectTimeout(2000)
            // 设置读超时时间
            .setReadTimeout(2000)
            // 设置写超时时间
            .setWriterTimeout(2000);
9️⃣【请求头设置】
    Request request = Request.post("url")
            // 添加请求头
            .addHeader("token", "2eefefergrthytu6u565kjgjn--")
            // 设置请求头
            .setHeader("Content-Type", "application/json")
            // 简单验证(basicAuth方法)
            .setAuthorization("user", "password")
            // 添加一个Cookie信息
            .addCookie("c1", "fk-7075");
🔟 【使用HttpExecutor.xxxForXxx()方法简化调用流程】
    HttpExecutor httpExecutor = new JdkHttpExecutor();

    // 使用ForString()方法直接获取String类型结果
    String stringResult = httpExecutor.getForString("https://api.oioweb.cn/api/qq/info?qq={}", 2809110992L);

    // 使用ForEntity()方法直接将响应结果反序列化为实体
    Map map = httpExecutor.getForEntity("https://api.oioweb.cn/api/qq/info?qq={}", Map.class, 2809110992L);

    // 使用ForMultipartFile方法获取响应体中的文件
    MultipartFile multipartFile = httpExecutor.getForMultipartFile("https://ts1.cn.mm.bing.net/th/id/R-C.jpeg");
    

@ 注解开发


注解开发是在编程式开发的基础上做了一层封装,进一步的简化了开发。注解开发模式下我们只需要声明一个接口,然后使用特定的注解进行相关的描述即可,lucky-httpclient底层会使用动态代理机制帮我们生成代理对象,通过代理对象便可以完成所有的http请求。

🍋 使用HttpClientProxyObjectFactory生成Http接口的代理对象以及配置重要的请求参数

  • HttpClientProxyObjectFactor中重要的方法

    重要方法方法注释
    getCglibProxyObject(Class<T> interfaceClass)使用Cglib代理生成代理对象并返回
    getJdkProxyObject(Class<T> interfaceClass)使用Jdk代理生成代理对象并返回
    addExpressionParam(String name, Object value)[static]添加一个SpEL表达式参数,该参数可以在支持SpEL表达式的注解中直接使用例如: #{key}
    setSpELConverter(SpELConvert spELConverter)[static]设置一个用于解析SpEL表达式的解析器
    setObjectCreator(ObjectCreator objectCreator)[static]设置用于创建组件对象的对象创建器
    setAsyncExecutor(Executor executor)设置一个用于执行异步请求线程池
    setAsyncExecutorSupplier(Supplier<Executor> executorSupplier)设置一个用于执行异步请求线程池Supplier对象,用于延迟创建
    setHttpExecutor(HttpExecutor httpExecutor)设置用于执行HTTP请求请求执行器
    setExceptionHandle(HttpExceptionHandle exceptionHandle)设置用于处理异常的异常处理器
    addRequestInterceptors(RequestInterceptor... requestInterceptors)设置请求拦截器,在之请求执行之前会执行该接口实例的方法
    addResponseInterceptors(ResponseInterceptor... responseInterceptors)设置响应拦截器,在之响应返回之后执行该接口实例的方法
    setConnectionTimeout(int connectionTimeout)设置连接超时时间
    setReadTimeout(int readTimeout)设置读超时时间
    setWriteTimeout(int writeTimeout)设置写超时时间
    setHeaders(ConfigurationMap headerMap)设置公共的请求头参数
    setProxyClassHeaders(Class<?> proxyClass, Map<String, Object> proxyClassHeaders)为代理类proxyClass设置专用的公共请求头参数
    setPathParameters(ConfigurationMap pathMap)设置公共的路径参数
    setProxyClassPathParameters(Class<?> proxyClass, Map<String, Object> proxyClassPathParameters)为代理类proxyClass设置专用的公共路径参数
    setQueryParameters(ConfigurationMap queryMap)设置公共的URL参数
    setProxyClassQueryParameter(Class<?> proxyClass, Map<String, Object> proxyClassQueryParameters)为代理类proxyClass设置专用的公共URL参数
    setFormParameters(ConfigurationMap formMap)设置公共的表单参数
    setProxyClassFormParameter(Class<?> proxyClass, Map<String, Object> proxyClassFormParameters)为代理类proxyClass设置专用的公共表单参数
    // 设置SpEL表达式参数
    HttpClientProxyObjectFactory.addExpressionParam("baiduUrl", "http://www.baidu.com");
    HttpClientProxyObjectFactory.addExpressionParam("googleUrl", "http://www.google.com");
    // 设置SpEl表达式转换器
    HttpClientProxyObjectFactory.setSpELConverter(new SpELConvert());

    HttpClientProxyObjectFactory factory = new HttpClientProxyObjectFactory();
    // 设置连接超时时间
    factory.setConnectionTimeout(2000);
    // 设置读超时时间
    factory.setReadTimeout(2000);
    // 设置写超时时间
    factory.setWriteTimeout(2000);
    // 设置HTTP执行器为Okhttp请求执行器
    factory.setHttpExecutor(new OkHttpExecutor());
    // 设置用于异步执行HTTP任务的线程池
    factory.setAsyncExecutor(Executors.newFixedThreadPool(10));
    // 设置异常处理器
    factory.setExceptionHandle(new DefaultHttpExceptionHandle());
    // 添加请求处理器
    factory.addRequestAfterProcessors(new PrintLogProcessor());
    // 添加响应处理器
    factory.addResponseAfterProcessors(new PrintLogProcessor());

    // 添加公共请求头参数
    ConfigurationMap headers = new ConfigurationMap();
    headers.put("X-TOKEN", "dscsdvfdgerggegrherh");
    headers.put("X-SESSION-ID", "SDSDSDSDSDXSSX");
    factory.setHeaders(headers);

    // 设置百度API专用的请求头
    ConfigurationMap baiduHeaders = new ConfigurationMap();
    baiduHeaders.put("BAIDU-USER", "nnig656464");
    baiduHeaders.put("BAIDU-TEST", "test-vi");
    factory.setProxyClassHeaders(BaiduApi.class, baiduHeaders);

    // 基于JDK实现的代理对象
    BaiduApi jdkBaiduApi = factory.getJdkProxyObject(BaiduApi.class);

    // 基于Cglib实现的代理对象
    BaiduApi cglibBaiduApi = factory.getCglibProxyObject(BaiduApi.class);

🍓 使用@HttpRequest系注解将接口方法标记为HTTP请求方法(支持SpEL表达式)


@HttpRequest系注解有:

注解请求方法
@Get GET请求
@PostPOST请求
@DeleteDELETE请求
@PutPUT请求
@HeadHEAD请求
@PatchPATCH请求
@ConnectCONNECT请求
@OptionsOPTIONS请求
@TraceTRACE请求

SpEL表达式内置参数有:

root: {
    通过{@link HttpClientProxyObjectFactory#addExpressionParams(Map)}、{@link HttpClientProxyObjectFactory#addExpressionParam(String, Object)}方法设置的参数
    pn: 参数列表第n个参数
    an: 参数列表第n个参数
    argsn:参数列表第n个参数
    paramName: 参数名称为paramName的参数
}
$mc$:     当前方法上下文{@link MethodContext}
$cc$:     当前类上下文{@link ClassContext}
$class$:  当前执行的接口所在类{@link Class}
$method$: 当前执行的接口方法实例{@link Method}
import com.luckyframework.httpclient.proxy.annotations.Delete;
import com.luckyframework.httpclient.proxy.annotations.Get;
import com.luckyframework.httpclient.proxy.annotations.Post;

public interface JSXSApi {

    /*
        使用HttpClientProxyObjectFactory.addExpressionParam("baiduUrl", "https://www.baidu.com")方法设置了表达式参数后,
        便可以在SpEL表达式中使用配置的key直接拿到value
     */
    @Get("#{baiduUrl}")
    String baidu();

    /*
        获取百度首页,使用SpEL表达式内置参数写法    
     */
    // @Get("http://#{p0}/")
    // @Get("http://#{a0}/")
    // @Get("http://#{args0}/")
    // @Get("http://#{args0}/")
    // @Get("http://#{#$mc$.getArguments()[0]}/")
    @Get("http://#{url}/")
    String getBaiduPage(@NotHttpParam String url);

    // 删除ID为1的book
    @Delete("http://localhost:8080/book/delete/1")
    void deleteBook();

    // 新增一个book
    @Post("http://localhost:8080/book/insert")
    void addBook(Book book);
}

🍊 使用@DomainName注解提取域名(支持SpEL表达式)


开发中建议将同一个域名或者同一域名中某个特定的模块下的Http接口组织到同一个Java接口,这样便可以使用 @DomainName 注解来提取公共域名,方便统一管理。例如:上面的接口加上 @DomainName 注解之后便可以简化为如下代码:

SpEL表达式内置参数有:

root: {
    通过{@link HttpClientProxyObjectFactory#addExpressionParams(Map)}、{@link HttpClientProxyObjectFactory#addExpressionParam(String, Object)}方法设置的参数
    pn: 参数列表第n个参数
    an: 参数列表第n个参数
    argsn:参数列表第n个参数
    paramName: 参数名称为paramName的参数
}
$mc$:     当前方法上下文{@link MethodContext}
$cc$:     当前类上下文{@link ClassContext}
$class$:  当前执行的接口所在类{@link Class}
$method$: 当前执行的接口方法实例{@link Method}
package com.springboot.testdemo.springboottest.api;
import com.luckyframework.httpclient.proxy.annotations.Delete;
import com.luckyframework.httpclient.proxy.annotations.DomainName;
import com.luckyframework.httpclient.proxy.annotations.Get;
import com.luckyframework.httpclient.proxy.annotations.Post;

// 直接配置域名
@DomainName("http://localhost:8080/book/")

/*
    使用HttpClientProxyObjectFactory.addExpressionParam("JSXS", "http://localhost:8080/book/")方法设置了表达式参数后,
    便可以在SpEL表达式中使用配置的key直接拿到value
 */
@DomainName("#{JSXS}")

// 使用SpEL表达式获取域名
@DomainName("#{T(com.springboot.testdemo.springboottest.api.JSXSApi).getDomainName()}")
public interface JSXSApi {

    // 获取百度首页
    @Get("https://www.baidu.com")
    String baidu();

    // 删除ID为1的book
    @Delete("/delete/1")
    void deleteBook();

    // 新增一个book
    @Post("/insert")
    void addBook(Book book);
    
    static String getDomainName() {
        return "http://localhost:8080/book/";
    }
}

🍎 使用@DynamicParam系列注解动态的设置请求参数


注解对应请求参数对应Request方法
@Url动态设置URLsetUrlTemplate()
@QueryParam动态设置URL参数addQueryParameter()
@PathParam动态设置填充URL占位符的参数addPathParameter()
@URLEncoderQuery动态设置URL参数(自动UrlEncoder编码)addQueryParameter()
@URLEncoderPath动态设置填充URL占位符的参数(自动UrlEncoder编码)addPathParameter()
@FormParam动态设置表单参数addFormParameter()
@HeaderParam动态设置请求头参数addHeader()
@CookieParam动态设置设置Cookie信息addCookie()
@ResourceParam动态设置文件参数addResources()
@InputStreamParam动态设置文件参数(InputStream方式)addHttpFiles()
@BodyParam动态设置请求体参数setBody()
@JsonBody动态设置JSON格式的请求体参数(自动序列化为JSON字符串)setBody()
@XmlBody动态设置XML格式的请求体参数(自动序列化为XML字符串)setBody()

注:遇到下面这些特殊类型@DynamicParam注解不会生效:

  1. 当方法参数为ResponseProcessor类型时,当得到结果时会执行该参数的process方法
  2. 当方法参数为FileResourceMultipartFileHttpFile类型或者为这些类型的数组集合时,会使用addHttpFiles()进行参数设置。
  3. 当方法参数为BodyObject类型时,会使用setBody()方法进行参数设置。
  4. 当方法参数被@NotHttpParam注解标记时,表示此参数不是一个HTTP参数。

如果方法或者方法参数上没有标注任何@DynamicParam注解时,则默认使用addQueryParameter()方法进行参数设置。
@DynamicParam注解的具体用法:



import com.luckyframework.httpclient.proxy.annotations.Get;
import com.luckyframework.httpclient.proxy.annotations.QueryParam;
import com.luckyframework.httpclient.proxy.annotations.Url;
import com.luckyframework.io.MultipartFile;

@DomainName("http://localhost:8080/users")
public interface UserApi {

    /*
        没有任何注解时,默认方法参数为URL参数
        GET http://localhost:8080/users/getById?id=id_value 
     */
    @Get("/getById")
    User getUserById(Integer id);

    /*
         @QueryParam注解标注的参数将设置为Url参数(query参数)
         GET http://localhost:8080/users/getById?id=number
     */
    @Get("/getById")
    User getUserById2(@QueryParam("id") Integer number);

    /*
        @PathParam注解标注的参数将设置为填充Url占位符'{}'的参数
        GET http://localhost:8080/users/get/num_value
     */
    @Get("/get/{id}")
    User getUser(@PathParam("id") Integer num);

    /*
        @HeaderParam注解标注的参数将设置为Header参数
        @CookieParam注解标注的参数将设置为Cookie参数
        
        DELETE  http://localhost:8080/users/cookieHeader
        token: token_value
        Cookie: sessionId=sessionId_value; userId=userId_value
     */
    @Delete("cookieHeader")
    void cookieHeader(@HeaderParam("token") String c, @CookieParam("sessionId") String h, @CookieParam("userId") String u);

    /*
        @FormParam注解表示表单提交,lucky底层会将展开User的所有属性来形成表单内容
        POST http://localhost:8080/users/get/insertByForm
        Content-Type: application/x-www-form-urlencoded
        
        id=id_value&
        name=name_value&
        sex=sex_value&
        age=age_value&
        email=email_value
     */

    @Post("insertByForm")
    void insertUser(@FormParam User user);

    /*
        @JsonBody注解标注的参数会被序列化为JSON格式字符串
        POST http://localhost:8080/users/get/insertByJson
        Content-Type: application/json;
        
        {
            "id": "id_value",
            "name": "name_value",
            "age": "age_value",
            "sex": "sex_value",
            "email": "email_value",
        }    
     */
    @Post("insertByJson")
    void insertByJson(@JsonBody User user);

    /*
        文件上传,File、Resource、MultipartFile、HttpFile这四种类型或者这些类型的数组或集合会自动的当做文件参数来处理
        POST http://localhost:8080/users/fileUpload
        Content-Type: multipart/form-data; boundary=LuckyBoundary
        
        --LuckyBoundary
        Content-Disposition: form-data; name="msg"
        Content-Type: text/plain
        
        msg_value
        --LuckyBoundary
        Content-Disposition: form-data; name="files"; filename="test.jpg"
        Content-Type: image/jpeg
        
        < D:/test/test.jpg
        --LuckyBoundary
        Content-Disposition: form-data; name="files"; filename="data.json"
        Content-Type: application/json
        
        < D:/json/data.json
     */
    @Post("fileUpload")
    void fileUpload(File[] files, @FormParam String msg);

    /*
        使用@ResourceParam注解来实现文件上传,lucky底层会将@ResourceParam注解标注的方法参数转化为Resource[]后进行文件参数处理
        这里支持String、String[]、Collection<String>等类型的参数转换,字符串内容为Spring的资源路径表达式,请参考ResourceLoader.getResource()
        例如:
        
        1. file:D:/test.jpg
        2. classpath:static/text.txt
        3. http://localhost:8080/files/test.jpg
        ...
     */
    @Post("fileUpload")
    void fileUpload(@ResourceParam String[] files, @FormParam String msg);

    /*
        使用@Url注解来实现动态Url切换的功能
        eg: 
        imageUrl="http://localhost:8080/files/test.jpg"
        GET http://localhost:8080/files/test.jpg
        
        imageUrl="http://localhost:8084/user/application.yml"
        GET http://localhost:8084/user/application.yml
     */
    @Get
    MultipartFile getImage(@Url String imageUrl);
}

🍒 使用@StaticParam系列注解设置静态请求参数(支持SpEL表达式)


SpEL表达式内置参数有:

  $mc$:      当前方法上下文{@link MethodContext}
  $cc$:      当前类上下文{@link ClassContext}
  $class$:   当前执行的接口所在类{@link Class}
  $method$:  当前执行的接口方法实例{@link Method}
  $ann$:     当前{@link StaticParam @StaticParam}注解实例
  pn:        参数列表第n个参数
  an:        参数列表第n个参数
  argsn:     参数列表第n个参数
  paramName: 参数名称为paramName的参数
注解对应请求参数示例支持SpEL表达式
@BasicAuth简单身份认证注解@BasicAuth(username = "admin", password = "#{password}")
@StaticHeader设置请求头参数@StaticHeader({"SESSION-ID=HUUYGBKJHNOIJJPO", "TOKEN=#{token}"})
@StaticQuery设置URL参数@StaticQuery({"appKey=#{appKey}", "version=v1.0.0"})
@StaticForm设置表单参数@StaticForm({"username=#{username}", "age=20", "sex=男"})
@StaticResource设置资源参数@StaticResource({"file1=#{file1Path}", "file2=classpath:statis/*.jpg", "file3=http://www.baidu.com/G-rc.jpg"})
@StaticPath设置路径参数@StaticPath({"api=#{api}", "fileName=test.jpg"})
@StaticCookie设置Cookie参数@StaticCookie({"sessionId=FE@GYGn56rnioIIHIH", "user-info=#{userInfo}"})
@Proxy设置代理@Proxy(ip="127.0.0.1", port=#{port})
@Timeout设置超时时间参数@Timeout(connectionTimeout = 2000, readTimeout = 2000, writeTimeoutExp=#{writeTimeout})

代码示例:

package com.springboot.testdemo.springboottest.api;

import com.luckyframework.httpclient.proxy.annotations.Delete;
import com.luckyframework.httpclient.proxy.annotations.Get;
import com.luckyframework.httpclient.proxy.annotations.Post;
import com.luckyframework.httpclient.proxy.annotations.PrintLog;
import com.luckyframework.httpclient.proxy.annotations.StaticCookie;
import com.luckyframework.httpclient.proxy.annotations.StaticForm;
import com.luckyframework.httpclient.proxy.annotations.StaticHeader;
import com.luckyframework.httpclient.proxy.annotations.StaticPath;
import com.luckyframework.httpclient.proxy.annotations.StaticQuery;
import com.luckyframework.httpclient.proxy.annotations.StaticResource;
import com.springboot.testdemo.springboottest.beans.User;

/**
 * 使用@StaticParam系列注解静态的设置请求参数
 */
@DomainName("http://localhost:8080/users")
public interface User2Api {
    
    /*
        使用@StaticQuery注解静态的设置URL参数
        GET http://localhost:8080/users/getById?id=666
     */
    @Get("/getById")
    @StaticQuery("id=666")
    User getUserById();
  
    /*
        使用@StaticPath注解静态的设置URL占位符'{}'参数   
        GET http://localhost:8080/users/get/999
     */
    @Get("/get/{id}")
    @StaticPath("id=999")
    User getUser();
  
    /*
        使用@StaticForm注解静态的设置表单参数
        HttpClientProxyObjectFactory.addExpressionParam("user", "JackFu")
        
        POST http://localhost:8080/users/get/insertByForm
        Content-Type: application/x-www-form-urlencoded
        
        id=888&
        name=JackFu&
        sex=男&
        age=32&
        email=JackFu@qq.com    
     */
    @Post("insertByForm")
    @StaticForm({"id=888", "name=#{user}", "sex=男", "age=32", "email=#{user}@qq.com"})
    void insertUser();
  
    /*
        使用@StaticResource住额吉静态的设置资源参数
        POST http://localhost:8080/users/fileUpload
        Content-Type: multipart/form-data; boundary=LuckyBoundary
        
        --LuckyBoundary
        Content-Disposition: form-data; name="msg"
        Content-Type: text/plain
        
        @StaticForm + @StaticResource fileUpload
        --LuckyBoundary
        Content-Disposition: form-data; name="files"; filename="test.jpg"
        Content-Type: text/plain
        
        < D:/test/application.properties
        --LuckyBoundary
        Content-Disposition: form-data; name="files"; filename="data.json"
        Content-Type: text/plain
        
        < D:/test/jndi.properties
     */
    @Post("fileUpload")
    @StaticForm("msg=@StaticForm + @StaticResource fileUpload")
    @StaticResource({"files=classpath*:*.properties"})
    void fileUpload();
  
  
    /*
        使用@StaticCookie注解设置静态Cookie参数
        使用@StaticHeader注解设置静态请求头参数
          
        DELETE  http://localhost:8080/users/cookieHeader
        token: TOKEN-FK-7075
        Cookie: userId=FK7075; sessionId=SESSION_ID-HUIHOIO23465VHJBHBNLKJP
     */
    @Delete("cookieHeader")
    @StaticCookie({"userId=FK7075", "sessionId=SESSION_ID-HUIHOIO23465VHJBHBNLKJP"})
    @StaticHeader("token=TOKEN-FK-7075")
    void cookieHeader();

    /*
        使用@Timeout注解设置超时时间
        使用@BasicAuth注解设置简单权限认证信息
        使用@Proxy注解设置代理服务器信息
        GET http://www.baidu.com/users/get/589, PROXY: HTTP @ /127.0.0.1:8080
        Authorization: Basic Rks3MDc1OlBBJCRXMFJE
        
     */
    @Get("http://www.baidu.com/users/get/589")
    @Timeout(connectionTimeout = 10000, readTimeout = 1000)
    @Proxy(ip = "127.0.0.1", port = "8080")
    @BasicAuth(username = "FK7075", password = "PA$$W0RD")
    User getUserByBasicAuth();

}

🍑 使用ResponseProcessor接口获取原始数据流


一般模式下lucky会将HTTP调用的结果以byte[]的形式保存在内存中,后续再做转换与返回,当遇到大文件下载或者返回结果很大时这种方案显然是不适用的,基于这个问题的解决方案就是ResponseProcessor,通过ResponseProcessor
接口可以获取到原始的数据输入流,便可以使用流式操作来避免内存被撑爆的问题。在注解开发模式下只需要在接口方法中定义好ResponseProcessor参数即可,lucky会在HTTP请求结束后自动找到参数列表中第一个ResponseProcessor
参数来进行回调。示例代码如下:

/**
 * 在注解开发模式下声明一个用于下载CentOS系统镜像的HTTP接口方法
 */
public interface LargeFileDownload {

  // 大文件下载场景,使用ResponseProcessor接口流式处理返回结果
  @Get("https://mirrors.sohu.com/centos/8/isos/x86_64/CentOS-8.5.2111-x86_64-dvd1.iso")
  void getSaveFile(ResponseProcessor processor);
}

// 生成代理对象并调用下载CentOS系统镜像的方法
public class Test {
  public static void main(String[] args) {
    HttpClientProxyObjectFactory factory = new HttpClientProxyObjectFactory();
    LargeFileDownload api = factory.getJdkProxyObject(LargeFileDownload.class);

    api.getSaveFile(rmd -> {
        try {
          String savePath = StringUtils.format("D:/test/files/{}", rmd.getDownloadFilename());
          OutputStream out = new BufferedOutputStream(Files.newOutputStream(Paths.get(savePath)));
          FileCopyUtils.copy(rmd.getInputStream(), out);
        }catch (IOException e) {
          throw new RuntimeException(e);
        }
    });
  }
}

🍉 异步请求的声明


  1. 对于void方法可以使用@Async注解将其标记为一个异步方法。在接口上使用@Async注解,则接口中所有的void方法都讲会使用异步方式来调用
  2. 对于非void方法,如果需要异步返回则只需要将返回值用Future包裹即可,lucky会自动识别类型并发起异步调用。
import com.luckyframework.httpclient.proxy.annotations.Async;

// 在接口上使用@Async注解,则接口中所有的void方法都讲会使用异步方式来调用
@Async
@DomainName("#{userModel}")
public interface UserApi {
    
  /*
      对于返回值为Future<?>类型的接口方法,lucky会默认采用异步调用的方式来进行请求
   */
  @Get("/get/{id}")
  Future<User> getUser(@PathParam Integer id);

  /*
      异步添加用户
      对于void方法,如果想使用异步调用,则必须使用@Async标注
   */
  @Async
  @Put("insertByJson")
  void insertByJson(@JsonBody User user);

  /*
      异步文件上传
      对于void方法,如果想使用异步调用,则必须使用@Async标注
   */
  @Async
  @Post("fileUpload")
  void fileUpload(File[] files, @FormParam String msg);

  /*
      异步文件下载
      大文件下载场景,可以使用@Async注解 + ResponseProcessor接口的方式进行异步流式处理
   */
  @Async
  @Get("https://mirrors.sohu.com/centos/8/isos/x86_64/CentOS-8.5.2111-x86_64-dvd1.iso")
  void largeFileDownload(ResponseProcessor processor);

}

🍇 使用@ResponseConvert系列注解对响应结果进行转换


注:如果接口上配置了@ResponseConvert系列注解,那么注解中配置的转化器会对接口中所有的HTTP方法生效,如果某个HTTP方法并不想使用接口上配置的转换器逻辑时便可以使用@ConvertProhibition
注解来禁止

1️⃣ @ResultSelect注解

可以使用@ResultSelect注解的value属性来对响应结果进行选取,如果取不到值但又想赋予默认值,则可以使用defaultValue来设置默认值,该属性支持SpEL表达式

SpEL表达式内置参数有:

 root:             当前响应的响应体部分{@link Response#getEntity(Class)}
 $req$:            当前响应对应的请求信息{@link Request}
 $resp$:           当前响应信息{@link Response}
 $status$:         当前响应的状态码{@link Integer}
 $contentType$:    当前响应的Content-Type{@link Integer}
 $contentLength$:  当前响应的Content-Length{@link Integer}
 $headers$:        当前响应头信息{@link HttpHeaderManager#getHeaderMap()}
 $mc$:             当前方法上下文{@link MethodContext}
 $cc$:             当前类上下文{@link ClassContext}
 $class$:          当前执行的接口所在类{@link Class}
 $method$:         当前执行的接口方法实例{@link Method}
 $ann$:            当前{@link ResultSelect @ResultSelect}注解实例
 pn:               参数列表第n个参数
 an:               参数列表第n个参数
 argsn:            参数列表第n个参数
 paramName:        参数名称为paramName的参数

具体用法为:

    value:
    取值表达式@resp.${key},请参照{@link ConfigurationMap#getProperty(String)}的用法,
    其中'@resp'为固定的前缀,表示整合响应结果。
    从数组中取值使用下标:@resp.array[0].user或@resp[1].user.password
    从对象中取值:@resp.object.user或@resp.user.password
    
    defaultValue:
    配置默认值,支持SpEL表达式,当value取值表达式中指定的值不存在时,便会使用该默认值返回
    
    exMsg:
    异常信息,当从条件表达式中无法获取值时又没有设置默认值时
    配置了该属性则会抛出携带该异常信息的异常,
    这里允许使用SpEL表达式来生成一个默认值,SpEL表达式部分需要写在#{}中

以高德的天气API为例:


@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
   
    @Get("/v3/weather/weatherInfo?city=荆州")
    Object queryWeather();
}
  • 不使用@ResultSelect注解时的返回结构为:
{
    "status": "1",
    "count": "2",
    "info": "OK",
    "infocode": "10000",
    "lives": [
        {
            "province": "湖北",
            "city": "荆州区",
            "adcode": "421003",
            "weather": "阴",
            "temperature": "23",
            "winddirection": "北",
            "windpower": "≤3",
            "humidity": "87",
            "reporttime": "2023-09-11 23:31:32",
            "temperature_float": "23.0",
            "humidity_float": "87.0"
        },
        {
            "province": "湖北",
            "city": "荆州市",
            "adcode": "421000",
            "weather": "中雨",
            "temperature": "23",
            "winddirection": "西",
            "windpower": "≤3",
            "humidity": "87",
            "reporttime": "2023-09-11 23:31:31",
            "temperature_float": "23.0",
            "humidity_float": "87.0"
        }
    ]
}
  • 如果只需要获取lives数组部分的数据,只需要在原来的接口方法上加上@ResultSelect("@resp.lives")即可:
@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
    @ResultSelect(key="@resp.lives", defaultValue="#{new java.util.ArrayList()}")
    @Get("/v3/weather/weatherInfo?city=荆州")
    Object queryWeather();
}

此时的返回结果为:

[
    {
        "province": "湖北",
        "city": "荆州区",
        "adcode": "421003",
        "weather": "阴",
        "temperature": "23",
        "winddirection": "北",
        "windpower": "≤3",
        "humidity": "87",
        "reporttime": "2023-09-11 23:31:32",
        "temperature_float": "23.0",
        "humidity_float": "87.0"
    },
    {
        "province": "湖北",
        "city": "荆州市",
        "adcode": "421000",
        "weather": "中雨",
        "temperature": "23",
        "winddirection": "西",
        "windpower": "≤3",
        "humidity": "87",
        "reporttime": "2023-09-11 23:31:31",
        "temperature_float": "23.0",
        "humidity_float": "87.0"
    }
]

同理,如果只需要lives数组的第一个元素则加上@ResultSelect("@resp.lives[0]")

2️⃣ @SpElSelect注解

@SpElSelect注解与@ResultSelect注解的用法类似,不同的是@SpElSelect注解取值使用的也是SpEL表达式,该注解的功能更加强大,支持SpEL中的所有取值功能:

  1. Elvis运算符:x?:y -> x != null ? x : y
  2. 安全导航操作符(避免空指针异常)object?.field
  3. 集合选择器语法,对集合或Map进行过滤 [collection | array | map].?[selectorExpression]
  4. 集合投影语法,对集合或Map的元素进行操作,生成一个新集合 [collection | array | map].![expression]

SpEL表达式内置参数有:

 root:             当前响应的响应体部分{@link Response#getEntity(Class)}
 $req$:            当前响应对应的请求信息{@link Request}
 $resp$:           当前响应信息{@link Response}
 $status$:         当前响应的状态码{@link Integer}
 $contentType$:    当前响应的Content-Type{@link Integer}
 $contentLength$:  当前响应的Content-Length{@link Integer}
 $headers$:        当前响应头信息{@link HttpHeaderManager#getHeaderMap()}
 $mc$:             当前方法上下文{@link MethodContext}
 $cc$:             当前类上下文{@link ClassContext}
 $class$:          当前执行的接口所在类{@link Class}
 $method$:         当前执行的接口方法实例{@link Method}
 $ann$:            当前{@link ResultSelect @ResultSelect}注解实例
 pn:               参数列表第n个参数
 an:               参数列表第n个参数
 argsn:            参数列表第n个参数
 paramName:        参数名称为paramName的参数

1、SpEL表达式取值,完成与@ResultSelect("@resp.lives[0]")同样的功能的@SpElSelect写法为:

@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
    @SpElSelect(expression="#{lives[0]}", defaultValue="#{new java.util.ArrayList()}")
    @Get("/v3/weather/weatherInfo?city=荆州")
    Object queryWeather();
}

2.集合过滤,如果需要进一步筛选出lives数组中元素的adcode属性值为'421000'的那些元素,则可以这样写:

@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
    @SpElSelect("#{lives.?[adcode == '421000']}")
    @Get("/v3/weather/weatherInfo?city=荆州")
    Object queryWeather();
}

此时的返回结果为:

[
    {
        "province": "湖北",
        "city": "荆州市",
        "adcode": "421000",
        "weather": "晴",
        "temperature": "15",
        "winddirection": "西南",
        "windpower": "≤3",
        "humidity": "100",
        "reporttime": "2023-10-30 05:32:46",
        "temperature_float": "15.0",
        "humidity_float": "100.0"
    }
]

3、Map过滤,如果只需要取出lives数组中的第一个元素,而且只需要中的provincecityweather这三个属性其他属性都不需要,则可以这样写:

@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
    @SpElSelect("#{lives[0].?[{'province', 'city', 'weather'}.contains(key)]}")
    @Get("/v3/weather/weatherInfo?city=荆州")
    Object queryWeather();
}

此时的返回结果为:

{
    "province": "湖北",
    "city": "荆州区",
    "weather": "晴"
}

4.集合投影,如果期望将lives数组中的每个元素都进行转化,最后以{"地名":"地名Value","天气": "t天气Value"}的形式进行输出,则可以这样写:

@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
    @SpElSelect("#{lives.![{'地名': province + '-' + city, '天气': weather + ',' + winddirection + '风,气温' + temperature + '度。'}]}")
    @Get("/v3/weather/weatherInfo?city=荆州")
    Object queryWeather();
}

此时的返回结果为:

[
    {
        "地名": "湖北-荆州区",
        "天气": "晴,西风,气温15度。"
    },
    {
        "地名": "湖北-荆州市",
        "天气": "晴,西南风,气温15度。"
    }
]
3️⃣ @ConditionalSelection注解

@ConditionalSelection注解的用法类似于 Java 的 switch语句,提供了一种可以根据条件来选择对结果的提取方式的功能。

SpEL表达式内置参数有:

 root:             当前响应的响应体部分{@link Response#getEntity(Class)}
 $req$:            当前响应对应的请求信息{@link Request}
 $resp$:           当前响应信息{@link Response}
 $status$:         当前响应的状态码{@link Integer}
 $contentType$:    当前响应的Content-Type{@link Integer}
 $contentLength$:  当前响应的Content-Length{@link Integer}
 $headers$:        当前响应头信息{@link HttpHeaderManager#getHeaderMap()}
 $mc$:             当前方法上下文{@link MethodContext}
 $cc$:             当前类上下文{@link ClassContext}
 $class$:          当前执行的接口所在类{@link Class}
 $method$:         当前执行的接口方法实例{@link Method}
 $ann$:            当前{@link ResultSelect @ResultSelect}注解实例
 pn:               参数列表第n个参数
 an:               参数列表第n个参数
 argsn:            参数列表第n个参数
 paramName:        参数名称为paramName的参数
@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
    @ConditionalSelection(
            defaultValue = "#{new HashMap()}",
            branch = {
              @Branch(assertion = "#{errmsg eq 'OK1'}", result = "#{data?.paths?.get(0)?.steps?.![instruction]}"),
              @Branch(assertion = "#{errmsg eq 'OK'}", result = "#{data?.paths?.get(0)?.steps?.![{'路线':instruction, '方向':action}]}")
            })
    @Get("/v4/direction/bicycling")
    Object bicycling(String origin, String destination);
}

🥝 使用@ExceptionHandle注解配置异常处理器


编写自己的异常处理类,将class设置给@ExceptionHandle注解的value属性上即可生效

  • 编写异常处理类
package com.springboot.testdemo.springboottest.api;

import com.luckyframework.common.Console;
import com.luckyframework.httpclient.core.Request;
import com.luckyframework.httpclient.proxy.HttpExceptionHandle;

public class MyExceptionHandle implements HttpExceptionHandle {

    @Override
    public void exceptionHandler(Request request, Exception exception) {
        Console.printlnMulberry("出异常啦老铁!-> {}", exception);
    }
}

  • 使用@ExceptionHandle注解标注HTTP方法并设置异常处理类
import com.luckyframework.httpclient.proxy.annotations.ExceptionHandle;

@DomainName("#{gaoDeApi}")
public interface GaoDeApi {
    
    /*
        出现异常时将会打印:
        出异常啦老铁!-> com.luckyframework.httpclient.exception.ResponseProcessException: A value for '@resp.lives.不存在的值' does not exist in the response body, and the default value configuration is not checked
     */
    @ExceptionHandle(MyExceptionHandle.class)
    @ResultSelect(key = "@resp.lives.不存在的值")
    @Get("/v3/weather/weatherInfo?city=荆州")
    Object queryWeather();
}

🍈 请求拦截器与响应拦截器


  • @RequestInterceptor中配置的请求处理器会在请求封装完成后和请求执行之前被调用,多个请求处理器的优先级由requestPriority属性值决定,数值越小优先级越高。
  • @ResponseInterceptor中配置的响应处理器会在请求执行完成得到响应结果之后被调用,多个请求处理器的优先级由responsePriority属性值决定,数值越小优先级越高。

框架中已经封装好的@RequestInterceptorHandle@ResponseInterceptorHandle注解有:

  1. @PrintRequestLog注解: 功能是在控制台中打印请求信息。
    在这里插入图片描述

  2. @PrintResponseLog注解: 功能是在控制台中打印响应信息。
    在这里插入图片描述

  3. @PrintLog注解: 功能是在控制台中打印请求信息和响应信息

  4. @RequestConditional注解: 功能是对请求实例进行条件判断,条件满足则继续执行,否则直接异常中断。

  5. @ResponseConditional注解:功能是对响应实例进行条件判断,条件满足则继续执行,否则直接异常中断。

  6. @HttpConditional注解:功能是对请求和响应实例进行条件判断,条件满足则继续执行,否则直接异常中断。

🐰 与SpringBoot整合开发

⚙️ 安装


🪶 Maven
在项目的pom.xmldependencies中加入以下内容:

    <dependency>
        <groupId>io.github.lucklike</groupId>
        <artifactId>lucky-httpclient-spring-boot-starter</artifactId>
        <version>1.0.0.FINAL</version>
    </dependency>

🐘 Gradle

    implementation group: 'io.github.lucklike', name: 'lucky-httpclient-spring-boot-starter', version: '1.0.0.FINAL'

🏄‍♂️ 开始使用


一、在SpingBoot的启动类上添加@EnableLuckyHttpClient注解来开启lucky-httpclient的注解开发功能

import io.github.lucklike.httpclient.EnableLuckyHttpClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/*
    添加@EnableLuckyHttpClient注解来开启lucky-httpclient的注解开发的功能    
 */
@EnableLuckyHttpClient
@SpringBootApplication
public class SpringbootTestApplication {


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

}


//----------------------------------------------------------------
//               @EnableLuckyHttpClient注解的介绍
//----------------------------------------------------------------

/**
 * lucky-httpclient自动导入注解
 *
 * @author fukang
 * @version 1.0.0
 * @date 2023/8/30 01:45
 */
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({LuckyHttpAutoConfiguration.class, LuckyHttpClientImportBeanDefinitionRegistrar.class})
public @interface EnableLuckyHttpClient {

    /**
     * 配置需要扫描的包,不做任何配置时默认扫描classpath下所有包中的class
     */
    @AliasFor("basePackages")
    String[] value() default {};

    /**
     * 配置需要扫描的包,不做任何配置时默认扫描classpath下所有包中的class
     */
    @AliasFor("value")
    String[] basePackages() default {};

    /**
     * 配置一些基本类,使用这些类的包名作为扫描包进行扫描
     */
    Class<?>[] basePackageClasses() default {};

    /**
     * Http接口代理对象是依赖{@link HttpClientProxyObjectFactory}来生成的,这里需要配置这个Bean的名称
     */
    String proxyFactoryName() default PROXY_FACTORY_BEAN_NAME;

    /**
     * 是否启用Cglib代码方法,默认使用Jdk的代码方式
     */
    boolean useCglibProxy() default false;
}


二、创建HTTP接口,并使用@HttpClient注解进行标注

(lucky底层会识别@HttpClient注解,并会为所有被注解的接口生成代理对象之后注入到Spring容器中,类似MybatisMapper接口)


/**
 * 高德开放平台API
 *
 * @author fukang
 * @version 1.0.0
 * @date 2023/8/30 05:32
 */
@PrintLog
@HttpClient("#{gaoDeApi}")
public interface GaoDeApi {

    /**
     * 高德开放平台API -- 天气查询
     * 
     * @param city 城市名称
     * @return 该城市的天气情况
     */
    @ResultSelect(key="@resp.lives", defaultValue = "#{new ArrayList()}")
    @Get("/{version}/weather/weatherInfo")
    Object queryWeather(String city);

    /**
     * 高德开放平台API -- 骑行路线查询
     * 
     * @param origin        出发地的高德坐标
     * @param destination   目的地的高德坐标
     * @return  出发地到目的地的骑行路线
     */
    @ResultSelect("@resp.data.paths")
    @Get("/v4/direction/bicycling")
    Object bicycling(String origin, String destination);

    /**
     * 高德开放平台API -- 将地址转化为高德坐标
     * 
     * @param address 地址
     * @return 该地址对应的高德坐标
     */
    @ResultSelect("@resp.geocodes[0].location")
    @Get("/{version}/geocode/geo")
    Future<String> getGeocode(String address);
    
}

三、在其他Spring组件中导入HTTP组件进行使用

package com.springboot.testdemo.springboottest.controller;

import com.luckyframework.async.EnhanceFuture;
import com.luckyframework.async.EnhanceFutureFactory;
import com.luckyframework.common.StopWatch;
import com.springboot.testdemo.springboottest.api.GaoDeApi;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author fukang
 * @version 1.0.0
 * @date 2023/8/30 05:46
 */
@AllArgsConstructor
@RestController
public class LuckyHttpClientController {
  private final EnhanceFutureFactory enhanceFutureFactory;
  private final GaoDeApi gaoDeApi;

  @GetMapping("weather")
  public Object call(String city) {
    StopWatch sw = new StopWatch();
    sw.start("proxy");
    Object result = gaoDeApi.queryWeather(city);
    sw.stopWatch();
    System.out.println(sw.prettyPrintMillis());
    return result;
  }

  @GetMapping("bicycling")
  public Object bicycling(String origin, String destination){
    EnhanceFuture<String> enhanceFuture = enhanceFutureFactory.create();
    enhanceFuture.addFuture(gaoDeApi.getGeocode(origin));
    enhanceFuture.addFuture( gaoDeApi.getGeocode(destination));
    return gaoDeApi.bicycling(enhanceFuture.getTaskResult(0), enhanceFuture.getTaskResult(1));
  }


}


🎱 SpEL功能增强

与SpringBoot整合后,原先所有支持SpEL表达式的地方现在均可以使用${}表达式直接获取到Spring环境变量中的配置值。
例如,application.yaml中有如下配置:

gaoDe: 
  url: https://restapi.amap.com
  weatherApi: /v3/weather/weatherInfo

那么可以使用${}直接将此配置引入:

/**
 * 高德开放平台API
 *
 * @author fukang
 * @version 1.0.0
 * @date 2023/8/30 05:32
 */
@PrintLog
@HttpClient("${gaoDe.url}")
public interface GaoDeApi {

    /**
     * 高德开放平台API -- 天气查询
     * 
     * @param city 城市名称
     * @return 该城市的天气情况
     */
    @ResultSelect(key="@resp.lives", defaultValue = "#{new ArrayList()}")
    @Get("${gaoDe.weatherApi}")
    Object queryWeather(String city);
}

🪛 常用配置

  • spring.lucky.http-client.connection-timeout
    设置连接超时时间

  • spring.lucky.http-client.read-timeout
    设置读超时时间

  • spring.lucky.http-client.write-timeout
    设置写超时时间

  • spring.lucky.http-client.print-log-packages
    在如下包中的HTTP接口将会打印日志

    spring:
      lucky:
        http-client:
          print-log-packages:
            - com.springboot.testdemo.springboottest.api.GaoDeApi
            - com.springboot.testdemo.springboottest.api2
            - com.springboot.testdemo.springboottest.api3
    
  • spring.lucky.http-client.enable-request-log
    开启请求日志

  • spring.lucky.http-client.enable-response-log
    开启响应日志

  • spring.lucky.http-client.allow-print-log-body-mime-types
    响应日志开启时,设置mime-types,只有响应的mime-types为配置值时才打印具体的响应体内容

    spring:
      lucky:
        http-client:
          allow-print-log-body-mime-types:
            - application/json
            - application/xml
    
  • spring.lucky.http.client.allow-print-log-body-max-length
    响应日志开启时,设置最大响应体长度,超过该长度则不会打印响应体内容,值小于等于0时表示没有限制

    spring:
      lucky:
        http-client:
          allow-print-log-body-max-length: 14500
    
  • spring.lucky.http-client.header-params
    设置公共请求头参数,支持给指定接口配置特有的参数

    spring:
      lucky:
        http-client:
          #公共请求头参数
          header-params:
            # 使用"[全类名]"的写法可以为接口配置特有的参数
            "[com.springboot.testdemo.springboottest.api.GaoDeApi]":
                gaoDe-header: 高德API特有的参数
            # 所有HTTP接口公用的请求头参数
            Cookie:
              - c1=12345666
              - c2=token-uuidm
    
  • spring.lucky.http-client.query-params
    设置公共URL参数,支持给指定接口配置特有的参数

  • spring.lucky.http-client.path-params
    设置公共URL占位符'{}'参数,支持给指定接口配置特有的参数

  • spring.lucky.http-client.form-params
    设置公共表单参数,支持给指定接口配置特有的参数

  • spring.lucky.http-client.resource-param
    设置公共文件资源参数,支持给指定接口配置特有的参数

  • spring.lucky.http-client.thread-pool-param.core-pool-size
    设置执行异步调用的线程池参数:核心线程数

  • spring.lucky.http-client.thread-pool-param.maximum-pool-size
    设置执行异步调用的线程池参数:最大线程数

  • spring.lucky.http-client.thread-pool-param.blocking-queue-size
    设置执行异步调用的线程池参数:阻塞队列的长度

  • spring.lucky.http-client.thread-pool-param.keep-alive-time
    设置执行异步调用的线程池参数:保活时间,空闲等待时间

  • spring.lucky.http-client.thread-pool-param.name-format
    设置执行异步调用的线程池参数:线程名格式

  • spring.lucky.http-client.thread-pool-param.blocking-queue-factory
    设置执行异步调用的线程池参数:设置阻塞队列的工厂的全类名

  • spring.lucky.http-client.thread-pool-param.rejected-execution-handler-factory
    设置执行异步调用的线程池参数:设置拒绝策略的工厂的全类名

  • spring.lucky.http-client.expression-params
    配置SpEL表达式参数,这里配置的参数可以在lucky-httpclient中支持SpEL表达式的注解中直接使用。

      spring:
        lucky:
          http-client:
            #SpEL表达式参数,例如:在进行如下配置后使用@HttpClient("#{gaoDeApi}"),便可以直接获取到值 'https://restapi.amap.com'
            expression-params:
              userModel: http://localhost:8080/users
              gaoDeApi: https://restapi.amap.com
              mirrors: https://mirrors.sohu.com
    
    
  • spring.lucky.http-client.spring-el-package-imports
    SpEL运行时环境导包,导入后在SpEL表达式中使用包中的类时便可以不用使用全类名,直接使用类名即可.

    spring:
      lucky:
        http-client:
          #向SpEL运行时环境导入的包,
          #导入前创建ArrayList实例(#{new java.util.ArrayList()})
          #导入后创建ArrayList实例(#{new ArrayList()})
          spring-el-package-imports:
            - java.util
    
  • spring.lucky.http-client.http-executor-factory
    设置HTTP执行器工厂的类的全类名

      spring:
        lucky:
          http-client:
            # 设置SpEL运行时环境工厂的类的全类名
            http-executor-factory: io.github.lucklike.httpclient.config.impl.OkHttpExecutorFactory
    
  • spring.lucky.http-client.http-executor
    设置HTTP执行器

      spring:
        lucky:
          http-client:
            # HTTP执行器,jdk、okhttp、http_client
            http-executor: okhttp
    
  • spring.lucky.http-client.object-creator-factory
    设置对象创建器工厂的类的全类名

      spring:
        lucky:
          http-client:
            # 设置对象创建器工厂的类的全类名
            object-creator-factory: io.github.lucklike.httpclient.config.impl.BeanObjectCreatorFactory
    
  • spring.lucky.http-client.spring-el-runtime-factory
    设置SpEL运行时环境工厂的类的全类名

      spring:
        lucky:
          http-client:
            # 设置SpEL运行时环境工厂的类的全类名
            spring-el-runtime-factory: io.github.lucklike.httpclient.config.impl.BeanSpELRuntimeFactoryFactory
    
  • spring.lucky.http-client.http-exception-handle-factory
    设置异常处理器工厂的类的全类名

      spring:
        lucky:
          http-client:
            # 设置异常处理器工厂的类的全类名
            http-exception-handle-factory: io.github.lucklike.httpclient.config.impl.DefaultHttpExceptionHandleFactory
    
  • spring.lucky.http-client.request-interceptors
    设置请求拦截器

      spring:
        lucky:
          http-client:
            # 请求拦截器实现类集合
            request-interceptors:
              - com.luckyframework.httpclient.proxy.impl.interceptor.PrintLogInterceptor
    
    
  • spring.lucky.http-client.response-interceptors
    设置响应拦截器

      spring:
        lucky:
          http-client:
            # 响应拦截器实现类集合
            response-interceptors:
              - com.luckyframework.httpclient.proxy.impl.interceptor.PrintLogInterceptor
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lucky:)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值