2014-04-29 1 views
0

이 JsonParamInjectable을 구현하는 데는 훌륭하고 깨끗한 방법이 필요합니다. 나는이 포럼과 다른 곳에서 수색했지만 아무렇지도 않게 그것을 구현하는 법을 알려주는 힌트는 찾지 못했다. 내가 InjectableProvider을 구현하는 일이 들어 A와 B메소드 multi param injectable의 사용자 정의 InjectableProvider, 구문 분석 오류

를 param으로 대체

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

그것이 JSON {1, "B"2 "A"를} 파싱하십시오 jaxrs의 방법

메소드 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; 
} 

마법이 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; 
    } 
} 

, 일부의 경우 개체가 비어에, 난, 유효한 HTTP 요청을 의심을 . 결과는 java.io.EOFException : 1 행 2 열의 입력 끝.이 문제는 프로덕션 환경에서 발생하지만 env 테스트에서이를 재현 할 수 없습니다.

문제가있는 경우 "context! = context2"와 반드시 관련이 있습니다. 각각의 주 사용 가능 인자는 param에 바인드되어 있고 주사 가능 인자는 제어 할 수없는 순서로 호출되며, 각각의 주 사용 가능 데이터는 요청 데이터에서 파싱 된 json입니다. 따라서 매번 엔티티를 다시 구문 분석하지 않으려면 context! = context2를 사용하여 새로운 요청인지 확인하십시오.

새 요청을 감지하는 훌륭하고 깨끗한 방법은 json 파싱이 요청 당 1 회만 발생할 수 있기 때문입니다.

답변

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

} 
1

나는 당신의 접근 방식이 너무 복잡하다고 생각합니다. 두 필드가있는 빈을 정의하고 두 필드가 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) 
.... 

그런 매핑은 잭슨을 사용하면 사소한 것이므로 매핑 코드를 손으로 돌릴 필요조차 없습니다. Hibernate Validator를 사용하는 경우 추가 코드를 쓰지 않고도 입력을 검증하기 위해 주석을 추가 할 수 있습니다.

+0

내 접근 방식은 실제로 복잡하지만이 방식으로 제작하는 데 많은 코드가 있습니다. 변화를 만드는 것은 정말로 시간이 많이 걸릴 것입니다. 나는 그렇게 할 수있는 길을 찾았다. 나는 그렇게 생각한다. 그것은 내 junit 테스트를 통과하므로, 생산을 통과한다면, 여기에 게시하십시오. – sstlaurent

+1

코드를 다시 보면 WeakReference를 사용하는 것이 문제가 될 수 있습니다. JVM의 메모리가 부족하고 GC가 첫 번째와 두 번째 매개 변수를 가져 오는 사이에 null을 반환하게되는 경우입니다. 이것은 매우 간헐적이며 대부분 바쁜 서버에서 발생합니다. 다음 요청이 덮어 쓸 때까지 마지막 객체를 유지할 수있는 일반 참조를 사용할 수는 있지만, 그렇게 크지 않은 경우에는 매우 추한 빠른 수정이 될 수 있습니다. – Swav

관련 문제