JAX-RS로 DRY 유지
동일한 경로 및 쿼리 매개 변수가 필요한 여러 JAX-RS 리소스 핸들러에 대해 반복되는 코드를 최소화하려고합니다. 각 리소스의 기본 URL 템플릿은 다음과 같습니다.
/{id}/resourceName
각 리소스에는 여러 하위 리소스가 있습니다.
/{id}/resourceName/subresourceName
따라서 리소스 / 하위 리소스 경로 (쿼리 매개 변수 포함)는 다음과 같을 수 있습니다.
/12345/foo/bar?xyz=0
/12345/foo/baz?xyz=0
/12345/quux/abc?xyz=0
/12345/quux/def?xyz=0
일반적인 자원에서 부품 foo
및 quux
있다 @PathParam("id")
및 @QueryParam("xyz")
. 다음 과 같은 리소스 클래스를 구현할 수 있습니다 .
// FooService.java
@Path("/{id}/foo")
public class FooService
{
@PathParam("id") String id;
@QueryParam("xyz") String xyz;
@GET @Path("bar")
public Response getBar() { /* snip */ }
@GET @Path("baz")
public Response getBaz() { /* snip */ }
}
// QuuxService.java
@Path("/{id}/quux")
public class QuxxService
{
@PathParam("id") String id;
@QueryParam("xyz") String xyz;
@GET @Path("abc")
public Response getAbc() { /* snip */ }
@GET @Path("def")
public Response getDef() { /* snip */ }
}
모든 단일 get*
메서드에 매개 변수 주입을 반복하지 않도록 관리했습니다 . 1 이것은 좋은 시작이지만 리소스 클래스 간의 반복도 피할 수 있기를 바랍니다. (나는 또한 필요가있는)을 사용하는 것입니다 CDI와 함께 작동하는 방법 abstract
기본 클래스 FooService
와이 QuuxService
수를 extend
:
// BaseService.java
public abstract class BaseService
{
// JAX-RS injected fields
@PathParam("id") protected String id;
@QueryParam("xyz") protected String xyz;
// CDI injected fields
@Inject protected SomeUtility util;
}
// FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
@GET @Path("bar")
public Response getBar() { /* snip */ }
@GET @Path("baz")
public Response getBaz() { /* snip */ }
}
// QuuxService.java
@Path("/{id}/quux")
public class QuxxService extends BaseService
{
@GET @Path("abc")
public Response getAbc() { /* snip */ }
@GET @Path("def")
public Response getDef() { /* snip */ }
}
get*
메서드 내 에서 CDI 주입 (기적적으로)이 올바르게 작동합니다. util
필드가 null이 아닙니다. 불행히도 JAX-RS 인젝션은 작동 하지 않습니다 . id
하고 xyz
있다 null
의 get*
방법 FooService
과 QuuxService
.
이 문제에 대한 수정 또는 해결 방법이 있습니까?
CDI가 내가 원하는대로 작동한다는 점을 감안할 때 @PathParam
s (등)를 서브 클래스 에 삽입하지 못하는 것이 버그인지 아니면 JAX-RS 사양의 일부인지 궁금합니다 .
이미 시도한 또 다른 방법은 사용하고 BaseService
대의원 것을 단일 진입 점으로 FooService
하고이 QuuxService
같은 필요합니다. 이는 기본적으로 하위 리소스 로케이터를 사용 하는 JAX-RS가 있는 RESTful Java에 설명 된대로입니다 .
// BaseService.java
@Path("{id}")
public class BaseService
{
@PathParam("id") protected String id;
@QueryParam("xyz") protected String xyz;
@Inject protected SomeUtility util;
public BaseService () {} // default ctor for JAX-RS
// ctor for manual "injection"
public BaseService(String id, String xyz, SomeUtility util)
{
this.id = id;
this.xyz = xyz;
this.util = util;
}
@Path("foo")
public FooService foo()
{
return new FooService(id, xyz, util); // manual DI is ugly
}
@Path("quux")
public QuuxService quux()
{
return new QuuxService(id, xyz, util); // yep, still ugly
}
}
// FooService.java
public class FooService extends BaseService
{
public FooService(String id, String xyz, SomeUtility util)
{
super(id, xyz, util); // the manual DI ugliness continues
}
@GET @Path("bar")
public Response getBar() { /* snip */ }
@GET @Path("baz")
public Response getBaz() { /* snip */ }
}
// QuuxService.java
public class QuuzService extends BaseService
{
public FooService(String id, String xyz, SomeUtility util)
{
super(id, xyz, util); // the manual DI ugliness continues
}
@GET @Path("abc")
public Response getAbc() { /* snip */ }
@GET @Path("def")
public Response getDef() { /* snip */ }
}
이 접근 방식의 단점은 CDI 주입이나 JAX-RS 주입이 하위 리소스 클래스에서 작동하지 않는다는 것입니다. 그 이유는 상당히 분명합니다 2 , 그러나 이것이 의미 하는 것은 필드를 하위 클래스의 생성자에 수동으로 다시 주입해야 한다는 것입니다. 이는 지저분하고 추하며 추가 주입을 쉽게 사용자 지정할 수 없습니다. 예 : I want to @Inject
an instance into FooService
but not QuuxService
. 의 하위 클래스를 명시 적으로 인스턴스화하고 있기 때문에 BaseService
CDI 주입이 작동하지 않으므로 추악함이 계속됩니다.
tl; dr JAX-RS 리소스 핸들러 클래스에 반복적으로 필드를 삽입하는 것을 방지하는 올바른 방법은 무엇입니까?
그리고 왜 CDI는 이것에 문제가 없는데 JAX-RS에 의해 주입 된 상속 된 필드가 없습니까?
편집 1
@Tarlog 의 약간의 지시 로 내 질문 중 하나에 대한 답을 찾은 것 같습니다.
상속 된 필드가 JAX-RS에 의해 주입되지 않는 이유는 무엇입니까?
에서 JSR-311 §3.6 :
서브 클래스 또는 구현 메소드에 JAX-RS 어노테이션 이있는 경우 수퍼 클래스 또는 인터페이스 메소드의 모든 어노테이션이 무시됩니다.
이 결정에 대한 실제 이유가 있다고 확신하지만, 불행히도이 특정 사용 사례에서 그 사실이 저에게 불리하게 작용하고 있습니다. 가능한 해결 방법에 여전히 관심이 있습니다.
1 필드 수준 주입 사용시주의 할 점은 이제 요청 당 리소스 클래스 인스턴스화에 묶여 있지만 그와 함께 살 수 있다는 것입니다.
2new FooService()
컨테이너 / JAX-RS 구현이 아닌 호출 이기 때문입니다.
다음은 내가 사용중인 해결 방법입니다.
'id'및 'xyz'를 매개 변수로 사용하여 BaseService에 대한 생성자를 정의하십시오.
// BaseService.java
public abstract class BaseService
{
// JAX-RS injected fields
protected final String id;
protected final String xyz;
public BaseService (String id, String xyz) {
this.id = id;
this.xyz = xyz;
}
}
주입을 사용하여 모든 하위 클래스에서 생성자를 반복합니다.
// FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
public FooService (@PathParam("id") String id, @QueryParam("xyz") String xyz) {
super(id, xyz);
}
@GET @Path("bar")
public Response getBar() { /* snip */ }
@GET @Path("baz")
public Response getBaz() { /* snip */ }
}
보면 잭스의 JIRA 누군가가 JAX-RS에 대한 이정표와 같은 주석 상속을 요구 보인다.
찾고있는 기능이 아직 JAX-RS에 존재하지 않지만 작동할까요? 추악하지만 반복되는 주사를 방지합니다.
public abstract class BaseService
{
// JAX-RS injected fields
@PathParam("id") protected String id;
@QueryParam("xyz") protected String xyz;
// CDI injected fields
@Inject protected SomeUtility util;
@GET @Path("bar")
public abstract Response getBar();
@GET @Path("baz")
public abstract Response getBaz();
@GET @Path("abc")
public abstract Response getAbc();
@GET @Path("def")
public abstract Response getDef();
}
// FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
public Response getBar() { /* snip */ }
public Response getBaz() { /* snip */ }
}
// QuuxService.java
@Path("/{id}/quux")
public class QuxxService extends BaseService
{
public Response getAbc() { /* snip */ }
public Response getDef() { /* snip */ }
}
또는 다른 해결 방법 :
public abstract class BaseService
{
@PathParam("id") protected String id;
@QueryParam("xyz") protected String xyz;
// CDI injected fields
@Inject protected SomeUtility util;
@GET @Path("{stg}")
public abstract Response getStg(@Pathparam("{stg}") String stg);
}
// FooService.java
@Path("/{id}/foo")
public class FooService extends BaseService
{
public Response getStg(String stg) {
if(stg.equals("bar")) {
return getBar();
} else {
return getBaz();
}
}
public Response getBar() { /* snip */ }
public Response getBaz() { /* snip */ }
}
그러나 솔직히 당신이 얼마나 감동적인지 볼 때이 추악한 코드로 인해 좌절감이 사라질 것 같지 않습니다. :)
RESTEasy에서는 클래스를 생성하고 평소처럼 @ * Param으로 주석을 달고 @Form 클래스에 주석을 달아 마무리 할 수 있습니다. 이 @Form 클래스는 다른 서비스의 메서드 호출에 대한 매개 변수 삽입이 될 수 있습니다. http://docs.jboss.org/resteasy/docs/2.3.5.Final/userguide/html/_Form.html
나는 항상 어노테이션 상속이 내 코드를 읽을 수 없게 만든다는 느낌이 들었다. 주입되는 위치 / 방법 (예 : 상속 트리의 어느 수준에서 주입 될 것인지, 대체 된 위치 (또는 모두)). 또한 변수를 보호 (아마도 최종적이지 않음)해야합니다. 이렇게하면 수퍼 클래스가 내부 상태를 누출하고 일부 버그가 발생할 수 있습니다 (적어도 확장 된 메서드를 호출 할 때 항상 스스로에게 묻습니다. ?). IMHO 그것은 DRY와 함께 아무것도 없습니다. 이것은 논리의 캡슐화가 아니라 주입의 캡슐화이기 때문에 과장된 것 같습니다.
마지막으로 JAX-RS 사양 인 3.6 Annotation Inheritance를 인용하겠습니다.
다른 Java EE 사양과의 일관성을 위해 주석 상속에 의존하는 대신 항상 주석을 반복하는 것이 좋습니다.
추신 : 나는 때때로 주석 상속을 사용하지만 메서드 수준에서 사용한다는 것을 인정합니다 :)
특히 AbstractHttpContextInjectable을 통해 사용자 지정 공급자를 추가 할 수 있습니다.
// FooService.java
@Path("/{id}/foo")
public class FooService
{
@Context CommonStuff common;
@GET @Path("bar")
public Response getBar() { /* snip */ }
@GET @Path("baz")
public Response getBaz() { /* snip */ }
}
@Provider
public class CommonStuffProvider
extends AbstractHttpContextInjectable<CommonStuff>
implements InjectableProvider<Context, Type>
{
...
@Override
public CommonStuff getValue(HttpContext context)
{
CommonStuff c = new CommonStuff();
c.id = ...initialize from context;
c.xyz = ...initialize from context;
return c;
}
}
물론 HttpContext에서 어려운 방법으로 경로 매개 변수 및 / 또는 쿼리 매개 변수를 추출해야하지만 한 곳에서 한 번만 수행합니다.
매개 변수 주입을 피하는 동기는 무엇입니까?
동기 부여가 하드 코딩 된 문자열을 반복하지 않는 것이므로 쉽게 이름을 바꿀 수있는 경우 "상수"를 재사용 할 수 있습니다.
// FooService.java
@Path("/" + FooService.ID +"/foo")
public class FooService
{
public static final String ID = "id";
public static final String XYZ= "xyz";
public static final String BAR= "bar";
@PathParam(ID) String id;
@QueryParam(XYZ) String xyz;
@GET @Path(BAR)
public Response getBar() { /* snip */ }
@GET @Path(BAR)
public Response getBaz() { /* snip */ }
}
// QuuxService.java
@Path("/" + FooService.ID +"/quux")
public class QuxxService
{
@PathParam(FooService.ID) String id;
@QueryParam(FooService.XYZ) String xyz;
@GET @Path("abc")
public Response getAbc() { /* snip */ }
@GET @Path("def")
public Response getDef() { /* snip */ }
}
(두 번째 답변을 게시 해 주셔서 죄송 합니다만, 이전 답변의 댓글에 넣기에는 너무 깁니다)
You can try @BeanParam for all the repeating params. so rather than injecting them every time you can simply inject you customBean which will do the trick.
Another approach which is more cleaner is that you can inject
@Context UriInfo
or
@Context ExtendedUriInfo
to your Resource Class and in very method you can simply access them. UriInfo is more flexible because your jvm will have one less java source file to manage and above all single instance of UriInfo or ExtendedUriInfo gives you a handle of a lot of things.
@Path("test")
public class DummyClass{
@Context UriInfo info;
@GET
@Path("/{id}")
public Response getSomeResponse(){
//custom code
//use info to fetch any query, header, matrix, path params
//return response object
}
Instead of using @PathParam
, @QueryParam
or any other param, you can use @Context UriInfo
to access any types of parameters. So your code could be:
// FooService.java
@Path("/{id}/foo")
public class FooService
{
@Context UriInfo uriInfo;
public static String getIdParameter(UriInfo uriInfo) {
return uriInfo.getPathParameters().getFirst("id");
}
@GET @Path("bar")
public Response getBar() { /* snip */ }
@GET @Path("baz")
public Response getBaz() { /* snip */ }
}
// QuuxService.java
@Path("/{id}/quux")
public class QuxxService
{
@Context UriInfo uriInfo;
@GET @Path("abc")
public Response getAbc() { /* snip */ }
@GET @Path("def")
public Response getDef() { /* snip */ }
}
Pay attention that getIdParameter
is static, so you can put it in some utility class and reuse accorss multiple classes.
UriInfo is guaranteed to be threadsafe, so you can keep resource class as singleton.
참고URL : https://stackoverflow.com/questions/5875772/staying-dry-with-jax-rs
'IT Share you' 카테고리의 다른 글
Avian을 사용하는 iOS의 Scala (0) | 2020.12.05 |
---|---|
YouTube iframe API 콘솔 오류를 제거하는 방법-Chrome 콘솔에서 "원본이있는 프레임 차단…" (0) | 2020.12.05 |
Xcode 9 iOS 11 BoringSSL SSL_ERROR_ZERO_RETURN (0) | 2020.12.05 |
접속사와 패턴 매칭 (PatternA AND PatternB) (0) | 2020.12.05 |
참조 : MySQL 확장을 사용하는 완벽한 코드 샘플은 무엇입니까? (0) | 2020.12.05 |