2015年12月30日 星期三

基於 Annotation container 設定 (Spring3.x 十二)

參考文件
這篇開始講annotation,基本的annotation不了解的可看這篇

※@Autowired

※XML設定


三個VO

public class Cartoon {
    public Cartoon() {}
}
    
public class Comic {
    public Comic() {}
}
    
public class Book {
    private Cartoon cartoon;
    private Comic comic;
    // setter/getter...
    
    public Book() {}
}



applicationContext.xml

<bean id="ca" class="vo.Cartoon" />
<bean id="co" class="vo.Comic" />
<bean id="bo" class="vo.Book" p:cartoon-ref="ca" p:comic-ref="co" />



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
// 總共有幾個bean
System.out.println(ctx.getBeanDefinitionCount());
    
// 將全部的bean名稱印出來
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
// 兩種寫法都可以
Book book1 = ctx.getBean("bo", Book.class);
Book book2 = (Book) ctx.getBean("bo");
    
System.out.println(book1.getComic());
System.out.println(book2.getCartoon());
    
((ClassPathXmlApplicationContext) ctx).close();

※結果:
3
ca
co
bo
vo.Comic@14acaea5
vo.Cartoon@46d56d67



※Annotation設定

Book.java

public class Book {
    private Cartoon cartoon;
    private Comic comic;
    
    public Book() {}
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    @Autowired
    public void setCartoon(Cartoon cartoon) {
        this.cartoon = cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
    
    @Autowired
    public void setComic(Comic comic) {
        this.comic = comic;
    }
}

※只是在setter上面加@Autowired

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="ca" class="vo.Cartoon" />
<bean id="co" class="vo.Comic" />
<bean id="bo" class="vo.Book" />

因為要用@Autowired,所以也要將相關的class宣告一下

@Autowired等同在 xml 設定的autowire


結果:
4
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#0
ca
co
bo
vo.Comic@6d00a15d
vo.Cartoon@51efea79

---------------------------------------------------------------------------------
還有很多的Annotation都要向上面的@Autowired一樣,都要宣告一支class,如果用了5個,就要宣告5支class,太麻煩了,所以spring提供了<context:component-scan />,所以xml修改如下:
<context:component-scan base-package="\" />
<bean id="ca" class="vo.Cartoon" />
<bean id="co" class="vo.Comic" />
<bean id="bo" class="vo.Book" />

※base-package裡面是要掃描哪些包,裡面寫包名,有很多包可用「,」隔開,而我是根目錄,所以就是「\」,但在 web 這樣寫會出「@EnableAsync annotation metadata was not injected」的錯

※結果:
8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
ca
co
bo
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
vo.Comic@1f554b06
vo.Cartoon@694e1548

※<context:annotation-config />,就是開啟上面迴圈預設的 annotation 的意思,但這類的設定都是針對 ClassPathXmlApplicationContext 才有用;對 AnnotationConfigApplicationContext 就沒用,所以這個設定是強制只能使用 XML 的設定,所有的 annotation 都沒效
而 context:component-scan 有個屬性 annotation-config,預設就是 true 了,所以可有可無

use-default-filters="false",表示不要用 base-package 裡的 @Component 等 annotation,使用子標籤的方式過濾(include、exclude)

resource-pattern="ComicBook*.class" 表示條件再加上 ComicBook開頭,.class結尾的才掃瞄,但要注意是 .class,不是 .java

annotation-config 有子標籤
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />,表示排除 @Controller 及其子類的,如 @Component 是 @Controller、@Service、@Repository、@Configuration 的父類
型態是 annotation,表示 expression 是什麼 annotation

有排除,當然也有包括(context:include-filter)

若型態是 assignable,如 <context:exclude-filter type="assignable" expression="xxx.ooo.Book" />,表示 expression 是什麼class


※annotation 設定要用 @ComponentScan(basePackages="ooo.xxx"),只會針對 annotation 有用,3.1 才有,然後使用 new AnnotationConfigApplicationContext(Book.class);,但要注意 Book.java 不在 ooo.xxx 的套件裡,但只要有宣告 @ComponentScan ,也下了如 @Component 之類的注解,一樣也會抓到
屬性 value 為 basePackages 的別名,所以也可用 value,且只要是 annotation 叫 value 的,還可以省略不寫
屬性 nameGenerator,如預設@Component 裡面不寫東西,那 bean 名稱就是小寫開頭的 class 名稱,想改就要繼承裡面的類

---------------------------------------------------------------------------------
@Autowired還可以放在屬性上面,而這時setter就可以刪除了,如下:
public class Book {
    @Autowired
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;
    
    public Book() {}
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
}

結果還是一樣

---------------------------------------------------------------------------------
如果將xml裡的ca拿掉就會出現 「org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [vo.Cartoon] found for dependency:」的錯
可以將ca的annotation變成@Autowired(required = false),表示不要報錯

※結果:
7
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
co
bo
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
vo.Comic@3c0f93f1
null
可以看到不會報錯,就變null了

※@Autowired 如果寫在 field 上,intellij 會出現警告,會建議寫在建構子比較好,因為有可能建構子有用到這個注入的變數,但注入順序是先注入建構子才會注入 field,所以這時會抓不到,所以會有警告




※@Required

表示一定要注入,但只能放在方法上

Book.java

public class Book {
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;
    
    public Book() {
    }
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    // @Autowired
    @Required
    public void setCartoon(Cartoon cartoon) {
        this.cartoon = cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
    
    public void setComic(Comic comic) {
        this.comic = comic;
    }
}



※如上,因為Cartoon沒注入,所以會報「org.springframework.beans.factory.BeanInitializationException: Property 'cartoon' is required for bean 'bo'」的錯,這是只要把注解打開注入即可解決

※注意@Autowired可以放在屬性上的,如下:
public class Book {
    @Autowired
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;
    
    public Book() {
    }
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    @Required
    public void setCartoon(Cartoon cartoon) {
        this.cartoon = cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
    
    public void setComic(Comic comic) {
        this.comic = comic;
    }
}

※這時還是會報上面的錯,因為和@Required合用,就一定都要放在方法上,不然它還是認為你沒注入




※@Qualifier

@Autowired是個by type,一定要剛好只有一個才有辦法注入,例如將Comic.java修改如下:
public class Comic extends Cartoon {}

※就只是多個繼承Cartoon,這時就會出「org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [vo.Cartoon] is defined: expected single matching bean but found 2: ca,co」的錯,錯誤也很明顯,它說ca和co都是Cartoon,所以無法注入,這時Book.java的Cartoon屬性可修改如下即可解決:
@Autowired
@Qualifier("ca")
private Cartoon cartoon;

※看起來也很清楚,如果型態一樣,就以名稱是ca的來注入
---------------------------------------------------------------------------------

※除了放在屬性,也可以放在方法上,和上面的@Required一樣,全部統一放在屬性或方法上,不要分開,不然還是會認為有很多型態
@Autowired
@Qualifier("ca")
public void setCartoon(Cartoon cartoon) {
    this.cartoon = cartoon;
}

※也就是將ca的bean給參數名稱cartoon

※但如果有很多參數,就只好寫在參數上了,如下:
@Autowired
public Book(Comic com, @Qualifier("ca") Cartoon car) {}




※@Resource

※我用的時候,居然沒提示,所以我用maven下載jar
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>jsr250-api</artifactId>
    <version>1.0</version>
</dependency>

和@Autowired很像,但是它可以不用 @Qualifier
// @Autowired
// @Qualifier("ca")
@Resource(name = "ca")
private Cartoon cartoon;

※上面的需求,它只要一行就可解決了

※@Resource有兩個重要屬性name和type
1.如果只有設定name,一定要剛好一筆,否則出錯
2.如果只有設定type,一定要剛好一筆,否則出錯
3.如果name和type都有設定,都要符合才注入,否則就出錯誤訊息
反正電腦是很笨的,一定要剛剛好,否則出錯
4.如果上面的例子沒有name="ca",會找 cartoon,找不到才找 type,但如果找到了卻不是 Cartoon 就會報錯

而官方說如果name和type都沒有設定,就等同name,但我試的結果還是可以注入,但一定要有預設建構子

Book.java

public class Book {
    @Resource
    private Cartoon xxx;
    
    @Resource
    private Comic ooo;
    
    // setter/getter...
}



Cartoon.java 和 Comic.java

public class Cartoon {
    private String cartoonName;
    // setter/getter...
}
    
public class Comic {
    private String comicName;
    // setter/getter...
}



applicationContext.xml

<context:annotation-config />
<bean id="ca" class="vo.Cartoon" p:cartoonName="卡通" />
<bean id="co" class="vo.Comic" p:comicName="漫畫" />
<bean id="bo" class="vo.Book" />



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
System.out.println(ctx.getBeanDefinitionCount());
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
Book book1 = ctx.getBean("bo", Book.class);
Book book2 = (Book) ctx.getBean("bo");
    
System.out.println(book1.getOoo().getComicName());
System.out.println(book2.getXxx().getCartoonName());
    
((ClassPathXmlApplicationContext) ctx).close();

※按官方所說xxx和ooo都沒有啊!但就是印的出卡通和漫畫,所以個人覺得都不寫應該是 by type 才對

※也可以在Comic.java的屬性或方法上加@Value("海賊王"),也可以塞值,這時xml就不用設了,但如果兩個都設,我試的結果是xml贏了

※最近發現其實 @Resource 裡面有寫,雖然 name 是寫空,但註解有寫是抓 name 的名稱



※@PostConstruct和@PreDestroy

這兩個annotation等同於第九篇的init-method 和 destroy-method,只是現在用的是annotation而已

public class Book {
    @Resource(name = "ca")
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;

    public Book() {
    }
    
    @PostConstruct
    public void init() {
        System.out.println("init");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("destroy");
    }
}


※可以使用很多個@PostConstruct和@PreDestroy

沒有留言:

張貼留言