Forest Forest
💒 首页
  • v1.5.30
  • v1.5.28
  • 🎄 ForestX
🌰 案例
💖 支持
🛫 更新记录
🧢 开发团队
⚒️ 参与贡献
  • MaxKey - 业界领先的身份管理和认证产品 (opens new window)
  • Snowy - 国内首个国密前后端分离快速开发平台 (opens new window)
  • Eoapi - 一个开源、可拓展的 API 工具平台 (opens new window)
  • Gitee (opens new window)
  • Github (opens new window)
💒 首页
  • v1.5.30
  • v1.5.28
  • 🎄 ForestX
🌰 案例
💖 支持
🛫 更新记录
🧢 开发团队
⚒️ 参与贡献
  • MaxKey - 业界领先的身份管理和认证产品 (opens new window)
  • Snowy - 国内首个国密前后端分离快速开发平台 (opens new window)
  • Eoapi - 一个开源、可拓展的 API 工具平台 (opens new window)
  • Gitee (opens new window)
  • Github (opens new window)
  • 序言

    • 🎁 新手介绍
    • 📖 文档
    • 🌰 使用案例
    • 🕵️‍ 关于作者
    • 👨‍🎓 贡献者列表
  • 入门

    • 🎬 安装配置说明
    • 🏹 Springboot环境安装
    • 📐 Springboot环境配置
    • 🎯 Springboot环境使用
    • 🏹 Spring环境安装
    • 📐 Spring环境配置
    • 🎯 Spring环境使用
    • 🏹 原生Java环境安装
    • 📐 原生Java环境配置
    • 🎯 原生Java环境使用
    • 🧬 编程式接口
  • 配置项

    • 👜 Springboot环境配置项
    • 👝 Spring环境配置项
    • 🎒 原生Java环境配置项
    • 📚 配置优先级/作用域
  • 声明式接口

    • 🧱 构建接口
    • 🍀 请求方法
    • 🚚 请求地址
    • 🎈 URL 参数
    • 🍭 请求头
    • 👔 请求体
    • 🍮 后端框架
    • 🧁 接口注解
    • 📬 接收数据
    • 🍛 数据转换
    • 🍓 成功/失败条件
    • 🍌 重试机制
    • 🥂 重定向
    • 🍔 Gzip解压
    • 🎂 日志管理
    • ⚽ 回调函数
    • 🍟 异步请求
    • 🛡️ HTTPS
    • 🍪 使用Cookie
    • 🛸 使用代理
    • 🍉 上传下载
    • 🚑 异常处理
  • 编程式接口

    • 请求API

      • 🚀 请求对象
      • 🚢 请求属性
      • ✨ 执行请求
      • 🎊 后端框架
      • 🎪 请求类型
      • 🔮 请求地址
      • 🧀 URL 参数
      • 🚅 请求头
      • 🚋 请求体
      • ⚓ 回调函数
      • 🚁 异步请求
      • 🥯 Cookie
      • 🍜 成功/失败条件
      • 🌶️ 重试机制
      • ⛵ 重定向
      • 🛰️ 请求代理
    • 响应API

      • 🌠 响应对象
      • ✒️ 读取数据
      • 🦋 响应状态码
      • 🏥 响应错误处理
      • 🎧 响应头
      • 🥞 Cookie
  • 模板表达式

    • 🍬 Hello World
    • 🍹 配置属性引用
    • 🍖 变量引用
    • 🥃 动态变量绑定
    • 🥗 参数序号引用
    • 🍍 引用对象属性
    • 🥝 调用对象方法
  • 高级特性

    • 🥪 拦截器
      • 🍏 自定义注解
      • 🍇 组合注解
      • 🥑 自定义转换器
    • v1.5.28文档
    • 高级特性
    公子骏
    2022-07-01
    目录

    🥪 拦截器

    用过Spring MVC的朋友一定对Spring的拦截器并不陌生,Forest也同样支持针对Forest请求的拦截器。

    如果您想在很多个请求发送之前或之后做一些事情(如打印日志、计数等等),拦截器就是您的好帮手。

    # 构建拦截器

    定义一个拦截器需要实现com.dtflys.forest.interceptor.Interceptor接口

    public class SimpleInterceptor<T> implements Interceptor<T> {
    
        private final static Logger log = LoggerFactory.getLogger(SimpleInterceptor.class);
    
        /**
         * 该方法在被调用时,并在beforeExecute前被调用 
         * @Param request Forest请求对象
         * @Param args 方法被调用时传入的参数数组 
         */
        @Override
        public void onInvokeMethod(ForestRequest req, ForestMethod method, Object[] args) {
            log.info("on invoke method");
            // req 为Forest请求对象,即 ForestRequest 类实例
            // method 为Forest方法对象,即 ForestMethod 类实例
            // addAttribute作用是添加和Forest请求对象以及该拦截器绑定的属性
            addAttribute(req, "A", "value1");
            addAttribute(req, "B", "value2");
        }
    
        /**
         * 在请求体数据序列化后,发送请求数据前调用该方法
         * 默认为什么都不做
         * 注: multlipart/data类型的文件上传格式的 Body 数据不会调用该回调函数
         *
         * @param request Forest请求对象
         * @param encoder Forest转换器
         * @param encodedData 序列化后的请求体数据
         */
        public byte[] onBodyEncode(ForestRequest request, ForestEncoder encoder, byte[] encodedData) {
            // request: Forest请求对象
            // encoder: 此次转换请求数据的序列化器
            // encodedData: 序列化后的请求体字节数组
            // 返回的字节数组将替换原有的序列化结果
            // 默认不做任何处理,直接返回参数 encodedData
            return encodedData;
        }
    
    
        /**
         * 该方法在请求发送之前被调用, 若返回false则不会继续发送请求
         * @Param request Forest请求对象
         */
        @Override
        public boolean beforeExecute(ForestRequest req) {
            log.info("invoke Simple beforeExecute");
            // 执行在发送请求之前处理的代码
            req.addHeader("accessToken", "11111111");  // 添加Header
            req.addQuery("username", "foo");  // 添加URL的Query参数
            return true;  // 继续执行请求返回true
        }
    
        /**
         * 该方法在请求成功响应时被调用
         */
        @Override
        public void onSuccess(T data, ForestRequest req, ForestResponse res) {
            log.info("invoke Simple onSuccess");
            // 执行成功接收响应后处理的代码
            int status = res.getStatusCode(); // 获取请求响应状态码
            String content = res.getContent(); // 获取请求的响应内容
            String result = (String)data;  // data参数是方法返回类型对应的返回数据结果,注意需要视情况修改对应的类型否则有可能出现类转型异常
            result = res.getResult(); // getResult()也可以获取返回的数据结果
            response.setResult("修改后的结果: " + result);  // 可以修改请求响应的返回数据结果
            
            // 使用getAttributeAsString取出属性,这里只能取到与该Forest请求对象,以及该拦截器绑定的属性
            String attrValue1 = getAttributeAsString(req, "A1");
    
        }
    
        /**
         * 该方法在请求发送失败时被调用
         */
        @Override
        public void onError(ForestRuntimeException ex, ForestRequest req, ForestResponse res) {
            log.info("invoke Simple onError");
            // 执行发送请求失败后处理的代码
            int status = res.getStatusCode(); // 获取请求响应状态码
            String content = res.getContent(); // 获取请求的响应内容
            String result = res.getResult(); // 获取方法返回类型对应的返回数据结果
        }
    
        /**
         * 该方法在请求发送之后被调用
         */
        @Override
        public void afterExecute(ForestRequest req, ForestResponse res) {
            log.info("invoke Simple afterExecute");
            // 执行在发送请求之后处理的代码
            int status = res.getStatusCode(); // 获取请求响应状态码
            String content = res.getContent(); // 获取请求的响应内容
            String result = res.getResult(); // 获取方法返回类型对应的最终数据结果
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94

    Interceptor接口带有一个泛型参数,其表示的是请求响应后返回的数据类型。 Interceptor<String>即代表返回的数据类型为 String。

    在拦截器的方法参数中基本都有 ForestRequest 类对象,即Forest请求对象,Forest的绝大部分操作都是围绕请求对象所作的工作。

    文档导航

    要详细了解 Forest 请求对象如何使用,请参见《请求对象》

    # 拦截器与 Spring 集成

    若我要在拦截器中注入 Spring 的 Bean 改如何做?

    
    /**
     * 在拦截器的类上加上@Component注解,并保证它能被Spring扫描到
     */
    @Component
    public class SimpleInterceptor implements Interceptor<String> {
    
        // 如此便能直接注入Spring上下文中所有的Bean了
        @Resource
        private UserService userService;
        
        ... ...
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 在拦截器中传递数据

    在Forest中,拦截器是基于单例模式创建的,也就是说一个拦截器类最多只能对应一个拦截器实例。

    那么以下这种通过共享变量的方式就可能造成错误:

    public class SimpleInterceptor implements Interceptor<String> {
      
        private String name;
       
        @Override
        public boolean beforeExecute(ForestRequest req) {
            this.name = req.getQuery("name");
        }
    
        @Override
        public void onSuccess(String data, ForestRequest req, ForestResponse res) {
            System.out.println("name = " + name);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    若有两个请求同时进入该拦截器(请求1 url=...?name=A1, 请求2 url=...?name=A2), 而最后当请求1进入onSuccess方法时,应该打印出 name = A2,却因为之前执行了请求2的beforeExecute方法,将类变量name的值改成了A2, 所以最终打印出来的是 name = A2 (其实应该是 name = A1),这明显是错误的。

    那该如何做能在传递数据的同时避免这类问题呢?

    方法也很简单,就是将您要传递的数据与请求对象绑定在一起,比如在 onSuccess 中调用req.getQuery方法。

    System.out.println("name = " + forest.getQuery("name"));
    
    1

    虽然这种方法能够解决并发问题,但有个明显的限制:如果要传递的数据不想出现在请求中的任何位置(包括URL、请求头、请求体),那就无能为力了。

    这时候就要使用 ForestRequest 的扩展绑定数据的方法了。

    # Attribute

    在拦截器中使用addAttribute方法和getAttribute方法来添加和获取Attribute。

    Attribute 是和请求以及所在拦截器绑定的属性值,这些属性值不能通过网络请求传递到远端服务器。

    而且,在使用getAttribute方法时,只能获取在相同拦截器,以及相同请求中绑定的Attribute,这两个条件缺一不可。

    public class SimpleInterceptor implements Interceptor<String> {
      
        @Override
        public void onInvokeMethod(ForestRequest req, ForestMethod method, Object[] args) {
            String methodName = method.getMethodName();
            addAttribute(req, "methodName", methodName); // 添加Attribute
            addAttribute(req, "num", (Integer) args[0]); // 添加Attribute
        }
    
        @Override
        public void onSuccess(String data, ForestRequest req, ForestResponse res) {
            Object value1 = getAttribute(req, "methodName");  // 获取名称为methodName的Attribute,不指定返回类型
            String value2 = getAttribute(req, "methodName", String.class);  // 获取名称为methodName的Attribute,并转换为指定的Class类型
            String value3 = getAttributeAsString(req, "methodName");  // 获取名称为methodName的Attribute,并转换为String类型
            Integer value4 = getAttributeAsInteger(req, "num");  // 获取名称为num的Attribute,并转换为Integer类型
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    # Attachment

    可以使用ForestRequest对象的addAttachment方法和getAttachment方法来添加和获取Attachment。

    Attachment 是和请求绑定的附件属性值,这些值不能通过网络请求传递到远端服务器。

    而且,在使用getAttachment方法时,只能获取在相同请求中绑定的Attachment,但不必是相同的拦截器。

    public class SimpleInterceptor1 implements Interceptor<String> {
      
        @Override
        public void onInvokeMethod(ForestRequest req, ForestMethod method, Object[] args) {
            String methodName = method.getMethodName();
            req.addAttachment("methodName", methodName); // 添加Attachment
            req.addAttachment("num", (Integer) args[0]); // 添加Attachment
        }
        ... ...
    }
    
    /**
     * Attachment不依赖任何一个拦截器,可以跨拦截器传递数据
     */
    public class SimpleInterceptor2 implements Interceptor<String> {
      
        @Override
        public void onSuccess(String data, ForestRequest req, ForestResponse res) {
            Object value1 = req.getAttachment("methodName");  // 获取名称为methodName的Attachment
            Object value2 = req.getAttachment("num");  // 获取名称为num的Attachment
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    # Attribute与Attachment的区别

    Attribute和Attachment都是能通过请求进行绑定的数据传递方式,但也有所不同。

    绑定请求 绑定拦截器
    Attribute ✔ ✔
    Attachment ✔ ✘

    # 配置拦截器

    Forest有三个地方可以添加拦截器:@Request、@BaseRequest、全局配置,这三个地方代表三个不同的作用域。

    # @Request上的拦截器

    若您想要指定的拦截器只作用在指定的请求上,只需要在该请求方法的@Request注解中设置interceptor属性即可。

    
    public interface SimpleClient {
    
        @Request(
                url = "http://localhost:8080/hello/user?username=foo",
                headers = {"Accept:text/plain"},
                interceptor = SimpleInterceptor.class
        )
        String simple();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    @Request中拦截器可以配置多个:

        @Request(
                url = "http://localhost:8080/hello/user?username=foo",
                headers = {"Accept:text/plain"},
                interceptor = {SimpleInterceptor1.class, SimpleInterceptor2.class, ...}
        )
        String simple();
    
    1
    2
    3
    4
    5
    6

    友情提示

    @Request上的拦截器只会拦截指定的请求

    # @BaseRequest 上的拦截器

    若您想使一个interface内的所有请求方法都指定某一个拦截器,可以在@BaseRequest的interceptor中设置

    
    @BaseRequest(baseURL = "http://localhost:8080", interceptor = SimpleInterceptor.class)
    public interface SimpleClient {
    
        @Request(url = "/hello/user1?username=foo" )
        String send1();
    
        @Request(url = "/hello/user2?username=foo" )
        String send2();
    
        @Request(url = "/hello/user3?username=foo" )
        String send3();
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    如以上代码所示,SimpleClient接口中的send1、send2、send3方法都被会SimpleInterceptor拦截器拦截

    @BaseRequest也如@Request中的interceptor属性一样,可以配1到多个拦截器,如代码所示:

    @BaseRequest(
        baseURL = "http://localhost:8080", 
        interceptor = {SimpleInterceptor1.class, SimpleInterceptor2.class, ...})
    public interface SimpleClient {
        // ... ...
    }
    
    1
    2
    3
    4
    5
    6

    # 全局拦截器

    若要配置能拦截项目范围所有Forest请求的拦截器也很简单,只要在全局配置中加上interceptors属性即可

    forest:
      ...
      interceptors:                   # 可配置1到多个拦截器
         - com.your.site.client.SimpleInterceptor1
         - com.your.site.client.SimpleInterceptor2
         ...
    
    1
    2
    3
    4
    5
    6
    帮助我们改善此文档 (opens new window)
    上次更新: 2023/03/07, 12:59:48
    🥝 调用对象方法
    🍏 自定义注解

    ← 🥝 调用对象方法 🍏 自定义注解→

    Theme by Vdoing | Copyright © 2016-2023 公子骏 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式