一篇文章帶你掌握主流基礎框架——Spring
這篇文章中我們將會介紹Spring的框架以及本體內容,包括核心容器,注解開發,AOP以及事務等內容
那么簡單說明一下Spring的必要性:
- Spring技術是JavaEE開發的必備技能,企業開發技術選型率高達90%!
- Spring可以幫助簡化開發,降低企業級開發的復雜度
- Spring可以進行框架整合,高效整合其他技術,提高企業級應用開發與運行效率
Spring的核心內容:
- Ioc技術
- DI技術
- AOP
- 事務處理
Spring可進行的框架整合:
- MaBatis
- MyBatis-plus
- Struts
- Struts2
- Hibernate
在接下來的文章中,我們會學習Spring的框架思想,學習Spring的基本操作,結合案例熟練掌握
溫馨提醒:在學習本篇文章前請先學習JavaWeb相關內容
(HTTP,Tomcat,Servlet,Request,Response,MVC,Cookie,Session,Ajax,Vue等內容)
初識Spring
官網:Spring | Home
Spring發展至今已經形成了一套開發的生態圈,Spring提供了相當多的專案,每個專案用于完成特定功能

我們常用的主流技術包括有:
- Spring Framework:Spring框架
- Spring Boot:Spring簡化代碼開發
- Spring Cloud:Spring分布設計
Spring FrameWork系統架構
在系統學習Spring之前,我們需要先來了解FrameWork系統結構
- Spring FrameWork是Spring生態圈中最基本的專案,是其他專案的根基

我們現在所使用的Spring FrameWork是4.0版本,已經趨于穩定
下面我們對架構圖進行解釋:
- Core Container:核心容器
- AOP:面向切面編程
- Aspects:AOP思想實作
- Data Access:資料訪問
- Data Intergration:資料集成
- Web:Web開發
- Test:單元測驗與集成測驗
我們可以在官方中獲得如此評價:
- 強大的基于 JavaBeans 的采用控制反轉(Inversion of Control,IoC)原則的配置管理,使得應用程式的組建更加快捷簡易,
- 資料庫事務的一般化抽象層,允許插件式事務管理器,簡化事務的劃分使之與底層無關,
- 一個可用于從 applet 到 Java EE 等不同運行環境的核心 Bean 工廠,
核心概念介紹
首先我們思索一下我們之前的業務層與資料層:
// 資料層介面
public interface BookDao {
public void save();
}
// 資料層實作
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
// 業務層介面
public interface BookService {
public void save();
}
// 業務層實作
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void save() {
bookDao.save();
}
}
如果我們修改BookDaoImpl內容,那么相對應的業務層實作中的bookDao的new實作也要進行修改,甚至下方方法的物件也要進行修改
Spring使用前問題
代碼書寫現狀:
- 耦合度偏高
解放方案:
- 使用物件時,在程式中不要主動使用new產生物件,轉換為由外部提供物件
Spring思想以及實作
IoC(Inversion of Control)控制反轉思想:
- 使用物件時,由主動new創建物件轉換為由外部提供物件
- 此程序中物件創建控制權由程式轉移到外部,被稱為控制反轉
DI(Dependency Injection)依賴注入:
- 在容器中建立Bean與Bean之間的依賴關系和整個程序,被稱為依賴注入
Spring技術對Ioc思想進行了實作:
- Spring提供了一個容器,被稱為Ioc容器,用來充當IoC思想的外部
- IoC容器負責物件的創建,初始化等一系列作業,被創建和管理的物件在IoC容器中被稱為Bean
// 資料層實作
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
// IoC容器
/*
包含
dao
service
兩者可以建立連接
*/
// 業務層實作
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void save() {
bookDao.save();
}
}
目的:充分解耦
- IoC:使用IoC容器管理bean
- DI:在IoC容器內將有依賴關系的bean進行關系系結
最終效果:
- 使用物件不僅可以直接從IoC容器中獲取,還可以將已獲得的Bean之間系結依賴關系
IoC入門
首先我們需要明白IoC的使用規則:
- IoC負責管理什么:Service和Dao
- 如何被管理的物件告知IoC容器:(配置)
- 被管理的物件交給IoC容器,如何獲得IoC容器:(介面)
- IoC容器得到之后,如何獲得Bean:(介面方法)
- 使用Spring所需要匯入的坐標:(pom.xml)
下面我們給出IoC入門的詳細步驟:
- 創建Maven專案,在pom.xml中匯入坐標
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
- 創建Spring.xml的配置包(applicationContext.xml,匯入坐標后xml中更新該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">
<!--2.配置bean-->
<!--bean標簽標示配置bean
id屬性標示給bean起名字
class屬性表示給bean定義型別(注意需要是實作類)-->
<bean id="bookDao" />
<bean id="bookService" />
</beans>
- 主函式
package com.itheima;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App2 {
public static void main(String[] args) {
//3.獲取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//4.獲取bean(根據bean配置id獲取)
//BookDao bookDao = (BookDao) ctx.getBean("bookDao");
//bookDao.save();
// 注意:需要型別轉化
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
DI入門
首先我們需要明白DI的使用規則:
- 基于IoC管理bean
- Service中使用new形式創建Dao物件是否保留:(否)
- Service中需要Dao物件如何進入到Service中:(提供方法)
- Service與Dao之間的關系如何描述:(配置)
下面我們給出DI入門的詳細步驟(基于IoC入門):
- 洗掉new方法
public class BookServiceImpl implements BookService {
//5.洗掉業務層中使用new的方式創建的dao物件
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
- 創建物件的set方法
public class BookServiceImpl implements BookService {
//5.洗掉業務層中使用new的方式創建的dao物件
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//6.提供對應的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
- 創建Dao和Service的連接
<?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">
<!--2.配置bean-->
<!--
bean標簽標示配置bean
id屬性標示給bean起名字
class屬性表示給bean定義型別
-->
<bean id="bookDao" />
<bean id="bookService" >
<!--7.配置server與dao的關系-->
<!--
注意:在server中配置關系
property標簽表示配置當前bean的屬性
name屬性表示配置哪一個具體的屬性
ref屬性表示參照哪一個bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
Bean整體介紹
Bean是保存在IoC中的物件,我們通過配置的方式獲得Bean
下面我們從三個方面分別講解Bean:
bean基本配置
首先我們先介紹bean本身性質:
| 類別 | 描述 |
|---|---|
| 名稱 | bean |
| 型別 | 標簽 |
| 所屬 | beans標簽 |
| 功能 | 定義Spring核心容器管理物件 |
| 格式 | <beans> <bean> </bean> </beans> |
| 屬性串列 | id:bean的id,使用容器可以通過id值獲得對應的bean,在一個容器中id值唯一 class:bean的型別,即配置的bean的全路徑類名 |
| 范例 | <bean id="bookService" > |
然后我們介紹一下bean的別名:
| 類別 | 描述 |
|---|---|
| 名稱 | name |
| 型別 | 標簽 |
| 所屬 | bean標簽 |
| 功能 | 定義bean的別名,可定義多個,使用逗號,分號,空格分隔 |
| 范例 | <bean id="bookService" name="service service4 bookEbi" > |
正常情況下,使用id和name都可以獲得bean,但推薦還是使用唯一id
獲得bean無論通過id還是name獲取,如果無法找到則拋出例外NosuchBeanDefinitionException
最后我們介紹一下bean的作用范圍scope:
| 類別 | 描述 |
|---|---|
| 名稱 | scope |
| 型別 | 標簽 |
| 所屬 | bean標簽 |
| 功能 | 定義bean的作用范圍,可選范圍如下: singleton:單列(默認) prototype:非單列 |
| 范例 | <bean id="bookDao" name="dao" scope="prototype"/> |
這里的scope指產生物件的數量
我們的scope在默認情況下是singleton,因為很多物件只需要創建一次,多次創建會導致記憶體膨脹
合適交給容器進行管理的bean(singleton):
- 表現層物件
- 業務層物件
- 資料層物件
- 工具物件
不合適交給容器進行管理的bean(prototype):
- 封裝物體的域物件(帶有狀態的bean)
bean實體化
bean的實體化通常分為四種方法,我們在下面一一介紹:
- 構造方法(常用)
我們需要在資料類中提供構造方法,配置條件中不需要改變
// 資料類
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
<?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-->
<bean id="bookDao" />
</beans>
若無參構造方法不存在,則拋出例外BeanCreationException
- 靜態工廠(了解)
我們在之前的案例中存在有物件工廠的說法,我們可以設定工廠并呼叫其方法得到bean
// 靜態工廠
package com.itheima.factory;
import com.itheima.dao.OrderDao;
import com.itheima.dao.impl.OrderDaoImpl;
//靜態工廠創建物件
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
<?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-->
<bean id="orderDao" factory-method="getOrderDao"/>
</beans>
- 實體工廠(了解)
和靜態工廠相同,但不同點是方法不是靜態,我們需要提前創建一個bean
// 實體工廠
package com.itheima.factory;
import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
//實體工廠創建物件
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
<?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-->
<bean id="userFactory" />
<!--
factory-bean:實體工廠本身bean
factory-method:使用呼叫bean的方法
-->
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
</beans>
- FactoryBean(重要實用)
除了我們之前自己定義的工廠外,Spring提供了一種官方版本的FactoryBean
// FactoryBean工廠(需介面,< >中填寫資料類介面)
package com.itheima.factory;
import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import org.springframework.beans.factory.FactoryBean;
//FactoryBean創建物件
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始實體工廠中創建物件的方法
// 回傳創建物件型別為UserDaoImpl()
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
// 這里填寫介面型別
public Class<?> getObjectType() {
return UserDao.class;
}
// 可以修改來修改其scope屬性
public boolean isSingleton() {
return false;
}
}
<?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">
<!--方式四:使用FactoryBean實體化bean-->
<bean id="userDao" />
</beans>
bean生命周期
我們先來接單介紹生命周期相關概念:
- 生命周期:從創建到消亡的完整程序
- bean生命周期:bean從創建到銷毀的整體程序
- bean生命周期控制:在bean創建后到銷毀前做一些事情
接下來我們介紹生命周期控制方法:
- 資料層提供控制方法
由資料層提供方法,在xml組態檔中設定該方法
// 資料層
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化對應的操作
public void init(){
System.out.println("init...");
}
//表示bean銷毀前對應的操作
public void destory(){
System.out.println("destory...");
}
}
<!--組態檔-->
<?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">
<!--init-method:設定bean初始化生命周期回呼函式-->
<!--destroy-method:設定bean銷毀生命周期回呼函式,僅適用于單例物件-->
<bean id="bookDao" init-method="init" destroy-method="destory"/>
<bean id="bookService" >
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
- 介面控制方法(了解)
Spring為創建提供了兩個介面,我們只需要繼承并實作該方法即可
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
// InitializingBean,DisposableBean 分別對應afterPropertiesSet,destroy方法,代表創建和銷毀
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
System.out.println("set .....");
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
<?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="bookDao" init-method="init" destroy-method="destory"/>
<!--直接呼叫即可-->
<bean id="bookService" >
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
我們需要提及一下bean的銷毀時機:(了解即可)
- 因為默認情況下,我們的bean不會被銷毀,因為虛擬機會直接退出,ClassPathXmlApplicationContext會被忽略銷毀程序
所以如果我們希望銷毀bean觀察到destroy的實作,需要手動關閉:
- 手動關閉容器方法:
package com.itheima;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForLifeCycle {
public static void main( String[] args ) {
// 注意:這里需要采用ClassPathXmlApplicationContext作為物件,因為只有這個類才具有close方法
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//關閉容器
ctx.close();
}
}
- 注冊關閉鉤子,在虛擬機退出前先關閉容器再推出虛擬機
package com.itheima;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForLifeCycle {
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//注冊關閉鉤子函式,在虛擬機退出之前回呼此函式,關閉容器
ctx.registerShutdownHook();
}
}
最后我們統計一下整體生命周期:
- 初始化容器:創建物件(分配記憶體)->執行構造方法->執行屬性注入(set操作)->執行bean初始化方法
- 使用bean:執行業務操作
- 關閉/銷毀容器:執行bean銷毀方法
依賴注入方式
首先我們要知道類中傳遞資料的方法有兩種:
- 普通方法(Set方法)
- 構造方法
然后我們要知道資料的型別大體分為兩種:
- 引入型別(資料層)
- 簡單型別(基本資料型別和String)
所以我們把依賴注入方式分為四種:
- setter注入
- 簡單型別
- 參考型別
- 構造器注入
- 簡單型別
- 引入型別
setter注入簡單型別
首先我們需要在bean種定義簡單型別屬性并提供可以訪問的set方法
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
//setter注入需要提供要注入物件的set方法
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
//setter注入需要提供要注入物件的set方法
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
然后在配置中使用property標簽value屬性注入簡單型別資料
<?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="bookDao" >
<!--property標簽:設定注入屬性-->
<!--name屬性:設定注入的屬性名,實際是set方法對應的名稱-->
<!--value屬性:設定注入簡單型別資料值-->
<property name="connectionNum" value="https://www.cnblogs.com/qiuluoyuweiliang/p/100"/>
<property name="databaseName" value="https://www.cnblogs.com/qiuluoyuweiliang/p/mysql"/>
</bean>
</beans>
setter注入參考型別
首先我們需要在bean種定義參考型別屬性并提供可以訪問的set方法
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.dao.UserDao;
import com.itheima.service.BookService;
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;
//setter注入需要提供要注入物件的set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//setter注入需要提供要注入物件的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
userDao.save();
}
}
然后在配置中使用property標簽ref屬性注入參考型別資料
<?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="bookDao" >
<property name="connectionNum" value="https://www.cnblogs.com/qiuluoyuweiliang/p/100"/>
<property name="databaseName" value="https://www.cnblogs.com/qiuluoyuweiliang/p/mysql"/>
</bean>
<bean id="userDao" />
<!--注入參考型別-->
<bean id="bookService" >
<!--property標簽:設定注入屬性-->
<!--name屬性:設定注入的屬性名,實際是set方法對應的名稱-->
<!--ref屬性:設定注入參考型別bean的id或name-->
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>
構造器注入簡單型別(了解)
在bean中定義簡單型別屬性并提供可訪問的set方法
public class BookDaoImpl implements BookDao{
private int connectionNumber;
pubilc void setConnectionNumber(int connectionNumber){
this.connectionNumber = connectionNumber;
}
}
配置中使用constructor-arg標簽value屬性注入簡單型別資料
<?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="bookDao" >
根據構造方法引數名稱注入
<constructor-arg name="connectionNum" value="https://www.cnblogs.com/qiuluoyuweiliang/p/10"/>
</bean>
<bean id="userDao" />
</beans>
構造器注入參考型別(了解)
在bean中定義參考型別屬性并提供可訪問的構造方法
public class BookDaoImpl implements BookDao{
private BookBao bookBao;
pubilc void setConnectionNumber(int connectionNumber){
this.bookBao = bookBao;
}
}
配置中使用constructor-arg標簽ref屬性注入簡單型別資料
<?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="bookService" >
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans>
構造器注入引數配置問題(了解)
在前面我們已經介紹了構造器的注入方法
但如果我們在bean中的資料名稱發生改變,配置就不再適配,所以提供了一些方法來解決引數配置問題:
- 配置中使用constructor-arg標簽type屬性設定按形參型別注入
<!--解決形參名稱的問題,與形參名不耦合-->
<bean id="bookDao" >
根據構造方法引數型別注入
<constructor-arg type="int" value="https://www.cnblogs.com/qiuluoyuweiliang/p/10"/>
<constructor-arg type="java.lang.String" value="https://www.cnblogs.com/qiuluoyuweiliang/p/mysql"/>
</bean>
<bean id="userDao" />
<bean id="bookService" >
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
- 配置中使用constructor-arg標簽index屬性設定按形參型別注入
<!--解決引數型別重復問題,使用位置解決引數匹配-->
<bean id="bookDao" >
<!--根據構造方法引數位置注入-->
<constructor-arg index="0" value="https://www.cnblogs.com/qiuluoyuweiliang/p/mysql"/>
<constructor-arg index="1" value="https://www.cnblogs.com/qiuluoyuweiliang/p/100"/>
</bean>
<bean id="userDao" />
<bean id="bookService" >
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
依賴注入方式選擇
依賴注入方式有以下選擇標準:
- 強制依賴使用構造器進行,使用setter注入有概率不進行注入導致null物件出現
- 可選依賴使用setter注入進行,靈活性高
- Spring框架倡導使用構造器,第三方框架內部大多數采用構造器注入的形式進行資料初始化,相對嚴謹
- 如果有必要可以兩者并用,使用構造器注入完成強制依賴的注入,使用setter注入完成可選依賴的注入
- 實際開發中根據情況分析,如果受控物件沒有提供setter方法則只能采用構造器注入
- 自己開發的模塊盡量推薦setter注入
依賴自動裝配
在前面我們學習了手動注入的方法,但Spring其實為我們提供了一種依賴自動裝配的語法:
- IoC容器根據bean所依賴的資源在容器中自動查找并注入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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean />
<!--autowire屬性:開啟自動裝配,通常使用按型別裝配-->
<bean id="bookService" autowire="byType"/>
</beans>
依賴自動裝配特征:
- 自動裝配用于參考型別注入,不能對簡單型別進行操作
- 使用按型別裝配時(byType)必須保障容器中相同型別的bean唯一,推薦使用
- 使用按名稱裝配時(byName)必須保障容器中具有指定名稱的bean,因變數名與配置耦合,不推薦使用
- 自動裝配優先級低于setter注入和構造器注入,同時出現時,自動裝配配置失效
依賴集合注入
除了基本型別和引入型別外,我們有時也需要注入集合
下面我們簡單介紹一下結合的注入:
// 資料類
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import java.util.*;
public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void save() {
System.out.println("book dao save ...");
System.out.println("遍歷陣列:" + Arrays.toString(array));
System.out.println("遍歷List" + list);
System.out.println("遍歷Set" + set);
System.out.println("遍歷Map" + map);
System.out.println("遍歷Properties" + properties);
}
}
<!--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="bookDao" >
<!--陣列注入-->
<!--
注意:
name:對應實作類中的內部成員名稱
<>里的array等為固定詞匯
-->
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<!--list集合注入-->
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
<!--set集合注入-->
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
<!--map集合注入-->
<property name="map">
<map>
<entry key="country" value="https://www.cnblogs.com/qiuluoyuweiliang/p/china"/>
<entry key="province" value="https://www.cnblogs.com/qiuluoyuweiliang/p/henan"/>
<entry key="city" value="https://www.cnblogs.com/qiuluoyuweiliang/p/kaifeng"/>
</map>
</property>
<!--Properties注入-->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
</beans>
案例:資料源物件管理
針對一個新的資料源物件,我們采用兩步來創建bean(我們以druid為案例):
- 匯入druid坐標
<?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.itheima</groupId>
<artifactId>spring_09_datasource</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--這里匯入druid坐標-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</project>
- 配置資料源物件作為Spring管理的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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<!-- 管理DruidDataSource物件-->
<!--起id 設定class地址-->
<bean id="dataSource" >
<!--配置基本資訊-->
<property name="driverClassName" value="https://www.cnblogs.com/qiuluoyuweiliang/p/com.mysql.jdbc.Driver"/>
<property name="url" value="https://www.cnblogs.com/qiuluoyuweiliang/p/jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="https://www.cnblogs.com/qiuluoyuweiliang/p/root"/>
<property name="password" value="https://www.cnblogs.com/qiuluoyuweiliang/p/123456"/>
</bean>
</beans>
案例:加載properties檔案
這個案例我們將會介紹如何加載properties檔案,并將檔案帶入到property基本資訊中
我們大致將步驟分為以下三步:
- 開辟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中的內容是我們的命名空間開辟程序
在原本的xml中只有:
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
">
在下面的內容中我們添加:
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
(整體從上述內容復制,然后修改末尾xsi即可)
-->
</beans>
- 使用context命名空間,加載指定properties檔案
<!-- 2.使用context空間加載properties檔案-->
<context:property-placeholder location="jdbc.properties"/>
- 使用${}讀取加載的屬性值
<!-- 3.使用屬性占位符${}讀取properties檔案中的屬性-->
<!-- 說明:idea自動識別${}加載的屬性值,需要手工點擊才可以查閱原始書寫格式-->
<bean >
<property name="driverClassName" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.driver}"/>
<property name="url" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.url}"/>
<property name="username" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.username}"/>
<property name="password" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.password}"/>
</bean>
除了上述的基本操作,我們在context命名空間的使用中有很多需要注意的點:
- 不加載系統屬性
<!--
因為我們的系統屬性優先級>定義優先級,當我們properties中的屬性與系統設定屬性名相同時,會優先匹配系統屬性導致錯誤
可以采用system-properties-mode進行設定
-->
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 加載多個properties檔案
<!--
我們可以采用逗號或空格分隔加載多個properties檔案
-->
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
- 加載所有properties檔案
<!--
我們可以采用通配符來設定加載檔案
用*來代替所有前綴,只保留后綴為properties即可
-->
<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
- 加載properties檔案標準格式
<!--
我們通常以classpath表示路徑,下述形式更為標準
classpath:*.properties : 設定加載當前工程類路徑中的所有properties檔案
-->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
- 從類路徑或jar包中搜索并加載properties檔案
<!--
我們通常以classpath*來表示路徑來源
classpath*:*.properties : 設定加載當前工程類路徑和當前工程所依賴的所有jar包中的所有properties檔案
-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
核心容器
前面已經完成bean與依賴注入的相關知識學習,接下來我們主要學習的是IOC容器中的核心容器,
這里所說的核心容器,大家可以把它簡單的理解為ApplicationContext,接下來我們從以下幾個問題入手來學習下容器的相關知識:
- 如何創建容器?
- 創建好容器后,如何從容器中獲取bean物件?
- 容器類的層次結構是什么?
- BeanFactory是什么?
容器的創建方式
案例中創建ApplicationContext的方式為(類路徑下的XML組態檔):
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
除了上面這種方式,Spring還提供了另外一種創建方式為(檔案的絕對路徑):
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
Bean的三種獲取方式
方式一,就是目前案例中獲取的方式:
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
這種方式存在的問題是每次獲取的時候都需要進行型別轉換
方式二:
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
這種方式可以解決型別強轉問題,但是引數又多加了一個,相對來說沒有簡化多少,
方式三:
BookDao bookDao = ctx.getBean(BookDao.class);
這種方式就類似我們之前所學習依賴注入中的按型別注入,必須要確保IOC容器中該型別對應的bean物件只能有一個,
容器類層次結構
下面我們給出容器的層次圖

BeanFactory的使用
使用BeanFactory來創建IOC容器的具體實作方式為:
public class AppForBeanFactory {
public static void main(String[] args) {
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
bookDao.save();
}
}
為了更好的看出BeanFactory和ApplicationContext之間的區別,在BookDaoImpl添加如下建構式:
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("constructor");
}
public void save() {
System.out.println("book dao save ..." );
}
}
如果不去獲取bean物件,列印會發現:
-
BeanFactory是延遲加載,只有在獲取bean物件的時候才會去創建
-
ApplicationContext是立即加載,容器加載的時候就會創建bean物件
-
ApplicationContext要想成為延遲加載,只需要按照如下方式進行配置
<?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="bookDao" lazy-init="true"/> </beans>
核心概念總結
接下來我們對前面知識的一個總結,共包含如下內容:
容器相關
- BeanFactory是IoC容器的頂層介面,初始化BeanFactory物件時,加載的bean延遲加載
- ApplicationContext介面是Spring容器的核心介面,初始化時bean立即加載
- ApplicationContext介面提供基礎的bean操作相關方法,通過其他介面擴展其功能
- ApplicationContext介面常用初始化類
- ClassPathXmlApplicationContext(常用)
- FileSystemXmlApplicationContext
bean相關

依賴注入相關

注解開發
在上述的開發中,我們采用xml組態檔的形式來說依舊顯得有些復雜
這時我們就需要發揮Spring的優點:簡化開發,通過注解來簡化開發程序
下面我們會通過多個方面將Bean逐步轉化為注解
注解開發Bean
在前面的內容中,我們的bean在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-->
<bean id="bookDao" />
</beans>
在后期,我們的bean可以采用注解的形式,直接在實作類中注解表示為bean
我們采用@Component定義bean,可以添加引數表示id,也可以不添加引數,后期我們采用class類的型別來進行匹配
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
//@Component定義bean
@Component("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
//@Component定義bean
@Component
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
@Componenty延伸出了三種型別,在實作手法上是一致,但可以具體使用于各種類中(僅用于自我識別)
- @Controller:用于表現層bean定義
- @Service:用于業務層bean定義
- @Repository:用于資料層定義
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
//@Component定義bean
//@Component("bookDao")
//@Repository:@Component衍生注解
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
//@Component定義bean
//@Component
//@Service:@Component衍生注解
@Service
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
但是,在上述情況下,即使我們將@Component的類定義為bean
我們的xml檔案是無法探測到的,所以我們需要配置相關掃描組件來掃描bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
<context:component-scan />:表示掃描檔案
base-package:表示掃描路徑
-->
<context:component-scan base-package="com.itheima"/>
</beans>
純注解開發
我們前面所提到的注解開發屬于2.5的附屬版本
在Spring3.0版本,Spring就提供了純注解開發模式,利用java類代替組態檔,開啟了Spring快速開發時代
在之前我們的xml組態檔是很繁瑣的:
<!--原生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="bookDao" />
</beans>
但是我們可以通過創建單獨的類來表示組態檔:
- @Configuration:用于宣告當前類為Spring配置類
- @ComponentScan:用于掃描類檔案(類似于<context:component-scan base-package="com.itheima"/>)
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//宣告當前類為Spring配置類
@Configuration
//設定bean掃描路徑,多個路徑書寫為字串陣列格式
@ComponentScan({"com.itheima.service","com.itheima.dao"})
public class SpringConfig {
}
注意:因為該類屬于配置類,我們通常單列一個檔案夾來表示
常用檔案夾:config
命名規范:SpringConfig,UserConfig...
因為我們的開發不再依靠于xml組態檔,所以在主函式中的Spring容器獲得方式也發生了改變:
package com.itheima;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 這是我們之前的獲取方式,采用路徑獲取xml檔案
// ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 這是新的獲取方式,直接提供配置類的型別
// AnnotationConfigApplicationContext加載Spring配置類初始化Spring容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 后面操作無需變化
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
//按型別獲取bean
BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);
}
}
注解開發Bean作用范圍與管理
既然我們的Bean開發從xml轉移到注解開發,那么一些配置設定同樣發生改變
首先我們介紹Scope范圍的設定方式:
- @Scope:定義bean的作用范圍
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Repository
//@Scope設定bean的作用范圍(singleton或prototype),可以不添加默認singleton
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
然后我們介紹一下bean生命周期的init和destroy操作:
- @PostConstruct:定義init操作,表示構造后操作
- @PreDestroy:定義destroy操作,表示銷毀前操作
依賴注入(自動裝配)
在Spring3.0中,省略掉了前面繁瑣的依賴注入,我們的bean依賴注入只留下了自動裝配這一操作:
- 使用@Autowired注解開啟自動裝配模式(按型別)
- 當存在相同型別時,我們采用@Qualifier開啟按名自動裝配
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入參考型別,自動裝配模式,默認按型別裝配
@Autowired
//@Qualifier:自動裝配bean時按bean名稱裝配
@Qualifier("bookDao")
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:自動裝配基于反射設計創建物件并暴力反射對應屬性為私有屬性初始化資料,因此無需提供setter方法
注意:自動轉配建議使用無參構造方法創建物件(默認),如果不提供對應構造方法,請提供唯一的構造方法
注意:@Qualifier是基于@Autowired實作的,必須保證先有Autowired才能存在Qualifier
除了上述的bean型別裝配,我們的簡單型別裝配依舊存在:
- 我們采用@Value的形式來配置簡單型別的值
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入簡單型別(無需提供set方法)
@Value("123")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
之所以使用@Value的形式配置,是因為我們的型別值不一定是由手動輸入的,有可能來自于Properties資源:
- 首先我們需要在Springconfig中配置相關資源
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.itheima")
//@PropertySource加載properties組態檔
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}
- 然后我們在資料層呼叫時,采用${}來匹配資料
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入簡單型別(無需提供set方法)
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
注解開發第三方bean
我們在實際開發中不僅僅需要對自己的bean進行管理,有時候可能需要引進其他的bean
下面我們以Druid為例進行講解:
- 首先在pom.xml中匯入Druid坐標
<?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.itheima</groupId>
<artifactId>spring_14_annotation_third_bean_manager</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
</dependencies>
</project>
- 使用@Bean配置第三方Bean
// 該bean同樣屬于config檔案,我們同樣放置在config檔案夾下
// 在后續我們將會講解如何進行連接
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
public class JdbcConfig {
// 1.定義一個方法獲得要管理的物件
// 2.添加@Bean,表示當前方法的回傳值是一個bean
// @Bean修飾的方法,形參根據型別自動裝配
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
}
- 將獨立的配置類加入核心配置(匯入法)
// SpringConfig
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.itheima")
//@Import:匯入配置資訊(如果需要多個,同樣采用{}陣列形式)
@Import({JdbcConfig.class})
public class SpringConfig {
}
// JdbcConfig
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
//@Configuration
public class JdbcConfig {
//@Bean修飾的方法,形參根據型別自動裝配
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
// 配置資訊
return ds;
}
}
注意:除了上述的匯入法外還存在有其他方法,但匯入法屬于主流,因此我們不介紹其他流派,感興趣的同學可以去查閱一下
注解開發為第三方匯入資源
我們的第三方bean也可能需要匯入部分資源,下面我們進行簡單介紹:
- 簡單型別依賴注入
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
//@Configuration
public class JdbcConfig {
//1.定義一個方法獲得要管理的物件
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//2.添加@Bean,表示當前方法的回傳值是一個bean
//@Bean修飾的方法,形參根據型別自動裝配
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 依賴型別依賴注入
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
public class JdbcConfig {
@Bean
public DataSource dataSource(BookDao bookDao){
// 我們只需要呼叫即可,系統會為我們自動裝配
System.out.println(bookDao);
}
}
引入型別注入只需要為bean定義方法設定形參即可,容器會根據型別自動裝配物件
注解開發對比
最后我們通過和前述非注解開發的對比來結束這一章節:
| 功能 | XML配置 | 注解 |
|---|---|---|
| 定義bean | bean標簽: - id標簽 - class標簽 |
@Component - @controller - @Service - @Repository @ComponentScan |
| 設定依賴注入 | Setter注入 構造器注入 自動裝配 |
@Autowired @Qualifier @Value |
| 配置第三方bean | bean標簽 靜態工廠 實體工廠 FactoryBean |
@Bean |
| 作用范圍 | scope屬性 | @Scope |
| 生命周期 | 標準介面 init-method destroy-method |
@PostConstructor @preDestroy |
Spring整合MyBatis和Junit
在前面的內容中我們已經學習了Spring的Framework的大部分內容
接下來讓我們來整合我們之前所學習的內容,整體的運用Spring來簡化操作
Spring整合MyBatis
首先我們來詳細講解MyBatis的整合
Spring整合MyBatis思維導論
在整合之前,我們回憶一下MyBatis的單體操作:
- 首先我們需要準備資料庫內容(這里不做展示)
- 連接資料庫組態檔
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"></properties>
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.driver}"></property>
<property name="url" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.url}"></property>
<property name="username" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.username}"></property>
<property name="password" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
- 與資料庫相關的物體類
package com.itheima.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
- 資料層(這里全做注解,采取Mapper全權管理的形式)
package com.itheima.dao;
import com.itheima.domain.Account;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface AccountDao {
@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
void save(Account account);
@Delete("delete from tbl_account where id = #{id} ")
void delete(Integer id);
@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
void update(Account account);
@Select("select * from tbl_account")
List<Account> findAll();
@Select("select * from tbl_account where id = #{id} ")
Account findById(Integer id);
}
- 服務層
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.domain.Account;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void save(Account account) {
accountDao.save(account);
}
public void update(Account account){
accountDao.update(account);
}
public void delete(Integer id) {
accountDao.delete(id);
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
public List<Account> findAll() {
return accountDao.findAll();
}
}
- 主函式
import com.itheima.dao.AccountDao;
import com.itheima.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class App {
public static void main(String[] args) throws IOException {
// 1. 創建SqlSessionFactoryBuilder物件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加載SqlMapConfig.xml組態檔
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml.bak");
// 3. 創建SqlSessionFactory物件
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 獲取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 執行SqlSession物件執行查詢,獲取結果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(2);
System.out.println(ac);
// 6. 釋放資源
sqlSession.close();
}
}
在上述內容中,我們重點分析組態檔和主函式的內容,因為我們的Spring的主要目的是為了管理Bean
所以我們需要在MyBatis中找到符合要求的Bean:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--properties屬于配置核心內容,屬于Bean(負責連接資料庫)-->
<properties resource="jdbc.properties"></properties>
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.driver}"></property>
<property name="url" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.url}"></property>
<property name="username" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.username}"></property>
<property name="password" value="https://www.cnblogs.com/qiuluoyuweiliang/p/${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!--映射配置包位置,屬于Bean(負責業務層)-->
<mappers>
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
import com.itheima.dao.AccountDao;
import com.itheima.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class App {
public static void main(String[] args) throws IOException {
// SqlSessionFactory屬于主體Bean
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml.bak");
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// SqlSession由SqlSessionFactory創建
SqlSession sqlSession = sqlSessionFactory.openSession();
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(2);
System.out.println(ac);
sqlSession.close();
}
}
所以我們的整體操作其實就是為了整合MyBatis的Bean
Spring整合MyBatis具體操作
接下來我們給出具體操作:
- 匯入相關包
<!--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.itheima</groupId>
<artifactId>spring_15_spring_mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--spring-jdbc:Spring與資料庫連接所需庫-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--mybatis-spring:MyBatis與Spring連接所需庫-->
<!--需要注意:mybatis-spring的版本與mybatis版本有一定對應關系,不要濫用版本-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
</project>
- 創建配置環境Config
// SpringConfig(前面已講解)
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.itheima")
//@PropertySource:加載類路徑jdbc.properties檔案
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
// JdbcConfig(前面已講解)
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@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(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
// MyBatisConfig(MyBatis重點內容)
package com.itheima.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
//定義bean,SqlSessionFactoryBean,用于產生SqlSessionFactory物件
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
/*
SqlSessionFactoryBean屬于mybatis-spring提供的新的物件,用于快速產生SqlSessionFactory物件
ssfb.setTypeAliasesPackage("com.itheima.domain");
對應于
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
ssfb.setDataSource(dataSource);
對應于
DataSource的配置資訊
上述陳述句基本均為固定陳述句
只有Package的別名包需要修改內容
*/
//定義bean,回傳MapperScannerConfigurer物件
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
/*
MapperScannerConfigurer屬于mybatis-spring提供的新的物件,回傳MapperScannerConfigurer物件
我們同樣只需要設定映射包setBasePackage
上述陳述句基本均為固定陳述句
只有Package的映射名包需要修改內容
*/
}
- 主函式(其他內容基本不做修改)
import com.itheima.config.SpringConfig;
import com.itheima.domain.Account;
import com.itheima.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = ctx.getBean(AccountService.class);
Account ac = accountService.findById(1);
System.out.println(ac);
}
}
Spring整合Junit
我們對于Junit的整合建立于Spring與MyBatis已經整合的基礎上,所以上述內容請務必明白!
Spring整合Junit具有一定固定格式,我們直接寫出步驟:
- 匯入包
<?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.itheima</groupId>
<artifactId>spring_16_spring_junit</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!--junit包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Spring與junit聯系包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
- 書寫Junit的Test代碼內容
// 下述內容均在test檔案夾下進行
package com.itheima.service;
import com.itheima.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//設定類運行器(固定形式)
@RunWith(SpringJUnit4ClassRunner.class)
//設定Spring環境對應的配置類(匹配你所使用的Spring,注意需要寫classes的形式)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
//支持自動裝配注入bean
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
@Test
public void testFindAll(){
System.out.println(accountService.findAll());
}
}
SpringAOP
我們在開篇有提及到AOP,現在讓我們來詳細介紹一下AOP~
SpringAOP簡介
首先我們來介紹一下AOP:
- AOP(Aspect Oriented Programming)面向切面編程,一種編程范式,指導開發者如何組織程式結構
- OOP(Object Oriented Programming)面向物件編程,也是一種編程范式,我們列舉出來是為了表示和AOP概念相似
AOP作用:
- 在不驚動原始設計的基礎上為其進行功能增強
Spring理念:
- 無入侵式/無侵入式
AOP核心概念:

- 連接點:程式執行程序中的任意位置,粒度為執行方法,拋出例外,設定變數等
- 切入點:匹配連接點的式子
- 通知:在切入點處執行的操作,也就是共性功能
- 通知類:存放通知的類
- 切面:描述通知與切入點的關系
通俗解釋:
實作類中的各個方法被稱為連接點
如果我們希望在這些連接點中設定相同的部分,可以采用通知進行設定
我們利用通知和連接點進行連接,連接點就可以執行通知中的方法并且同時執行連接點的方法
被連接的連接點被稱為切入點,存放通知的類被稱為通知類
SpringAOP入門
我們同樣采用一個案例進行SpringAOP入門介紹
案例設定:測驗介面執行效率
簡化設定:在介面執行前輸出當前系統時間
開發模式:XML or 注解(我們現在大部分使用注解)
具體操作:
- 匯入坐標(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.itheima</groupId>
<artifactId>spring_18_aop_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--aspectjweaver坐標,對應aspects(AOP的具體實作)-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
</project>
- 制作連接點方法(原始方法,不發生改變)
// 介面BookDao
com.itheima.dao;
public interface BookDao {
public void save();
public void update();
}
// 實作類BookDaoImpl
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
public void update(){
System.out.println("book dao update ...");
}
}
- 制作共性功能(通知類與通知)
// 我們推薦單獨列出一個AOP檔案夾,寫下所有通知相關代碼
// MyAdvice通知類
package com.itheima.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/*
具體流程:
1.創建該類
2.將該類設定為Spring中的Bean集中管理:@Component
3.設定為切面類注明該類作用:@Aspect
4.首先寫出具體共性方法method,正常書寫即可
5.寫出切入點pt:切入點定義依托一個不具有實際意義的方法進行,即無引數,無回傳值,無方法體,最好私有
6.對切入點進行設定:@Pointcut;我們后續講解
7.對切入點和通知進行連接:@Before;我們后續進行講解
這里簡單介紹一下@Pointcut("execution(void com.itheima.dao.BookDao.update())")
@Pointcut:注釋
execution:表示運行
void:回傳型別
com.itheima.dao.BookDao.update():地址+類/介面+方法+方法引數
*/
//通知類必須配置成Spring管理的bean
@Component
//設定當前類為切面類類
@Aspect
public class MyAdvice {
//設定切入點,要求配置在方法上方
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
//設定在切入點pt()的前面運行當前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
/*
在運行后,我們會發現,每次呼叫方法后,在執行前給出當前系統時間
*/
- 為SpringConfig設定相關需求
// SpringConfig
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.itheima")
//開啟注解開發AOP功能(我們的通知類采用注解開發)
@EnableAspectJAutoProxy
public class SpringConfig {
}
SpringAOP作業流程
我們先簡單介紹AOP的大概作業流程便于講解底層知識:
- Spring容器啟動
- 讀取所有切面配置中的切入點
- 初始化bean,判定對應的類中的方法是否能匹配到任意切入點
- 獲得bean執行方法
首先我們要注意切入點的讀取問題:
- 我們在讀取切入點時只讀取匹配成功的切入點,其他未使用的切入點不進行讀取,節省記憶體
然后我們會根據bean是否能匹配切入點來分別處理:
- 當我們匹配失敗時,創建物件,獲得bean,呼叫方法并執行,完成操作
- 當我們匹配成功時,創建原始物件(目標物件)的代理物件,獲得代理物件的bean,根據代理物件的運行模式運行原始方法與增強內容,完成操作
這里我們進行幾個名詞解釋:
目標物件:我們的初始物件,被一個或者多個切面所通知的物件
代理物件:我們根據目標物件所衍生出來的物件,不再是原物件;我們希望通過對代理物件的修改來完成AOP操作
SpringAOP切入點運算式
我們已經簡單了解了SpringAOP的具體使用,接下來讓我們來仔細分析AOP的各部分
首先我們先來介紹AOP的切入點和切入點運算式定義:
- 切入點:要進行加強的方法
- 切入點運算式:要進行增強的方法的描述方式
AOP切入點運算式大致分為兩種:
- 介面下的方法:execution(void com.itheima.dao.BookDao.update())
- 實作類的方法:execution(void com.itheima.dao.impl.BookDaoImpl.update())
切入點運算式的具體格式:
- 動作關鍵字(訪問修飾符 回傳值 包名.類/介面.方法名(引數)例外名)
- execution(public User com.itheima.service.UserService.findById(int))
具體名詞解釋:
- 動作關鍵字:描述切入點的行為動作,例如execution表示執行到指定切入點
- 訪問修飾符:public,private,可省略(省略為public)
- 例外名:方法定義中拋出指定例外,可省略
AOP切入點運算式通配符:
- *:單個的獨立的任意符號,可以獨立出現,也可以作為前綴或者后綴的匹配符出現
- ..:多個連續的任意符號,可以獨立出現,常用于簡化包名與引數的書寫
- +:專用于匹配子型別別
我們給出相關例子:
package com.itheima.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
//切入點運算式:
// 表示介面下的方法
// @Pointcut("execution(void com.itheima.dao.BookDao.update())")
// 表示實作類的方法
// @Pointcut("execution(void com.itheima.dao.impl.BookDaoImpl.update())")
// 表示任意回傳型別的單個引數的BookDaoImpl實作類的update方法
// @Pointcut("execution(* com.itheima.dao.impl.BookDaoImpl.update(*))")
// 表示com開頭兩層檔案(第三層為類或介面)的update方法
// @Pointcut("execution(void com.*.*.*.update())")
// 表示所有以e結尾方法
// @Pointcut("execution(* *..*e(..))")
// 表示以com開頭所有無參方法
// @Pointcut("execution(void com..*())")
// 表示com.itheima下的任意檔案夾下的以Service結尾的實作類的以find開頭的方法
// @Pointcut("execution(* com.itheima.*.*Service.find*(..))")
//執行com.itheima包下的任意包下的名稱以Service結尾的類或介面中的save方法,引數任意,回傳值任意
@Pointcut("execution(* com.itheima.*.*Service.save(..))")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
AOP切入點書寫技巧:
- 所有代碼按照規范開發,否則下述技巧失效
- 描述切入點通常描述介面,而不是描述實作類
- 訪問控制修飾符針對介面開發均采用public描述(可省略訪問控制修飾符描述)
- 回傳值型別對于增刪改類使用精準型別加速匹配,對于查詢類使用*通配快速描述
- 包名書寫盡量不使用..匹配,效率過低,常用*做單個包描述匹配,或精準匹配
- 介面名/類名書寫名稱與模塊相關的采用*匹配,例如UserService采用*Service,系結業務層介面名
- 方法名書寫以動詞進行精準匹配,名詞采用*匹配,例如getById采用getBy*
- 引數規則較為復雜,根據業務方法靈活調整
- 通常不使用例外作為匹配規則
SpringAOP通知型別
AOP通知描述了抽取的共性功能,根據共性功能抽取位置的不同,最終運行代碼時要加入到合理的位置
AOP通知一共分為五種:
- 前置通知
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//@Before:前置通知,在原始方法運行之前執行
@Before("pt()")
public void before() {
System.out.println("before advice ...");
}
}
- 后置通知
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//@After:后置通知,在原始方法運行之后執行
@After("pt2()")
public void after() {
System.out.println("after advice ...");
}
}
- 環繞通知(重點)
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//@Around:環繞通知,在原始方法運行的前后執行
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示對原始操作的呼叫
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
}
/*
@Around注意事項:
1.環繞通知必須依賴形參ProceedingJoinPoint才能實作對原始方法的呼叫,進而實作原始方法呼叫前后同時添加通知
2.通知中如果未使用ProceedingJoinPoint對原始方法進行呼叫將跳過原始方法的執行
3.對原始方法的呼叫可以不接識訓傳值,通知方法設定為void即可,如果接識訓傳值,必須設定為Object型別
4.原始方法的回傳型別如果是void型別,通知方法的回傳型別可以設定成void,也可以設定為Object
5.由于無法預知原始方法運行后是否出現問題,因此需要拋出例外,拋出Throwable物件
*/
- 回傳后通知(了解)
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//@AfterReturning:回傳后通知,在原始方法執行完畢后運行,且原始方法執行程序中未出現例外現象
@AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
}
- 拋出例外后通知(了解)
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//@AfterThrowing:拋出例外后通知,在原始方法執行程序中出現例外后運行
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
下面我們針對環繞通知給出一個案例講解:
需求:任意業務層介面執行均顯示其執行效率(執行時長)
// 我們這里只給出SpringAOP的代碼解釋
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class ProjectAdvice {
//匹配業務層的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//設定環繞通知,在原始操作的運行前后記錄執行時間
@Around("servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//獲取執行的簽名物件
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("萬次執行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
}
}
SpringAOP通知獲得資料
我們可以注意到在上述通知中我們是存在有引數的,接下來我們針對這些引數做出相關解釋~
通知可選引數:
- 環繞通知:ProceedingJoinPoint物件
- 其他通知:JoinPoint物件
注意:JoinPoint是ProceedingJoinPoint的父類
接下來我們分別從引數資料,回傳值資料,例外資料三個方面進行講解:
- 引數資料:JoinPoint物件描述了連接點方法的運行狀態,可以獲得到原始方法的呼叫引數
package com.itheima.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
//JoinPoint:用于描述切入點的物件,必須配置成通知方法中的第一個引數,可用于獲取原始方法呼叫的引數
@Before("pt()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
@After("pt()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("after advice ...");
}
//ProceedingJoinPoint:專用于環繞通知,是JoinPoint子類,可以實作對原始方法的呼叫
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
}
}
- 回傳值資料
package com.itheima.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object ret = pjp.proceed(args);
return ret;
}
//設定回傳后通知獲取原始方法的回傳值,要求returning屬性值必須與方法形參名相同
@AfterReturning(value = "https://www.cnblogs.com/qiuluoyuweiliang/p/pt()",returning = "ret")
public void afterReturning(JoinPoint jp,String ret) {
System.out.println("afterReturning advice ..." + ret);
}
}
- 回傳例外資料
package com.itheima.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try {
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
//設定拋出例外后通知獲取原始方法運行時拋出的例外物件,要求throwing屬性值必須與方法形參名相同
@AfterThrowing(value = "https://www.cnblogs.com/qiuluoyuweiliang/p/pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
}
下面我們針對資料處理給出一個案例講解:
需求:對密碼的尾部空格作出兼容性處理
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class DataAdvice {
@Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
private void servicePt(){}
@Around("DataAdvice.servicePt()")
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
//判斷引數是不是字串
if(args[i].getClass().equals(String.class)){
args[i] = args[i].toString().trim();
}
}
Object ret = pjp.proceed(args);
return ret;
}
}
Spring事務
我們在之前的文章中已經多次提及過事務,這里再重新宣告一下事務:
- 事務作用:在資料層保障一系列的資料庫操作同成功與失敗
- Spring事務作用:在資料層或業務層保障一系列的資料庫操作成功與失敗
Spring事務入門
我們通過一個案例來進行事務的講解:
需求:實作任意兩個賬戶間轉賬操作
需求微縮:A賬戶減錢,B賬戶加錢
分析:
- 資料層提供基礎操作,指定用戶減錢,指定用戶加錢
- 業務層提供轉賬操作,呼叫減錢和加錢操作
- 提供兩個賬號和操作金額執行轉賬操作
- 基于Spring整合MyBatis環境搭配上述操作
結果分析:
- 程式正常執行,賬戶A減錢賬戶B加錢
- 程式失敗執行,轉賬失敗,但例外前操作成功,例外后操作失敗,整體業務失敗
具體修改實施步驟:
- 業務層介面上添加Spring事務管理
package com.itheima.service;
import org.springframework.transaction.annotation.Transactional;
import java.io.FileNotFoundException;
import java.io.IOException;
public interface AccountService {
/**
* 轉賬操作
* @param out 傳出方
* @param in 轉入方
* @param money 金額
*/
//配置當前介面方法具有事務
@Transactional
public void transfer(String out,String in ,Double money) ;
}
/*
Spring注解式事務通常添加在業務層介面而不會添加到業務層實作類,降低耦合
注解式事務可以添加到業務方法上表示當前方法開始事務,也可以添加到介面上表示當前介面所有方法開啟事務
*/
- 設定事務管理器
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@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(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
//配置事務管理器,mybatis使用的是jdbc事務
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
/*
同MyBatis的配置java檔案一樣上述陳述句基本屬于固定陳述句
事務管理器根據實作技術進行選擇
MyBatis框架使用的是Jdbc事務
*/
- 開啟注解式事務驅動
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//開啟注解式事務驅動
@EnableTransactionManagement
public class SpringConfig {
}
這里我們介紹兩個新概念:
- 事務管理員:發起事務方,在Spring中通常指業務層開啟事務的方法(上述表示transfer方法)
- 事務協調員:加入事務方,在Spring中通常代表資料層方法,也可以是業務層方法(上述表示out和in方法)
Spring事務屬性
Spring的事務通常用@Transactional注解來表示
我們同樣可以為@Transactional注解攜帶一些資訊來管理事務的屬性
| 屬性 | 作用 | 示例 |
|---|---|---|
| readOnly | 設定是否為只讀事務 | readOnly=true 只讀事務 |
| timeout | 設定事務超時時間 | timeout=-1永不超時 |
| rollbackFor | 設定事務回滾例外(class) | rollbackFor={NullPointException.class} |
| rollbackForClassName | 設定事務回滾例外(String) | 同上格式為字串 |
| noRollbackFor | 設定事務不回滾例外(class) | noRollbackFor={NullPointException.class} |
| noRollbackForClassName | 設定事務不回滾例外(String) | 同上格式為字串 |
| propagation | 設定事務傳播行為 | ........ |
除了上述屬性外,我們還需要仔細介紹propagation屬性:
- 事務傳播行為:事務協調員對事務管理員所攜帶事務的處理態度
在實際開發中我們會利用propagation屬性完成一些特殊操作
我們采用一個案例來進行說明:
需求:在上述轉賬的基礎上,無論失敗成功均保存一條日志記錄轉賬資訊
需求微縮:A賬戶減錢,B賬戶加錢,資料庫記錄日志
分析:
- 基于轉賬操作案例添加日志模塊,實作資料庫中記錄日志
- 業務層轉賬操作,呼叫減錢,加錢與記錄日志功能
實作效果預期:
- 無論轉賬操作是否成功,均進行日志記錄
存在問題:
- 日志記錄與轉賬操作隸屬于一個事務,兩者只能同時成功同時失敗
新增代碼:
- 給出日志相關資訊
// 日志資料層
package com.itheima.dao;
import org.apache.ibatis.annotations.Insert;
public interface LogDao {
@Insert("insert into tbl_log (info,createDate) values(#{info},now())")
void log(String info);
}
// 日志業務層
package com.itheima.service.impl;
import com.itheima.dao.LogDao;
import com.itheima.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
public void log(String out,String in,Double money ) {
logDao.log("轉賬操作由"+out+"到"+in+",金額:"+money);
}
}
- 主函式
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import com.itheima.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.*;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private LogService logService;
public void transfer(String out,String in ,Double money) {
try{
accountDao.outMoney(out,money);
int i = 1/0;
accountDao.inMoney(in,money);
}finally {
logService.log(out,in,money);
}
}
}
修改后代碼:
- 在日志業務層設定日志操作為單獨事務
// 日志業務層
package com.itheima.service.impl;
import com.itheima.dao.LogDao;
import com.itheima.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
logDao.log("轉賬操作由"+out+"到"+in+",金額:"+money);
}
}
最后我們給出事務傳播行為表:
| 傳播屬性 | 事務管理員 | 事務協調員 |
|---|---|---|
| REQUIRED(默認) | 開啟T | 加入T |
| REQUIRED(默認) | 無 | 新建T |
| REQUIRES_NEW | 開啟T | 新建T |
| REQUIRES_NEW | 無 | 新建T |
| SUPPORTS | 開啟T | 加入T |
| SUPPORTS | 無 | 無 |
| NOT_SUPPORTED | 開啟T | 無 |
| NOT_SUPPORTED | 無 | 無 |
| MANDATORY | 開啟T | 加入T |
| MANDATORY | 無 | ERROR |
| NEVER | 開啟T | ERROR |
| NEVER | 無 | 無 |
| NESTED |
NESTED:設定savePoint,一旦事務回滾,事務將回滾到savePoint處,交由客戶回應提交/回滾
結束語
好的,關于Spring的內容就介紹到這里,希望能為你帶來幫助!
附錄
該文章屬于學習內容,具體參考B站黑馬程式員李老師的SMM框架課程
這里附上鏈接:Spring-00-Spring課程介紹_嗶哩嗶哩_bilibili
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/510936.html
標籤:架構設計
上一篇:使用tkintercoolprop的python計算器錯誤
下一篇:沒有所謂的B/S架構,只有C/S
