顯示具有 Annotations 標籤的文章。 顯示所有文章
顯示具有 Annotations 標籤的文章。 顯示所有文章

12 2月 2010

Guice Injector and Spring AnnotationConfigApplicationContext

目前Dependency Injection Framework比較活躍的除了Spring外就是Google的Guice了,Guice以Annotation為主,不需要複雜的設定檔,很容易就能上手,而且所需要的Library很小,對於一些比較小的系統,不希望使用太多Library的開發者而言(例如Android),Guice提供了一個較Spring有利的DI Framework。

Spring在使用Annotation上有些舊包袱,但在JSR-299,JSR-330後也逐漸為大家接受,但是在3.0之前,仍需要一個XML設定檔,相較Guice完全不用的情形下是有些許的不便(當然2.5自己加工一下也是可以達成不用讀取XML而直接使用Annotaion)。

Spring3.0多了個AnnotationConfigApplicationContext,可以讓我們完全不用讀取任何XML的檔案就能依Annotation完成DI的組裝工作,下面就簡單列一下兩種DI Framework的做法吧。

package org.elliot.di;

public interface Module {
 public String getModuleName();
}
package org.elliot.di;

import org.springframework.stereotype.Component;

@Component //Spring component => a bean
public class DefaultModule implements Module{
 public String getModuleName() {
  return "Default";
 }
}
訂了一個非常沒用的Interface,再實作一個很無聊的Implementation,DefaultModule上訂的@Component是Spring自定的,也可以改用JSR-299所定的@Resource,這個的做用基本上就是將它當做是之前Spring xml configuration中所訂的一個bean

package org.elliot.di;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.inject.Inject;

@Component //Spring component => a bean
public class Service {
 @Autowired //Spring Autowired
 @Inject //Guice Inject
 private Module module;
 
 public Module getModule() {
  return module;
 }

 public void setModule(Module module) {
  this.module = module;
 }

 public void showModuleName() {
  System.out.println(this.module.getModuleName());
 }
}
Service提供一個被注入的標的module,@Autowired是Spring的Annotaion,@Inject則是Guice的Annotaion,做用雷同,代表這是一個可以被注入的Field。

再來是Spring與Guice想法不同之處,Spring必需把Service也定為一個Component,這樣才可以透過BeanFactory或是Context取得,但Guice則不用,你可以留到你程式要用時再透過Guice Container來組裝,Spring目前似乎沒有這樣的想法(不確定...)。我比較想要的是可以自行new 一個Service instance,再丟給DI Container來將所需要的東西注入。


Guice雖然沒有設定檔,但你還是需要一個AbstractModule來指出一個組裝的需求,就像下列這樣,

package org.elliot.guice;

import org.elliot.di.DefaultModule;
import org.elliot.di.Module;

import com.google.inject.AbstractModule;

public class GuiceConfigModule extends AbstractModule {

 @Override
 protected void configure() {
  bind(Module.class).to(DefaultModule.class);
 }

}
必需要extends AbstractModule,實做protected void configure();這裡指定了只要Field型態是Module的都用DefaultModule的instance來注入。


再來就是簡單的測試,順便展示基本的用法

package org.elliot.guice;

import static org.junit.Assert.assertNotNull;

import org.elliot.di.Service;
import org.junit.Before;
import org.junit.Test;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class GuiceDITest {
 private Service service;
 
 @Before
 public void setUp() throws Exception {
  Injector injector = Guice.createInjector(new GuiceConfigModule());
  service = injector.getInstance(Service.class);
 }
 
 @Test
 public void testGuice() {
  assertNotNull(service.getModule());
  service.showModuleName();
 }
}
這是Guice的簡單測試,例用Guice.createInjector來產生一個Injector,這個Injector就同於Spring的Context,你需要相關的instance都跟Injector要。


Spring的也很簡單

package org.elliot.spring;

import static org.junit.Assert.assertNotNull;

import org.elliot.di.Service;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringDITest {
 private Service service;

 @Before
 public void setUp() throws Exception {
  AnnotationConfigApplicationContext context = 
   new AnnotationConfigApplicationContext("org.elliot");
  service = context.getBean(Service.class);
 }

 @Test
 public void testGuice() {
  assertNotNull(service.getModule());
  service.showModuleName();
 }
}
基本上就是將之前常用的ClassPathXmlApplicationContext, FileSystemXmlApplicationContext換成AnnotationConfigApplicationContext。

兩個TestCase要做的事完全一樣,看得出來Spring也能縮減相當程度的複雜度,但是Guice在速度跟耗用記憶體上還是具有優勢,只是我又少了一個用Guice的理由...

27 1月 2010

Java Annotation: Inherited

java.lang.annotation中@Retention跟@Target都很容易瞭解,但@Inherited就比較麻煩些,所以簡單列個例子來看@Inherited的影響。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
public @interface InheritedAnn {

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface NonInheritedAnn {

}
上列兩個Annotation僅差別在有無@Inherited。簡單看一下@Inherited對extends跟implements的影響
@InheritedAnn
@NonInheritedAnn
public class Parent {
 
 @InheritedAnn
 @NonInheritedAnn
 public void notBeOverrided() {}
 
 @InheritedAnn
 @NonInheritedAnn
 public void beOverrided() {}
}

public class Child extends Parent {
 @Override
 public void beOverrided() {}
}


@InheritedAnn
public interface SimpleInterface {
 @InheritedAnn
 void simple();
}

public class SimpleImpl implements SimpleInterface {
 @Override
 public void simple() {}
}
寫個Test吧
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;

import java.lang.reflect.Method;

import org.junit.Test;
public class AnnTest {
 /**
  * 驗証具有@Inherited 的Annotation可以透過extends保留在subclass中
  */
 @Test
 public void testConcreteClassInheritence() throws Exception {
  //測試Annotated Type,僅具有@Inherited會被保留
  assertNotNull(Child.class.getAnnotation(InheritedAnn.class));
  assertNull(Child.class.getAnnotation(NonInheritedAnn.class));
  //測試沒被Override 的Annotated Method,無論是否有@Inherited皆會被保留
  Method notBeOverrided = Child.class.getMethod("notBeOverrided", null);
  assertNotNull(notBeOverrided.getAnnotation(InheritedAnn.class));
  assertNotNull(notBeOverrided.getAnnotation(NonInheritedAnn.class));
  //測試被Override 的Annotated Method,無論是否有@Inherited皆不會被保留
  Method beOverrided = Child.class.getMethod("beOverrided", null);
  assertNull(beOverrided.getAnnotation(InheritedAnn.class));
  assertNull(beOverrided.getAnnotation(NonInheritedAnn.class));
 }
 
 /**
  * 驗証即便具有@Inherited 的Annotation仍無法透過implements interface保留
  */
 @Test
 public void testInterfaceInheritence() throws Exception {
  //無論是Type或Method皆無法保留Annotation
  //測試Annotated Type
  assertNull(SimpleImpl.class.getAnnotation(InheritedAnn.class));
  //測試Annotated Method
  Method simple = SimpleImpl.class.getMethod("simple", null);
  assertNull(simple.getAnnotation(InheritedAnn.class));
 }
}

要說的都寫在Test中...

08 2月 2007

Hibernate Annotations - One-to-One mappedBy的影響

One-to-One mappedBy的影響 Hibernate Annotations的說明
The association may be bidirectional. In a bidirectional relationship, one of the sides (and only one) has to be
the owner: the owner is responsible for the association column(s) update. To declare a side as not responsible
for the relationship, the attribute mappedBy is used. mappedBy refers to the property name of the association on
the owner side.
在Hibernate中雙向的Relationship的維護上,必定有一方為Owner來記錄此Relationship. 而mappedBy就是為了宣告其本身不負責記錄Relationship,交由另一方記錄所使用的annotation attribute. Bidirectional 的 One-to-One通常還是有主從的概念,以下列Person為主,Passport為從來看 (1)將mappedBy設在Person(從)上
public class Person implements Serializable {
 @Id
 @GeneratedValue(generator = "uuid-gen")
 @GenericGenerator(name = "uuid-gen", strategy = "uuid")
 @Column(length = 32)
 private String id;
 
 @OneToOne(cascade= CascadeType.ALL)
 private Passport passport;
}
public class Passport implements Serializable {
 @Id
 @GeneratedValue(generator = "uuid-gen")
 @GenericGenerator(name = "uuid-gen", strategy = "uuid")
 @Column(length = 32)
 private String identificationCode;
 
 @OneToOne(mappedBy="passport")
 private Person owner;
}
Create Table SQL
create table Passport (identificationCode varchar(32) not null, primary key (identificationCode))
create table Person (id varchar(32) not null, version integer, name varchar(50), firstName varchar(50), lastName varchar(50), mutable bit, birthday date, age integer, modifyDate timestamp, description longvarchar, passport_identificationCode varchar(32), primary key (id))
alter table Person add constraint FK8E488775EB061869 foreign key (passport_identificationCode) references Passport
Hibernate SQL Execution
Hibernate: insert into Person (version, name, firstName, lastName, mutable, birthday, age, modifyDate, description, passport_identificationCode, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: select this_.id as id0_1_, this_.version as version0_1_, this_.name as name0_1_, this_.firstName as firstName0_1_, this_.lastName as lastName0_1_, this_.mutable as mutable0_1_, this_.birthday as birthday0_1_, this_.age as age0_1_, this_.modifyDate as modifyDate0_1_, this_.description as descrip10_0_1_, this_.passport_identificationCode as passport11_0_1_, passport2_.identificationCode as identifi1_1_0_ from Person this_ left outer join Passport passport2_ on this_.passport_identificationCode=passport2_.identificationCode
Hibernate: insert into Passport (identificationCode) values (?)
Hibernate: update Person set version=?, name=?, firstName=?, lastName=?, mutable=?, birthday=?, age=?, modifyDate=?, description=?, passport_identificationCode=? where id=? and version=?
Hibernate會將passport_identificationCode建在Person的Table中,而在Select時,會一併帶出Passport的資料 (2)將mappedBy設在Passport(主)上
public class Person implements Serializable {
 @Id
 @GeneratedValue(generator = "uuid-gen")
 @GenericGenerator(name = "uuid-gen", strategy = "uuid")
 @Column(length = 32)
 private String id;
 
 @OneToOne(mappedBy="owner",cascade= CascadeType.ALL)
 private Passport passport;
}
public class Passport implements Serializable {
 @Id
 @GeneratedValue(generator = "uuid-gen")
 @GenericGenerator(name = "uuid-gen", strategy = "uuid")
 @Column(length = 32)
 private String identificationCode;
 
 @OneToOne
 private Person owner;
}
Create Table SQL
create table Passport (identificationCode varchar(32) not null, owner_id varchar(32), primary key (identificationCode))
create table Person (id varchar(32) not null, version integer, name varchar(50), firstName varchar(50), lastName varchar(50), mutable bit, birthday date, age integer, modifyDate timestamp, description longvarchar, primary key (id))
alter table Passport add constraint FK4C60F032538A0EEB foreign key (owner_id) references Person
Hibernate SQL Execution
Hibernate: insert into Person (version, name, firstName, lastName, mutable, birthday, age, modifyDate, description, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: select this_.id as id0_1_, this_.version as version0_1_, this_.name as name0_1_, this_.firstName as firstName0_1_, this_.lastName as lastName0_1_, this_.mutable as mutable0_1_, this_.birthday as birthday0_1_, this_.age as age0_1_, this_.modifyDate as modifyDate0_1_, this_.description as descrip10_0_1_, passport2_.identificationCode as identifi1_1_0_, passport2_.owner_id as owner2_1_0_ from Person this_ left outer join Passport passport2_ on this_.id=passport2_.owner_id
Hibernate: insert into Passport (owner_id, identificationCode) values (?, ?)
Hibernate: update Person set version=?, name=?, firstName=?, lastName=?, mutable=?, birthday=?, age=?, modifyDate=?, description=? where id=? and version=?
Hibernate會將owner_id建在Passport的Table中,而在Select時,不會一併帶出Passport的資料 可能的話,應該還是將mappedBy設在(主)的那一方上,讓(從)的一方記錄relationship的欄位. 否則的話就如同Component,不如就整在同一Table裡,還可以省去join的時間.