简体   繁体   English

用于方法多参数注入的自定义InjectableProvider,解析时出错

[英]Custom InjectableProvider for method multi param injectable, Error in parsing

I have a hard time having a nice and clean way to implement this JsonParamInjectable. 我很难有一个很好的方法来实现这个JsonParamInjectable。 I searched on this forum and elsewhere but found nowhere an hint that would tell me how to implement it nice and clean. 我在这个论坛和其他地方搜索过,但发现没有任何暗示可以告诉我如何实现它干净整洁。

For a jaxrs method : 对于jaxrs方法:

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

It parse a json {"a":1, "b":2} to param a and b 它将json {“a”:1,“b”:2}解析为param a和b

For this to work i implement an InjectableProvider, which create one JsonInjectable instance by method param. 为此,我实现了一个InjectableProvider,它通过方法param创建一个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;
}

The magic is done in this JsonInjectable, and its where id did a dirty trick : 魔术是在这个JsonInjectable中完成的,其中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;
    }
}

The problem is, in some case entity is empty for, i suspect, a valid http request. 问题是,在某些情况下,实体是空的,我怀疑是有效的http请求。 Resulting in java.io.EOFException: End of input at line 1 column 2. This problem arise in production, but i am unable to reproduce it in testing env. 导致java.io.EOFException:第1行第2列的输入结束。生产中出现此问题,但我无法在测试环境中重现它。

If there is a problem, it is surely related with "context != context2". 如果有问题,肯定与“context!= context2”有关。 For each injectable is binded to a param and injectables are called in an order i dont control, and each injectable work on the same data : parsed json from request entity. 对于每个注射剂被绑定到参数并且注射剂以我不控制的顺序被调用,并且每个注射剂在相同的数据上工作:从请求实体解析json。 So to avoid re-parsing entity each time, i use context != context2 to detect if its a new request. 因此,为了避免每次重新解析实体,我使用context!= context2来检测它是否是新请求。

What is the nice and clean way to detect a new request so json parsing can only occur 1 time per request. 检测新请求的好方法是什么,所以json解析每次请求只能发生一次。

It seems to me like your approach is way too complicated. 在我看来,你的方法太复杂了。 All is required is to define bean with two fields and have that as a single param with two fields populated from json. 所有必需的是定义具有两个字段的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)
....

Mapping like that is trivial using Jackson so don't even need to hand roll the mapping code. 使用Jackson这样的映射是微不足道的,所以甚至不需要手动映射代码。 If you use Hibernate Validator you could add annotations to validate input without writing any extra code. 如果您使用Hibernate Validator,您可以添加注释来验证输入,而无需编写任何额外的代码。

Ok this solution seem to work and is cleaner than the first one. 好的,这个解决方案似乎比第一个解决方案更有效。 Thanks for helping. 谢谢你的帮助。

@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 + "]";  }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM