1. 程式人生 > >spring學習(十一)——spring官方文件閱讀(5.0.7)——spring的@Bean與@Configuration註解

spring學習(十一)——spring官方文件閱讀(5.0.7)——spring的@Bean與@Configuration註解

@Bean與@Configuration註解

@Bean註解用於方法上,返回的例項將由Spring IOC管理,當在@Configuration註解的類中使用@Bean註解時,@Bean相當於<bean/>元素,@Configuration相當於<beans>元素

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

上述程式碼等價於:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

當我們在非@Configuration的類中使用@Bean時,每個被@Bean註解的方法都相當於一個工廠方法,每次都會返回一個新的例項,原因是非@Configuration註解的類在使用@Bean註解時沒有使用CGLIB代理,不會被容器攔截,因此不會查詢容器中是否已經存在例項化的bean,直接進行例項化

使用AnnotationConfigApplicationContext初始化Spring容器

AnnotationConfigApplicationContext可以解析Spring以及JSR-330的註解,對於ClassPathCmlApplicationContext來說,XML檔案作為建構函式的引數,對於AnnotationConfigApplicationContext來說,使用@Configuration、@Component、JSR-330的類可以作為建構函式的引數,其餘的使用方式與ClassPathCmlApplicationContext一致,AnnotationConfigApplicationContext會將@Configuration註解的類解析為BeanDefinition,其內部用@Bean註解的方法也將被註冊為BeanDefinition

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

AnnotationConfigApplicationContext可以通過無參建構函式例項化,此時可以通過register()方法註冊BeanDefinition,需要用refresh()方法重新整理容器,以便完成註冊

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

啟動元件掃描

有三種方式啟動spring的自動元件掃描:

1、使用@ComponentScan註解

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig  {
    ...
}

2、使用xml

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

3、AnnotationConfigApplicationContext提供了scan(String)方法

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

使用@Bean註解

@Bean註解作用於方法上,支援設定<bean/>元素的某些屬性,例如init—method、destory—method、autowired以及name

@Bean註解可以在@Component、@Configuration註解的類中使用,也可以在普通的類中使用(不建議)

預設情況下,建立的bean的id和@Bean註解的method的名字一樣

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

上述例子建立的bean的id為transferService

也可以用@Bean修飾返回介面型別的方法:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

容器接收到這個bean時,會認為它是TransferService型別,假設有某個Bean依賴於TransferServiceImpl,在例項化這個bean時,容器才會意識到名為transferService的bean的bean,其型別為TransferServiceImpl,對於某個繼承了許多介面的類來說,例項化該bean的方法的返回型別應該儘可能精確,至少和依賴注入點的型別相同

通過@Bean註解例項化的bean也可以具有依賴,將依賴作為方法的引數即可:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

任何使用了@Bean註解的方法生成的bean都支援常規的生命週期回撥,也可以使用@PostConstruct、@PreDestroy註解,@Bean註解支援指定initialization和destruction生命週期回撥:

public class Foo {

    public void init() {
        // initialization logic
    }
}

public class Bar {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
}

預設情況下,bean的close或是shutdown方法在destruction回撥時將會被自動呼叫,如果我們不想呼叫,可以使用@Bean(destroyMethod="")

指定bean的生命週期 

使用@Scope註解

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

生命週期不一致的情況下可以考慮使用代理,@Bean註解也可以使用代理,通過設定ScopedProxyMode即可,預設情況下是不使用代理(ScopedProxyMode.No),也可以指定ScopedProxyMode.TARGET_CLASS (CGLIB代理)或是 ScopedProxyMode.INTERFACES(JDK代理)

指定bean的名字

@Bean註解提供了name屬性用於設定bean的名字:

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }
}

指定bean的別名

有時候可以給定一個bean多個名字:

@Configuration
public class AppConfig {

    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

bean的描述

有時候我們會對bean進行一些功能的描述:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }
}

使用@Configuration註解

@Configuration用於類上,相當於xml中的<beans/>元素,通過public、@Bean註解的函式來宣告bean

@Bean註解的bean依賴於其他bean時,可以通過下列方式獲得容器管理的依賴bean:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

這種宣告bean間依賴關係的方法只在@Bean方法在@Configuration類中宣告時有效。不能使用純@Component類宣告bean間依賴關係(獲得不是容器管理的bean,而是新的依賴bean)

@Confiuration註解的類會在啟動時成為CGLIB的子類,即使用CGLIB代理,在獲取bean時,首先會檢查容器中是否有現成的bean,如果沒有,則會呼叫代理的方法例項化bean,否則,直接進行依賴注入


@import註解

@import註解允許從其他的類中匯入@Bean定義:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

使用時,只需要在AnnotationConfigApplicationContext中註冊ConfigB即可:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

@import註解也支援匯入普通的java類,並將其宣告成一個bean

@Configuration註解的類會被註冊成bean,也可以使用@Autowired、@Value註解,由於@Configuration註解的類會比較早初始化,因此在其中使用@Autowired、@Value註解可能會導致某些類提前初始化,對於BeanPostProcessor和BeanFactoryPostProcessor類,由於需要比所有bean都先初始化,一般會用@Bean註解靜態方法生成例項

可以使用@Lazy實現懶載入,@DependsOn用於指定依賴項

有時根據系統環境,我們需要選擇啟動或是關閉某些@Configuration註解的類,可以使用@Profile或是@Conditional註解

結合java註解和xml配置

xml配置有時能完成java註解無法完成的事情,兩者使用的Application不同,我們可以結合兩者一起使用

一、如果我們打算使用ClassPathXmlApplicationContext,可以在xml中的<bean/>標籤描述@Configuration註解的類,並且使用<context:annotation-config/>

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

system-test-config.xml

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

jdbc.properties:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

由於xml配置了識別註解,因此@Configuration註解會被識別出來,從而註冊其中的bean,由於@Component註解是@Configuration註解的元註解,因此在xml中使用 <context:component-scan/>也可以掃描到@Configuration註解

二、如果我們使用AnnotationConfigApplicationContext,可以通過@ImportResource註解引入xml配置

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}