SpringIoC

Spring

  • IoC:容器
  • AOP:事務處理

http://spring.io

Spring發展至今已經形成了一種開發的生態圈,Spring提供了若干個項目,每個項目用戶完成特定的功能。

Spring發展史

Spring Framework 系統架構

Spring Framework是Spring生態圈中最基礎的項目,也是其他項目的根基。

架構歷史
系統架構
系統架構 說明
Core Contianer 核心容器
AOP 面向切面編程
Aspects AOP思想實現
Data Access 數據訪問
Data Integration 數據集成
Web Web開發
Test 單元測試與集成測試

核心容器

傳統代碼書寫業務層實現與數據層實現耦合度偏高,因為使用對象時主動靠new產生。如何解決這個問題?只要轉換為由外部提供對象,就可以達到解耦的目的。

IoC(Inversion of Control)控制反轉,其核心思想是將對象創建的控制權由程序內部轉移到外部。使用對象時由原來主動new產生對象的方式,轉換為由外部提供對象,此過程中對象創建的控制權就由程序內部轉移到外部。

Spring技術對IoC思想進行了實現,Spring提供了一個容器(IoC容器),用來充當IoC思想中的“外部”。

IoC容器負責對象的創建、初始化等一系列工作,被創建或被管理的對象在IoC容器稱為Bean。

DI(Dependency Injection)依賴注入,在容器中建立Bean與Bean之間的依賴關系的整個過程稱為注入。

依賴注入

為了充分解耦,使用IoC容器管理Bean,在IoC容器內將具有依賴關系的Bean進行關系綁定(DI)。最終達到效果是使用對象時不僅可以直接從IoC容器中獲取,而且獲取到的Bean已經綁定了所有依賴關系。

案例:XML版的IoC快速入門

  1. 導入SpringFramework的坐標spring-context
$ vim 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jc</groupId>
    <artifactId>sl</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
    </dependencies>
</project>
  1. 創建Spring配置文件,配置指定類作為Spring管理的Bean。
$ vim resources/applicationContext.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 id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" />
        <bean id="baseService" class="com.jc.service.impl.BaseServiceImpl" />
</beans>

Bean配置格式:

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" />
屬性 說明
id 表示為Bean起名字,同一個上下文中禁止同名。
class 表示為Bean定義類型

注意:需要提前創建IoC容器要管理的對象

  1. 初始化IoC容器,通過容器獲取指定Bean。
$ vim src/App.java
package com.jc;

import com.jc.dao.BaseDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BaseDao bd = (BaseDao)ctx.getBean("baseDao");
        System.out.println(bd);
    }
}
  • ClassPathXmlApplicationContext()方法用于加載配置文件以得到上下文對象(容器對象)
  • getBean()方法用于獲取資源

案例:DI入門案例

配置Service與DAO之間的關系

$ vim resource/applicationContext.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 id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" />
        <bean id="baseService" class="com.jc.service.impl.BaseServiceImpl">
                <property name="baseDao" ref="baseDao" />
        </bean>
</beans>

property屬性標簽用于配置當前Bean的屬性

<property name="baseDao" ref="baseDao" />
屬性 說明
name 表示配置具體哪個屬性,baseDao對應的是com.jc.dao.impl.BaseDaoImpl類中的名為private BaseDaoImpl baseDao;的屬性。
ref 表示參照那個Bean,baseDao對應的是<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" />

刪除以往使用new的方式創建對象,提供依賴對象userDao對應的setter方法,配置Service與DAO之間的關系。

$ vim jc/com/service/impl/BaseServiceImpl.java
package com.jc.service.impl;

import com.jc.dao.impl.BaseDaoImpl;
import com.jc.service.BaseService;

public class BaseServiceImpl implements BaseService {
    private BaseDaoImpl baseDao;

    public void setBaseDao(BaseDaoImpl baseDao) {
        this.baseDao = baseDao;
    }
}

Bean的配置

  • Bean基礎配置
  • Bean別名配置
  • Bean的作用范圍
類別 說明
標簽 <bean>
所屬 <beans>
功能 定義Spring核心容器管理的對象
格式 <beans><bean></bean></beans>
屬性 說明
id Bean的id,使用容器時可通過Bean的id獲取對應的Bean,在一個容器中id值唯一。
name Bean的別名,支持多個,支持逗號/分號/空格分割。功能和id相同。
class Bean的類型,配置Bean的全路徑類名。

獲取Bean時無論是采用id還是name屬性,無法獲取時都會拋出異常NoSuchBeanDefinitionException

默認IoC創建的Bean是單例的singleton

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BaseDao bd1 = (BaseDao)ctx.getBean("baseDao");
BaseDao bd2 = (BaseDao)ctx.getBean("baseDao");
System.out.println(bd1);//com.jc.dao.impl.BaseDaoImpl@2f686d1f
System.out.println(bd2);//com.jc.dao.impl.BaseDaoImpl@2f686d1f
System.out.println(bd1==bd2);//true

若需要IoC創建的Bean不是單例的則需要在配置Bean時添加并修改scope屬性,默認scope不寫其值是singleton單例的。

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" scope="prototype" />
類別 說明
屬性 scope
所屬 <bean>
功能 定義Bean的作用范圍

scope的取值范圍

scope 說明
singleton 默認值,單例模式
prototype 非單例,原生值。
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BaseDao bd1 = (BaseDao)ctx.getBean("baseDao");
BaseDao bd2 = (BaseDao)ctx.getBean("baseDao");
System.out.println(bd1);//com.jc.dao.impl.BaseDaoImpl@2f686d1f
System.out.println(bd2);//com.jc.dao.impl.BaseDaoImpl@3fee9989
System.out.println(bd1==bd2);//false

為什么Bean默認是單例的呢?

Spring的IoC管理的Bean是會重復使用的,因此默認采用的單例。因此適合交給容器管理的Bean主要包括以下幾種:表現層對象(Servlet)、業務層對象(Service)、數據層對象(DAO)、工具對象(Util)。不適合交給容器管理的Bean主要是有狀態信息記錄的對象,比如:封裝實體的域對象。

Bean的創建(實例化)

  • 使用構造方法來創建
  • 使用靜態工廠來創建
  • 使用實例工廠來創建
  • 使用FactoryBean來創建

Bean本質上是對象,創建Bean也就是創建對象,創建對象一般采用的是【new+構造方法】的方式來完成的。

  • Spring創建對象采用的是反射機制

例如:私有構造方法能被調用是由于采用了反射機制來創建對象

public class BaseDaoImpl implements BaseDao {
    private BaseDaoImpl(){
        System.out.println("BASE DAO CONSTRUTOR IS RUNNING...");
    }
}
  • Spring創建對象時調用的是無參的構造方法
public class BaseDaoImpl implements BaseDao {
    public BaseDaoImpl(int i){
        System.out.println("BASE DAO CONSTRUTOR IS RUNNING...");
    }
}

出現異常

BeanCreationException: Error creating bean with name 'baseDao' defined in class path resource [applicationContext.xml]...
BeanInstantiationException: Failed to instantiate [com.jc.dao.impl.BaseDaoImpl]: No default constructor found; 
Caused by: java.lang.NoSuchMethodException: com.jc.dao.impl.BaseDaoImpl.<init>()

實例化Bean的方式

  1. 提供可訪問的構造方法

使用構造方法創建的對象如何交給容器來管理呢?

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" />
  1. 使用靜態工廠創建對象如何交給Spring的IoC來管理呢?

此種方式主要是為了兼容早期遺留下的系統的使用方式

<bean id="baseDao" class="com.jc.factory.BaseFactory" factory-method="createBaseDao" />

創建靜態工廠類

$ vim factory/BaseFactory.java
package com.jc.factory;

import com.jc.dao.BaseDao;
import com.jc.dao.impl.BaseDaoImpl;

public class BaseFactory {
    public static BaseDao createBaseDao(){
        System.out.println("BASE FACTORY SETUP...");
        return new BaseDaoImpl();
    }
}
  1. 使用實例工廠實例化Bean

創建實例工廠

$ vim /factory/BaseFactory.java
public class BaseFactory {
    public BaseDao createBaseDao(){
        System.out.println("BASE FACTORY SETUP...");
        return new BaseDaoImpl();
    }
}

配置實例工廠

$ vim resources/applicationContext.xml
<bean id="baseFactory" class="com.jc.factory.BaseFactory" />
<bean id="baseDao" factory-bean="baseFactory" factory-method="createBaseDao"></bean>

Spring為簡化實例工廠編寫的復雜性,創建了一個名為FactoryBean的泛型接口。

  1. 使用FactoryBean方式實例化Bean

創建實例工廠同時實現FactoryBean接口

$ vim factory/BeaseFactory.java
public class BaseFactory implements FactoryBean<BaseDao> {
    //代替原始實例工廠中創建對象的方法,統一采用相同的名稱都為getObject。
    @Override
    public BaseDao getObject() throws Exception {
        return new BaseDaoImpl();
    }
    @Override
    public Class<?> getObjectType() {
        return BaseDao.class;
    }
    @Override
    public boolean isSingleton(){
        return true;
    }
}

默認創建是工廠實例對象是單例的,此時可不寫isSingleton()方法,若需禁用則需重寫isSingleton()方法。

配置實例工廠

$ vim resources/applicationContext.xml
<bean id="baseDao" class="com.jc.factory.BaseFactory" />

Bean的生命周期

生命周期指的是從創建到消亡的完整過程,Bean的生命周期也就是從Bean的創建到銷毀的完整過程,而Bean生命周期控制指的是在Bean創建后到銷毀前做的一些事情。

初始化容器:創建對象(內存分配)-> 執行構造方法 -> 執行屬性注入(set操作)-> 執行Bean初始化方法

Bean生命周期控制可采用兩種方式

  • 提供生命周期控制方法,配置生命周期控制方法。
  • 實現InitializingBean和DisposableBean接口

例如:為Bean配置初始化執行方法和銷毀時執行方法

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" init-method="init" destroy-method="destroy" />

為對應的類添加配置的方法

public class BaseDaoImpl implements BaseDao {
    public void init(){
        System.out.println("BASE DAO IMPL INIT...");
    }
    public void destroy(){
        System.out.println("BASE DAO IMPL DESTROY...");
    }
}

Bean正常銷毀需要在容器關閉是才會觸發

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

然而ApplicationContext類中并沒有提供關閉容器的方法,ClassPathXmlApplicationContext類中提供了用于關閉容器的close方法(暴力方式)。

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BaseDao bd1 = (BaseDao)ctx.getBean("baseDao");
BaseDao bd2 = (BaseDao)ctx.getBean("baseDao");
System.out.println(bd1);//com.jc.dao.impl.BaseDaoImpl@2f686d1f
System.out.println(bd2);//com.jc.dao.impl.BaseDaoImpl@3fee9989
System.out.println(bd1==bd2);//false
ctx.close();

此時查看打印信息

BASE DAO IMPL INIT...
com.jc.dao.impl.BaseDaoImpl@7085bdee
com.jc.dao.impl.BaseDaoImpl@7085bdee
true
BASE DAO IMPL DESTROY...

直接使用close()方法關閉容器相對比較暴力,優雅地做法的采用關閉鉤子的方式。

設置registerShutdownHook容器關閉鉤子,容器啟動后添加一個標記,當虛擬機退出時根據標記判斷是否關閉容器。

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
BaseDao bd1 = (BaseDao)ctx.getBean("baseDao");
BaseDao bd2 = (BaseDao)ctx.getBean("baseDao");
System.out.println(bd1);//com.jc.dao.impl.BaseDaoImpl@2f686d1f
System.out.println(bd2);//com.jc.dao.impl.BaseDaoImpl@3fee9989
System.out.println(bd1==bd2);//false

Spring為簡化并統一Bean生命周期的控制,若不生工指定init-methoddestroy-method數量。可直接在對應Bean的類中實現Spring接口:InitializingBean, DisposableBean來完成Bean生命周期的控制。

  • InitializingBean接口提供了afterPropertiesSet方法
  • DisposableBean接口提供了destroy方法
$ vim service/impl/BaseServiceImpl.java
public class BaseServiceImpl implements BaseService, InitializingBean, DisposableBean {
    private BaseDaoImpl baseDao;

    public void setBaseDao(BaseDaoImpl baseDao) {
        this.baseDao = baseDao;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BASE SERVICE IMPL START PROPERTIES SET...");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("BASE SERVICE IMPL DESTROY...");
    }
}

運行測試

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
BaseService bs = (BaseService) ctx.getBean("baseService");
System.out.println(bs);
BASE DAO IMPL INIT...
BASE SERVICE IMPL START PROPERTIES SET...
com.jc.service.impl.BaseServiceImpl@6fd02e5
BASE SERVICE IMPL DESTROY...
BASE DAO IMPL DESTROY...

依賴注入(DI)

如何向一個類中傳遞數據呢?只有一種方式就是通過方法參數來傳遞數據。使用方法參數時根據方法可分為兩種:一種是普通方法即采用setter方法來傳遞,另一種是采用構造方法來傳遞數據。

依賴注入描述了在容器中建立Bean與Bean之間依賴關系的過程,如果Bean運行需要的是數字或字符串,該怎么辦呢?因此依賴注入時根據傳遞數據類型的不同可劃分為兩種:一種是引用類型、一種是簡單類型(包含基礎數據類型和String)

綜上所述,依賴注入最終可分為4種方式

依賴注入 普通方法(setter方法) 構造方法(構造器)
簡單數據類型 使用setter方法傳遞簡單類型 使用構造方法傳遞簡單類型
引用數據類型 使用setter方法傳遞引用類型 使用構造方法傳遞引用類型

依賴注入方法的選擇

  • 強制依賴:采用構造器注入(嚴謹)
  • 可選依賴:采用Setter注入靈活性強,使用Setter注入有概率發生無法注入將導致null對象出現。

Spring框架推薦使用構造器注入,第三方框架內部大多采用構造器注入的方式實現數據初始化。

若有必要兩者可同時使用,使用構造器注入強制依賴,使用Setter注入可選依賴。

自己開發模塊推薦使用Setter注入,別人開發的模塊若受控對象沒有提供Setter方法就只能使用構造器注入。

setter注入引用類型

  • 在Bean中定義引用類型屬性并提供可訪問的Setter方法
public class BaseServiceImpl implements BaseService {
    private BaseDaoImpl baseDao; // ref="baseDao"
    public void setBaseDao(BaseDaoImpl baseDao) {
        this.baseDao = baseDao;
    }
}
  • 使用時在Bean配置中是用<property>標簽的ref屬性來注入引用類型對象
<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" />
<bean id="baseService" class="com.jc.service.impl.BaseServiceImpl">
        <property name="baseDao" ref="baseDao" />
</bean>

setter注入簡單類型

  • 在Bean中定義簡單類型屬性并提供可訪問的Setter方法
public class BaseDaoImpl implements BaseDao {
    private Long appId;
    private String appKey;
    public void setAppId(Long appId){
        this.appId = appId;
    }
    public void setAppKey(String appKey){
        this.appKey = appKey;
    }
}

配置中是用<property>標簽的value屬性來注入簡單類型數據

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl">
        <property name="appId" value="741852963" />
        <property name="appKey" value="b64f1a77b1b317d347f5cb79332c86d2" />
</bean>

構造器注入

配置構造器注入標簽<constructor-arg>,其中name屬性對應構造器形參名稱,value對應參數值。

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" init-method="init" destroy-method="destroy">
        <constructor-arg name="appId" value="741852963" />
        <constructor-arg name="appKey" value="b64f1a77b1b317d347f5cb79332c86d2" />
</bean>
<bean id="baseService" class="com.jc.service.impl.BaseServiceImpl">
        <constructor-arg name="baseDao" ref="baseDao"/>
</bean>

構造器注入簡單類型

public class BaseDaoImpl implements BaseDao {
    private Long appId;
    private String appKey;
    public BaseDaoImpl(Long appId, String appKey){
        this.appId = appId;
        this.appKey = appKey;
    }
}

構造器注入引用類型

public class BaseServiceImpl implements BaseService {
    private BaseDao baseDao;
    public BaseServiceImpl(BaseDao baseDao){
        this.baseDao = baseDao;
    }
}

問題:由于<constructor-arg>標簽中name屬性必須與構造器中形參名稱保持一致,相當于緊耦合,不便于修改調整。為了解決形參名稱耦合問題,可去掉name屬性替換為type屬性,即根據指定的數據類型來識別出形參。

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" init-method="init" destroy-method="destroy">
        <constructor-arg type="java.lang.Long" value="741852963" />
        <constructor-arg type="java.lang.String" value="b64f1a77b1b317d347f5cb79332c86d2" />
</bean>
<bean id="baseService" class="com.jc.service.impl.BaseServiceImpl">
        <constructor-arg type="com.jc.dao.BaseDao" ref="baseDao"/>
</bean>

使用type屬性雖然能解決name屬性帶來的耦合問題,但如果出現相同類型的形參,該怎么辦呢?為了解決這種問題,可使用index屬性即指定形參的索引位置,這樣上面問題都可以得到解決。

<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" init-method="init" destroy-method="destroy">
        <constructor-arg index="0" value="741852963" />
        <constructor-arg index="1" value="b64f1a77b1b317d347f5cb79332c86d2" />
</bean>
<bean id="baseService" class="com.jc.service.impl.BaseServiceImpl">
        <constructor-arg index="0" ref="baseDao"/>
</bean>

依賴自動裝配(autowire)

IoC容器根據Bean所依賴的資源,在容器內自動查找并注入到Bean中的過程,稱為自動裝配。

自動裝配方式 說明
按類型 autowire="byType"
按名稱 autowire="byName"
按構造方法 autowire="contructor"
禁用 autowire="no"

自動裝配只需在Bean標簽上添加autowire屬性來設置自動裝配的類型

<bean id="baseService" class="com.jc.service.impl.BaseServiceImpl" autowire="byType" />
<bean id="baseService" class="com.jc.service.impl.BaseServiceImpl" autowire="byName />

依賴自動裝配的特征

  • 自動裝配只能用于引用類型依賴注入,不能對簡單類型進行操作。
  • 使用byType按類型裝配時,必須保障容器中相同類型的Bean唯一,推薦使用。
  • 使用byName按名稱裝配時,必須保證容器中具有指定名稱的Bean,這種方式變量名和配置項會產生耦合,不推薦使用。
  • 自動裝配優先級低于Setter注入和構造器注入,同時出現時自動裝配配置會失效。

集合注入

public class BaseServiceImpl implements BaseService {
    private int[] array;
    public void setArray(int[] array){
        this.array = array;
    }

    private List<String> list;
    public void setList(List<String> list){
        this.list = list;
    }

    private Set<String> set;
    public void setSet(Set<String> set){
        this.set = set;
    }

    private Map<String, String> map;
    public void setMap(Map<String, String> map){
        this.map = map;
    }

    private Properties propertis;
    public void setProperties(Properties propertis){
        this.propertis = propertis;
    }
}

配置

<bean id="baseService" class="com.jc.service.impl.BaseServiceImpl" autowire="byName">
        <property name="array">
                <array>
                        <value>1</value>
                        <value>2</value>
                        <value>3</value>
                </array>
        </property>
        <property name="list">
                <list>
                        <value>v1</value>
                        <value>v2</value>
                        <value>v3</value>
                </list>
        </property>
        <property name="set">
                <set>
                        <value>s1</value>
                        <value>s2</value>
                        <value>s3</value>
                </set>
        </property>
        <property name="map">
                <map>
                        <entry key="k1" value="v1" />
                        <entry key="k2" value="v2" />
                        <entry key="k3" value="v3" />
                </map>
        </property>
        <property name="properties">
                <props>
                        <prop key="k1">v1</prop>
                        <prop key="k2">v2</prop>
                        <prop key="k3">v3</prop>
                </props>
        </property>
</bean>

管理數據源對象(連接池對象)

示例:管理阿里的Druid數據源對象

添加依賴并加載

$ vim pom.xml
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

在Bean配置文件中管理DruidDataSource對象

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/fw"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
</bean>

從容器中讀取

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
DataSource ds = (DataSource) ctx.getBean("dataSource");
System.out.println(ds);

示例:管理C3PO數據源對象

訪問 https://mvnrepository.com/ 查找C3P0 庫的坐標參數,添加到pom.xml中。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>

查找到c3p0的數據源對象名稱后配置Bean

<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/fw"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
</bean>

從容器中獲取Bean

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
DataSource ds = (DataSource) ctx.getBean("comboPooledDataSource");
System.out.println(ds);

加載Properties文件

Spring加載外部Properties文件之前,要開啟一個全新的命名空間context

<?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
               http://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context.xsd
        ">
</beans>

添加context命名空間

xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
"

創建Properties屬性配置文件

$ vim resources/jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/fw
jdbc.user=root
jdbc.password=root

使用context命名空間加載Properties配置文件

<context:property-placeholder location="jdbc.properties" />

為避免加載的配置文件與系統配置同名造成自定義配置加載失敗的問題,添加system-properties-mode="NEVER"屬性來解決。

<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER" />

若自定義屬性配置文件存在多個,可在location屬性中使用逗號分割后加載多個。

<context:property-placeholder location="jdbc.properties,app.properties" system-properties-mode="NEVER" />

可使用*.properties加載所有的后綴為.properties的配置文件

<context:property-placeholder location="*.properties" system-properties-mode="NEVER" />

標準寫法

<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER" />

使用classpath:*.properties寫法只能從當前工程讀取到自定義配置文件,若當前項目中添加的第三方jar包包含了自定義配置文件,則需要采用classpath*:*.properties寫法。classpath*:*.properties表示不僅可以從當前項目中讀取,也可以從所依賴的第三方jar中讀取自定義的屬性配置文件。

<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER" />

使用屬性占位符${}讀取properties文件中的屬性

<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.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
</bean>

容器

創建容器

加載類路徑下的配置 ,ClassPathXmlApplicationContext支持同時加載多個配置。

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

也可以采用從文件系統下加載配置文件,這種方式需要采用配置文件的完整路徑。

ApplicationContext ctx = new FileSystemXmlApplicationContext("E:\\java\\sl\\src\\main\\resources\\applicationContext.xml");

早期的創建容器也可采用BeanFactory來實現,BeanFactory創建完畢后所有Bean都是延遲加載的。

Resource r = new ClassPathResource("applicationContext.xml");
BeanFactory ctx = new XmlBeanFactory(r);

獲取Bean

  • 使用Bean的名稱獲取
DataSource ds = (DataSource) ctx.getBean("comboPooledDataSource");
  • 使用Bean名稱獲取并指定類型
DataSource ds = ctx.getBean("comboPooledDataSource", DataSource.class);
  • 使用Bean類型獲取
DataSource ds = ctx.getBean(DataSource.class);

總結

  • BeanFactory是IoC容器的頂層接口,初始化BeanFactory對象時加載的Bean采用的是延遲加載。
  • ApplicationContext接口是Spring容器的核心接口,初始化時Bean采用的立即加載。
  • ApplicationContext接口提供了基礎的Bean操作方法,通過其他接口擴展其功能。
  • ApplicationContext接口常用初始化類包括:ClassPathXmlApplicationContext、FileSystemXmlApplicationContext

注解開發

注解開發定義Bean

  • 使用@Component注解來定義Bean
@Component("baseDao")
public class BaseDaoImpl implements BaseDao {
}
  • 核心配置文件中通過組件掃描來加載Bean
<context:component-scan base-package="com.jc.dao.impl" />

Spring為@Component注解提供了三個衍生注解

@Component衍生 說明
@Controller 用于表現層Bean定義
@Service 用于業務層Bean定義
@Repository 用于數據層Bean定義

純注解開發

Spring3.0升級了純注解開發模式,使用Java類替代配置文件開啟了Spring快速開發賽道。

使用Java類代替Spring核心配置文件

$ vim config/SpringConfig.java
@Configuration
@ComponentScan("com.jc")
public class SpringConfig {

}
  • Configuration注解用于設定當前類為配置類
  • ComponentScan注解用于設定掃描路徑,它只能添加一次,支持多個數據(數組格式)。

使用純注解開發后,讀取Spring核心配置文件初始化容器對象,需切換為讀取Java配置類初始化容器對象。

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BaseDao bd = ctx.getBean(BaseDao.class);
System.out.println(bd);

Bean管理

  • Bean作用范圍:使用@Scope定義Bean的作用范圍
  • Bean生命周期:使用@PostConstruct@PreDestroy定義Bean的生命周期

例如:

@Repository("baseDao")
@Scope("singleton")
public class BaseDaoImpl implements BaseDao {
    @PostConstruct
    public void init(){
        System.out.println("BASE DAO IMPL INIT...");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("BASE DAO IMPL DESTROY...");
    }
}

測試運行結果

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//ctx.registerShutdownHook();
BaseDao bd1 = ctx.getBean(BaseDao.class);
BaseDao bd2 = ctx.getBean(BaseDao.class);
System.out.println(bd1);
System.out.println(bd2);
System.out.println(bd1 == bd2);
ctx.close();

依賴注入

自動裝配

  • 使用@Autowired注解開啟自動裝配模式,默認采用的按類型注入(byType)。
  • @Autowired自動裝配是基于反射設計創建對象,并暴力反射對應屬性為私有屬性來初始化數據,因此無需提供Setter方法。
  • @Autowired自動裝配建議默認使用無參構造方法創建對象,若沒有提供對應的構造方法則需提供唯一的構造方法。
@Service("baseService")
public class BaseServiceImpl implements BaseService {
    @Autowired
    private BaseDao baseDao;
}
  • 使用@Qualifier注解開啟指定名稱裝配Bean,@Qualifier注解無法單獨使用必須配合@Autowired注解使用。
@Service("baseService")
public class BaseServiceImpl implements BaseService {
    @Autowired
    @Qualifier("baseDao")
    private BaseDao baseDao;
}
@Repository("baseDao")
@Scope("singleton")
public class BaseDaoImpl implements BaseDao {
    @PostConstruct
    public void init(){
        System.out.println("BASE DAO IMPL INIT...");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("BASE DAO IMPL DESTROY...");
    }
}
  • 可使用@Value注解實現簡單類型注入
public class BaseDaoImpl implements BaseDao {
    @Value("test")
    private String name;
}

使用使用外部配置文件作為值設置到@Value注解中呢?

  • 加載Properties文件

使用@PropertySource注解加載外部屬性文件,@PropertySource路徑僅支持單一文件配置,多文件需使用數組格式配置,禁止使用通配符*

@Configuration
@ComponentScan("com.jc")
@PropertySource("jdbc.properties")
public class SpringConfig {

}

外部屬性文件加載成功后,即可在@Value中直接使用指定屬性值。

@Repository("baseDao")
@Scope("singleton")
public class BaseDaoImpl implements BaseDao {
    @Value("${jdbc.driver}")
    private String name;
}

管理第三方Bean

使用@Bean配置第三方Bean

$ vim config/SpringConfig.java
@Configuration
public class SpringConfig {
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        //ds.setDriverClassName();
        //ds.setUrl();
        //ds.setUsername();
        //ds.setPassword();
        return ds;
    }
}

退漿將第三方配置類獨立封裝后載入到主配置文件中

$ vim config/JdbcConfig.java
public class JdbcConfig {
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        //ds.setDriverClassName();
        //ds.setUrl();
        //ds.setUsername();
        //ds.setPassword();
        return ds;
    }
}

在主配置文件內通過@import標注手工導入到配置類的核心配置中,@import注解只能添加一次,若存在多個待導入項目則需使用數組格式。

$ vim config/SpringConfig.java
@Configuration
@Import({JdbcConfig.class})
public class SpringConfig {

}

將獨立的配置類加入到核心配置,也可以使用@ComponentScan({"com.jc.config"})注解掃描配置類所在的包,來加載對應的配置類信息。此種方法不推薦。

獨立的配置類中對簡單類型,可直接采用@Value注解獲取注入。

$ vim config/JdbcConfig.java
@PropertySource("jdbc.properties")
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

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

獨立配置類中若存在引用類型,主要為Bean定義的方法設置形參即可,容器會根據類型自動裝載對象。

Spring整合MyBatis

Spring整合MyBatis核心在于兩個Bean,分別是SqlSessionFactoryBean和MapperScannerConfigurer。

MyBatis程序核心對象分析

步驟 說明
1 加載MyBatis配置文件
2 創建SqlSessionFactoryBuilder對象
3 創建SqlSessionFactory對象
4 獲取SqlSession對象
5 SqlSession對象執行查詢獲取結果集
6 釋放資源
// 加載MyBatis配置文件
InputStream is = null;
try {
    is = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
    e.printStackTrace();
}
//創建SqlSessionFactoryBuilder對象
SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
//創建SqlSessionFactory對象
SqlSessionFactory ssf = ssfb.build(is);
//獲取SqlSession對象
SqlSession ss = ssf.openSession();
//SqlSession對象執行查詢獲取結果集
SysUserMapper m = ss.getMapper(SysUserMapper.class);
List<SysUser> l = m.list();
System.out.println(l);
//釋放資源
ss.close();

Spring整合MyBatis時是圍繞著核心對象SqlSessionFactory展開的

$ vim resources/jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/fw
jdbc.username=root
jdbc.password=root
$ vim resources/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--初始化屬性數據-->
    <properties resource="jdbc.properties" />
    <!--初始化類型別名-->
    <typeAliases>
        <package name="com.jc.domain" />
    </typeAliases>
    <!--初始化數據源配置-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>
    <!--初始化映射配置-->
    <mappers>
        <package name="com.jc.mapper" />
    </mappers>
</configuration>

Spring整合MyBatis需添加兩個依賴分別是spring-jdbcmybatis-spring

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.23</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

使用注解的方式開發,替換掉原來的mybatis-config.xml文件。

創建Spring核心配置,并導入JDBC與MyBatis配置。

$ vim config/SpringConfig.java
@Configuration
@ComponentScan("com.jc")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {

}

創建JDBC配置文件,并添加Druid數據源配置項目。

$ vim config/JdbcConfig.java
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

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

創建MyBatis配置文件,使用sqlSessionFactorymapperScannerConfigurer替換mybatis-config.xml中的內容。

$ vim config/MybatisConfig.java
public class MybatisConfig{
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource ds){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.jc.domain");
        ssfb.setDataSource(ds);
        return ssfb;
    }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.jc.mapper");
        return msc;
    }
}

Spring整合JUnit

  • 關鍵點:配置@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SpringConfig.class)

配置junitspring-test兩個依賴

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.23</version>
</dependency>

在測試包下創建測試文件

$ vim AccountServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testGetById(){
        System.out.println(accountService.getById(11));
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容