[Java] Java EE 7 and JAX-RS 2.0

Posted by Unknown Selasa, 16 April 2013 0 komentar
原文はこちら。
http://www.oracle.com/technetwork/articles/java/jaxrs20-1929352.html

この記事はJava EE 7の新機能を紹介するものです。詳細はJava.netのJava EE Platform Specificationから情報を入手して下さい。
Java EE Platform Specification
http://java.net/projects/javaee-spec/pages/Home
サンプルコード (Zip)
ほとんどのJava EE 6アプリケーションでリモートAPIと自由選択を要件とする場合、多かれ少なかれJAX-RS 1.0仕様のRESTfulな趣を使っています。Java EE 7とJAX-RS 2.0により、種々の有用な機能がもたらされ、さらに開発が簡単になり、もっと洗練された、しかも効率的なJava SE/EE RESTfulアプリケーションを作成することができます。

Roast House

Roast HouseとはJavaフレンドリーではあるもののシンプルなJAX-RS 2.0のサンプルで、コーヒー豆を管理し、煎るサンプルです。 このroast house自体はCoffeeBeansResourceとして表現されています。"coffeebeans"というURIは、CoffeeBeansResourceを一意に識別します(コード1を参照)。
コード1
//...
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
@ApplicationScoped
@Path("coffeebeans")
public class CoffeeBeansResource {

@Context
ResourceContext rc;

Map<String, Bean> bc;

@PostConstruct
public void init() {
this.bc = new ConcurrentHashMap<>();
}

@GET
public Collection<Bean> allBeans() {
return bc.values();
}

@GET
@Path("{id}")
public Bean bean(@PathParam("id") String id) {
return bc.get(id);
}

@POST
public Response add(Bean bean) {
if (bean != null) {
bc.put(bean.getName(), bean);
}
final URI id = URI.create(bean.getName());
return Response.created(id).build();
}

@DELETE
@Path("{id}")
public void remove(@PathParam("id") String id) {
bc.remove(id);
}

@Path("/roaster/{id}")
public RoasterResource roaster(){
return this.rc.initResource(new RoasterResource());
}
}
以前のJAX-RS仕様では、リソースは@Singleton もしくは @Stateless EJBとすることができます。さらに、すべてのルートリソースやプロバイダー、Applicationのサブクラスをmanaged beanもしくはCDI-managed beanとしてデプロイすることができます。@Providerアノテーションをつけたすべての拡張でInjectionも利用できます。これを使うと既存のコードとの統合が簡単になります。JAX-RS固有のコンポーネントをResourceContextを使ってサブリソースに注入することも可能です。
コード2
@Context
ResourceContext rc;

@Path("/roaster/{id}")
public RoasterResource roaster(){
return this.rc.initResource(new RoasterResource());
}
興味深いことに、javax.ws.rs.container.ResourceContextを使うと、JAX-RSの情報を既存インスタンスに注入できるだけでなく、ResourceContext#getResource(Class<T> resourceClass)メソッドをもつリソースクラスにアクセスすることもできます。JAX-RSランタイムが現在のコンテキストからResourceContext#initResourceメソッドに渡されるインスタンスの注入ポイントを値で設定します。RoasterResourceクラス(コード3参照)のidというString型のフィールドは、親リソースのパスパラメータの値を受け取ります。
コード3
public class RoasterResource {

@PathParam("id")
private String id;

@POST
public void roast(@Suspended AsyncResponse ar, Bean bean) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
bean.setType(RoastType.DARK);
bean.setName(id);
bean.setBlend(bean.getBlend() + ": The dark side of the bean");
Response response = Response.ok(bean).header("x-roast-id", id).build();
ar.resume(response);
}
}
javax.ws.rs.container.AsyncResponseというパラメータは、Servlet 3.0のjavax.servlet.AsyncContextクラスと類似しており、非同期リクエストの実行が可能です。上の例では、リクエストを処理時間の間遅延させ、AsyncResponse#resumeメソッドの呼び出しによりクライアントへレスポンスを返しています。roastメソッド自体は同期実行されるため、非同期実行による挙動は全く現れませんが、EJBの@javax.ejb.Asynchronous アノテーションと@Suspended AsyncResponseアノテーションの組み合わせを使うと、関心のあるクライアントの最終的な通知をする、業務ロジックを非同期で実行することができます。任意のJAX-RSルートリソースを@Stateless or @Singleton アノテーションで修飾することができ、事実上、EJBとして機能することができます(コード4を参照)。
コード4
import javax.ejb.Asynchronous;
import javax.ejb.Singleton;

@Stateless
@Path("roaster")
public class RoasterResource {

@POST
@Asynchronous
public void roast(@Suspended AsyncResponse ar, Bean bean) {
//heavy lifting
Response response = Response.ok(bean).build();
ar.resume(response);
}
}
@Suspended AsyncResponse パラメータをもつ@Asynchronousリソースのメソッドをfire-and-forgetの一方向非同期型で実行します。リクエスト処理スレッドは即座に解放されますが、AsyncResponse によりは以前としてクライアントに対し便利なハンドルを提供します。時間のかかる作業が終わってから、結果をクライアントにタイミングよく返すことができます。通常の場合、JAX-RS固有の挙動を実際の業務ロジックから切り離したいと思うことでしょう。すべての業務ロジックは簡単に専用の境界のEJBに展開できますが、CDIのイベントを使うとこのfire-and-forgetの場合にずっとよく適合することができます。カスタムイベントクラスであるRoastRequestは処理対象の入力として(Beanクラスの)ペイロードをとり、結果の送信のためにAsyncResponseとります(コード5参照)。
コード5
public class RoastRequest {

private Bean bean;
private AsyncResponse ar;

public RoastRequest(Bean bean, AsyncResponse ar) {
this.bean = bean;
this.ar = ar;
}

public Bean getBean() {
return bean;
}

public void sendMessage(String result) {
Response response = Response.ok(result).build();
ar.resume(response);
}

public void errorHappened(Exception ex) {
ar.resume(ex);
}
}
CDIイベントは業務ロジックとJAX-RSのAPIを切り離すだけでなく、JAX-RSのコードを非常にシンプルにします(コード6参照)。
コード6
public class RoasterResource {

@Inject
Event<RoastRequest> roastListeners;

@POST
public void roast(@Suspended AsyncResponse ar, Bean bean) {
roastListeners.fire(new RoastRequest(bean, ar));
}
}
すべてのCDI managed beanやEJBはpublish-subscribe型のRoastRequestを受け取ることができ、同期・非同期とわず、簡単なvoid onRoastRequest(@Observes RoastRequest request){}というobserverメソッドを使ってペイロードを処理することができます。AsyncResponseクラスを使って、JAX-RS仕様はHTTPプロトコルでリアルタイムで情報をプッシュする簡単な方法を導入しています。クライアントの観点からすると、サーバーでの非同期リクエストは未だにブロックされているため同期型です。REST設計の立場からすれば、すべての長時間実行しているタスクは処理が完了した後に結果を入手する方法に関する追加情報とともに、HTTPステータスコード202で即座に返すべきでしょう。

The Return of Aspects

人気のあるREST APIは多くの場合、クライアントがメッセージのフィンガープリントを計算し、リクエストと一緒に送信する必要があります。サーバー側では、フィンガープリントを計算し、添付された情報と比較します。両方が一致しない場合、メッセージを拒否します。JAX-RSが出てきて、javax.ws.rs.ext.ReaderInterceptor javax.ws.rs.ext.WriterInterceptorの導入に伴い、トラフィックをクライアント側でもサーバ側でも傍受される可能性があります。サーバー上のReaderInterceptorインタフェースの実装はMessageBodyReader#readFromをラップしており、実際のシリアル化前に実行されます。
The PayloadVerifier はヘッダから署名を取り出し、ストリームからフィンガープリントを計算し、最終的にReaderInterceptorContext#proceedメソッドを呼び出します。このメソッドはチェーンの次のインターセプターもしくは MessageBodyReaderインスタンスを呼び出します(コード7参照)。
コード7
public class PayloadVerifier implements ReaderInterceptor{

public static final String SIGNATURE_HEADER = "x-signature";

@Override
public Object aroundReadFrom(ReaderInterceptorContext ric) throws IOException,
WebApplicationException {
MultivaluedMap<String, String> headers = ric.getHeaders();
String headerSignagure = headers.getFirst(SIGNATURE_HEADER);
InputStream inputStream = ric.getInputStream();
byte[] content = fetchBytes(inputStream);
String payload = computeFingerprint(content);
if (!payload.equals(headerSignagure)) {
Response response = Response.status(Response.Status.BAD_REQUEST).header(
SIGNATURE_HEADER, "Modified content").build();
throw new WebApplicationException(response);
}
ByteArrayInputStream buffer = new ByteArrayInputStream(content);
ric.setInputStream(buffer);
return ric.proceed();
}
//...
}
変更されたコンテンツは結果として異なるフィンガープリントになるため、BAD_REQUEST (400) というレスポンスコードとともにWebApplicationExceptionを例外として送出する原因になります。
すべてのフィンガープリントの計算や送出するリクエストの計算をWriterInterceptorの実装により簡単に自動化できます。WriterInterceptorの実装はMessageBodyWriter#writeToをラップしており、この実装はエンティティをシリアル化してストリームに投入する前に実行されます。フィンガープリントの計算については、送信中のエンティティの最終的な表現を必要とするので、バッファとしてのByteArrayOutputStreamに渡し、WriterInterceptorContext#proceed()メソッドを呼び出し、生のコンテンツを取り出してフィンガープリントを計算します(コード8参照)。
コード8
public class PayloadVerifier implements WriterInterceptor {
public static final String SIGNATURE_HEADER = "x-signature";

@Override
public void aroundWriteTo(WriterInterceptorContext wic) throws IOException,
WebApplicationException {
OutputStream oos = wic.getOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wic.setOutputStream(baos);
wic.proceed();
baos.flush();
byte[] content = baos.toByteArray();
MultivaluedMap<String, Object> headers = wic.getHeaders();
headers.add(SIGNATURE_HEADER, computeFingerprint(content));
oos.write(content);

}
//...
}
最終的に、計算された署名をリクエストにヘッダとして追加し、バッファを元のストリームに書き出します。その上で、リクエスト全体をクライアントに送信します。当然ながら、一つのクラスで同時に両方のインターフェースを実装することもできます。
コード9
import javax.ws.rs.ext.Provider;
@Provider
public class PayloadVerifier implements ReaderInterceptor, WriterInterceptor {
}
以前のJAX-RSリリースのように、カスタム拡張を自動的に発見し、@Providerアノテーションを使って登録します。MessageBodyWriterMessageBodyReaderインスタンスの傍受については、ReaderInterceptorWriterInterceptorの実装のみを@Providerアノテーションで修飾すればよく、追加の構成やAPIの呼び出しは不要です。

Request Interception

ContainerRequestFilterContainerResponseFilterの実装はエンティティの読み書き処理だけではなく、リクエスト全体を傍受します。両インターセプターの機能は、生のjavax.servlet.http.HttpServletRequestインスタンスに含まれている情報のロギングよりも、はるかに便利です。TrafficLoggerクラスはHttpServletRequestに含まれる情報をログに記録するだけでなく、特定のリクエストに一致するリソースに関する情報をトレースすることもできます(コード10参照)。
コード10
@Provider
public class TrafficLogger implements ContainerRequestFilter, ContainerResponseFilter {

//ContainerRequestFilter
public void filter(ContainerRequestContext requestContext) throws IOException {
log(requestContext);
}
//ContainerResponseFilter
public void filter(ContainerRequestContext requestContext, ContainerResponseContext
responseContext) throws IOException {
log(responseContext);
}

void log(ContainerRequestContext requestContext) {
SecurityContext securityContext = requestContext.getSecurityContext();
String authentication = securityContext.getAuthenticationScheme();
Principal userPrincipal = securityContext.getUserPrincipal();
UriInfo uriInfo = requestContext.getUriInfo();
String method = requestContext.getMethod();
List<Object> matchedResources = uriInfo.getMatchedResources();
//...
}

void log(ContainerResponseContext responseContext) {
MultivaluedMap<String, String> stringHeaders = responseContext.getStringHeaders();
Object entity = responseContext.getEntity();
//...
}
}
従って、登録されたContainerResponseFilterの実装はContainerResponseContextのインスタンスを取得して、サーバーが生成したデータにアクセスすることができます。ステータスコードとヘッダーの内容、例えばLocationヘッダーは簡単にアクセスできます。ContainerRequestContextContainerResponseContextはフィルターによって変更される可能性のある可変クラスです。
追加の構成をせずに、ContainerRequestFilterをHTTPーリソース照合フェーズの後で実行します。この時点ではもはや入ってくるリクエストを変更してリソースバインディングをカスタマイズすることはできません。リクエストとリソース間のバインディングに影響を与えたい場合には、ContainerRequestFilterを構成し、リソースバインディングフェーズの前に呼び出されるようにすればよいのです。javax.ws.rs.container.PreMatchingアノテーションで修飾されている任意のContainerRequestFilterをリソースバインディングの前に実行するので、HTTPリクエストコンテンツを所望のマッピングに微調整することができます。よくある@PreMatchingフィルターのユースケースではHTTP同士を調整し、ネットワーク基盤の制限を克服しています。PUTOPTIONSHEADDELETEのような、より「難解」なメソッドをファイアウォールでフィルタリングしたり、HTTPクライアントでサポートしなかったりする可能性があります。@PreMatching ContainerRequestFilter実装は所望のHTTP動詞が示しているヘッダーから情報を取り出し(例えば"X-HTTP-Method-Override")たり、POSTリクエストを実行中にPUTリクエストに変更したりすることが可能です(コード11参照)。
コード11
@Provider
@PreMatching
public class HttpMethodOverrideEnabler implements ContainerRequestFilter {

public void filter(ContainerRequestContext requestContext) throws IOException {
String override = requestContext.getHeaders()
.getFirst("X-HTTP-Method-Override");
if (override != null) {
requestContext.setMethod(override);
}
}
}

Configuration

@Providerアノテーションで登録されているすべてのインターセプターおよびフィルターはすべてのリソースで利用できます。デプロイ時にサーバーは@Providerアノテーションのデプロイメント単位をスキャンし、アプリケーションがアクティベートされる前に自動的にすべての拡張を登録します。すべての拡張を専用のJARにパッケージし、オンデマンドでWAR( WEB-INF/libフォルダの中に)でデプロイします。JAX-RSランタイムはJARをスキャンし、自動的に拡張を登録します。自己完結型のJARの一時的なデプロイメントはよいのですが、きめ細かい拡張のパッケージングを必要とします。JARに含まれるすべての拡張をすぐにアクティベートすることになるでしょう。
JAX-RSでは選択的なリソースの装飾のためのバインディングアノテーションを導入しています。機構はCDIのqualifierに類似しています。javax.ws.rs.NameBindingメタアノテーションがついた任意のカスタムアノテーションを使ってインターセプト・ポイントを宣言することができます。
コード12
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Tracked {
}
Trackedアノテーションがついたすべてのインターセプターやフィルターをアプリケーションのクラスやメソッド、サブクラスに同じTrackedアノテーションをつけることで選択的にアクティベートすることができます。
コード13
@Tracked
@Provider
public class TrafficLogger implements ContainerRequestFilter, ContainerResponseFilter {
}
アプリケーション開発者はカスタムのNameBindingアノテーションを対応するフィルターやインターセプターとともにパッケージングして、選択的にリソースへ適用することができます。アノテーション駆動アプローチによりすばらしく柔軟性が増し、より粗いプラグインパッケージを使用できますが、バインディングはまだ静的です。インターセプターまたはフィルタチェーンを変更するには、アプリケーションの再コンパイルし、効果的に再デプロイする必要があります。
分野横断的な機能のグローバルおよびアノテーション駆動型の構成に加えて、JAX-RS2.0では動的な拡張登録のための新しいAPIを導入しました。 @Providerアノテーションを付けたjavax.ws.rs.container.DynamicFeatureインタフェースの実装をインターセプターやフィルタの動的な登録のためのフックとしてコンテナが利用しますので、再コンパイルが不要になります。LoggerRegistrationの拡張は、条件付きで事前定義されたシステムプロパティの有無を照会することによってPayloadVerifierインターセプターとTrafficLoggerフィルタを登録します(コード14参照)。
コード14
@Provider
public class LoggerRegistration implements DynamicFeature {

@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
String debug = System.getProperty("jax-rs.traffic");
if (debug != null) {
context.register(new TrafficLogger());
}
String verification = System.getProperty("jax-rs.verification");
if (verification != null) {
context.register(new PayloadVerifier());
}
}
}

The Client Side

JAX-RS 1.1仕様ではクライアントを考慮していませんでした。クライアントREST APIのプロプライエタリな実装、例えばRESTEasyやJerseyなどは(Java EEで実装されていないものであっても)任意のHTTPリソースと通信できましたが、クライアントコードは特定の実装に直接依存していました。JAX-RS 2.0では新たに標準化されたClient APIが導入されています。標準化されたブート処理を使うと、Service Provider Interface (SPI) は置き換え可能です。APIはいろいろな機能を有しており、大部分のRESTクライアントの独自実装に類似しています (コード15参照)。
コード15
import java.util.Collection;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class CoffeeBeansResourceTest {

Client client;
WebTarget root;

@Before
public void initClient() {
this.client = ClientBuilder.newClient().register(PayloadVerifier.class);
this.root = this.client.target("http://localhost:8080/roast-house/api/coffeebeans");
}

@Test
public void crud() {
Bean origin = new Bean("arabica", RoastType.DARK, "mexico");
final String mediaType = MediaType.APPLICATION_XML;
final Entity<Bean> entity = Entity.entity(origin, mediaType);
Response response = this.root.request().post(entity, Response.class);
assertThat(response.getStatus(), is(201));

Bean result = this.root.path(origin.getName()).request(mediaType).get(Bean.class);
assertThat(result, is(origin));
Collection<Bean> allBeans = this.root.request().get(
new GenericType<Collection<Bean>>() {
});
assertThat(allBeans.size(), is(1));
assertThat(allBeans, hasItem(origin));

response = this.root.path(origin.getName()).request(mediaType).delete(Response.class);
assertThat(response.getStatus(), is(204));

response = this.root.path(origin.getName()).request(mediaType).get(Response.class);
assertThat(response.getStatus(), is(204));
}
//..
}
上記の統合テストでは、デフォルトのClientインスタンスを、パラメータなしのClientFactory.newClient()メソッドを使用して取得しています。ブートストラッププロセス自体はjavax.ws.rs.ext.RuntimeDelegateという内部抽象ファクトリで標準化されています。RuntimeDelegateの既存のインスタンスをClientFactoryに注入する(例えば、依存性注入フレームワークによって)か、ファイルMETA-INF/services/javax.ws.rs.ext.RuntimeDelegateファイルと${java.home}/lib/jaxrs.propertiesファイルのヒントを探し、最終的にはjavax.ws.rs.ext.RuntimeDelegateシステムプロパティを検索して取得します。発見できない場合にはデフォルト(Jersey)の実装では、初期化しようとします。
javax.ws.rs.client.Clientの主要な目的はjavax.ws.rs.client.WebTargetjavax.ws.rs.client.Invocationインスタンスにスムーズにアクセスできるようにすることです。WebTargetはJAX−RSリソースを表し、Invocationは発行を待つ、すぐ使えるリクエストです。WebTargetInvocationファクトリでもあります。
CoffeBeansResourceTest#crud()メソッドで、Beanオブジェクトはクライアントとサーバーの間で送受信されます。MediaType.APPLICATION_XMLを選択すると、XMLドキュメントでシリアル化されたDTOの送受信のためにほんの数個のJAXBアノテーションを必要とします。
コード16
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Bean {

private String name;
private RoastType type;
private String blend;

}
サーバーの表記でマーシャリングがうまくいくためにクラスや属性の名前は一致する必要がありますが、DTOがバイナリ互換である必要はありません。上の例では、両Beanクラスを異なるパッケージに配置し、異なるメソッドを実装しています。所望のMediaTypeWebTarget#request()メソッドに渡します(このメソッドは同期Invocation.Builderクラスのインスタンスを返します)。HTTP動詞(GETPOSTPUTDELETEHEADOPTIONS, もしくはTRACE)にちなんで名付けられたメソッドの最後の呼び出しは同期リクエストを開始します。
この新しいクライアントAPIは非同期リソース呼び出しもサポートしています。先ほど述べたように、Invocationインスタンスはsubmissionからリクエストを分離します。非同期リクエストを連鎖した、(AsyncInvokerインスタンスを返す)async()メソッドの呼び出しにより開始することができます(コード17参照)。
コード17
@Test
public void roasterFuture() throws Exception {
//...
Future<Response> future = this.root.path("roaster").path("roast-id").request().async().post(entity);
Response response = future.get(5000, TimeUnit.SECONDS);
Object result = response.getEntity();
assertNotNull(result);
assertThat(roasted.getBlend(),containsString("The dark side of the bean"));
}
上の例での「疑似非同期」の通信スタイルにはそれほどメリットはありません。クライアントは依然としてブロックしレスポンス到着を待つ必要があるからです。しかしながら、Futureベースの呼び出しはバッチ処理において非常に有用です。クライアントは様々なリクエストを一度に発行、Futureインスタンスを収集し、後で処理することが可能です。

本当に非同期の実装はコールバックを登録することで可能です(コード18参照)。
コード18
@Test
public void roasterAsync() throws InterruptedException {
//...
final Entity<Bean> entity = Entity.entity(origin, mediaType);
this.root.path("roaster").path("roast-id").request().async().post(
entity, new InvocationCallback<Bean>() {
public void completed(Bean rspns) {
}

public void failed(Throwable thrwbl) {
}
});
}
Futureを返す各メソッドについて、対応するコールバックメソッドを利用することもできます。メソッド(上記の例ではpost())の最後のパラメータとしてInvocationCallbackインタフェースの実装を受け入れ、呼び出しに成功した場合にはペイロードを、失敗した場合には例外をそれぞれ非同期で通知します。

組み込みのテンプレート機構を使い、URIの構築を自動化・合理化できます。事前定義されたプレースホルダはリクエストの実行の直前に置き換えられ、WebTargetインスタンスを繰り返し生成して保存することができます。
コード19
@Test
public void templating() throws Exception {
String rootPath = this.root.getUri().getPath();
URI uri = this.root.path("{0}/{last}").
resolveTemplate("0", "hello").
resolveTemplate("last", "REST").
getUri();
assertThat(uri.getPath(), is(rootPath + "/hello/REST"));
}
小さいけれども重要なことですが、クライアント側では、拡張を初期化時に発見するのではなく、明示的にClientインスタンスのClientFactory.newClient().register(PayloadVerifier.class)を使って登録する必要があります。ただし、同じエンティティのインターセプターの実装をクライアントとサーバーで共有することができます。これにより、テストを簡単にし、潜在的なバグを削減し、生産性を向上します。すでに導入されているPayloadVerifierインターセプターも同様にクライアント側で変更せずに再利用できます。

まとめ: Java EE or Not?

興味深いことに、JAX-RSは本格的なアプリケーションサーバーを必要としません。特定のContext Typeを満たした後、JAX-RS2.0に準拠したAPIを使用すると、何でもすることができます。ただし、EJB3.2との組み合わせにより、非同期処理、プーリング(スロットル)、監視が可能になります。Servlet 3との緊密な統合により、AsyncContextのサポートを通じて@Suspendedのレスポンスの効率的な非同期処理が可能になり、CDIランタイムがイベント機能をもたらします。また、Bean Validationはよく統合されており、リソースパラメータの検証に使用することができます。他のJava EE7のAPIと一緒にJAX-RS2.0を使用すると、リモートシステムへオブジェクトを公開するための、最も便利(=構成不要)にして最も生産的(=再発明不要)な方法を入手することができます。

参考資料

著者について

Adam Bienはコンサルタントで、Java EE 6/7、EJB 3.x、JAX-RS、JPA 2.xのJSRのExpert Groupメンバーです。JDK 1.0のころからServlets/EJB 1.0を使ってJavaテクノロジーを扱う仕事をしており、今ではJava SEおよびJava EEプロジェクトのアーキテクト、開発者です。JavaFX、J2EE、Java EEに関する書籍を執筆しており、「Real World Java EE Pattrerns - Rethinking Best Practices」と「Real World Java EE Night Hacks」の著者でもあります。
Real World Java EE Patterns—Rethinking Best Practices
http://realworldpatterns.com/
Real World Java EE Night Hacks
http://press.adam-bien.com/real-world-java-ee-night-hacks-dissecting-the-business-tier.htm
AdamはJava Championにして、Top Java Ambassador 2012、JavaOne 2009、2011、2012のRock Starでもあります。Adamはミュンヘン空港で時々Java (EE)ワークショップを開催しています。
Java EE 6/7 Workshops @Airport Munich, Germany
http://airhacks.com/

Join the Conversation

Facebook、Twitter、Oracle Java BlogでのJavaコミュニティの会話に参加して下さい!
Facebook
https://www.facebook.com/ilovejava
Twitter
https://twitter.com/java
Oracle Java Blog
https://blogs.oracle.com/java/
TERIMA KASIH ATAS KUNJUNGAN SAUDARA
Judul: [Java] Java EE 7 and JAX-RS 2.0
Ditulis oleh Unknown
Rating Blog 5 dari 5
Semoga artikel ini bermanfaat bagi saudara. Jika ingin mengutip, baik itu sebagian atau keseluruhan dari isi artikel ini harap menyertakan link dofollow ke https://apk-zipalign.blogspot.com/2013/04/java-java-ee-7-and-jax-rs-20.html. Terima kasih sudah singgah membaca artikel ini.

0 komentar:

Posting Komentar

Trik SEO Terbaru support Online Shop Baju Wanita - Original design by Bamz | Copyright of apk zipalign.