Java8 - 对象类型 - 动态添加更多属性

Java8 - Object Type - Dynamically add more property

提问人:topgun 提问时间:6/24/2022 最后编辑:topgun 更新时间:6/25/2022 访问量:280

问:

我想从 http 调用的回复中获取标头值,将其存储在缓存中,并通过将其传递给 http 请求的标头来在下一个请求中使用此值,以实现粘性会话。我的项目是用Java 8编写的。

我有这样的假装 http 客户端构建器,它使用 JacksonDecoder 进行解码。

   public <T> T buildClient(Class<T> type, String url) {
            return Feign.builder()
                    .contract(new JAXRSContract())
                    .encoder(new JacksonEncoder(mapper))
                    .decoder(new CustomDecoder(mapper))
                    .logger(new Slf4jLogger(Constants.FFA_LOGGER_NAME))
                    .logLevel(Level.FULL)
                    .options(new Options(connectTimeout, readTimeout))
                    .retryer(Retryer.NEVER_RETRY)
                    .requestInterceptor(auth2FeignRequestInterceptor)
                    .invocationHandlerFactory(factory)
                    .target(type, url);
        }

jackson 的默认解码器仅解码正文而不是标头,因此我正在实现自己的 CustomDecoder。

我想实现的是获取 resonse.headers 的值并将其映射到 body,或者在从映射器获取值后动态地在对象 responseBodyObject 中添加属性。

public final class CustomDecoder extends JacksonDecoder implements Decoder {
    @Override
      public Object decode(Response response, Type type) throws IOException {
          //Here the default decoder only decoding body.
          Reader reader = response.body().asReader();
          Object responseBodyObject = mapper.readValue(reader, mapper.constructType(type));
Java 动态 反射 投射 假装

评论

1赞 M. Prokhorov 6/24/2022
“我如何让它工作”是什么意思?当前版本中哪些功能不起作用?它会正确地对你构造的对象进行类型检查。
0赞 topgun 6/24/2022
我想将类型“Object”转换为动态类的myclass对象。我收到以下错误:java.lang.ClassCastException:tech.csp.api.ffa.GetAccountsResponse 无法转换为 java.lang.Class。我已经编辑了问题的最后一部分。
0赞 M. Prokhorov 6/24/2022
你总是知道应该是什么类型吗?如果是,则转换为该类型而不是转换为 - 这显然是错误的。responseBodyObjectClass<?>
0赞 topgun 6/24/2022
不,我不知道会是什么类型。它是动态的。这就是为什么我试图探索是否可以在 Java 8 中像这样动态分配类。
0赞 M. Prokhorov 6/24/2022
我不明白你说的“分配类”是什么意思。你不是在解码器中选择类,而是从外部上下文接收它 - 端点代码指示你它需要特定类型的对象,它还为你提供它用于响应的字节,然后你作为一个解码器,期望将其解析为对象字段。你现在做的方式很好,但我个人引入了一个带有令牌 setter 的接口,如果实现该接口,我会设置令牌,而不是将额外的部分注入到身体对象中。returnObject

答:

0赞 topgun 6/24/2022 #1

在这里,我如何用不同的方法解决自己的问题。为了将标头值复制到正文,我最终将响应转换为 JSON 树,然后将 sessionid/token 的标头值添加到正文,然后将其转换回对象。

  @Override
      public Object decode(Response response, Type type) throws IOException {
          Map<String, Collection<String>> headers = response.headers();
          Collection<String> values = headers.get("sessionid");
          String value = null;
          if (values != null && values.isEmpty() == false) {
            String[] array = new String[values.size()];
            values.toArray(array);
            value = array[0];
          }
          if (response.status() == 404)
              return Util.emptyValueOf(type);
          if (response.body() == null)
              return null;
          
          ByteArrayOutputStream output = new ByteArrayOutputStream();
          try{
            StreamUtils.copy(response.body().asInputStream(), output);
          } catch (FileNotFoundException ex) {
           
          }
         JsonNode actualObj = mapper.readTree(output.toByteArray());
         JsonNode node1=actualObj.get("header");
         ObjectNode parentObjectNode = (ObjectNode) node1;
         parentObjectNode.put("token", value);
         Object returnObject = mapper.treeToValue(actualObj, mapper.constructType(type).getClass());
         return returnObject;
      }
    }
1赞 M. Prokhorov 6/24/2022 #2

如果我理解正确的话,这是构造类型具有应从标头填充的其他属性的情况。然后,这里有几种方法。首先,我更喜欢的是有一个特殊的接口类型,你的解码器知道,重新编码的对象可以实现以接收值:

public interface SessionTokenAware {

    void setSessionToken(String value);
}

然后在解码器中:

@Override
public Object decode(Response response, Type type) throws IOException {
    Object parsedResponse = mapper.readValue(
        response.body().asReader(),
        mapper.constructType(type)
    );
    if (parsedResponse instanceof SessionTokenAware) {
        SessionTokenAware sessionAware = (SessionTokenAware) parsedResponse;
        Collection<String> sessionHeader = response.headers().get("sessionId");
        if (sessionHeader != null && !sessionHeader.isEmpty()) {
            sessionAware.setSessionToken(sessionHeader.iterator().next());
        }
    }
    return parsedResponse;
}

当你的响应实现接口时,它将获得解码器设置的会话令牌值,而不必破解正文流并向其添加任何内容:

public class MyResponseBody implements SesionTokenAware {
    private String token;

    @Override
    public void setSessionToken(String value) {
        token = value;
    }

    public String getSessionToken() {
        return token;
    }
}

另外,如果你不喜欢为你想要接收的每种可能的标头使用单独的接口,你可以创建一个包罗万象的接口,让每个响应实现自己整理标头:

public interface HeadersAware {

    void onHeaders(Map<String, Collection<String>> headerValues);
}


public Object decode(Response response, Type type) throws IOException {
    Object parsedResponse = ....
    if (parsedResponse instanceof HeadersAware) {
        HeaderAware headerAware = (HeaderAware) parsedResponse;
        headerAware.onHeaders(response.headers());
    }
    return parsedResponse;
}

public class MyResponse implements HeaderAware {
    private String token;

    public void onHeaders(Map<String, Collection<String>> headers) {
        Collection<String> sessionHeader = headers.getOrDefault("sessionId", emptySet());
        if (!sessionHeader.isEmpty()) {
            token = sessionHeader.iterator().next();
        }
    }
}

我不喜欢后一种方法,原因有两个:

  1. 响应可能会看到它没有看到的标头的值,因为它接收所有值,而不仅仅是实现特定响应 POJO 所需的值。
  2. 如果您有多个不同的会话感知响应层次结构(因此不可能有通用的超类),则必须将 sessionId 处理代码复制粘贴到每个响应中,而不是将该代码整齐地放置在类中。CustomDecoder

评论

0赞 topgun 6/24/2022
您非常准确地确定了问题所在。您似乎正在添加接口以获得其他属性。但是如何在映射器返回的 Object 类型中实现接口呢?
0赞 topgun 6/24/2022
我无法控制映射器对象来更改或实现接口。mapper 类来自 jackson。
0赞 M. Prokhorov 6/25/2022
映射器无需更改接口或将接口添加到反序列化类型 - 您可以在要反序列化的类型上执行此操作。例如,从您的异常中,您有一个似乎在您的控制范围内,您可以在其中添加一个接口。tech.csp.api.ffa.GetAccountsResponse
0赞 topgun 6/25/2022
感谢您的回复。我理解你。但我不明白如何执行returnObject以将令牌值与原始对象值一起返回。从您的代码中:“SessionTokenAware sessionAware 的 parsedResponse 实例”我看到您期望 SessionAware 由 parsedResponse 实现,但我将如何实现它?我总是从 mapper.readValue() 获取对象类型。
0赞 topgun 6/25/2022
我也在使用 Java 8。