Spring 源碼分析(一)之 Spring IOC 容器基礎介紹

spring.png

Spring 源碼分析(一)之Spring IOC 容器基礎介紹

Spring IOC 容器在項目中的作用

  • 將對象的構建統一解決
  • 並自動維護對象的依賴關係,從而降低實現成本

spring-1.jpg

IOC(Inversion of Control) 控制反轉

是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。

系統中通過引入實現了IoC模式的IoC容器,即可由IoC容器來管理對象的生命週期、依賴關係等,從而使得應用程序的配置和依賴性規範與實際的應用程序代碼分離。其中一個特點就是通過文本的配置文件進行應用程序組件間相互關係的配置,而不用重新修改並編譯具體的代碼。


在我們開發中,經常會A類引用B類、C類、D類…,如果全部交給我們自己來維護這些類的依賴關係,工作量比較大而且很容易出錯,而IOC容器的出現,正式爲解決這一問題,其可以將對象的構建方法進行統一,並自動維護對象的依賴關係,從而降低系統的實現成本。實現方式:提前對目標對象基於XML進行聲明,或使用註解進行聲明。

具體demo如下。

pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo.spring</groupId>
    <artifactId>spring-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <spring.version>4.3.8.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
    </dependencies>
</project>

實現實體Bean的構建

  • 基於ClassName的構建
  • 基於構造方法的構建
  • 靜態工廠的方法創建
  • FactoryBean創建

spring.xml

        <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- 默認構造函數構建 基於反射構建-->
        <bean class="com.demo.spring.HelloSpring"></bean>
    
        <!-- 指定構造函數構建 基於反射構建-->
        <bean class="com.demo.spring.HelloSpring">
            <constructor-arg index="0" value="zyy"/>
            <constructor-arg name="age" value="18"/>
        </bean>
    
        <!-- 靜態工廠方法構建-->
        <bean id="helloSpring" class="com.demo.spring.HelloSpring" factory-method="buid">
            <constructor-arg name="type" value="A"></constructor-arg>
        </bean>
    
        <!-- FactoryBean創建 自定義創建bean-->
        <bean id="driver" class="com.demo.spring.DriverFactoryBean" >
           <property name="jdbcUrl" value="jdbc:mysql://192.168.5.104:3306"></property>
        </bean>
    </beans>

HelloSpring,java

    package com.demo.spring;

    /**
     * com.demo.spring
     *
     * @author Zyy
     * @date 2019/2/12 15:50
     */
    public class HelloSpring {
        private String name;
        private int age;
    
        public HelloSpring() {
        }
    
        public HelloSpring(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        //靜態工廠以及AB測試
        public static HelloSpring buid(String type) {
            if ("A".equals(type)) {
                return new HelloSpring("zyy",18);
            } else if ("B".equals(type)) {
                return new HelloSpring("xxx",20);
            } else {
               throw new IllegalArgumentException("argument must A or B");
            }
    
        }
    }

DriverFactoryBean.java

package com.demo.spring;

import org.springframework.beans.factory.FactoryBean;

import java.sql.Driver;
import java.sql.DriverManager;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/12 16:13
 */
public class DriverFactoryBean implements FactoryBean {
    private String jdbcUrl;

    public Object getObject() throws Exception {
        return DriverManager.getDriver(jdbcUrl);
    }

    public Class<?> getObjectType() {
        return Driver.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }
}
bean的基本特性
  • 作用範圍
  • 生命週期
  • 裝載機制
作用範圍

bean很多是無狀態的,對於無狀態的對象可以採取單例,而有狀態的對象則必須用是多例模式進行創建,通過scope可以進行設置

scope="prototype"
scope="singleton"

例如

<bean id="di" class="com.demo.spring.DI" scope="singleton"/>

如果一個bean設置成prototype 則可以通過可以BeanFactoryAware 獲取BeanFactory對象,即可每次獲取都是新對象。

生命週期

Bean對象的創建、初始化、銷燬即是Bean的生命週期。通過init-method、destroy-method屬性可以分別指定構建方法和初始方法。

<bean id="di" class="com.demo.spring.DI" scope="singleton" init-method="init" destroy-method="destroy"/>

或者可以讓Bean去實現initializingBean.afterPropertiesSet()、DisposableBean.destroy()方法。分別對應初始方法和銷燬方法。

加載機制

指定Bean在何時進行加載。設置lazy-init。
當爲true:懶加載,用到的時候才創建對象
當爲false:容器啓動時,就創建對象

<bean id="di" lazy-init="true" class="com.demo.spring.DI" scope="singleton" init-method="init" destroy-method="destroy"/>

什麼時候使用懶加載?

懶加載會使容器啓動更快,而非懶加載可以讓容器啓動時更快的發現程序中的錯誤,一般我們選擇非懶加載。

spring依賴注入

一個bean依賴其他的bean由springIOC容器統一的管理進行注入,無需自己外部傳入,內部創建進行處理。

依賴注入的幾種方式

  • set方法注入
  • 構造方法注入
  • 自動注入(byName,byType)
  • 依賴方法注入(lookup-method)

自動注入(byName,byType)

DI.java

 package com.demo.spring;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/12 16:35
 */
public class DI {
}

HelloSpring.java

package com.demo.spring;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/12 15:50
 */
public class HelloSpring {
    private String name;
    private int age;
    private DI di;

    public HelloSpring() {
    }

    public HelloSpring(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public DI getDi() {
        return di;
    }

    public void setDi(DI di) {
        this.di = di;
    }

    //靜態工廠以及AB測試
    public static HelloSpring buid(String type) {
        if ("A".equals(type)) {
            return new HelloSpring("zyy",18);
        } else if ("B".equals(type)) {
            return new HelloSpring("xxx",20);
        } else {
           throw new IllegalArgumentException("argument must A or B");
        }

    }
}

spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 默認構造函數構建 基於反射構建-->
    <bean class="com.demo.spring.HelloSpring"></bean>

    <!-- 指定構造函數構建 基於反射構建-->
    <bean id="helloByName" class="com.demo.spring.HelloSpring" autowire="byName">
        <constructor-arg index="0" value="zyy"/>
        <constructor-arg name="age" value="18"/>
    </bean>
    <!-- byName-->
    <bean id="di" class="com.demo.spring.DI"/>

    <!-- byType時,如果bean相同 可以指定primary爲true 來選擇要注入哪一個-->
    <bean class="com.demo.spring.DI" primary="true"/>
    <bean class="com.demo.spring.DI"/>

    <!-- 靜態工廠方法構建-->
    <bean id="helloSpring" class="com.demo.spring.HelloSpring" factory-method="buid">
        <constructor-arg name="type" value="A"></constructor-arg>
    </bean>

    <!-- FactoryBean創建 自定義創建bean-->
    <bean id="driver" class="com.demo.spring.DriverFactoryBean" >
       <property name="jdbcUrl" value="jdbc:mysql://192.168.5.104:3306"></property>
    </bean>
</beans>
依賴方法注入(lookup-method)

一個單例的bean依賴了一個多例的bean,爲了每次獲取多例的bean是不同的

DI.java

package com.demo.spring;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/12 16:35
 */
public class DI {

    public void inject() {
        System.out.println(" inject ");
    }

    public void init() {

    }

    public void destroy () {

    }
}

LookUpTest.java

package com.demo.spring;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/12 18:20
 */
public abstract class LookUpTest {

    public void create() {
        getDi().inject();
    }

    //這個抽象方法由spring採用cglib(動態字節碼)進行實現
    public abstract DI getDi();
}

spring.xml

<bean class="com.demo.spring.LookUpTest" >
    <lookup-method name="getDi"/>
</bean>

該操作的原理是基於動態代理技術,重新生成一個類繼承目標類(cglib),然後重寫抽象方法達到注入目的。

對於單例bean依賴多例bean這種情也可以通過實現ApplicationContextAware、BeanFactoryAware接口獲取BeanFactory實例,從而調用geBean()方法獲取新的實例,推薦使用這種方法,比lookup-method邏輯清晰。

DI.java

package com.demo.spring;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/12 16:35
 */
public class DI {

    public void inject() {
        System.out.println(" inject ");
    }

    public void init() {

    }

    public void destroy () {

    }
}

BeanFactoryAwareTest.java

package com.demo.spring;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/12 22:24
 */
public class BeanFactoryAwareTest implements BeanFactoryAware {

    private BeanFactory beanFactory;

    public void create() {
        beanFactory.getBean(DI.class).inject();
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

測試

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * PACKAGE_NAME
 *
 * @author Zyy
 * @date 2019/2/12 16:02
 */
public class SpringIOCTest {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        //context.getBean("helloSpring");
        //context.getBean("driver");
        context.getBean("helloByName");
    }
}

如有問題,歡迎留言:)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章