深入理解Spring IOC容器及擴充套件

女友在高考發表於2021-11-15

本文將從純xml模式、xml和註解結合、純註解的方式講解Spring IOC容器的配置和相關應用。

純XML模式

例項化Bean的三種方式

  1. 使用無參建構函式

預設情況下,會使用反射呼叫無參建構函式來建立物件。

<bean id="connectionUtils" class="com.mmc.ioc.utils.ConnectionUtils"></bean>
  1. 使用靜態方法建立

在實際開發中,我們使用的方法有時候不是通過建構函式建立出來的,他可能在建立的時候會做很多額外的操作。此時會提供一個建立物件的方法,如果這個方法是static修飾的,就是用這種配置方式。

<bean id="druidUtils" class="com.mmc.ioc.utils.DruidUtils" factory-method="getInstance"></bean>
  1. 使用例項化方法建立

當方法不是靜態的時候,用這種方式

 <bean id="connectionUtils" class="com.mmc.ioc.utils.ConnectionUtils"></bean>
 <bean id="account" factory-bean="connectionUtils" factory-method="createAccount"></bean>

bean的作用範圍和宣告週期

常用的是singleton【預設】(單例模式)和prototype(原型模式或多例模式)。通過scope屬性可以進行配置

<bean id="account" factory-bean="connectionUtils" factory-method="createAccount" scope="singleton"></bean>

不同作用範圍的生命週期

單例模式:singleton

物件建立:當建立容器時,物件就被建立

物件活著:只要容器在,物件一直活著

物件死亡:當容器銷燬,物件就被銷燬

總結:單例模式的bean物件生命週期與容器相同

多例模式:prototype

物件建立:當使用物件時,建立新的物件例項

物件活著:只要物件在使用中,就一直活著

物件死亡:當物件長時間不用時,被垃圾回收器回收

總結:多例模式的bean物件,spring框架只負責建立,不負責銷燬。

Bean的標籤屬性

  • id屬性: ⽤於給bean提供⼀個唯⼀標識。在⼀個標籤內部,標識必須唯⼀。
  • class屬性:⽤於指定建立Bean物件的全限定類名。
  • name屬性:⽤於給bean提供⼀個或多個名稱。多個名稱⽤空格分隔。
  • factory-bean屬性:⽤於指定建立當前bean物件的⼯⼚bean的唯⼀標識。當指定了此屬性之後,
    class屬性失效。
  • factory-method屬性:⽤於指定建立當前bean物件的⼯⼚⽅法,如配合factory-bean屬性使⽤,
    則class屬性失效。如配合class屬性使⽤,則⽅法必須是static的。
  • scope屬性:⽤於指定bean物件的作⽤範圍。通常情況下就是singleton。當要⽤到多例模式時,
    可以配置為prototype。
  • init-method屬性:⽤於指定bean物件的初始化⽅法,此⽅法會在bean物件裝配後調⽤。必須是
    ⼀個⽆參⽅法。
  • destory-method屬性:⽤於指定bean物件的銷燬⽅法,此⽅法會在bean物件銷燬前執⾏。它只
    能為scope是singleton時起作⽤。

DI依賴注入

  1. 按照注入的方式分類
  • 建構函式注入:就是利用帶參建構函式實現對類成員的屬性賦值
<bean id="account" class="com.mmc.ioc.bean.Account">
        <constructor-arg name="cardNo" value="123"></constructor-arg>
        <constructor-arg name="money" value="23"></constructor-arg>
        <constructor-arg name="name" value="aa"></constructor-arg>
    </bean>
  • set方法注入:通過類成員的set方法實現資料注入
 <bean id="account" class="com.mmc.ioc.bean.Account">
        <property name="name" value="mmc"></property>
        <property name="cardNo" value="abc"></property>
        <property name="money" value="22"></property>
    </bean>
  1. 按照注入的資料型別分類
  • 基本資料型別和String
  • 其他Bean型別
  • 複雜型別(集合型別)

基本型別使用value,其他bean型別使用ref,複雜型別使用對應的array、map、set標籤

<bean id="user" class="com.mmc.ioc.bean.User">
        <property name="id" value="1"></property>
        <property name="account" ref="account"></property>
        <property name="list">
            <array>
                <value>aa</value>
                <value>bb</value>
            </array>
        </property>
        <property name="map">
            <map>
                <entry key="a" value="1"></entry>
                <entry key="b" value="2"></entry>
            </map>
        </property>
    </bean>

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!--配置spring ioc容器的配置檔案-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>


  <!--使用監聽器啟動Spring的IOC容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

xml與註解結合的方式

注意:實際開發中,純xml模式使用已經很少了,引入註解功能,不需要引入額外的jar包。xml+註解結合模式中,xml檔案依然存在,所以Spring IOC容器的啟動仍然從載入xml開始。

一般來說第三方jar包裡面的bean定義在xml裡面,自己開發的bean使用註解。

將第三方jar包的bean放入容器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
">

    <!--包掃描-->
    <context:component-scan base-package="com.mmc.ioc"></context:component-scan>

    <!--引入配置檔案-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>


</beans>

xml中標籤與註解的對應

xml形式 註解
標籤 @Component,註解加在類上。預設情況下bean的id為類名(首字母小寫)。另外,針對分層程式碼開發提供了@Componenet的三種別名@Controller、

@Service、@Repository分別⽤於控制層類、服務層類、dao層類的bean定義,這
四個註解的⽤法完全⼀樣,只是為了更清晰的區分⽽已
標籤的scope屬性 | @Scope("prototype")

DI依賴注入的註解實現方式

  1. @Autowired(推薦使用)

@Autowired為Spring提供的註解。策略是按型別注入

public class TransferServiceImpl implements TransferService {

    @Autowired
    private AccountDao accountDao;

}

如上程式碼所示,這樣裝配會去spring容器中找到型別為AccountDao的bean,然後將其中如。但如果一個型別有多個bean怎麼辦呢?可以配合@Qualifier("bean的id")使用。

public class TransferServiceImpl implements TransferService {

    @Autowired
    @Qualifier("jdbcAccountDao")
    private AccountDao accountDao;
}
  1. @Resource

@Resource註解由j2EE提,如果指定了name或type就會根據指定的來,如果都沒有指定就自動按照ByName方式裝配

注意:@Resource在Jdk11中已經移除,如果要使用,需要單獨引入jar包。

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

在Servlet類裡面獲取applicationContext

public class TransferServlet extends HttpServlet {

  

    @Override
    public void init() throws ServletException {
        WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        ProxyFactory proxyFactory = (ProxyFactory) webApplicationContext.getBean("proxyFactory");
        transferService= (TransferService) proxyFactory.getJdkProxy(webApplicationContext.getBean("transferService"));
    }
}

純註解模式

將xml配置改為java程式碼:

在配置類上宣告@Configuration,表明是配置類。

@Configuration
@ComponentScan("com.mmc.ioc")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {


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

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

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

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


    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName(driverClass);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}

如果還有其他配置類,可以通過@Import引入進來。

web.xml配置如下:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

   <!--告訴ContextLoaderListener是註解方式啟動ioc容器-->
   <context-param>
     <param-name>contextClass</param-name>
     <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
   </context-param>


  <!--配置spring ioc容器的配置檔案-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.mmc.ioc.SpringConfig</param-value>
  </context-param>


  <!--使用監聽器啟動Spring的IOC容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

高階特性

延遲載入

xml方式:

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />

也可以在容器層次配置預設釋放延遲載入,如:

<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>

註解方式:

@Lazy註解

    @Bean
    @Lazy
    public DataSource dataSource(){
    
    }

FactoryBean

Spring中的Bean有兩種,一種是普通bean,一種是工廠bean(FactoryBean),FactoryBean可以生產某一個型別的Bean例項,也就是說我們可以藉助它自定義Bean的建立過程。

@Component("user")
public class UserFactoryBean implements FactoryBean<User> {

    @Override
    public User getObject() throws Exception {
        User user=new User();
        Account account=new Account();
        account.setName("mmc");
        user.setAccount(account);
        List<String> list=new ArrayList<>();
        list.add("a");
        user.setList(list);
        user.setId(2);
        return user;
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

執行測試程式碼,取出beanname為user的物件。

@Test
    public void testAno(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Object user = applicationContext.getBean("user");
        System.out.println(user);
    }

執行結果如下:

可以看出雖然是UserFactoryBean放入了容器,但是取出來的卻是User物件。這就是FactoryBean的作用。

Spring擴充套件介面和方法

  • BeanNameAware
  • BeanFactoryAware
  • ApplicationContextAware
  • InitializingBean
  • DisposableBean
  • @PostConstruct
  • @PreDestroy
  • init-method
  • destroy-method
  • BeanPostProcessor
  • BeanFactoryPostProcessor

從獲取Spring裡的東西來分有:

  • BeanNameAware
  • BeanFactoryAware
  • ApplicationContextAware

初始化:

  • @PostConstruct
  • InitializingBean
  • init-method

銷燬:

  • @PreDestroy
  • DisposableBean
  • destroy-method

初始化和銷燬的執行先後順序都是註解->介面->xml

使用示例:

<bean id="account" class="com.mmc.ioc.bean.Account" init-method="initMethod" destroy-method="destroyMethod"></bean>

public class Account implements BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean {

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

    @Override
    public void setBeanName(String name) {
        System.out.println("BeanNameAware:"+name);
    }

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

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

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

    @PostConstruct
    public void postConstruct(){
        System.out.println("postConstruct");
    }

    @PreDestroy
    public void preDestroy(){
        System.out.println("preDestroy");
    }

    public void initMethod(){
        System.out.println("init-method");
    }

    public void destroyMethod(){
        System.out.println("destroy-method");
    }
}

全域性的:

BeanFactoryPostProcessor是在BeanFactory初始化之後可以處理一些事情,是針對Bean的工廠進行處理,典型應用:PropertyPlaceholderConfigurer

BeanPostProcessor是針對所有的bean進行攔截進行處理,使用如下:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("account")){
            System.out.println("BeanPostProcessor before"+bean);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("account")){
            System.out.println("BeanPostProcessor after"+bean);
        }
        return bean;
    }
}

全部配置好後,列印檢視執行先後順序:

可以得到下面的執行流程圖:

高頻面試題:

BeanFactory、FactoryBean、ApplicationContext的區別

  • BeanFactory是Spring框架中IOC容器的頂層介面,它只是用來定義一些基礎功能
  • ApplicationContext是它的一個子介面,它擁有更多的功能,如國際化支援和資源訪問等等。
  • FactoryBean:一般情況下,Spring通過反射機制例項化Bean,在某些情況下,例項化bean過程比較複雜,這時配置起來就比較麻煩。如果採用編碼的方式會簡單一些。於是Spring給我們提供了FactoryBean的介面,使用者就可以通過實現這個介面來自定義例項化Bean的邏輯。

總結:BeanFactory是負責生產和管理Bean的一個工廠介面,提供一個Spring Ioc容器規範。FactoryBean是一種Bean建立的方法,對Bean的一種擴充套件。

類圖如下:

相關文章