普段Javaで開発することが多いですが、DIフレームワークとして以前は、spring framework を使っていましたが、最近 google-guice を最近使うようになりました。以下guiceの使い方メモです。
spring framework における定義. applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="target" class="Target" >
<property name="message" >
<value>Hello World!</value>
</property>
</bean>
</beans>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="target" class="Target" >
<property name="message" >
<value>Hello World!</value>
</property>
</bean>
</beans>
guiceにおける定義は、GenericsとAnnotationを使って定義します。
@ImplementedBy(value = DefaultFace.class)
public interface Face {
void print();
}
public interface Face {
void print();
}
@Singleton
public class DefaultFace implements Face {
@Override
public void print() {
System.out.println("(^_^;)");
}
}
public class DefaultFace implements Face {
@Override
public void print() {
System.out.println("(^_^;)");
}
}
public class FacePrinter {
@Inject Face face;
public void printFace() {
face.print();
}
}
@Inject Face face;
public void printFace() {
face.print();
}
}
public class InjectTest {
@Test
public void test_default() {
FacePrinter printer = new FacePrinter();
Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
@Override
protected void configure() {
}
}).injectMembers(printer);
printer.printFace();
}
}
@Test
public void test_default() {
FacePrinter printer = new FacePrinter();
Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
@Override
protected void configure() {
}
}).injectMembers(printer);
printer.printFace();
}
}
では、Faceインタフェースに他のインスタンスをインジェクトしてみましょう.
public class HappyFace implements Face {
@Override
public void print() {
System.out.println("(∩´∀`)∩ワーイ");
}
}
@Override
public void print() {
System.out.println("(∩´∀`)∩ワーイ");
}
}
public class InjectTest {
@Test
public void test_inject() {
FacePrinter printer = new FacePrinter();
Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
@Override
protected void configure() {
bind(Face.class).to(HappyFace.class).in(Scopes.SINGLETON);
}
}).injectMembers(printer);
printer.printFace();
}
}
@Test
public void test_inject() {
FacePrinter printer = new FacePrinter();
Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
@Override
protected void configure() {
bind(Face.class).to(HappyFace.class).in(Scopes.SINGLETON);
}
}).injectMembers(printer);
printer.printFace();
}
}
はい。これでFacePrinterインスタンスにインジェクトされるFaceメンバ変数は、HappyFaceのインスタンスになったと思います。
では、もうちょっと複雑なものを...
public class FacePrinter2 {
@Inject Map<String, Face> faces;
@Inject Face defaultFace;
static int cnt = 1;
@Inject(optional = true)
public void setPrintCounts(@Named("printer.printcounts") int n) {
cnt = n;
}
public void printFace(String yourheart) {
for ( int i = 0; i < cnt; i++) {
if ( faces.containsKey(yourheart)) faces.get(yourheart).print();
else defaultFace.print();
}
}
}
@Inject Map<String, Face> faces;
@Inject Face defaultFace;
static int cnt = 1;
@Inject(optional = true)
public void setPrintCounts(@Named("printer.printcounts") int n) {
cnt = n;
}
public void printFace(String yourheart) {
for ( int i = 0; i < cnt; i++) {
if ( faces.containsKey(yourheart)) faces.get(yourheart).print();
else defaultFace.print();
}
}
}
上記クラスの Map<String, Face> は、yourheartパラメータによってFaceを設定できるようにします。また、
setPrintCounts(int) でデフォルトのprint回数を指定できます。
class DefaultModule extends AbstractModule {
@Override protected void configure() {
bind(Face.class).annotatedWith(Names.named("!!")).to(KitaFace.class).in(Scopes.SINGLETON);
bind(Face.class).annotatedWith(Names.named("happy")).to(HappyFace.class).in(Scopes.SINGLETON);
bind(Face.class).annotatedWith(Names.named("unhappy")).to(UnhappyFace.class).in(Scopes.SINGLETON);
bind(new TypeLiteral<Map<String, Face>>() {}).toProvider(FaceProvider.class);
}
static class FaceProvider implements Provider<Map<String, Face>> {
static Map<String, Face> faces = new HashMap<String, Face>();
@Override
public Map<String, Face> get() {
return faces;
}
@Inject
public FaceProvider(@Named("!!") Face kita, @Named("happy") Face happy, @Named("unhappy") Face unhappy) {
faces.put("!!", kita);
faces.put("happy", happy);
faces.put("unhappy", unhappy);
}
}
}
@Override protected void configure() {
bind(Face.class).annotatedWith(Names.named("!!")).to(KitaFace.class).in(Scopes.SINGLETON);
bind(Face.class).annotatedWith(Names.named("happy")).to(HappyFace.class).in(Scopes.SINGLETON);
bind(Face.class).annotatedWith(Names.named("unhappy")).to(UnhappyFace.class).in(Scopes.SINGLETON);
bind(new TypeLiteral<Map<String, Face>>() {}).toProvider(FaceProvider.class);
}
static class FaceProvider implements Provider<Map<String, Face>> {
static Map<String, Face> faces = new HashMap<String, Face>();
@Override
public Map<String, Face> get() {
return faces;
}
@Inject
public FaceProvider(@Named("!!") Face kita, @Named("happy") Face happy, @Named("unhappy") Face unhappy) {
faces.put("!!", kita);
faces.put("happy", happy);
faces.put("unhappy", unhappy);
}
}
}
public class InjectTest2 {
@Test public void test() {
System.out.println("### test injectMembers.");
FacePrinter2 printer = new FacePrinter2();
Guice.createInjector(Stage.PRODUCTION, new DefaultModule()).injectMembers(printer);
printer.printFace("!!");
printer.printFace("happy");
printer.printFace("unhappy");
printer.printFace("default");
}
}
@Test public void test() {
System.out.println("### test injectMembers.");
FacePrinter2 printer = new FacePrinter2();
Guice.createInjector(Stage.PRODUCTION, new DefaultModule()).injectMembers(printer);
printer.printFace("!!");
printer.printFace("happy");
printer.printFace("unhappy");
printer.printFace("default");
}
}
さぁ実行してみましょう。正しく実行されましたね!!
bind(Face.class).annotatedWith(Names.named("!!")).to(KitaFace.class).in(Scopes.SINGLETON);
annotatedWith(@Named("!!")) で"!!"という名前のFaceインタフェースはKitaFace.classをsingletonでインジェクトするという定義です。
これをTypeLiteral で Map<String,Face> というtypeにインジェクトするという定義が以下で行われています。
bind(new TypeLiteral<Map<String, Face>>() {}).toProvider(FaceProvider.class);
では、FacePrinter2クラスで @Inject(optional=true)とありますが、ここではModuleクラスで定義をしていませんので、初期値の1が適用されていると思います。
テストケースを以下に編集し実行してみましょう。propsは、propertiesファイルを用意してロードしても問題ありません。
public class InjectTest3 {
@Test public void test2() {
FacePrinter2 printer = new FacePrinter2();
Guice.createInjector(Stage.PRODUCTION, new DefaultModule() {
@Override protected void configure() {
super.configure();
// key-values data configuration settings.
Map<String, String> props = new HashMap<String, String>();
props.put("printer.printcounts", new Integer(3).toString());
Names.bindProperties(this.binder(), props);
}
}).injectMembers(printer);
printer.printFace("!!");
}
}
@Test public void test2() {
FacePrinter2 printer = new FacePrinter2();
Guice.createInjector(Stage.PRODUCTION, new DefaultModule() {
@Override protected void configure() {
super.configure();
// key-values data configuration settings.
Map<String, String> props = new HashMap<String, String>();
props.put("printer.printcounts", new Integer(3).toString());
Names.bindProperties(this.binder(), props);
}
}).injectMembers(printer);
printer.printFace("!!");
}
}
これで、指定した回数(今回は3)printされたと思います。
次にAOP(Aspect Oriented Programming)についてのサンプルです。
@ImplementedBy(value=ServiceImpl.class)
public interface Service {
void execute(String a);
}
public interface Service {
void execute(String a);
}
@Singleton
public class ServiceImpl implements Service {
@TraceLog
public void execute(String a) {
System.out.println("this is 'Guice' Aspect oriented programming!! #" + a);
}
}
public class ServiceImpl implements Service {
@TraceLog
public void execute(String a) {
System.out.println("this is 'Guice' Aspect oriented programming!! #" + a);
}
}
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
public @interface TraceLog {
}
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
public @interface TraceLog {
}
public class TraceLogInterceptor implements org.aopalliance.intercept.MethodInterceptor {
@Override
public Object invoke(org.aopalliance.intercept.MethodInvocation invocation) throws Throwable {
System.out.println("### start trace log !!");
long bef = System.currentTimeMillis();
Object rtn = invocation.proceed();
long tat = System.currentTimeMillis() - bef;
System.out.println("### end trace log !! tat:" + tat + "ms");
return rtn;
}
}
@Override
public Object invoke(org.aopalliance.intercept.MethodInvocation invocation) throws Throwable {
System.out.println("### start trace log !!");
long bef = System.currentTimeMillis();
Object rtn = invocation.proceed();
long tat = System.currentTimeMillis() - bef;
System.out.println("### end trace log !! tat:" + tat + "ms");
return rtn;
}
}
public class AopModule extends AbstractModule {
@Override
protected void configure() {
Matcher<Object> classes = Matchers.any();
Matcher<AnnotatedElement> methods = Matchers.annotatedWith(TraceLog.class);
bindInterceptor(classes, methods, new TraceLogInterceptor());
}
}
@Override
protected void configure() {
Matcher<Object> classes = Matchers.any();
Matcher<AnnotatedElement> methods = Matchers.annotatedWith(TraceLog.class);
bindInterceptor(classes, methods, new TraceLogInterceptor());
}
}
public class AOPTest {
@Test
public void test() {
Injector injector = Guice.createInjector(Stage.PRODUCTION, new AopModule());
Service sercie = injector.getInstance(Service.class);
sercie.execute(" hello world!!");
}
}
@Test
public void test() {
Injector injector = Guice.createInjector(Stage.PRODUCTION, new AopModule());
Service sercie = injector.getInstance(Service.class);
sercie.execute(" hello world!!");
}
}
AOP Alliance を使ってメソッドにトレースログを入れるサンプルです。これもAnnotationだけで実装されています。
http://aopalliance.sourceforge.net/
このように、Guiceでは、DIをGenericsとAnnotationを使って全て定義することができるのです。
springframeworkのように全てをXMLファイルで管理するメリットはソースとDI定義を分離し、ソースの変更が必要ないことがメリットと思います。ただ、大規模開発となり分業で開発が必要になった場合、クラス名などを編集するとあわせてXMLファイルも修正する必要があります。また多くの定義を行うとどこに記述されているかなど管理面でコストがかかるように個人的には感じます。
その点、guiceではソースコードに全てが定義されますのでソースの改変が合った場合のリファクタリングもeclipseのようなIDEを使っている場合はあわせておこなってくれますし、間違っていればコンパイル時にエラーとなります。またソースの検索なども便利です。
補足としてservletでguiceを使うとき、
public class GuiceContainer implements ServletContextListener {
public static final String INJECTOR_ATTRIBUTE = Injector.class.getName();
public static final String INJECTOR_DOMAIN = "guice-context";
private static boolean initialized = false;
@Override
public void contextDestroyed(ServletContextEvent ctx) {
ctx.getServletContext().removeAttribute(INJECTOR_ATTRIBUTE);
}
@Override
public void contextInitialized(ServletContextEvent ctx) {
String modules = ctx.getServletContext().getInitParameter("guice-modules");
List<Module> _modules = new ArrayList<Module>();
if ( modules != null && modules.trim().length() != 0) {
for ( String fqcn : modules.split(":")) {
fqcn = fqcn.trim();
if ( fqcn.trim().length() > 0) {
try {
_modules.add((Module) Class.forName(fqcn).newInstance());
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
}
Injector injector = Guice.createInjector(Stage.PRODUCTION, _modules);
ctx.getServletContext().setAttribute(INJECTOR_ATTRIBUTE, injector);
if ( !initialized) {
try {
Manager.manage(INJECTOR_DOMAIN, injector);
initialized = true;
} catch (Exception e) {
}
}
}
}
public static final String INJECTOR_ATTRIBUTE = Injector.class.getName();
public static final String INJECTOR_DOMAIN = "guice-context";
private static boolean initialized = false;
@Override
public void contextDestroyed(ServletContextEvent ctx) {
ctx.getServletContext().removeAttribute(INJECTOR_ATTRIBUTE);
}
@Override
public void contextInitialized(ServletContextEvent ctx) {
String modules = ctx.getServletContext().getInitParameter("guice-modules");
List<Module> _modules = new ArrayList<Module>();
if ( modules != null && modules.trim().length() != 0) {
for ( String fqcn : modules.split(":")) {
fqcn = fqcn.trim();
if ( fqcn.trim().length() > 0) {
try {
_modules.add((Module) Class.forName(fqcn).newInstance());
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
}
Injector injector = Guice.createInjector(Stage.PRODUCTION, _modules);
ctx.getServletContext().setAttribute(INJECTOR_ATTRIBUTE, injector);
if ( !initialized) {
try {
Manager.manage(INJECTOR_DOMAIN, injector);
initialized = true;
} catch (Exception e) {
}
}
}
}
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>sample-webapp</display-name>
<description>sample-webapp</description>
<context-param>
<param-name>guice-modules</param-name>
<param-value>
org.test.modules.InjectModule:
org.test.modules.AopModule
</param-value>
</context-param>
<listener>
<listener-class>org.test.web.GuiceContainer</listener-class>
</listener>
<!-- Your web-app Settings -->
</web-app>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>sample-webapp</display-name>
<description>sample-webapp</description>
<context-param>
<param-name>guice-modules</param-name>
<param-value>
org.test.modules.InjectModule:
org.test.modules.AopModule
</param-value>
</context-param>
<listener>
<listener-class>org.test.web.GuiceContainer</listener-class>
</listener>
<!-- Your web-app Settings -->
</web-app>
これで、サーブレットコンテナが起動したときにguiceの初期化を行います。必要なModuleをinjectorに設定するだけです。そしてサーブレットとフィルターでは以下のようにinitメソッドで自分のメンバにinjectします
public abstract class InjectedFilter implements Filter {
protected Injector injector;
@Override
public void init(FilterConfig fc) throws ServletException {
ServletContext context = fc.getServletContext();
injector = (Injector) context.getAttribute(GuiceContainer.INJECTOR_ATTRIBUTE);
if ( injector != null) {
injector.injectMembers(this);
}
}
}
protected Injector injector;
@Override
public void init(FilterConfig fc) throws ServletException {
ServletContext context = fc.getServletContext();
injector = (Injector) context.getAttribute(GuiceContainer.INJECTOR_ATTRIBUTE);
if ( injector != null) {
injector.injectMembers(this);
}
}
}
public abstract class InjectedServlet extends HttpServlet {
protected Injector injector;
@Override
public void init(ServletConfig sc) throws ServletException {
super.init(sc);
injector = (Injector)sc.getServletContext().getAttribute(GuiceContainer.INJECTOR_ATTRIBUTE);
if ( injector != null) {
injector.injectMembers(this);
}
}
}
protected Injector injector;
@Override
public void init(ServletConfig sc) throws ServletException {
super.init(sc);
injector = (Injector)sc.getServletContext().getAttribute(GuiceContainer.INJECTOR_ATTRIBUTE);
if ( injector != null) {
injector.injectMembers(this);
}
}
}
また、Android版DIフレームワークとしてguiceをベースとした、roboguiceというものもあります。
Android開発するときには是非使ってみたいと思います。
http://code.google.com/p/roboguice/
0 件のコメント:
コメントを投稿