JAX-RS客户端可以抛出自定义异常类型吗?

时间:2018-04-13 22:22:39

标签: java rest error-handling jersey jax-rs

我希望我的服务客户端的消费者获得比标准JAX-RS WebApplicationExceptions更有意义的异常,因此他们可以根据500异常的原因执行重试等操作。

我在服务端有一个ExceptionMapper,它将实际的异常作为响应的实体放在返回的异常中,如下所示:

public Response toResponse(InternalException ex) {
    return Response.status(ex.getStatus)
        .entity(ex)
        .type(MediaType.APPLICATION_JSON_TYPE)
        .build();
}

这似乎有效。问题是在客户端中,即使我有一个ClientResponseFilter从响应中提取InternalException并抛出它,JAX-RS将它包装在ProcessingException中,这违背了这一点!

Testcase: badRequest_clientCall_modeledExceptionThrown took 0.272 sec
    Caused an ERROR
Unexpected exception, expected<not.actual.package.InternalException> but was<javax.ws.rs.ProcessingException>
java.lang.Exception: Unexpected exception, expected<not.actual.package.InternalException> but was<javax.ws.rs.ProcessingException>
Caused by: javax.ws.rs.ProcessingException
    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:263)
    at org.glassfish.jersey.client.JerseyInvocation$3.call(JerseyInvocation.java:709)
    <Incredibly secret stuff ommitted>
Caused by: not.actual.package.InternalException

如何实际让这个客户端抛出正确的异常,而不是将最终输出包装在try / catch中(我讨厌这样做,因为它会使重试逻辑复杂化)?

1 个答案:

答案 0 :(得分:1)

您可以使用spring或JavaEE的AOP(JaveEE APO example)。使用@AroundInvoke调用您的服务方法。只需在服务方法中抛出异常即可。我建议您创建自己的例外,例如UsernameInvalidExceptionParameterErrorException 以下是JavaEE的代码示例:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;

import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import java.io.IOException;
import java.lang.reflect.Field;

@YourExceptionHandler
@Interceptor
public class YourExceptionInterceptor {

    @Inject
    private Logger logger;

    @AroundInvoke
    public Object handleException(InvocationContext ctx) {

        //logger.debug("hash:{}", System.identityHashCode(this));
        Result returnResult = new Result();

        Field resultField = null;
        Object result = null;
        Class<? extends Object> returnType = ctx.getMethod().getReturnType();

        try
        {
            logger.info("method:{},return type:{}", ctx.getMethod(), 
                ctx.getMethod().getGenericReturnType());
            returnType = ctx.getMethod().getReturnType();
            result = ctx.proceed(); // this invoke your service
        }
        catch ( UsernameInvalidException e )
        {
            try
            {
                result = returnType.newInstance();
                resultField = result.getClass().getDeclaredField("result");

                if ( resultField == null )
                {
                    return null;
                }

                returnResult.setResultType(ResultType.ERROR);
                returnResult.setResultCode(e.getErrorCode()); // code you defined
             // error text you set in UsernameInvalidException when thrown
                returnResult.setText(e.getMessage()); 
            }
            catch ( Exception e1 )
            {
                e1.printStackTrace();
            }
        }
        catch ( Exception e ) // catch other unexpected exceptions
        {
            e.printStackTrace();
        }

        try
        {
            if ( resultField != null )
            {
                resultField.setAccessible(true);
                resultField.set(result, returnResult);
            }
        }
        catch ( Exception e )
        {
            e.printStackTrace();
        }

        return result;
    }

}

定义您的例外:

public class BaseException extends RuntimeException {

    protected static final String SCANPAY_EXCEPTION_CODE = "300";

    protected static final String BASE_EXCEPTION_CODE = "400";        

    protected static final String USERNAME_INVALID_EXCEPTION_CODE = "405";

    protected static final String DAO_EXCEPTION_CODE = "401";

    protected static final String SERVICE_NOT_AVAILABLE_CODE = "414";

    protected static final String PARAMETER_ERROR_CODE = "402";

    protected String errorCode;

    public BaseException() {

        initErrorCode();
    }

    public BaseException(String message) {

        super(message);
        initErrorCode();
    }

    public BaseException(Throwable cause) {

        super(cause);
        initErrorCode();
    }

    public BaseException(String message, Throwable cause) {

        super(message, cause);
        initErrorCode();
    }

    public BaseException(String message, Throwable cause,
        boolean enableSuppression, boolean writableStackTrace) {

        super(message, cause, enableSuppression, writableStackTrace);
        initErrorCode();
    }

    protected void initErrorCode() {

        errorCode = BASE_EXCEPTION_CODE;
    }

    public String getErrorCode() {

        return errorCode;
    }

}

您的UsernameInvalidExcepton:

@SuppressWarnings("serial")
public class UsernameInvalidExcepton extends BaseException {

    @Override
    @SuppressWarnings("static-access")
    protected void initErrorCode() {

        this.errorCode = this.USERNAME_INVALID_EXCEPTIO_CODE;
    }

    public UsernameInvalidException(String message) {

        super(message);
    }

定义您自己的ExceptionHandler,它是一个注释(请参阅此处custom annotation):

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.interceptor.InterceptorBinding;

@InterceptorBinding
@Inherited
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@Documented
public @interface YourExceptionHandler {

}

定义一个异常抛出工厂(这个工厂不是必需的,它只是用于抛出新的不同异常。当你需要抛出它时可以出现新的异常。):

public abstract class YourExceptionThrowFactory {

    private YourExceptionThrowFactory() {

    }

    public static void throwUsernameInvalidException(String message) {

        throw new UsernameInvalidException(message);
    }

如何使用它? 在您的服务方法中,使用注释:

@YourExceptionHandler
public UserInfo getUserInfo(String username) {
   // your service code
   if (username == null) {
    YourExceptionThrowFactory.throwUsernameInvalidException(
        "Username is not valid");  // throw your exception with your message.
}
}

然后对于客户端,它将获取您的错误代码和错误消息。

上面的所有代码都是针对JavaEE的,如果你使用spring,你会发现相应的注释和技术。