수색…


비고

이 절에서는 cxf가 무엇인지, 그리고 왜 개발자가이를 사용하려고하는지에 대한 개요를 제공합니다.

또한 cxf 내의 큰 주제를 언급하고 관련 주제에 링크해야합니다. cxf에 대한 문서는 새로운 것이므로 관련 주제의 초기 버전을 만들어야 할 수도 있습니다.

공급자가있는 기본 웹 클라이언트

시작하려면 WebClient를 생성하는 팩토리가 필요합니다.

public class ClientFactory {
    private Map<String, WebClient> cache = new HashMap<>();

    public enum RESTClient {
        PORTAL;
    }

    public WebClient fetchRestClient(RESTClient restClient) {

        if (this.cache.containsKey(restClient)) {
            return WebClient.fromClient(this.cache.get(rc));
        }

        if (RESTClient.enum.equals(rc)) {

            List<Object> providers = new ArrayList<Object>();
            providers.add(new GsonMessageBodyProvider());

            WebClient webClient = WebClient.create("https://blah.com", providers);

            HTTPConduit conduit = WebClient.getConfig(webClient).getHttpConduit();
            conduit.getClient().setReceiveTimeout(recieveTimeout);
            conduit.getClient().setConnectionTimeout(connectionTimout);

            this.cache.put(RESTClient.CAT_DEVELOPER_PORTAL.name(), webClient);
            return WebClient.fromClient(webClient);// thread safe
        }
    }
}
  • 먼저 공급자 목록을 만듭니다 (나중에 제공 될 예정 임).
  • 다음으로 우리는 정적 팩토리 "create ()"를 사용하여 새로운 웹 클라이언트를 만듭니다. 여기에 Basic Auth cred와 스레드 안전과 같은 몇 가지 사항을 추가 할 수 있습니다. 지금은 그냥 이걸 사용하십시오.
  • 다음으로 우리는 HTTPConduit을 꺼내서 타임 아웃을 설정합니다. CXF는 Java 기본 클래스 클라이언트와 함께 제공되지만 Glassfish 또는 기타를 사용할 수 있습니다.
  • 마지막으로 WebClient를 캐시합니다. 지금까지 코드를 작성하는 데 비용이 많이 들기 때문에 이것은 중요합니다. 다음 줄은 스레드 안전을 만드는 방법을 보여줍니다. 이것은 캐시에서 코드를 가져 오는 방법이기도합니다. 본질적으로 우리는 REST 호출의 모델을 만든 다음 필요할 때마다 복제한다.
  • 여기에없는 것 : 매개 변수 나 URL을 추가하지 않았습니다. 여기에 추가 할 수 있지만 특정 끝점을 만들 것이고 일반적인 것을 원할 것입니다. 또한 요청에 헤더가 추가되지 않습니다. 이것들은 "복제본"을 지나치게 만들지 않으므로 나중에 추가해야합니다.

이제 우리는 준비가 된 WebClient를 가지고 있습니다. 나머지 통화를 설정할 수 있습니다.

public Person fetchPerson(Long id) {
    long timer t = System.currentTimeMillis();
    Person person = null;
    try {
        wc = this.factory.findWebClient(RESTClient.PORTAL);
        wc.header(AUTH_HEADER, SUBSCRIPTION_KEY);
        wc.header(HttpHeaders.ACCEPT, "application/person-v1+json");

        wc.path("person").path("base");
        wc.query("id", String.valueOf(id));

        person = wc.get(Person.class);
    }
    catch (WebApplicationException wae) {
        // we wanna skip these. They will show up in the "finally" logs.
    }
    catch (Exception e) {
        log.error(MessageFormat.format("Error fetching Person: id:{0} ", id), e);
    }
    finally {
        log.info("GET HTTP:{} - Time:[{}ms] - URL:{} - Content-Type:{}", wc.getResponse().getStatus(), (System.currentTimeMillis() - timer), wc.getCurrentURI(), wc.getResponse().getMetadata().get("content-type"));
        wc.close();
    }
    return p;
}
  • "try"내부에서 우리는 공장에서 WebClient를 가져옵니다. 이것은 새로운 "서리가 내린"것입니다.
  • 다음으로 헤더를 설정합니다. 여기에 Auth 헤더를 추가 한 다음 Accept 헤더를 추가합니다. 맞춤 수신 허용 헤더가 있습니다.
  • 다음에 경로와 쿼리 문자열을 추가합니다. 이 단계를 수행 할 순서가 없다는 것을 명심하십시오.
  • 마지막으로 우리는 "get"을합니다. 당연히 이것을하는 몇몇 방법이있다. 여기 CXF에서 JSON 매핑을 수행합니다. 이렇게하면 WebApplicationExceptions를 처리해야합니다. 따라서 404를 얻으면 CXF는 예외를 throw합니다. 여기에 주목하자. 나는 그 예외를 먹는다. 왜냐하면 나는 마침내 응답을 기록하기 때문이다. 그러나 나는 그들이 중요 할 수도 있으므로 다른 예외를 원합니다.
  • 이 예외 처리 전환이 마음에 들지 않으면 "get"에서 Response 객체를 다시 얻을 수 있습니다. 이 객체는 엔티티와 HTTP 상태 코드를 보유합니다. 절대로 WebApplicationException을 던지지 않습니다. 유일한 단점은 JSON 매핑을 수행하지 않는다는 것입니다.
  • 마지막으로, "finally"절에서 우리는 "wc.close ()"를가집니다. Get Clause에서 객체를 얻는다면 실제로이 작업을 수행 할 필요가 없습니다. 뭔가 잘못 될 수 있으므로 좋은 안전 장치입니다.

그렇다면 "accept"헤더는 어떻게 될까요? application / person-v1 + json CXF는 어떻게 파싱 하는지를 어떻게 알 수 있습니까? CXF에는 JSON 파서가 내장되어 있지만 Gson을 사용하고 싶습니다. Json 파서의 다른 많은 구현에는 Gson이 아닌 주석이 필요합니다. 그것은 사용하기 쉬운 바보입니다.

public class GsonMessageBodyProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {

    private Gson gson = new GsonBuilder().create();
    
    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return StringUtils.endsWithIgnoreCase(mediaType.getSubtype(), "json");
    }

    @Override
    public T readFrom(Class<T> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
        try {
            return gson.fromJson(new BufferedReader(new InputStreamReader(entityStream, "UTF-8")), type);
        }
        catch (Exception e) {
            throw new IOException("Trouble reading into:" + type.getName(), e);
        }
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return StringUtils.containsIgnoreCase(mediaType.getSubtype(), "json");
    }

    @Override
    public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return 0;
    }

    @Override
    public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
        try {
            JsonWriter writer = new JsonWriter(new OutputStreamWriter(entityStream, "UTF-8"));
            writer.setIndent("  ");
            gson.toJson(t, type, writer);
            writer.close();
        }
        catch (Exception e) {
            throw new IOException("Trouble marshalling:" + type.getName(), e);
        }
    }
}

이 코드는 간단합니다. MessageBodyReader와 MessageBodyWriter (또는 하나만)의 두 인터페이스를 구현 한 다음 WebClient를 만들 때이를 "공급자"에 추가합니다. CXF는 거기에서 그것을 계산합니다. 하나의 옵션은 "isReadable ()" "isWriteable ()"메소드에서 "true"를 리턴하는 것입니다. 이렇게하면 CXF가 모든 내장 클래스 대신이 클래스를 사용한다는 것을 보장합니다. 추가 된 제공 업체가 먼저 확인됩니다.

JAX-RS 용 CXF 설정

CXF JAX-RS의 항아리는 메이븐에서 발견된다 :

<!-- https://mvnrepository.com/artifact/org.apache.cxf/cxf-rt-rs-client -->
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-rs-client</artifactId>
    <version>3.1.10</version>
</dependency>

이 항아리 만 있으면 실행할 수 있습니다.

cxf-rt-rs-client-3.1.10.jar
cxf-rt-transports-http-3.1.10.jar
cxf-core-3.1.10.jar
woodstox-core-asl-4.4.1.jar
stax2-api-3.1.4.jar
xmlschema-core-2.2.1.jar
cxf-rt-frontend-jaxrs-3.1.10.jar
javax.ws.rs-api-2.0.1.jar
javax.annotation-api-1.2.jar

클라이언트 필터

필터를 사용하는 좋은 이유 중 하나는 로깅입니다. 이 기술을 사용하면 REST 호출을 쉽게 기록하고 시간을 측정 할 수 있습니다.

public class RestLogger implements ClientRequestFilter, ClientResponseFilter {
    private static final Logger log = LoggerFactory.getLogger(RestLogger.class);

    // Used for timing this call.
    private static final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
    private boolean logRequestEntity;
    private boolean logResponseEntity;

    private static Gson GSON = new GsonBuilder().create();

    public RestLogger(boolean logRequestEntity, boolean logResponseEntity) {
        this.logRequestEntity = logRequestEntity;
        this.logResponseEntity = logResponseEntity;
    }


    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        startTime.set(System.currentTimeMillis());
    }

    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        StringBuilder sb = new StringBuilder();
        sb.append("HTTP:").append(responseContext.getStatus());
        sb.append(" - Time:[").append(System.currentTimeMillis() - startTime.get().longValue()).append("ms]");
        sb.append(" - Path:").append(requestContext.getUri());
        sb.append(" - Content-type:").append(requestContext.getStringHeaders().getFirst(HttpHeaders.CONTENT_TYPE.toString()));
        sb.append(" - Accept:").append(requestContext.getStringHeaders().getFirst(HttpHeaders.ACCEPT.toString()));
        if (logRequestEntity) {
            sb.append(" - RequestBody:").append(requestContext.getEntity() != null ? GSON.toJson(requestContext.getEntity()) : "none");
        }
        if (logResponseEntity) {
            sb.append(" - ResponseBody:").append(this.logResponse(responseContext));
        }
        log.info(sb.toString());
    }

    private String logResponse(ClientResponseContext response) {
        StringBuilder b = new StringBuilder();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        InputStream in = response.getEntityStream();
        try {
            ReaderWriter.writeTo(in, out);
            byte[] requestEntity = out.toByteArray();
            b.append(new String(requestEntity));
            response.setEntityStream(new ByteArrayInputStream(requestEntity));
        }
        catch (IOException ex) {
            throw new ClientHandlerException(ex);
        }
        return b.toString();
    }
}

위에는 응답이 보내지기 전에 요 청이 가로 채어지고 ThreadLocal Long이 설정되어 있음을 볼 수 있습니다. 응답이 반환되면 요청 및 응답과 모든 종류의 관련 데이터를 기록 할 수 있습니다. 물론 Gson 응답 등에서 만 작동하지만 쉽게 수정할 수 있습니다. 이것은 다음과 같이 설정됩니다.

List<Object> providers = new ArrayList<Object>();
providers.add(new GsonMessageBodyProvider());
providers.add(new RestLogger(true, true)); <------right here!

WebClient webClient = WebClient.create(PORTAL_URL, providers);

제공된 로그는 다음과 같습니다.

7278 [main] INFO  blah.RestLogger - HTTP:200 - Time:[1391ms] - User:unknown - Path:https://blah.com/tmet/moduleDescriptions/desc?languageCode=en&moduleId=142 - Content-type:null - Accept:application/json - RequestBody:none - ResponseBody:{"languageCode":"EN","moduleId":142,"moduleDescription":"ECAP"}


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow