1. 程式人生 > 實用技巧 >Spring 學習筆記(三):Spring Bean

Spring 學習筆記(三):Spring Bean

1 Bean配置

Spring可以看做是一個管理Bean的工廠,開發者需要將Bean配置在XML或者Properties配置檔案中。實際開發中常使用XML的格式,其中<bean>中的屬性或子元素如下:

  • idBeanBeanFactory中的唯一標識,在程式碼中通過BeanFactory獲取Bean的例項時候需要以此作為索引
  • classBean的具體實體類,使用包名+類名的形式指定
  • scope:指定Bean例項的作用域
  • <constructor-arg>:使用構造方法注入,指定構造方法的引數,index表示序號,ref指定對BeanFactory中其他Bean的引用關係,type
    指定引數型別,value指定引數常量值
  • <property>:用於設定一個屬性,表示使用setter注入,name指定屬性的名字,value指定要注入的值,ref指定注入的某個Beanid
  • <list>:用於封裝List或者陣列型別的依賴注入
  • <map>:封裝Map型別的依賴注入
  • <set>:封裝Set型別的依賴注入
  • <entry><map>的子元素,用於設定一個鍵值對

2 Bean例項化

Spring例項化Bean有三種方式:

  • 構造方法例項化
  • 靜態工廠例項化
  • 例項工廠例項化

下面進行簡單的演示。

2.1 構造方法例項化

Spring可以呼叫Bean對應的類的無參構造方法進行例項化,比如:

public class TestBean {
    public TestBean()
    {
        System.out.println("構造方法例項化");
    }
}

配置檔案如下:

<bean id="testBean" class="TestBean"/>

則會呼叫無參構造方法初始化。

其實就是隻寫一個<bean>就可以了,預設的話會呼叫無參構造方法初始化。

2.2 靜態工廠例項化

靜態工廠例項化需要在工廠類中配置一個靜態方法來建立Bean

,並新增factory-method元素,首先建立工廠類:

public class TestBeanFactory {
    private static final TestBean testBean = new TestBean();
    public static TestBean getInstance()
    {
        return testBean;
    }
}

接著配置檔案通過class指定該工廠類,通過factory-method指定獲取例項的方法:

<bean id="testBeanFactory" class="TestBeanFactory" factory-method="getInstance"/>

這樣就可以通過id獲取了:

TestBean test = (TestBean) context.getBean("testBeanFactory");

2.3 例項工廠例項化

例項工廠例項化與靜態工廠例項化類似,不過是非靜態方法,然後加上一個factory-bean元素,同樣首先建立工廠類:

public class TestBeanFactory {
    public TestBean getInstance()
    {
        return new TestBean();
    }
}

在配置檔案需要新增兩個Bean,一個指定工廠類,一個指定使用哪一個工廠類以及使用工廠類的哪一個方法:

<bean id="factory" class="TestBeanFactory" /> <!--指定工廠類-->
<bean id="testBeanFactory" factory-bean="factory" factory-method="getInstance" /> <!--指定工廠Bean以及哪一個工廠方法-->

獲取:

TestBean test = (TestBean) context.getBean("testBeanFactory");

3 Bean作用域

3.1 分類

<bean>中的scope可以指定的作用域如下:

  • singleton:預設作用域,在Spring容器只有一個Bean例項
  • prototype:每次獲取Bean都會返回一個新的例項
  • request:在一次HTTP請求中只返回一個Bean例項,不同HTTP請求返回不同的Bean例項,僅在Spring Web應用程式上下文使用
  • session:在一個HTTP Session中,容器將返回同一個Bean例項,僅在Spring Web應用程式上下文中使用
  • application:為每個ServletContext物件建立一個例項,即同一個應用共享一個Bean例項,僅在Spring Web應用程式上下文使用
  • websocket:為每個WebSocket物件建立一個Bean例項,僅在Spring Web應用程式上下文使用

下面具體說一下最常用的兩個:singletonprototype

3.2 singleton

scope設定為singleton時,Spring IoC僅生成和管理一個Bean例項,使用id/name獲取Bean例項時,IoC容器返回共享的Bean例項。設定方式如下:

<bean id="testBean" class="TestBean"/>
<bean id="testBean" class="TestBean" scope="singleton"/>

因為這是預設的作用域,設定的話IDE也智慧提示是多餘的:

所以通過不需要加上scope,測試例子:

TestBean test1 = (TestBean) context.getBean("testBean");
TestBean test2 = (TestBean) context.getBean("testBean");
System.out.println(test1 == test2);

輸入的結果為True

3.3 prototype

每次獲取Bean時都會建立一個新的例項,例子如下:

<bean id="testBean" class="TestBean" scope="prototype"/>
TestBean test1 = (TestBean) context.getBean("testBean");
TestBean test2 = (TestBean) context.getBean("testBean");
System.out.println(test1 == test2);

測試結果為False

4 Bean生命週期

Spring可以管理作用域為singleton的生命週期,在此作用域下Spring能精確知道Bean何時被建立,何時初始化完成以及何時被摧毀。Bean的整個生命週期如下:

  • 例項化Bean
  • 進行依賴注入
  • 如果Bean實現了BeanNameAware,呼叫setBeanName
  • 如果Bean實現了BeanFactoryAware,呼叫setBeanFactory
  • 如果Bean實現了ApplicationContextAware,呼叫setApplicationContext
  • 如果Bean實現了BeanPostProcessor,呼叫postProcessBeforeInitialization
  • 如果Bean實現了InitializingBean,呼叫afterPropertiesSet
  • 如果配置檔案配置了init-method屬性,呼叫該方法
  • 如果實現了BeanPostProcessor,呼叫postProcessAfterInitialization,注意介面與上面的相同但是方法不一樣
  • 不需要時進入銷燬階段
  • 如果Bean實現了DisposableBean,呼叫destroy
  • 如果配置檔案配置了destroy-method,呼叫該方法

下面用程式碼進行演示:

public class TestBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, BeanPostProcessor, InitializingBean, DisposableBean {
    public TestBean()
    {
        System.out.println("呼叫構造方法");
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("呼叫BeanNameAware的setBeanName");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("呼叫BeanFactoryAware的setBeanFactory");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("呼叫ApplicationContextAware的setApplicationContext");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("呼叫BeanPostProcessor的postProcessBeforeInitialization");
        return null;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("呼叫InitializingBean的afterPropertiesSet");
    }

    public void initMethod()
    {
        System.out.println("呼叫XML配置的init-method");
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("呼叫BeanPostProcessor的postProcessAfterInitialization");
        return null;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("呼叫DisposableBean的destroy");
    }

    public void destroyMethod()
    {
        System.out.println("呼叫XML配置的destroy-method");
    }
}

配置檔案如下,指定了init-method以及destroy-method

<bean id="testBean" class="TestBean" init-method="initMethod" destroy-method="destroyMethod"/>

測試:

public static void main(String[] args) {
    ConfigurableApplicationContext context = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
    TestBean test = (TestBean) context.getBean("testBean");
    ((BeanDefinitionRegistry) context.getBeanFactory()).removeBeanDefinition("testBean");
}

輸出如下:

如果沒有最後一行的手動刪除Bean定義是不會看見最後兩行的輸出的,另外,這裡沒有呼叫BeanPostProcessor介面的兩個方法,如果把scope改為prototype,輸出如下:

可以看到首先對Bean進行一次初始化,並且再次生成一個新的例項,而且呼叫了BeanPostProcessor的兩個方法。但是需要注意Spring不會管理scopeprototype的銷燬,所以圖中沒有看到呼叫銷燬的方法。

5 Bean裝配方式

Spring支援以下兩種裝配方式:

  • 基於XML裝配
  • 基於註解裝配
  • 顯式Bean裝配

Bean的裝配方式也就是Bean的依賴注入方式,下面分別進行闡述。

5.1 基於XML裝配

基於XML裝配也就是在XML檔案中指定使用構造方法注入或者setter注入,比如:

public class TestBean {
    private final List<String> stringList;
    private String s;

    public TestBean(List<String> stringList) {
        this.stringList = stringList;
    }

    public void setS(String s)
    {
        this.s = s;
    }

    @Override
    public String toString() {
        return stringList.toString() + "\n" + s + "\n";
    }
}

Bean有一個帶引數的構造方法以及一個setter,接著在XML中指定相應的值即可:

<bean id="testBean" class="TestBean">
    <constructor-arg index="0">
        <list>
            <value>1</value>
            <value>2</value>
        </list>
    </constructor-arg>
    <property name="s" value="444" />
</bean>

測試:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(context.getBean("testBean"));

5.2 基於註解裝配

儘管XML方式可以簡單地裝配Bean,但是一旦Bean過多就會造成XML檔案過於龐大,不方便以後的升級和維護,因此推薦使用基於註解的裝配方式,先來看一下常用的註解:

  • @Autowired:自動裝配,預設按照Bean的型別進行裝配,這是Spring的註解
  • @Resource:與@Autowired類似,但是是按名稱進行裝配,當找不到與名稱匹配的Bean時才按照型別進行裝配,這是JDK的註解
  • @Qualifier:與@Autowired配合使用,因為@Autowired預設按Bean型別進行裝配,使用@Qualifier可以按名稱進行裝配
  • @Bean:方法上的註解,用於產生一個Bean,然後交由Spring管理
  • @Component:表示一個元件物件,加上了該註解就能實現自動裝配,預設的Beanid為使用小駝峰命名法的類
  • @Repository/@Service/@Controller:實際上是@Component的別名,只不過是專門用於持久層/業務層/控制層的,從原始碼可以看出三個註解的定義除了名字不一樣其他都一致,並且都是@Component的別名:

官方文件也提到相比起使用@Component,使用@Repository/@Service/@Controller在持久層/業務層/控制層更加合適,而不是統一使用@Component

5.3 註解使用示例

5.3.1 @Bean

@Bean示例如下:

public class TestBean implements BeanNameAware{
    @Override
    public void setBeanName(String s) {
        System.out.println("setBeanName");
    }
}

@Configuration
public class Config {
    @Bean
    public TestBean getBean()
    {
        return new TestBean();
    }
}

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        context.getBean("getBean");
    }
}

注意通過@Bean自動產生的Beanid為方法名,而不是Bean的類名的小駝峰形式。

5.3.2 其他

@Autowired/@Resource/@Qualifier/@Repository/@Service/@Controller綜合示例,首先建立如下包以及檔案:

@Controller
public class TestController {
    @Resource
    private TestService service;

    public void save()
    {
        System.out.println("controller save");
        service.save();
    }
}
@Service
public class TestService {
    @Autowired
    @Qualifier("testRepository1")
    private TestInterface repository1;

    @Autowired
    @Qualifier("testRepository2")
    private TestInterface repository2;
    public void save()
    {
        System.out.println("service save");
        repository1.save();
        repository2.save();
    }
}
@Repository
public class TestRepository1 implements TestInterface{
    @Override
    public void save() {
        System.out.println("repository1 save");
    }
}
@Repository
public class TestRepository2 implements TestInterface{
    @Override
    public void save() {
        System.out.println("repository2 save");
    }
}
public interface TestInterface {
    void save();
}
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        ((TestController)context.getBean("testController")).save();
    }
}

配置檔案:

<context:component-scan base-package="bean" />

TestService中,使用了@Qualifier

@Autowired
@Qualifier("testRepository1")
private TestInterface repository1;

@Autowired
@Qualifier("testRepository2")
private TestInterface repository2;

因為TestInterface有兩個實現類,@Autowired不知道是選擇TestRepository1還是TestRepository2,因此需要加上@Qualifier,指定需要注入的Beanid,或者使用@Resouce

@Resource
private TestInterface testRepository1;

@Resource
private TestInterface testRepository2;

但是要注意這樣默認了成員的名字就是Beanid,可以看到這裡的名字是testRepository1testRepository2而不是repository1repository2