深入理解Spring IOC容器及擴展


本文將從純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的一種擴展。

類圖如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM