0

我很难用一种干净利落的方式来实现这个 JsonParamInjectable。我在这个论坛和其他地方进行了搜索,但没有发现任何提示可以告诉我如何干净利落地实现它。

对于 jaxrs 方法:

public Object add(
        @JsonParam("a") int a,
        @JsonParam("b") int b
)

它将 json {"a":1, "b":2} 解析为参数 a 和 b

为此,我实现了一个 InjectableProvider,它通过方法参数创建一个 JsonInjectable 实例。

@Provider
public class JsonParamProvider implements InjectableProvider<JsonParam, Type> {

private Gson gson;

public JsonParamProvider(@Context ServletConfig sc) throws Exception {
    super();
    this.gson = GsonFactory.newGson(sc);
}

@Override
public Injectable<Object> getInjectable(ComponentContext cc, JsonParam a, Type type) {
    if (a.value() != null) {
        String signature = cc.getAccesibleObject().toString();
        return new JsonInjectable(signature, a, type, gson);
    }
    return null;
}

@Override
public ComponentScope getScope() {
    return ComponentScope.Singleton;
}

魔术是在这个 JsonInjectable 中完成的,它的 where id 做了一个肮脏的把戏:

public class JsonInjectable extends AbstractHttpContextInjectable<Object> {

private final JsonParam param;
private final Type type;
private final String signature;
private final Gson gson;

private static ThreadLocal<WeakReference<HttpContext>> contextCache = new ThreadLocal<WeakReference<HttpContext>>(){
    @Override
    protected WeakReference<HttpContext> initialValue() {
        return new WeakReference<HttpContext>(null);
    }
};
private static ThreadLocal<WeakReference<JsonElement>> entityCache = new ThreadLocal<WeakReference<JsonElement>>(){
    @Override
    protected WeakReference<JsonElement> initialValue() {
        return new WeakReference<JsonElement>(null);
    }
};

public JsonInjectable(String signature, JsonParam param, Type type, Gson gson) {
    this.signature = signature;
    this.param = param;
    this.type = type;
    this.gson = gson;
}

@Override
public Object getValue(HttpContext context) {
    try {
        JsonElement methodJsonElement = entityCache.get().get();
        HttpContext context2 = contextCache.get().get();
        if (context != context2) {
            contextCache.set(new WeakReference<HttpContext>(context));
            String entity = context.getRequest().getEntity(String.class);
            System.out.println("entity:"+entity);
            JsonParser parser = new JsonParser();
            methodJsonElement = parser.parse(entity);
            entityCache.set(new WeakReference<JsonElement>(methodJsonElement));
        }
        if (methodJsonElement == null || methodJsonElement.isJsonNull()) {
            return null;
        }
        final JsonElement valueJsonElement = ((JsonObject)methodJsonElement).get(this.param.value());
        if (valueJsonElement == null || valueJsonElement.isJsonNull()) {
            return null;
        }
        if (this.type.equals(java.lang.Integer.class)) {
            Number number = valueJsonElement.getAsNumber();
            return number.intValue();
        }
        if (this.type.equals(java.lang.String.class)) {
            return valueJsonElement.getAsString();
        }
        Class<?> c = ((Class<?>) this.type);
        if (int.class.equals(c)) {
            return valueJsonElement.getAsInt();
        }

                    //other parsing code...

        //try with gson
        return this.gson.fromJson(valueJsonElement, this.type);

    } catch (RuntimeException e) {
        throw e;
    }
}

问题是,在某些情况下,实体是空的,我怀疑是一个有效的 http 请求。导致 java.io.EOFException: End of input at line 1 column 2. 这个问题出现在生产中,但我无法在测试环境中重现它。

如果有问题,肯定与“context != context2”有关。对于每个可注入对象都绑定到一个参数,并且以我无法控制的顺序调用可注入对象,并且每个可注入对象都处理相同的数据:从请求实体解析 json。所以为了避免每次都重新解析实体,我使用 context != context2 来检测它是否是一个新请求。

检测新请求的好方法是什么,因此每个请求只能发生 1 次 json 解析。

4

2 回答 2

1

在我看来,您的方法太复杂了。所需要的只是定义具有两个字段的 bean,并将其作为单个参数,其中两个字段从 json 填充。

public class TwoParams {
public int a;
public int b;
// instead of public fields you could do proper bean 
// or even make immutable with final
}

public Object add(@JsonParam TwoParams params)
....

使用 Jackson 进行这样的映射是微不足道的,因此甚至不需要手动滚动映射代码。如果您使用 Hibernate Validator,您可以添加注释来验证输入,而无需编写任何额外的代码。

于 2014-07-11T15:45:54.010 回答
1

好的,这个解决方案似乎有效,并且比第一个更干净。感谢您的帮助。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public abstract @interface JsonParam {

    public abstract String value();

}

@Provider
public class JsonParamProvider implements InjectableProvider<JsonParam, Type> {

    private Gson gson;

    public JsonParamProvider(@Context ServletConfig sc) throws Exception {
        super();
        this.gson = GsonFactory.newGson(sc);
    }

    @Override
    public Injectable<Object> getInjectable(ComponentContext cc, JsonParam a, Type type) {
        if (a.value() != null) {
            String signature = cc.getAccesibleObject().toString();
            return new JsonParamInjectable(signature, a, type, gson);
        }
        return null;
    }

    @Override
    public ComponentScope getScope() {
        return ComponentScope.Singleton;
    }

}

@Provider
public class JsonParamDispatchProvider extends AbstractResourceMethodDispatchProvider {

    @Override
    protected InjectableValuesProvider getInjectableValuesProvider(AbstractResourceMethod abstractResourceMethod) {
        return new JsonParamInjectableValuesProvider(processParameters(abstractResourceMethod));
    }

    //copied from EntityParamDispatchProvider source code
    private List<Injectable> processParameters(AbstractResourceMethod method) {

        if ((null == method.getParameters()) || (0 == method.getParameters().size())) {
            return Collections.emptyList();
        }

        boolean hasEntity = false;
        final List<Injectable> is = new ArrayList<Injectable>(method.getParameters().size());
        for (int i = 0; i < method.getParameters().size(); i++) {
            final Parameter parameter = method.getParameters().get(i);

            if (Parameter.Source.ENTITY == parameter.getSource()) {
                hasEntity = true;
                is.add(processEntityParameter(
                        parameter,
                        method.getMethod().getParameterAnnotations()[i]));
            } else {
                is.add(getInjectableProviderContext().
                        getInjectable(method.getMethod(), parameter, ComponentScope.PerRequest));
            }
        }

        if (hasEntity)
            return is;

        // Try to find entity if there is one unresolved parameter and
        // the annotations are unknown
        if (Collections.frequency(is, null) == 1) {
            final int i = is.lastIndexOf(null);
            final Parameter parameter = method.getParameters().get(i);
            if (Parameter.Source.UNKNOWN == parameter.getSource()) {
                if (!parameter.isQualified()) {
                    final Injectable ij = processEntityParameter(
                        parameter,
                        method.getMethod().getParameterAnnotations()[i]);
                    is.set(i, ij);
                }
            }
        }

        return is;
    }

    //copied from EntityParamDispatchProvider source code
    static final class EntityInjectable extends AbstractHttpContextInjectable<Object> {
        final Class<?> c;
        final Type t;
        final Annotation[] as;

        EntityInjectable(Class c, Type t, Annotation[] as) {
            this.c = c;
            this.t = t;
            this.as = as;
        }

        @Override
        public Object getValue(HttpContext context) {
            return context.getRequest().getEntity(c, t, as);
        }
    }

    //copied from EntityParamDispatchProvider source code
    private Injectable processEntityParameter(
            Parameter parameter,
            Annotation[] annotations) {
        return new EntityInjectable(parameter.getParameterClass(),
                parameter.getParameterType(), annotations);
    }

}

@SuppressWarnings("rawtypes")
public class JsonParamInjectableValuesProvider extends InjectableValuesProvider {

    public static final String JSON_ELEMENT_CONTEXT_PROPERTY_KEY = "JsonParamInjectableValuesProvider.jsonElementContextPropertyKey";

    public JsonParamInjectableValuesProvider(List<Injectable> is) {
        super(is);
    }

    /**
     * Get the injectable values.
     *
     * @param context the http contest.
     * @return the injectable values. Each element in the object array
     *         is a value obtained from the injectable at the list index
     *         that is the element index.
     */
    @Override
    public Object[] getInjectableValues(HttpContext context) {
        List<AbstractHttpContextInjectable> is = getInjectables();
        final Object[] params = new Object[is.size()];
        try {
            //parse json element and add it to context
            context.getProperties().put(JSON_ELEMENT_CONTEXT_PROPERTY_KEY, parseJsonElement(context));
            //map jsonElement with injectable
            int index = 0;
            for (AbstractHttpContextInjectable i : is) {
                params[index++] = i.getValue(context);
            }
            return params;
        } catch (WebApplicationException e) {
            throw e;
        } catch (ContainerException e) {
            throw e;
        } catch (RuntimeException e) {
            throw new ContainerException("Exception obtaining parameters", e);
        } finally {
            context.getProperties().remove(JSON_ELEMENT_CONTEXT_PROPERTY_KEY);
        }
    }

    private static JsonElement parseJsonElement(HttpContext context) {
        String entity = context.getRequest().getEntity(String.class);
        if (StringUtils.isBlank(entity)) {
            throw new ContainerException("entity is blank for request " + context.getRequest());
        }
        JsonParser parser = new JsonParser();
        return parser.parse(entity);
    }
}

public class JsonParamInjectable extends AbstractHttpContextInjectable<Object> {

    private final JsonParam param;
    private final Type type;
    private final String signature;
    private final Gson gson;

    public JsonParamInjectable(String signature, JsonParam param, Type type, Gson gson) {
        this.signature = signature;
        this.param = param;
        this.type = type;
        this.gson = gson;
    }

    @Override
    public Object getValue(HttpContext context) {
        try {
            JsonElement jsonElement = (JsonElement) context.getProperties().get(JsonParamInjectableValuesProvider.JSON_ELEMENT_CONTEXT_PROPERTY_KEY);
            if (jsonElement == null) {
                throw new ContainerException("invalid json element in context. " + context.getRequest());
            }
            if (jsonElement == null || jsonElement.isJsonNull()) {
                return null;
            }
            final JsonElement valueJsonElement = ((JsonObject)jsonElement).get(this.param.value());
            if (valueJsonElement == null || valueJsonElement.isJsonNull()) {
                return null;
            }
            if (this.type.equals(java.lang.Integer.class)) {
                Number number = valueJsonElement.getAsNumber();
                return number.intValue();
            }
            if (this.type.equals(java.lang.String.class)) {
                return valueJsonElement.getAsString();
            }
            Class<?> c = ((Class<?>) this.type);
            if (int.class.equals(c)) {
                return valueJsonElement.getAsInt();
            }
            if (long.class.equals(c)) {
                return valueJsonElement.getAsLong();
            }
            if (boolean.class.equals(c)) {
                return valueJsonElement.getAsBoolean();
            }
            //other parsing code...

            //try with gson
            return this.gson.fromJson(valueJsonElement, this.type);

        } catch (RuntimeException e) {
            throw e;
        }
    }

    @Override
    public String toString() {
        return "JsonParamInjectable " + this.hashCode() + " [param=" + this.param + ", type=" + this.type + ", signature=" + this.signature + "]";  }

}
于 2014-07-25T15:51:19.810 回答