1. 程式人生 > >Spring入門之五-------SpringIoC之通過註解實現

Spring入門之五-------SpringIoC之通過註解實現

string類型 protected abstract 準備工作 @service urn 解決辦法 sin val

一、準備工作

創建一個Class註解@Configuration,如下例子:

@Configuration // 該註解可理解為將當前class等同於一個xml文件
@ComponentScan("com.imooc.springClass5.annotation") // 開啟包掃描
public class BeanConfiguration {

}

我們創建了一個Class(類名可隨意)並註解了@Configuration,這樣可以將該Class看做一個spring的xml文件。同時我們增加了@ComponentScan註解開啟了包掃描,在掃描包及其子包下面的所有被註解了@Component、@Controller、@Service、@Repository的Class會自動被實例化。

測試代碼類似之前通過加載xml格式文件:

@Test
public void testBean() throws Exception {
    final AbstractApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);
    // 省略其他代碼...
}

二、通過構造方法實例化Bean

直接在Class上面註解@Component即可,當然也可以註解@Controller、@Service、@Repository。@Component為通用型註解,@Controller、@Service、@Repository

則各具有一定的業務含義,一般的@Controller被標註在Controller層、@Service被標註在Service層、@Repository被標註在Dao層。

@Component // 通過構造方法實例化bean
public class Bean1 {
}
@Component // 通過構造方法實例化bean
public class Bean2 {
    private final Bean1 bean1;
@Autowired // 自動註入Bean1的實例
public Bean2(Bean1 bean1) { this.bean1 = bean1;
} }

在上面的兩個例子中,Bean1有默認構造方法,則Spring會通過默認構造方法實例化Bean1;Bean2的構造方法包含參數Bean1,所以Spring在實例化Bean2的時候回在IoC容器中尋找Class為Bean1的實例並註入到該構造方法中以完成對Bean2的實例化。其中:@Autowired表示自動註入。

三、註入Bean

1. 通過方法註入Bean

(1)可通過構造方法註入Bean,如上面Bean2的例子

(2)可通過Set方法註入Bean,例如:

@Component // 通過構造方法實例化bean
public class Bean2 {  
    private Bean1 bean1;
    @Autowired // 自動註入Bean1的實例
    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }
    // get方法省略......   
}

2. 通過屬性註入Bean

@Component // 通過構造方法實例化bean
public class Bean2 {  
    @Autowired // 自動註入Bean1的實例
    private Bean1 bean1;
    // get/set方法省略......   
}

3. 集合類型Bean的註入

(1)直接註入集合實例

@Component // 通過構造方法實例化bean
public class Bean {
    private List<String> stringList;
    private Map<String, String> stringMap;

    public List<String> getStringList() {
        return stringList;
    }

    @Autowired // 通過set方法註入bean
    public void setStringList(List<String> stringList) {
        this.stringList = stringList;
    }

    public Map<String, String> getStringMap() {
        return stringMap;
    }

    @Autowired // 通過set方法註入bean
    public void setStringMap(Map<String, String> stringMap) {
        this.stringMap = stringMap;
    }
}

當然,同時也需要在IoC容器中存在List的Bean和Map的Bean,那麽我們怎麽實例化這兩個Bean呢?修改最開始我們創建的BeanConfiguration即可:

@Configuration // 該註解可理解為將當前class等同於一個xml文件
@ComponentScan("com.imooc.springClass5.annotation") // 開啟包掃描
public class BeanConfiguration {

    @Bean() // 實例化一個List
    public List<String> stringList() {
        List<String> list = new ArrayList<String>();
        list.add("aaaaa");
        list.add("bbbbb");
        return list;
    }

    @Bean() // 實例化一個Map
    public Map<String, String> stringMap() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("aaa", "111");
        map.put("bbb", "222");
        return map;
    }
}

通常情況下,@Bean用來實例化那些我們不能修改源碼的Class,或者需要多次實例化的Class。而@Component系列的四個註解用來實例化我們自己創建的且只需要一次實例化的Class。

那麽小夥伴會問了,在上面的例子中,如果我們實例化了多個List或多個Map,Spring在為Bean註入的時候會給我註入哪個呢?答案是:Spring會報錯給你看!解決辦法就是在通過@Bean和@Component系列實例化Bean的時候指定BeanId,通過@Autowired註入Bean的時候同時通過@Qualifier指定要註入的BeanId。那麽上面的例子可以改成:

@Component // 通過構造方法實例化bean
public class Bean {
    // ... 省略部分代碼
    @Autowired // 通過set方法註入bean
    @Qualifier("stringList") // 指定註入id為stringList的bean
    public void setStringList(List<String> stringList) {
        this.stringList = stringList;
    }
    // ... 省略部分代碼
    @Autowired // 通過set方法註入bean
    @Qualifier("stringMap") // 指定註入id為mapString的bean
    public void setStringMap(Map<String, String> stringMap) {
        this.stringMap = stringMap;
    }
}
@Configuration // 該註解可理解為將當前class等同於一個xml文件
@ComponentScan("com.imooc.springClass5.annotation") // 開啟包掃描
public class BeanConfiguration {

    @Bean("stringList") // 實例化一個List,id為stringList
    public List<String> stringList() {
         // ... 省略部分代碼
    }

    @Bean("stringMap") // 實例化一個Map,id為stringMap
    public Map<String, String> stringMap() {
        // ... 省略部分代碼
    }
}

(2)將多個泛型的實例註入到集合

Spring支持將多個泛型的實例註入到集合,舉例如下:

@Configuration // 該註解可理解為將當前class等同於一個xml文件
@ComponentScan("com.imooc.springClass5.annotation") // 開啟包掃描
public class BeanConfiguration {

    @Bean("integer1") // 實例化一個Integer,id為integer1
    public Integer integer1() {
        return 10001;
    }

    @Bean("integer2") // 實例化一個Integer,id為integer2
    public Integer integer2() {
        return 10002;
    }
}

在上面的代碼中,我們實例化了兩個Integer類型數據,並為每個實例設定了beanId。然後:

@Component // 通過構造方法實例化bean,類似的還有@Controller、@Service、@Repository
public class Bean {
    private List<Integer> integerList;
    private Map<String, Integer> integerMap;

    public List<Integer> getIntegerList() {
        return integerList;
    }

    @Autowired // 通過set方法註入bean,將註入所有已經交由IoC容器管理的Integer類型的bean
    public void setIntegerList(List<Integer> integerList) {
        this.integerList = integerList;
    }

    public Map<String, Integer> getIntegerMap() {
        return integerMap;
    }

    @Autowired // 通過set方法註入bean,將註入所有已經交由IoC容器管理的Integer類型的bean,其中beanId即為鍵值
    public void setIntegerMap(Map<String, Integer> integerMap) {
        this.integerMap = integerMap;
    }
}

上面兩個@Autowired會將我們在BeanConfiguration中創建的兩個Integer類型的Bean全部註入到集合當中,當註入到Map類型(鍵為String類型)時,beanId即作為鍵值註入。

測試代碼:

@Test
public void testBean() throws Exception {
    final AbstractApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);   
    System.out.println("bean.getIntegerList() = " + bean.getIntegerList());
    System.out.println("bean.getIntegerMap() = " + bean.getIntegerMap());       
}

輸出:

bean.getIntegerList() = [10001, 10002]
bean.getIntegerMap() = {integer1=10001, integer2=10002}

當然,如果你想控制泛型實例在List中的順序,可以通過增加@Order註解的方式實現:

@Configuration // 該註解可理解為將當前class等同於一個xml文件
@ComponentScan("com.imooc.springClass5.annotation")
public class BeanConfiguration {

    @Bean("integer1") // 實例化一個Integer,id為integer1
    @Order(5) // 該註解可決定這個bean被註入到list時候的順序,可以不連續
    public Integer integer1() {
        return 10001;
    }

    @Bean("integer2") // 實例化一個Integer,id為integer2
    @Order(2) // 該註解可決定這個bean被註入到list時候的順序,可以不連續
    public Integer integer2() {
        return 10002;
    }
}

則輸出結果即變為:

bean.getIntegerList() = [10002, 10001]
bean.getIntegerMap() = {integer1=10001, integer2=10002}

4. String、Integer等類型直接賦值

String、Integer等類型直接賦值可用@Value實現,例如:

@Component // 通過構造方法實例化bean,類似的還有@Controller、@Service、@Repository
public class Bean {
    private String string;
    
    public String getString() {
        return string;
    }

    @Value("zzzzz") // 直接將zzzzz這個值註入進去
    public void setString(String string) {
        this.string = string;
    }
}

四、設定Bean的作用域scope

可直接通過@Scope來實現,例如:

@Component // 通過構造方法實例化bean
@Scope("singleton") // 設定bean作用域
public class Bean1 {
}
@Configuration // 該註解可理解為將當前class等同於一個xml文件
@ComponentScan("com.imooc.springClass5.annotation")
public class BeanConfiguration {
    @Bean // 實例化一個Bean1
    @Scope("singleton")  // 設定bean作用域
    public Bean1 bean1() {
        return new Bean1();
    } 
}

五、Bean的懶加載

可直接通過@Lazy實現,例如:

@Component // 通過構造方法實例化bean
@Lazy // 開啟懶加載
public class Bean1 {
}
@Configuration // 該註解可理解為將當前class等同於一個xml文件
@Lazy // 開啟懶加載
public class BeanConfiguration {
    @Bean // 實例化一個Bean1
    @Scope("singleton")  // 設定bean作用域
    public Bean1 bean1() {
        return new Bean1();
    } 
}
@Configuration // 該註解可理解為將當前class等同於一個xml文件
@Lazy // 開啟懶加載
public class BeanConfiguration {
}

註意第三段代碼中的@Lazy註解,這意味著在該Configuration中實例化的Bean都將默認為懶加載模式

六、Bean別名

Spring允許一個Bean擁有多個BeanId,但暫時只能在@Bean中實現,@Component系列四個註解暫不支持,@Bean舉例如下:

@Configuration // 該註解可理解為將當前class等同於一個xml文件
public class BeanConfiguration {
    @Bean({"bean1_1", "bean1_2"}) // 實例化一個Bean1設定其擁有兩個BeanId,分別為bean1_1和bean1_2
    public Bean1 bean1() {
        return new Bean1();
    } 
}

七、引入其他註解了@Configuration的Class 或 其他xml文件格式配置

可直接通過@Import實現,例如:

@Configuration // 該註解可理解為將當前class等同於一個xml文件
@Import(BeanConfiguration1.class)
@ImportResource("classpath:spring.xml")
public class BeanConfiguration { 
}

並且BeanConfiguration1無需註解@Configuration

public class BeanConfiguration1 {}

八、方法註入

可能存在如下場景:Class A 的某個方法依賴於Class B的實例,Class A使用scope=singleton單例模式,但是Class A每次執行方法的時候都希望獲取一個新的Class B的實例,這個時候就用到了方法註入。舉例:

@Component // 通過構造方法實例化bean
@Scope("prototype") // 設定bean作用域
public class Bean3 {
   
}
@Component // 通過構造方法實例化bean,類似的還有@Controller、@Service、@Repository
public abstract class Bean {
    @Lookup
    protected abstract Bean3 createBean3();
    public void printBean3() {
        System.out.println("createBean3().toString() = " + createBean3().toString());
    }
}

測試代碼:

public class BeanTest {
    @Test
    public void testBean() throws Exception {
        final AbstractApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);
        Bean bean = context.getBean(Bean.class);    
        bean.printBean3();
        bean.printBean3();
        bean.printBean3();
      
    }
}

輸出:

createBean3().toString() = com.imooc.springClass5.annotation.Bean3@1722011b
createBean3().toString() = com.imooc.springClass5.annotation.Bean3@57ad2aa7
createBean3().toString() = com.imooc.springClass5.annotation.Bean3@5b3f61ff

可以看到Bean.printBean3()方法每次拿到的Bean3都是不同的實例

九、@PostConstruct和@PreDestroy

如果需要在Bean實例化完成之後或需要在Bean銷毀之前執行一些邏輯,

  1. 可通過@PostConstruct和@PreDestroy實現。
  2. 可通過@Bean的initMethod和destroyMethod實現。
@Component // 通過構造方法實例化bean
public class Bean1 {

    public Bean1() {
        System.out.println(this.getClass().getSimpleName() + ":" + this.toString() + " has been created");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println(this.getClass().getSimpleName() + ":postConstruct()");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println(this.getClass().getSimpleName() + ":preDestroy()");
    }
}
public class Bean4 {

    public Bean4() {
        System.out.println(this.getClass().getSimpleName() + ":" + this.toString() + " has been created");
    }

    public void init() {
        System.out.println(this.getClass().getSimpleName() + ":init()");
    }

    public void destroy() {
        System.out.println(this.getClass().getSimpleName() + ":destroy()");
    }

}

測試:

@Test
public void testBean() throws Exception {
    final AbstractApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);
    System.out.println("=================context has bean created=====================");
    context.close();
}

輸出:

Bean1:com.imooc.springClass5.annotation.Bean1@415b0b49 has been created
Bean1:postConstruct()
Bean4:com.imooc.springClass5.annotation.Bean4@7ef27d7f has been created
=================context has bean created=====================
Bean4:init()
Bean4:destroy() Bean1:preDestroy()

十、SpringIoC容器本身接口實例註入

Spring支持我們直接註入其相關接口實例,例如:ApplicationContext、BeanFactory、Environment、ResourceLoader、ApplicationEventPublisher、MessageSource接口及其實現類,舉例其中一個說明:

@Component // 通過構造方法實例化bean,類似的還有@Controller、@Service、@Repository
public class Bean {

    private ApplicationContext context;

    public ApplicationContext getContext() {
        return context;
    }

    @Autowired // 可直接將ApplicationContext註入進來,也可以註入BeanFactory、Environment、ResourceLoader、ApplicationEventPublisher、MessageSource及其實現類
    public void setContext(ApplicationContext context) {
        this.context = context;
    }
}

Spring入門之五-------SpringIoC之通過註解實現