Spring
- IoC:容器
- AOP:事務處理
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快速入門
- 導入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>
- 創建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容器要管理的對象
- 初始化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的方式
- 提供可訪問的構造方法
使用構造方法創建的對象如何交給容器來管理呢?
<bean id="baseDao" class="com.jc.dao.impl.BaseDaoImpl" />
- 使用靜態工廠創建對象如何交給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();
}
}
- 使用實例工廠實例化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
的泛型接口。
- 使用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-method
和destroy-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-jdbc
和mybatis-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配置文件,使用sqlSessionFactory
和mapperScannerConfigurer
替換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)
配置junit
和spring-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));
}
}