Jersey2に組み込まれてるDI機能（HK2）を試す

Jersey は version 2 から、 [HK2](https://hk2.java.net/2.3.0-b09/) を組み込んでいる。

HK2 は [JSR330](https://jcp.org/en/jsr/detail?id=330) （Dependency Injection for Java） の実装の一つ（他には Google Guice も JSR330 を実装している）。

つまり、 Jersey2 を使えばそれだけで DI が使えるようになる。

ちょこっと触ってみた感じ DI と AOP は最低限使えるので、軽く DI と AOP を使いたいだけなら、わざわざ Google Guice や Weld を組み込まなくても Jersey + HK2 だけで事足りそう。

#環境
##AP サーバー
Tomcat 7.0.50

##Java
1.8

##Jersey
2.10

#プロジェクト下地
```groovy:build.gradle
dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    compile'org.glassfish.jersey.containers:jersey-container-servlet:2.10'
}
```

```java:MyConfig.java
package sample.jersey;

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("rest")
public class MyConfig extends ResourceConfig {
    
    public MyConfig() {
        packages(this.getClass().getPackage().getName());
    }
}
```

コンテキストパスが `jersey-with-hk2` となるようにして Tomcat にデプロイ。

#HttpServletRequest をインジェクションする
```java:SampleResource.java
package sample.jersey.resource;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("sample")
public class SampleResource {
    
    @Inject
    private HttpServletRequest request;
    
    @GET
    public void method() {
        System.out.println("param = " + request.getParameter("param"));
    }
}
```

ブラウザを開いて `http://localhost:8080/jersey-with-hk2/rest/sample?param=hoge` にアクセス。

```text:コンソール出力
param = hoge
```

- HttpServletRequest なら何もせずに `@Inject` でインジェクションできる。

#HttpSession をインジェクションする
```java:SessionFactory.java
package sample.jersey;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.glassfish.hk2.api.Factory;

public class SessionFactory implements Factory<HttpSession> {
    
    private final HttpServletRequest request;
    
    @Inject
    public SessionFactory(HttpServletRequest request) {
        this.request = request;
    }
    
    @Override
    public HttpSession provide() {
        return request.getSession();
    }

    @Override
    public void dispose(HttpSession instance) {/* no use */}
}
```

```java:MyConfig.java
package sample.jersey;

import javax.servlet.http.HttpSession;
import javax.ws.rs.ApplicationPath;

import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("rest")
public class MyConfig extends ResourceConfig {
    
    public MyConfig() {
        packages(this.getClass().getPackage().getName());
        
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(SessionFactory.class).to(HttpSession.class);
            }
        });
    }
}
```

```java:SampleResource.java
package sample.jersey.resource;

import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("sample")
public class SampleResource {
    
    @Inject
    private HttpSession session;
    
    @GET
    public void method() {
        System.out.println("session id = " + this.session.getId());
    }
}
```

ブラウザからアクセスする。

```text:コンソール出力
session id = FBB60C5BDCF561D5FB834CC34B1066C6
```

- HttpSession はデフォルトではインジェクションできない。
- HttpSession を提供するクラス（SessionFactory）を作成して、 ResourceConfig を継承したクラス内でコンテナに登録するとインジェクションできるようになる。

#普通のクラスをインジェクションする
```java:SampleService.java
package sample.jersey.service;

public class SampleService {
    
    public void method() {
        System.out.println("sample service. hash = " + this.hashCode());
    }
}
```

```java:MyConfig.java
package sample.jersey;

import javax.ws.rs.ApplicationPath;

import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;

import sample.jersey.service.SampleService;

@ApplicationPath("rest")
public class MyConfig extends ResourceConfig {
    
    public MyConfig() {
        packages(this.getClass().getPackage().getName());
        
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindAsContract(SampleService.class);
            }
        });
    }
}
```

```java:SampleResource.java
package sample.jersey.resource;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import sample.jersey.service.SampleService;

@Path("sample")
public class SampleResource {
    
    @Inject
    private SampleService service;
    
    @GET
    public void method() {
        this.service.method();
    }
}
```

ブラウザから何回かアクセスする。

```text:コンソール出力
sample service. hash = 502430113
sample service. hash = 813139719
sample service. hash = 187967658
sample service. hash = 252427540
```

- `bindAsContract()` でクラスをコンテナに登録できる。
- インスタンスはインジェクションのたびに生成される。

#インターフェースとその実装を指定する
```java:SampleService.java
package sample.jersey.service;

public interface SampleService {
    
    void method();
}
```

```java:SampleServiceImpl.java
package sample.jersey.service;

public class SampleServiceImpl implements SampleService {
    
    @Override
    public void method() {
        System.out.println("sample service");
    }
}
```

```java:MyConfig.java
package sample.jersey;

import javax.ws.rs.ApplicationPath;

import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;

import sample.jersey.service.SampleService;
import sample.jersey.service.SampleServiceImpl;

@ApplicationPath("rest")
public class MyConfig extends ResourceConfig {
    
    public MyConfig() {
        packages(this.getClass().getPackage().getName());
        
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(SampleServiceImpl.class).to(SampleService.class);
            }
        });
    }
}
```

```java:SampleResource.java
package sample.jersey.resource;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import sample.jersey.service.SampleService;

@Path("sample")
public class SampleResource {
    
    @Inject
    private SampleService service;
    
    @GET
    public void method() {
        this.service.method();
    }
}
```

ブラウザからアクセスする。

```text:コンソール出力
sample service
```

- `bind(<実装クラス>).to(<インターフェース>)` でインターフェースとその実装クラスを指定できる。

#シングルトンで定義する
```java:SampleServiceImpl.java
package sample.jersey.service;

public class SampleServiceImpl implements SampleService {
    
    @Override
    public void method() {
        System.out.println("sample service. hash=" + this.hashCode());
    }
}
```

```java:MyConfig.java
package sample.jersey;

import javax.inject.Singleton;
import javax.ws.rs.ApplicationPath;

import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;

import sample.jersey.service.SampleService;
import sample.jersey.service.SampleServiceImpl;

@ApplicationPath("rest")
public class MyConfig extends ResourceConfig {
    
    public MyConfig() {
        packages(this.getClass().getPackage().getName());
        
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(SampleServiceImpl.class).to(SampleService.class).in(Singleton.class);
            }
        });
    }
}
```

ブラウザから何回かアクセスする。

```text:コンソール出力
sample service. hash=1081395549
sample service. hash=1081395549
sample service. hash=1081395549
sample service. hash=1081395549
sample service. hash=1081395549
```

- `bind(~~).to(~~).in(Singleton.class)` でシングルトンにできる。

#コンテナ初期化時にシングルトンを生成するようにする

Google Guice でいう `asEagerSingleton()` みたいなこと。

できないっぽい。

[java - What corresponds to asEagerSingleton in HK2 in Jersey 2? - Stack Overflow](http://stackoverflow.com/questions/20713086/what-corresponds-to-aseagersingleton-in-hk2-in-jersey-2)

#インターセプター
```java:SampleInterceptor.java
package sample.jersey.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SampleInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before proceed >>>");
        Object result = invocation.proceed();
        System.out.println("<<< after proceed");
        
        return result;
    }
}
```

```java:MyInterceptorService.java
package sample.jersey.interceptor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import org.aopalliance.intercept.ConstructorInterceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.glassfish.hk2.api.Filter;
import org.glassfish.hk2.api.InterceptionService;
import org.glassfish.hk2.utilities.BuilderHelper;

public class MyInterceptorService implements InterceptionService {

    private static final List<MethodInterceptor> METHOD_INTERCEPTORS = Arrays.asList(new SampleInterceptor());
    
    @Override
    public Filter getDescriptorFilter() {
        return BuilderHelper.allFilter();
    }

    @Override
    public List<MethodInterceptor> getMethodInterceptors(Method method) {
        if ("target".equals(method.getName())) {
            return METHOD_INTERCEPTORS;
        }
        
        return null;
    }

    @Override
    public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
        return null;
    }
}
```

```java:MyConfig.java
package sample.jersey;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.ApplicationPath;

import org.glassfish.hk2.api.InterceptionService;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;

import sample.jersey.interceptor.MyInterceptorService;
import sample.jersey.service.SampleService;

@ApplicationPath("rest")
public class MyConfig extends ResourceConfig {
    
    @Inject
    public MyConfig() {
        packages(this.getClass().getPackage().getName());
        
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindAsContract(SampleService.class);
                bindAsContract(MyInterceptorService.class).to(InterceptionService.class).in(Singleton.class);
            }
        });
    }
}
```

```java:SampleResource.java
package sample.jersey.resource;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import sample.jersey.service.SampleService;

@Path("sample")
public class SampleResource {
    
    @Inject
    private SampleService service;
    
    @GET
    public void method() {
        this.service.method();
        this.service.target();
    }
}
```

```java:SampleService.java
package sample.jersey.service;

public class SampleService {
    
    public void method() {
        System.out.println("method");
    }
    
    public void target() {
        System.out.println("target");
    }
}
```

ブラウザからアクセスしてみる。

```text:コンソール出力
method
before proceed >>>
target
<<< after proceed
```

- インターセプターは、 `MethodInterceptor` を実装させる。
- `InterceptionService` を実装したクラスを作り、そこの `getMethodInterceptors()` で引数に渡されたメソッドに応じて適用するインターセプターのリストを返すように実装する（ちょっと泥臭い・・・）。
    - コンストラクタをインターセプトする場合は `getConstructorInterceptors()` の方で実装する。
- `InterceptionService` を実装したクラスを、 <ins>Singleton</ins> 指定でコンテナに登録する。

#参考
- [Chapter&nbsp;21. ;Custom Injection and Lifecycle Management](https://jersey.java.net/documentation/latest/ioc.html)
- [HK2 - Aspect Oriented Programming (AOP) Example](https://hk2.java.net/2.2.0/aop-example.html)