idtitlesidebar_label
interceptor
拦截器
拦截器

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

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

构建拦截器

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

  1. public class SimpleInterceptor implements Interceptor<String> {
  2. private final static Logger log = LoggerFactory.getLogger(SimpleInterceptor.class);
  3. /**
  4. * 该方法在被调用时,并在beforeExecute前被调用
  5. * @Param request Forest请求对象
  6. * @Param args 方法被调用时传入的参数数组
  7. */
  8. @Override
  9. public void onInvokeMethod(ForestRequest request, ForestMethod method, Object[] args) {
  10. log.info("on invoke method");
  11. // addAttribute作用是添加和Request以及该拦截器绑定的属性
  12. addAttribute(request, "A", "value1");
  13. addAttribute(request, "B", "value2");
  14. }
  15. /**
  16. * 该方法在请求发送之前被调用, 若返回false则不会继续发送请求
  17. * @Param request Forest请求对象
  18. */
  19. @Override
  20. public boolean beforeExecute(ForestRequest request) {
  21. log.info("invoke Simple beforeExecute");
  22. // 执行在发送请求之前处理的代码
  23. request.addHeader("accessToken", "11111111"); // 添加Header
  24. request.addQuery("username", "foo"); // 添加URL的Query参数
  25. return true; // 继续执行请求返回true
  26. }
  27. /**
  28. * 该方法在请求成功响应时被调用
  29. */
  30. @Override
  31. public void onSuccess(String data, ForestRequest request, ForestResponse response) {
  32. log.info("invoke Simple onSuccess");
  33. // 执行成功接收响应后处理的代码
  34. int status = response.getStatusCode(); // 获取请求响应状态码
  35. String content = response.getContent(); // 获取请求的响应内容
  36. String result = data; // data参数是方法返回类型对应的返回数据结果
  37. result = response.getResult(); // getResult()也可以获取返回的数据结果
  38. response.setResult("修改后的结果: " + result); // 可以修改请求响应的返回数据结果
  39. // 使用getAttributeAsString取出属性,这里只能取到与该Request对象,以及该拦截器绑定的属性
  40. String attrValue1 = getAttributeAsString(request, "A1");
  41. }
  42. /**
  43. * 该方法在请求发送失败时被调用
  44. */
  45. @Override
  46. public void onError(ForestRuntimeException ex, ForestRequest request, ForestResponse response) {
  47. log.info("invoke Simple onError");
  48. // 执行发送请求失败后处理的代码
  49. int status = response.getStatusCode(); // 获取请求响应状态码
  50. String content = response.getContent(); // 获取请求的响应内容
  51. String result = response.getResult(); // 获取方法返回类型对应的返回数据结果
  52. }
  53. /**
  54. * 该方法在请求发送之后被调用
  55. */
  56. @Override
  57. public void afterExecute(ForestRequest request, ForestResponse response) {
  58. log.info("invoke Simple afterExecute");
  59. // 执行在发送请求之后处理的代码
  60. int status = response.getStatusCode(); // 获取请求响应状态码
  61. String content = response.getContent(); // 获取请求的响应内容
  62. String result = response.getResult(); // 获取方法返回类型对应的最终数据结果
  63. }
  64. }

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

拦截器与 Spring 集成

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

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

在拦截器中传递数据

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

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

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

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

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

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

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

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

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

Attribute

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

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

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

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

Attachment

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

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

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

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

Attribute与Attachment的区别

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

绑定请求绑定拦截器
Attribute
Attachment

配置拦截器

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

@Request上的拦截器

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

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

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

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

:::info 友情提示 @Request上的拦截器只会拦截指定的请求 :::

@BaseRequest 上的拦截器

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

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

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

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

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

全局拦截器

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

  1. forest:
  2. ...
  3. interceptors: # 可配置1到多个拦截器
  4. com.your.site.client.SimpleInterceptor1
  5. com.your.site.client.SimpleInterceptor2
  6. ...