這篇文章中我們將會介紹Spring的框架以及本體內容,包括核心容器,註解開發,AOP以及事務等內容
那麼簡單說明一下Spring的必要性:
Spring的核心內容:
Spring可進行的框架整合:
在接下來的文章中,我們會學習Spring的框架思想,學習Spring的基本操作,結合案例熟練掌握
溫馨提醒:在學習本篇文章前請先學習JavaWeb相關內容
(HTTP,Tomcat,Servlet,Request,Response,MVC,Cookie,Session,Ajax,Vue等內容)
Spring發展至今已經形成了一套開發的生態圈,Spring提供了相當多的專案,每個專案用於完成特定功能
我們常用的主流技術包括有:
在系統學習Spring之前,我們需要先來了解FrameWork系統結構
我們現在所使用的Spring FrameWork是4.0版本,已經趨於穩定
下面我們對架構圖進行解釋:
我們可以在官方中獲得如此評價:
首先我們思索一下我們之前的業務層與資料層:
// 資料層介面
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實現也要進行修改,甚至下方方法的物件也要進行修改
程式碼書寫現狀:
解放方案:
IoC(Inversion of Control)控制反轉思想:
DI(Dependency Injection)依賴注入:
Spring技術對Ioc思想進行了實現:
// 資料層實現
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入門的詳細步驟:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
<?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" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
</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入門):
public class BookServiceImpl implements BookService {
//5.刪除業務層中使用new的方式建立的dao物件
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
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;
}
}
<?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" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--7.設定server與dao的關係-->
<!--
注意:在server中設定關係
property標籤表示設定當前bean的屬性
name屬性表示設定哪一個具體的屬性
ref屬性表示參照哪一個bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
Bean是儲存在IoC中的物件,我們通過設定的方式獲得Bean
下面我們從三個方面分別講解Bean:
首先我們先介紹bean本身性質:
類別 | 描述 |
---|---|
名稱 | bean |
型別 | 標籤 |
所屬 | beans標籤 |
功能 | 定義Spring核心容器管理物件 |
格式 | <beans> <bean> </bean> </beans> |
屬性列表 | id:bean的id,使用容器可以通過id值獲得對應的bean,在一個容器中id值唯一 class:bean的型別,即設定的bean的全路徑類名 |
範例 | <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> |
然後我們介紹一下bean的別名:
類別 | 描述 |
---|---|
名稱 | name |
型別 | 標籤 |
所屬 | bean標籤 |
功能 | 定義bean的別名,可定義多個,使用逗號,分號,空格分隔 |
範例 | <bean id="bookService" name="service service4 bookEbi" class="com.itheima.service.impl.BookServiceImpl"> |
正常情況下,使用id和name都可以獲得bean,但推薦還是使用唯一id
獲得bean無論通過id還是name獲取,如果無法找到則丟擲異常NosuchBeanDefinitionException
最後我們介紹一下bean的作用範圍scope:
類別 | 描述 |
---|---|
名稱 | scope |
型別 | 標籤 |
所屬 | bean標籤 |
功能 | 定義bean的作用範圍,可選範圍如下: singleton:單列(預設) prototype:非單列 |
範例 | <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/> |
這裡的scope指產生物件的數量
我們的scope在預設情況下是singleton,因為很多物件只需要建立一次,多次建立會導致記憶體膨脹
合適交給容器進行管理的bean(singleton):
- 表現層物件
- 業務層物件
- 資料層物件
- 工具物件
不合適交給容器進行管理的bean(prototype):
- 封裝實體的域物件(帶有狀態的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" class="com.itheima.dao.impl.BookDaoImpl"/>
</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" class="com.itheima.factory.OrderDaoFactory" 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" class="com.itheima.factory.UserDaoFactory"/>
<!--
factory-bean:範例工廠本身bean
factory-method:使用呼叫bean的方法
-->
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
</beans>
除了我們之前自己定義的工廠外,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" class="com.itheima.factory.UserDaoFactoryBean"/>
</beans>
我們先來接單介紹生命週期相關概念:
接下來我們介紹生命週期控制方法:
由資料層提供方法,在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" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<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" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
<!--直接呼叫即可-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
我們需要提及一下bean的銷燬時機:(瞭解即可)
所以如果我們希望銷燬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銷燬方法
首先我們要知道類中傳遞資料的方法有兩種:
然後我們要知道資料的型別大體分為兩種:
所以我們把依賴注入方式分為四種:
首先我們需要在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" class="com.itheima.dao.impl.BookDaoImpl">
<!--property標籤:設定注入屬性-->
<!--name屬性:設定注入的屬性名,實際是set方法對應的名稱-->
<!--value屬性:設定注入簡單型別資料值-->
<property name="connectionNum" value="100"/>
<property name="databaseName" value="mysql"/>
</bean>
</beans>
首先我們需要在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" class="com.itheima.dao.impl.BookDaoImpl">
<property name="connectionNum" value="100"/>
<property name="databaseName" value="mysql"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<!--注入參照型別-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--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" class="com.itheima.dao.impl.BookDaoImpl">
根據構造方法引數名稱注入
<constructor-arg name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
</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" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans>
在前面我們已經介紹了構造器的注入方法
但如果我們在bean中的資料名稱發生改變,設定就不再適配,所以提供了一些方法來解決引數設定問題:
<!--解決形參名稱的問題,與形參名不耦合-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
根據構造方法引數型別注入
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
<!--解決引數型別重複問題,使用位置解決引數匹配-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<!--根據構造方法引數位置注入-->
<constructor-arg index="0" value="mysql"/>
<constructor-arg index="1" value="100"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
依賴注入方式有以下選擇標準:
在前面我們學習了手動注入的方法,但Spring其實為我們提供了一種依賴自動裝配的語法:
自動裝配方式:
自動裝配語法:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire屬性:開啟自動裝配,通常使用按型別裝配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" 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" class="com.itheima.dao.impl.BookDaoImpl">
<!--陣列注入-->
<!--
注意:
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="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="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為案例):
<?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>
<?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" class="com.alibaba.druid.pool.DruidDataSource">
<!--設定基本資訊-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
</beans>
這個案例我們將會介紹如何載入properties檔案,並將檔案帶入到property基本資訊中
我們大致將步驟分為以下三步:
<?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>
<!-- 2.使用context空間載入properties檔案-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 3.使用屬性預留位置${}讀取properties檔案中的屬性-->
<!-- 說明:idea自動識別${}載入的屬性值,需要手工點選才可以查閱原始書寫格式-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
除了上述的基本操作,我們在context名稱空間的使用中有很多需要注意的點:
<!--
因為我們的系統屬性優先順序>定義優先順序,當我們properties中的屬性與系統設定屬性名相同時,會優先匹配系統屬性導致錯誤
可以採用system-properties-mode進行設定
-->
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
<!--
我們可以採用逗號或空格分隔載入多個properties檔案
-->
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
<!--
我們可以採用萬用字元來設定載入檔案
用*來代替所有字首,只保留字尾為properties即可
-->
<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
<!--
我們通常以classpath表示路徑,下述形式更為標準
classpath:*.properties : 設定載入當前工程類路徑中的所有properties檔案
-->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
<!--
我們通常以classpath*來表示路徑來源
classpath*:*.properties : 設定載入當前工程類路徑和當前工程所依賴的所有jar包中的所有properties檔案
-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
前面已經完成bean與依賴注入的相關知識學習,接下來我們主要學習的是IOC容器中的核心容器。
這裡所說的核心容器,大家可以把它簡單的理解為ApplicationContext,接下來我們從以下幾個問題入手來學習下容器的相關知識:
案例中建立ApplicationContext的方式為(類路徑下的XML組態檔):
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
除了上面這種方式,Spring還提供了另外一種建立方式為(檔案的絕對路徑):
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
方式一,就是目前案例中獲取的方式:
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
這種方式存在的問題是每次獲取的時候都需要進行型別轉換
方式二:
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
這種方式可以解決型別強轉問題,但是引數又多加了一個,相對來說沒有簡化多少。
方式三:
BookDao bookDao = ctx.getBean(BookDao.class);
這種方式就類似我們之前所學習依賴注入中的按型別注入。必須要確保IOC容器中該型別對應的bean物件只能有一個。
下面我們給出容器的層次圖
使用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" class="com.itheima.dao.impl.BookDaoImpl" lazy-init="true"/>
</beans>
接下來我們對前面知識的一個總結,共包含如下內容:
在上述的開發中,我們採用xml組態檔的形式來說依舊顯得有些複雜
這時我們就需要發揮Spring的優點:簡化開發,通過註解來簡化開發過程
下面我們會通過多個方面將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" class="com.itheima.dao.impl.BookDaoImpl"/>
</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延伸出了三種型別,在實現手法上是一致,但可以具體使用於各種類中(僅用於自我識別)
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" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>
但是我們可以通過建立單獨的類來表示組態檔:
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開發從xml轉移到註解開發,那麼一些設定設定同樣發生改變
首先我們介紹Scope範圍的設定方式:
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操作:
在Spring3.0中,省略掉了前面繁瑣的依賴注入,我們的bean依賴注入只留下了自動裝配這一操作:
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型別裝配,我們的簡單型別裝配依舊存在:
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資源:
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
下面我們以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同樣屬於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的Framework的大部分內容
接下來讓我們來整合我們之前所學習的內容,整體的運用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="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${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 +
'}';
}
}
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="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${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
接下來我們給出具體操作:
<!--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>
// 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);
}
}
我們對於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>
// 下述內容均在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());
}
}
我們在開篇有提及到AOP,現在讓我們來詳細介紹一下AOP~
首先我們來介紹一下AOP:
AOP作用:
Spring理念:
AOP核心概念:
通俗解釋:
實現類中的各個方法被稱為連線點
如果我們希望在這些連線點中設定相同的部分,可以採用通知進行設定
我們利用通知和連線點進行連線,連線點就可以執行通知中的方法並且同時執行連線點的方法
被連線的連線點被稱為切入點,存放通知的類被稱為通知類
我們同樣採用一個案例進行SpringAOP入門介紹
案例設定:測試介面執行效率
簡化設定:在介面執行前輸出當前系統時間
開發模式:XML or 註解(我們現在大部分使用註解)
具體操作:
<?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
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 {
}
我們先簡單介紹AOP的大概工作流程便於講解底層知識:
首先我們要注意切入點的讀取問題:
然後我們會根據bean是否能匹配切入點來分別處理:
這裡我們進行幾個名詞解釋:
目標物件:我們的初始物件,被一個或者多個切面所通知的物件
代理物件:我們根據目標物件所衍生出來的物件,不再是原物件;我們希望通過對代理物件的修改來完成AOP操作
我們已經簡單瞭解了SpringAOP的具體使用,接下來讓我們來仔細分析AOP的各部分
首先我們先來介紹AOP的切入點和切入點表示式定義:
AOP切入點表示式大致分為兩種:
切入點表示式的具體格式:
具體名詞解釋:
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*
- 引數規則較為複雜,根據業務方法靈活調整
- 通常不使用異常作為匹配規則
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");
}
}
我們可以注意到在上述通知中我們是存在有引數的,接下來我們針對這些引數做出相關解釋~
通知可選引數:
注意:JoinPoint是ProceedingJoinPoint的父類別
接下來我們分別從引數資料,返回值資料,異常資料三個方面進行講解:
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 = "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 = "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;
}
}
我們在之前的文章中已經多次提及過事務,這裡再重新宣告一下事務:
我們通過一個案例來進行事務的講解:
需求:實現任意兩個賬戶間轉賬操作
需求微縮:A賬戶減錢,B賬戶加錢
分析:
結果分析:
具體修改實施步驟:
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的事務通常用@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