JavaWeb开发框架——Spring
目錄
1、Spring簡介
1.1、Spring是什么
1.2、Spring發展歷程
1.3、Spring的優勢
1.3.1、方便解耦,簡化開發
1.3.2、AOP編程的支持
1.3.3、聲明式事務的支持
1.3.4、方便程序的測試
1.3.5、方便繼承各種優秀框架
1.3.6、降低JavaEE API 的使用難度
1.3.7、Java源碼的經典學習范例
1.4、Spring的體系結構
2、Spring快速入門
2.1、Spring程序開發步驟
2.2、Spring的開發步驟
3、Spring配置文件
3.1、Bean標簽基本配置
3.2、Bean標簽范圍配置
3.3、Bean生命周期
3.3.1、生命周期控制方式1
3.3.2、生命周期控制方式2
3.4、Bean實例化的四種方式
3.5、Bean的依賴注入分析
3.5.1、Bean的依賴注入概念
3.6、Bean的依賴注入方式
3.6.1、setter注入——引用類型
3.6.2、setter注入——簡單類型
3.6.3、構造器(構造方法)注入——引用類型
3.6.4、構造器(構造方法)注入——簡單類型
3.6.5、p命名空間方式注入
3.6.6、注入集合數據類型
3.6.7、依賴注入方式的選擇
3.6.8、案例——數據源對象管理
3.7、依賴自動裝配
3.7.1、自動裝配方式
3.7.2、依賴自動裝配的特征
3.8、Spring加載外部properties文件
3.9、引入其他配置文件(分模塊開發)
4、Spring相關API
4.1、ApplicationContext的繼承體系
4.2、ApplicationContext的實現類
4.3、getBean()方法使用
5、Spring配置數據源
5.1、數據源(連接池)的作用
5.2、數據源的開發步驟
5.3、Spring配置數據源
5.4、抽取jdbc配置文件
6、Spring注解開發
6.1、Spring原始注解
6.1.1、@Scope & @PostConstruct & @PreDestroy
6.1.2、@Autowired & @Qualifier 實現引用類型依賴注入
6.1.3、@Value 實現簡單類型依賴注入
6.2、Spring新注解
6.2.1、@Configuration & @ComponentScan實現替代Spring配置文件
6.2.2、@Import & @Bean 實現配置類分離和第三方bean管理
6.3、XML配置對比注解配置
7、Spring整合MyBatis
8、Spring整合Junit
8.1、原始Junit測試Spring的問題
8.2、上述問題解決思路
8.3、Spring整合Junit步驟
9、Spring集成Web環境
9.1、ApplicationContext應用上下文獲取方式
9.2、Spring提供獲取應用上下文的工具
9.3、Spring集成web環境步驟
10、Spring JdbcTemplate的基本使用
10.1、JdbcTemplate概述
10.2、JdbcTemplate開發步驟
10.3、Spring產生JdbcTemplate對象
11、Spring AOP
11.1、什么是AOP
10.2、AOP的作用及其優勢
10.3、AOP的底層實現
10.4、AOP的動態代理技術
10.4.1、基于JDK的動態代理
10.4.2、基于cglib的動態代理
10.5、AOP相關概念
10.5.1、AOP切入點表達式
10.5.2、AOP通知類型
10.6、AOP開發明確的事項
10.6.1、需要編寫的內容
10.6.2、AOP技術實現的內容
10.6.3、AOP底層使用哪種代理方式
10.7、AOP工作流程
10.8、基于XML的AOP開發
10.8.1、快速入門
10.8.2、XML配置AOP詳解
10.9、基于注解的AOP開發
10.9.1、快速入門
10.9.2、注解配置AOP詳解
10.9.3、AOP通知獲取數據
10.10、AOP案例
10.10.1、案例1——測量業務層接口萬次執行效率
10.10.2、案例2——百度網盤密碼數據兼容處理
11、Spring的事務控制
11.1、事務相關定義
11.1.1、事務角色
11.1.2、事務的隔離級別
11.1.3、事務的傳播行為
11.2、編程式事務控制相關對象
11.2.1、PlatformTransactionManager
11.2.2、TransactionDefinition
11.2.3、TransactionStatus
11.3、基于XML的聲明式事務控制
11.3.1、什么是聲明式事務控制
11.3.2、聲明式事務控制的實現
11.4、基于注解的聲明式事務控制
11.4.1、使用步驟
11.4.2、注解配置聲明式事務控制解析
11.5、Spring事務相關配置
11.5、Spring事務案例
11.5.1、案例1——銀行賬戶轉賬
11.5.2、案例2——轉賬業務追加日志
1、Spring簡介
1.1、Spring是什么
Spring是分層的Java SE/EE 應用full-stack輕量級開源框架,以IoC(Inversion Of Control:反轉控制)和AOP(Aspect Oriented Programming:面向切面編程)為內核。
- IoC(Inversion of Control):控制反轉。使用對象時,由主動new產生對象轉換為由外部提供對象,此過程中對象創建控制權由程序轉移到外部,此思想稱為控制反轉。
- DI(Dependency Injection):依賴注入。
Spring技術對IoC思想進行了實現:
- Spring提供了一個容器,用來充當IoC思想中的“外部”;
- IoC容器負責對象的創建、初始化等一系列工作,被創建或被管理的對象在IoC容器中統稱為Bean。
提供了展現層SpringMVC和持久層Spring JDBCTemplate以及業務層事務管理等眾多的企業級應用技術,還能整合開源世界眾多著名的第三方框架和類庫,逐漸成為使用最多的JavaEE企業應用開源框架。
1.2、Spring發展歷程
1.3、Spring的優勢
1.3.1、方便解耦,簡化開發
通過Spring提供的IoC容器,可以講對象間的依賴關系交由Spring進行控制,避免編碼所造成的過度耦合,用戶也不必在為單例模式類、屬性文件解析等這些很底層的需求編寫代碼,可以更專注上層的應用。
1.3.2、AOP編程的支持
通過Spring的AOP功能,方便進行面向切面編程,許多不容易用傳統OOP實現的功能可以通過AOP輕松實現。
1.3.3、聲明式事務的支持
可以將我們從單調煩悶的事務管理代碼中解脫出來,通過聲明式方式靈活地進行事務管理,提高開發效率和質量。
1.3.4、方便程序的測試
可以用非容器依賴的編程方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可做的事情。
1.3.5、方便繼承各種優秀框架
Spring對各種優秀框架(Struts、Hibemate、Hessian、Quartz等)的支持。
1.3.6、降低JavaEE API 的使用難度
Spring對JavaEE API(如JDBC、JavaMail、遠程調用等)進行了薄薄的封裝層,使這些API的使用難度大為降低。
1.3.7、Java源碼的經典學習范例
Spring的源代碼設計精妙、結構清晰、匠心獨用,處處體現著大師對Java設計模式靈活運用以及對Java技術的高深造詣。它的源代碼無疑是Java技術的最佳實踐的范例。
1.4、Spring的體系結構
- Data Access:數據訪問;
- Data Integration:數據集成;
- Web:Web開發;
- AOP:面向切面編程;
- Aspects:AOP思想實現;
- Core Container:核心容器。
- Test:單元測試與集成測試。
2、Spring快速入門
2.1、Spring程序開發步驟
問題提出:
- 管理什么?(Service與Dao)
- 如何將被管理的對象告知IoC容器?(配置)
- 被管理的對象交給IoC容器,如何獲取到IoC容器?(接口)
- IoC容器得到后,如何從容器中獲取bean?(接口方法)
- 使用Spring導入哪些坐標?(pom.xml)
2.2、Spring的開發步驟
注意:bean定義時id屬性在同一個上下文中不能重復。
package com.clp.impl;class UserDaoImpl implements com.clp.UserDao {@Overridepublic void save() {System.out.println("save running ...");} }package com.clp.demo;import com.clp.UserDao; import javafx.application.Application; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class UserDaoDemo {public static void main(String[] args) {ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext.xml");UserDao userDao = (UserDao) app.getBean("userDao");userDao.save();} }3、Spring配置文件
3.1、Bean標簽基本配置
用于配置對象交由Spring來創建。默認情況下它調用的是類中的無參構造函數,如果沒有無參構造函數則不能創建成功。
| 名稱 | <bean> |
| 類型 | 標簽 |
| 所屬 | <beans>標簽 |
| 功能 | 定義Spring核心容器管理的對象 |
| 格式 | <beans> ??????? <bean></bean> <beans> |
| 屬性列表 | id:bean的id,使用容器可以通過id值獲取對應的bean,在一個容器中id值唯一。 name:bean的別名,可以設置多個,使用,(逗號)或;(分號)或 (空格)分隔,可以用來代替id。 class:bean的類型,即配置的bean的全路徑類名。 scope:定義bean的作用范圍。singleton為單例(默認),prototype為非單例。 init-method:生命周期初始化方法。 destroy-method:生命周期銷毀方法。 autowire:自動裝配類型。 factory-method:bean工廠方法,應用于靜態工廠或實例工廠。 factory-bean:實例工廠bean。 lazy-init:控制bean延遲加載。 |
| 范例 | <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" /> <bean id="bookService" name="service service2 bookEbi"? class="com.itheima.service.impl.BookServiceImpl" /> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype" /> |
| 名稱 | <property> |
| 類型 | 標簽 |
| 所屬 | <bean>標簽 |
| 功能 | bean的屬性注入 |
| 格式 | <bean> ??????? <property></property> </bean> |
| 屬性列表 | name:屬性名稱。 value:注入的普通屬性值。 ref:注入的對象引用值。 |
| 范例 | <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource"><!-- 使用setter依賴注入方式 --><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/mydb" /><property name="username" value="root" /><property name="password" value="123456" /> </bean> |
注意事項:獲取bean無論是通過id還是name獲取,如果無法獲取到,將拋出異常NoSuchBeanDefinitionException。NoSuchBeanDefinitionException: No bean named 'bookServiceImpl' available 。
3.2、Bean標簽范圍配置
scope:指對象的作用范圍,取值如下:singleton:默認值,單例的。prototype:多例的。request:WEB項目中,Spring創建一個Bean的對象,將對象存入到request域中。session:WEB項目中,Spring創建一個Bean的對象,將對象存入到session域中。global session:WEB項目中,應用在Portlet環境,如果沒有Portlet環境那么globalSession相當于session。Bean的創建時機:當scope的取值為singleton時:Bean的實例化個數:1個;Bean的實例化時機:當Spring核心文件被加載時,實例化配置的Bean實例。Bean的生命周期:對象創建:當應用加載,創建容器時,對象就被創建了;對象運行:只要容器在,對象一直或者。對象銷毀:當應用卸載,銷毀容器時,對象就被銷毀了。當scope的取值為prototype時:Bean的實例化個數:多個。Bean的實例化時機:當調用getBean()方法時實例化Bean。對象創建:當使用對象時,創建新的對象實例。對象運行:只要對象在使用中,就一直活著。對象銷毀:當對象長時間不用時,被Java的垃圾回收器回收了。適合交給容器進行管理的bean:表現層對象;業務層對象;數據層對象;工具對象。
不適合交給容器進行管理的對象:封裝實體的域對象。
3.3、Bean生命周期
生命周期:從創建到消亡的完整過程。
Bean生命周期:Bean從創建到銷毀的整體過程。
- 初始化容器1、創建對象(內存分配)2、執行構造方法3、執行屬性注入(set操作)4、執行bean初始化方法 - 使用bean1、執行業務操作 - 關閉/銷毀容器1、執行bean銷毀方法Bean的銷毀時機:
- 容器關閉前觸發bean的銷毀- 關閉容器方式:1、手工關閉容器ConfigurationApplicationContext接口的close()操作2、注冊關閉鉤子,在虛擬機退出前先關閉容器再退出虛擬機ConfigurationApplicationContext接口registerShutdownHook()操作Bean生命周期控制:在Bean創建后到銷毀前做一些事情。
3.3.1、生命周期控制方式1
提供生命周期控制方法:class UserDaoImpl implements com.clp.UserDao {public UserDaoImpl() {System.out.println("UserDaoImpl創建..");}public void init() {System.out.println("初始化方法");}public void destroy() {System.out.println("銷毀方法");}@Overridepublic void save() {System.out.println("save running ...");} } 配置生命周期控制方法:init-method:指定類中的初始化方法名稱。destroy-method:指定類中銷毀方法名稱。<?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="userDao" class="com.clp.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean></beans> public class App {public static void main(String[] args) {// 3、獲取IoC容器ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");// 4.1、獲取bean:BookDao,參數為bean 的idBookDao bookDao = (BookDao) ctx.getBean("bookDao");bookDao.save();ctx.close();// Spring容器注冊關閉鉤子。表示Java虛擬機在關閉之前會先將容器關閉掉 // ctx.registerShutdownHook();// 4.2、獲取bean:BookService,參數為bean的id // BookService bookService = (BookService) aCtx.getBean("bookService"); // bookService.save();} }3.3.2、生命周期控制方式2
提供生命周期控制方法:/*** 實現initializingBean和DisposableBean接口,重寫其中的方法*/ public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {public BookDao bookDao;public void setBookDao(BookDao bookDao) {System.out.println("set ...");this.bookDao = bookDao;}@Overridepublic void save() {System.out.println("book service save ...");bookDao.save();}/*** 在調用完所有setXxx()的setter方法之后會調用該方法,如本類的setBookDao()方法* @throws Exception*/@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("book service afterPropertiesSet");}@Overridepublic void destroy() throws Exception {System.out.println("book service destroy...");} } applicationContext.xml中配置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 id="bookDao" class="com.clp.dao.impl.BookDaoImpl"/><bean id="bookService" class="com.clp.service.impl.BookServiceImpl"><property name="bookDao" ref="bookDao" /></bean> </beans>3.4、Bean實例化的四種方式
bean本質上就是對象,創建bean使用構造方法完成。
- 無參構造方法實例化:- 提供可訪問的構造方法 public UserDao() {}注意:無參構造方法如果不存在,將拋出異常BeanCreationException- 配置<beanid="userDao"class="com.clp.impl.UserDaoImpl"init-method="init"destroy-method="destroy"</bean>- 工廠靜態方法實例化:public class StaticFactory {public static UserDao getUserDao() {return new UserDaoImpl();}}<beanid="userDao"class="com.clp.factory.StaticFactory"<!-- 指定為靜態工廠中的某個方法返回的對象 -->factory-method="getUserDao"></bean>- 工廠實例方法實例化:public class UserDaoFactory {public UserDao getUserDao() {return new UserDaoImpl();}}<!-- 先造工廠對象 --><bean id="userDaoFactory" class="com.clp.factory.UserDaoFactory" /><!-- 再造UserDao對象 --><bean id="userDao" factory-bean="userDaoFactory" factory-method="getUserDao" />- 工廠實例方法實例化-改進版(使用FactoryBean): public class UserDaoFactoryBean implements FactoryBean<UserDao> {/*** 代替原始實例工廠中創建對象的方法* @return* @throws Exception*/@Overridepublic UserDao getObject() throws Exception {return new UserDaoImpl();}/*** 獲取對象的類型* @return*/@Overridepublic Class<?> getObjectType() {return UserDao.class;}/*** 該對象是否為單例* @return*/@Overridepublic boolean isSingleton() {return true;}}<!-- 使用factorybean實例化 --><bean id="userDao" class="com.clp.factory.UserDaoFactoryBean" />3.5、Bean的依賴注入分析
目前UserService實例和UserDao實例都存在于Spring容器中,當前的做法是在容器外部獲得UserService實例和UserDao實例,然后在程序中進行結合。
因為UserService和UserDao都在Spring容器中,而最終程序直接使用的是UserService,所以可以在Spring容器中,將UserDao設置到UserService內部。
3.5.1、Bean的依賴注入概念
依賴注入(Dependency Injection):它是Spring框架核心IOC的具體實現。
在編寫程序時,通過控制反轉,把對象的創建交給了Spring,但是代碼中不可能出現沒有依賴的情況。IOC解耦只是降低它們的依賴關系,但不會消除。例如:業務層仍會調用持久層的方法。
那這種業務層和持久層的依賴關系,在使用Spring之后,就讓Spring來維護了。簡單地說,就是坐等框架把持久層對象傳入業務層,而不用我們自己去獲取。
3.6、Bean的依賴注入方式
思考:怎么將UserDao注入到UserService內部呢?/ 向一個類中傳遞數據的方式有幾種?1、構造方法2、普通方法(set方法)思考:依賴注入描述了在容器中建立bean與bean之間依賴關系的過程,如果bean運行需要的是數字或字符串呢?1、引用類型2、簡單類型(基本數據類型與String)3、集合類型 - 依賴注入方式setter注入:簡單類型引用類型構造器注入:簡單類型引用類型3.6.1、setter注入——引用類型
在bean中定義引用類型屬性并提供可訪問的set方法:
public class BookServiceImpl implements BookService {public BookDao bookDao;public UserDao userDao;public void setBookDao(BookDao bookDao) {System.out.println("set ...");this.bookDao = bookDao;}public void setUserDao(UserDao userDao) {this.userDao = userDao;} }配置中使用property標簽ref屬性注入引用類型對象:
applicationContext.xml:...<bean id="bookDao" class="com.clp.dao.impl.BookDaoImpl"/><bean id="userDao" class="com.clp.dao.impl.UserDaoImpl"/><bean id="bookService" class="com.clp.service.impl.BookServiceImpl"><!-- property標簽中的name屬性對應該bean的setXxx()方法的xxx(首字母變小寫)--><property name="bookDao" ref="bookDao" /><property name="userDao" ref="userDao" /></bean>...3.6.2、setter注入——簡單類型
在bean中定義引用類型屬性并提供可訪問的set方法:
public class BookDaoImpl implements BookDao {private int connectionNum;private String databaseName;public void setConnectionNum(int connectionNum) {this.connectionNum = connectionNum;}public void setDatabaseName(String databaseName) {this.databaseName = databaseName;}@Overridepublic void save() {System.out.println("book dao save ..." + databaseName + ", " + connectionNum);} }配置中使用property標簽value屬性注入簡單類型數據:
applicationContext.xml:...<bean id="bookDao" class="com.clp.dao.impl.BookDaoImpl"><!-- value屬性代表值(直接寫入值) --><property name="databaseName" value="mysql" /><property name="connectionNum" value="10" /></bean>...3.6.3、構造器(構造方法)注入——引用類型
在bean中定義引用類型屬性并提供可訪問的構造方法:
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {private BookDao bookDao;private UserDao userDao;public BookServiceImpl(BookDao bookDao, UserDao userDao) {this.bookDao = bookDao;this.userDao = userDao;} }配置中使用constructor-arg標簽ref屬性注入引用類型對象:
applicationContext.xml:...<bean id="bookDao" class="com.clp.dao.impl.BookDaoImpl" /><bean id="userDao" class="com.clp.dao.impl.UserDaoImpl" /><bean id="bookService" class="com.clp.service.impl.BookServiceImpl"><!-- name指的是構造方法中的形參名稱;ref指的是要注入的bean的id --><constructor-arg name="bookDao" ref="bookDao" /><constructor-arg name="userDao" ref="userDao" /></bean>...3.6.4、構造器(構造方法)注入——簡單類型
public class BookDaoImpl implements BookDao {private int connectionNum;private String databaseName;public BookDaoImpl(int connectionNum, String databaseName) {this.connectionNum = connectionNum;this.databaseName = databaseName;} } applicationContext.xml:...<!-- 方案1:name屬性為形參名 value為形參的值 --><bean id="bookDao" class="com.clp.dao.impl.BookDaoImpl"><constructor-arg name="databaseName" value="mysql" /><constructor-arg name="connectionNum" value="666" /></bean><!-- 方案2:使用type屬性代替name,解決形參名稱與name屬性耦合的問題。type為形參的類型 --><bean id="bookDao" class="com.clp.dao.impl.BookDaoImpl"><constructor-arg type="java.lang.String" value="mysql" /><constructor-arg type="int" value="666" /></bean><!-- 方案3:使用index屬性代替type,來解決參數類型重復問題。index為形參的索引(從0開始)--><bean id="bookDao" class="com.clp.dao.impl.BookDaoImpl"><constructor-arg index="0" value="666"/><constructor-arg index="1" value="mysql"/></bean>...3.6.5、p命名空間方式注入
P命名空間本質也是set()方法注入,但比起上述的set()方法注入更加方便,主要體現在配置文件中,如下:
- 首先,需要引入P命名空間:xmlns:p="http://www.springframework.org/schema/p"
- 其次,需要修改注入方式:<bean id="userServiceId" class="com.clp.service.impl.UserServiceImpl" p:userDao-ref="userDaoId" />
3.6.6、注入集合數據類型
除了對象的引用可以注入,普通數據類型,集合等都可以在容器中進行注入。
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;}@Overridepublic 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);} } applicationContext.xml:...<bean id="bookDao" class="com.clp.dao.impl.BookDaoImpl"><!-- name屬性對應BookDao的setXxx()中的xxx(首字母小寫)--><property name="array"><array><value>100</value><value>200</value><value>300</value><!-- 如果是引用類型,則格式如下 --> <!-- <ref bean="beanId" />--></array></property><property name="list"><list><value>Laaa</value><value>Lbbb</value><value>Lccc</value></list></property><property name="set"><set><value>Saaa</value><value>Sbbb</value><!-- 同名的會自動過濾 --><value>Sccc</value><value>Sccc</value></set></property><property name="map"><map><entry key="Mkey1" value="Mvalue1" /><entry key="Mkey2" value="Mvalue2" /><entry key="Mkey3" value="Mvalue3" /></map></property><property name="properties"><props><prop key="Pkey1">Pvalue1</prop><prop key="Pkey2">Pvalue2</prop><prop key="Pkey3">Pvalue3</prop></props></property></bean>... public class App {public static void main(String[] args) {// 3、獲取IoC容器ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");// 4.1、獲取bean:BookDao,參數為bean 的idBookDao bookDao = (BookDao) ctx.getBean("bookDao");bookDao.save();} }結果: book dao save ... 遍歷數組[100, 200, 300] 遍歷List[Laaa, Lbbb, Lccc] 遍歷Set[Saaa, Sbbb, Sccc] 遍歷Map{Mkey1=Mvalue1, Mkey2=Mvalue2, Mkey3=Mvalue3} 遍歷Properties{Pkey3=Pvalue3, Pkey2=Pvalue2, Pkey1=Pvalue1}3.6.7、依賴注入方式的選擇
- 強制使用構造器進行,使用setter注入有概率不進行注入導致null對象出現;
- 可選依賴使用setter注入進行,靈活性強;
- Spring框架倡導使用構造器,第三方框架內部大多數采用構造器注入的形式進行數據初始化,相對嚴謹;
- 如果有必要可以兩者同時使用,使用構造器完成強制依賴的注入,使用setter注入完成可選依賴的注入;
- 實際開發過程中還要根據實際情況分析,如果受控對象沒有提供setter方法就必須使用構造器注入;
- 自己開發的模塊推薦使用setter注入。
3.6.8、案例——數據源對象管理
添加依賴(坐標):
pom.xml:...<!-- druid數據源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><!-- c3p0數據源 --><dependency><groupId>c3p0</groupId><artifactId>c3p0</artifactId><version>0.9.1.2</version></dependency><!-- 使用c3p0是需要用到的依賴 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency>...配置數據源對象作為Spring容器管理的bean:
applicationContext.xml:...<!-- 管理DruidDataSource對象 --><bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource"><!-- 使用setter依賴注入方式 --><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/mydb" /><property name="username" value="root" /><property name="password" value="123456" /></bean><bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource"><!-- 使用setter依賴注入方式 --><property name="driverClass" value="com.mysql.jdbc.Driver"/><property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/><property name="user" value="root"/><property name="password" value="123456"/></bean>... 測試代碼:public class App {public static void main(String[] args) {// 3、獲取IoC容器ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");DataSource dataSource1 = (DataSource) ctx.getBean("dataSource1");DataSource dataSource2 = (DataSource) ctx.getBean("dataSource2");System.out.println(dataSource1);System.out.println(dataSource2);} }3.7、依賴自動裝配
IoC容器根據bean所依賴的資源在容器中自動查找并注入到bean中的過程稱為自動裝配。
3.7.1、自動裝配方式
- 按類型(常用);
- 按名稱;
- 按構造方法;
- 不啟動自動裝配。
3.7.2、依賴自動裝配的特征
- 自動裝配用于引用類型依賴注入,不能對簡單類型進行操作;
- 使用按類型裝配時(byType)必須保障容器中相同類型的bean唯一,推薦使用;
- 使用按名稱裝配時(byName)必須保障容器中具有指定名稱的bean,因變量名與配置耦合,不推薦使用;
- 自動裝配優先級低于setter注入與構造器注入,同時出現時自動裝配配置失效。
3.8、Spring加載外部properties文件
jdbc.properties:jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssmdb jdbc.username=root jdbc.password=123456 applicationContext.xml:...<!-- 1、開啟context命名空間<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/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/context <-這里http://www.springframework.org/schema/context/spring-context-3.2.xsd"> <-這里</beans>--><!-- 2、使用context空間加載properties文件。 system-properties-mode="NEVER"表示不加載系統屬性--><!-- 方式1:加載多個properties --><context:property-placeholder location="jdbc.properties, other.properties" system-properties-mode="NEVER"/><!-- 方式2:加載所有properties,classpath:*.properties意思是從類加載路徑讀取properties文件 --><context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER" /><!-- 方式2的擴充版:加載所有properties,classpath*:*.properties意思是從類加載路徑以及其他jar包下讀取properties文件。--><context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER" /><!-- 3、使用屬性占位符${}讀取properties文件中的屬性 --><bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource"><!-- 使用setter依賴注入方式 --><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>...3.9、引入其他配置文件(分模塊開發)
實際開發中,Spring的配置內容非常多,這就導致Spring配置很繁雜且體積很大,所以,可以將部分配置拆解到其他的配置文件中,而在Spring主配置文件通過import標簽進行加載。
<import resource="applicationContext-xxx.xml" />4、Spring相關API
4.1、ApplicationContext的繼承體系
applicationContext:接口類型,代表應用上下文,可以通過其實例獲得Spring容器中的Bean對象。
- BeanFactory是Ioc容器的頂層接口,初始化BeanFactory對象時,加載的bean延遲加載。
- ApplicationContext接口是Spring容器的核心接口,初始化時bean立即加載。
- ApplicationContext接口提供基礎的bean操作相關方法,通過其他接口擴展其功能。
- ApplicationContext接口常用初始化類:① ClassPathXMLApplicationContext;② FileSystemXMLApplicationContext。
4.2、ApplicationContext的實現類
ClassPathXmlApplicationContext:它是從類的根路徑下(resource文件夾下)加載配置文件,推薦使用這種。FileSystemXmlApplicationContext:它是從磁盤路徑上加載配置文件,配置文件可以在磁盤的任意位置。AnnotationConfigApplicationContext:當使用注解配置容器對象時,需要使用此類來創建Spring容器,它用來讀取注解。4.3、getBean()方法使用
public Object getBean(String name);當參數的數據類型是字符串時,標識根據Bean的id從容器中獲得Bean實例,返回是Object,需要強轉。public <T> T getBean(Class<T> requiredType);當參數的數據類型是Class類型時,標識根據類型從容器中匹配Bean實例,當容器中相同類型的Bean有多個時,則此方法會報錯。public <T> T getBean(String beanName, Class<T> beanType);使用bean名稱獲取并指定類型。5、Spring配置數據源
5.1、數據源(連接池)的作用
常見的數據源(連接池):DBCP、C3P0、BoneCP、Druid等。
package test;import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidPooledConnection; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Test;import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.SQLException; import java.util.ResourceBundle;public class DataSourceTest {@Test//測試手動創建 c3p0 數據源public void test1() throws PropertyVetoException, SQLException {ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setDriverClass("com.mysql.jdbc.Driver");dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/bysj?characterEncoding=utf-8");dataSource.setUser("root");dataSource.setUser("123456");Connection connection = dataSource.getConnection();System.out.println(connection);connection.close();}@Test//測試手動創建 c3p0 數據源(加載properties配置文件)public void test3() throws PropertyVetoException, SQLException {//讀取配置文件ResourceBundle rb = ResourceBundle.getBundle("jdbc");String driver = rb.getString("jdbc.driver");String url = rb.getString("jdbc.url");String username = rb.getString("jdbc.username");String password = rb.getString("jdbc.password");//創建數據源對象,設置連接參數ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setDriverClass(driver);dataSource.setJdbcUrl(url);dataSource.setUser(username);dataSource.setPassword(password);Connection connection = dataSource.getConnection();System.out.println(connection);connection.close();}@Test//測試手動創建 druid 數據源public void test2() throws PropertyVetoException, SQLException {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/bysj?characterEncoding=utf-8");dataSource.setUsername("root");dataSource.setPassword("123456");DruidPooledConnection connection = dataSource.getConnection();System.out.println(connection);connection.close();} }5.2、數據源的開發步驟
5.3、Spring配置數據源
可以將DataSource的創建權交給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"><beanid="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource" ><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/bysj?characterEncoding=utf-8"></property><property name="user" value="root"></property><property name="password" value="123456"></property></bean> </beans> package test;import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidPooledConnection; import com.mchange.v2.c3p0.ComboPooledDataSource; import javafx.application.Application; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;import javax.sql.DataSource; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.SQLException; import java.util.ResourceBundle;public class DataSourceTest {@Test//測試Spring容器產生數據源對象public void test4() throws PropertyVetoException, SQLException {ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");DataSource dataSource = app.getBean(DataSource.class);Connection connection = dataSource.getConnection();System.out.println(connection);connection.close();} }5.4、抽取jdbc配置文件
applicationContext.xml加載jdbc.properties配置文件獲得連接信息。
首先,需要引入context命名空間和約束路徑:命名空間:xmlns:context="http://www.springframework.org/schema/context"約束路徑:http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdSpring容器加載properties文件:<context:property-placeholder location="xx.properties" /><property name="" value="${key}" /> <?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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!--加載外部的properties文件--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><beanid="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource" ><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean> </beans>6、Spring注解開發
Spring是輕代碼而重配置的框架,配置比較繁重,影響開發效率,所以注解開發是一種趨勢,注解代替xml配置文件可以簡化配置,提高開發效率。
6.1、Spring原始注解
@Component 使用在類上用于實例化Bean- Spring提供@Component注解的3個衍生注解:@Controller使用在web層類上用于實例化Bean@Service使用在service層上用于實例化Bean@Repository使用在dao層類上用于實例化Bean@Autowired 使用在字段上根據類型依賴注入@Qualifier 結合@Autowired一起使用用于根據名稱進行依賴注入@Resource 相當于@Autowired+@Qualifier,按照名稱進行注入。@Value 注入普通屬性@Scope 標注Bean的作用范圍@PostConstruct 使用在方法上標注該方法是Bean的初始化方法@PreDestroy 使用在方法上標注該方法是Bean的銷毀方法注意:使用注解進行開發時,需要在applicationContext.xml中配置組件掃描,作用是指定哪個包及其子包下的Bean需要進行掃描以便識別使用注解配置的類、字段和方法。
<!--注解的組件掃描--> <context:component-scan base-package='com.clp'></context:component-scan>6.1.1、@Scope & @PostConstruct & @PreDestroy
//<bean id="userServiceId" class="service.impl.UserServiceImpl"> //@Component("userServiceId") @Service("userServiceId") @Scope("singleton") public class UserServiceImpl implements UserService {@Value("${jdbc.driver}") //從容器中找鍵為jdbc.driver的值,并賦給driverprivate String driver;//<property name="userDao" ref="userDaoId"></property> // @Autowired //按照數據類型從Spring容器中進行匹配的 // @Qualifier("userDaoId") //按照id名稱從Spring容器中進行匹配的,但是注意此處@Qualifier需要結合@Autowired一起使用@Resource(name = "userDaoId") //@Resource相當于@Qualifier+@Autowiredprivate UserDao userDao;//使用xml配置需要set()方法,使用注解方式可以不寫set()方法 // public void setUserDao(UserDao userDao) { // this.userDao = userDao; // }@Overridepublic void save() {System.out.println(driver);userDao.save();}@PostConstructpublic void init() {System.out.println("service對象的初始化方法");}@PreDestroypublic void destroy() {System.out.println("service對象的銷毀方法");} }6.1.2、@Autowired & @Qualifier 實現引用類型依賴注入
@Service public class BookServiceImpl implements BookService {/*** 使用@Autowired注解開啟自動裝配模式:按類型依賴注入(通過暴力反射)* 如果有多個相同類型的bean,則需再添加@Qualifier指定bean的名稱(bean的id)*/@Autowired@Qualifier("bookDao1")private BookDao bookDao;@Overridepublic void save() {System.out.println("book service save ...");bookDao.save();} }注意:
- 自動裝配基于反射設計創建對象并暴力反射對應屬性為私有屬性初始化數據,因此無需提供setter方法;
- 自動裝配建議使用無參構造方法創建對象(默認),如果不提供對應構造方法,請提供唯一的構造方法;
- @Qualifier注解無法單獨使用,必須配合@Autowired注解使用。
6.1.3、@Value 實現簡單類型依賴注入
@Repository("bookDao") public class BookDaoImpl implements BookDao {@Value("mysql")private String dbName;@Overridepublic void save() {System.out.println("book dao save ..." + dbName);} }注入properties文件中的值:
- 配置類加上@PropertiesSource注解指定要加載的properties文件@Configuration@ComponentScan("com.clp")@PropertySource("classpath:jdbc.properties") <-這里public class SpringConfig {} 注意:如果需要加載多個properties文件,使用數組方式加載:@PropertiesSource({"jdbc.properties", "xxx.properties"}) 注意:路徑僅支持單一文件配置,多文件請使用數組格式配置,不允許使用通配符*。允許添加"classpath:"前綴。- 配置依賴注入@Repository("bookDao")public class BookDaoImpl implements BookDao {@Value("${jdbc.url}")private String dbName;@Overridepublic void save() {System.out.println("book dao save ..." + dbName);}}6.2、Spring新注解
使用上面的注解還不能全部替代xml配置文件,還需要使用注解替代的配置如下:
- 非自定義的Bean的配置:<bean>
- 加載properties文件的配置:<context:property-placeholder>
- 組件掃描的配置:<context:component-scan>
- 引入其他文件:<import>
Spring 3.0升級了純注解開發模式,使用Java類代替配置文件,開啟了Spring快速開發賽道。在純注解開發中,Java類代替Spring核心配置文件。
@Configuration 用于指定當前類是一個Spring配置類(相當于一個applicationContext.xml),當創建容器時會從該類上加載注解。@ComponentScan 用于指定Spring在初始化容器時要掃描的包。 作用和在Spring的xml配置文件中的<context:component-scan base-package="com.clp" />一樣@Bean 使用在方法上,標注該方法的返回值存儲到Spring容器中。@PropertySource 用于加載.properties文件中的配置@Import 用于導入其他配置類6.2.1、@Configuration & @ComponentScan實現替代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"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.2.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><!-- 配置組件掃描 service 和 mapper --><context:component-scan base-package="com.clp" /> </beans>上述配置文件可用以下類替換:
@Configuration @ComponentScan("com.clp") public class SpringConfig { }@Configuration注解用于設定當前類為配置類。 @ComponentScan注解用于設定掃描路徑,此注解只能添加一次,多個數據請用數組格式:@ComponentScan({"com.clp.service", "com.clp.dao"})讀取Spring核心配置文件初始化容器對象切換為讀取Java配置類初始化容器對象:
public class AppAnno {public static void main(String[] args) {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = (BookDao) ctx.getBean("bookDao");bookDao.save();} }測試代碼:
public class AppAnno {public static void main(String[] args) {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao1 = (BookDao) ctx.getBean(BookDao.class);BookDao bookDao2 = (BookDao) ctx.getBean(BookDao.class);System.out.println(bookDao1);System.out.println(bookDao2);} }6.2.2、@Import & @Bean 實現配置類分離和第三方bean管理
- 使用獨立的配置類管理第三方bean:public class JdbcConfig {// 1、定義一個方法獲得要管理的對象// 2、添加@Bean表示當前方法的返回值是一個bean@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/ssmdb");dataSource.setUsername("root");dataSource.setPassword("123456");return dataSource;}}- 將獨立的配置類加入核心配置:- 方式1:導入式。使用@Import注解手動加入配置類到核心配置,此注解只能添加一次,多個數據請用數組格式public class JdbcConfig {@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();// 相關配置return dataSource;}}@Configuration@Import({JdbcConfig.class})public class SpringConfig {}- 方式2:掃描式。使用@ComponentScan注解掃描配置類所在的包,加載對應的配置類信息。@Configurationpublic class JdbcConfig {@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();// 相關配置return dataSource;}}@Configuration@ComponentScan({"com.clp.config", "com.clp.service", "com.clp.dao"})public class SpringConfig {}第三方bean的依賴注入(創建該bean還需要其他東西):
public class JdbcConfig {/** 簡單類型的依賴注入使用@Value* */@Value("com.mysql.jdbc.Driver")private String driver;@Value("jdbc:mysql://localhost:3306/ssmdb")private String url;@Value("root")private String username;@Value("123456")private String password;// 1、定義一個方法獲得要管理的對象// 2、添加@Bean表示當前方法的返回值是一個bean@Bean/** 引用類型的依賴注入:主需要為bean定義方法設置形參即可,容器會根據 類型 自動裝配對象。* */public DataSource dataSource(BookDao bookDao) {System.out.println(bookDao);DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;} }6.3、XML配置對比注解配置
| 定義bean | bean標簽: ??????? - id屬性 ??????? - class屬性 | @Component ??????? @Controller ??????? @Service ??????? @Repository @ComponentScan |
| 設置依賴注入 | setter注入(set方法) ??????? 引用/簡單 構造器逐日(構造方法) ??????? 引用/簡單 自動裝配 | @Autowired ??????? @Qualifier @Value |
| 配置第三方bean | bean標簽 靜態工廠、實例工廠、FactoryBean | @Bean |
| 作用范圍 | bean標簽: ??????? - scope屬性 | @Scope |
| 生命周期 | bean標簽: ??????? init-method ??????? destroy-method | @PostConstruct @PreDestroy |
7、Spring整合MyBatis
MyBatis程序核心對象分析:
// 1、創建SqlSessionFactoryBean對象 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 2、加載sqlMapConfig.xml配置文件 InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml"); // 3、創建SqlSessionFactory對象 SqlSessionFactory sqlSessionFactory = sqlSessionFactory.build(inputStream); // 4、獲取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 5、執行SqlSession對象執行查詢,獲取結果User AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account account = accountDao。findById(2); System.out.println(account); // 6、釋放資源 sqlSession.close();整合MyBatis:
pom.xml中導入坐標:...<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.0.5.RELEASE</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.1</version></dependency>... 編寫配置類: public class JdbcConfig {/** 簡單類型的依賴注入使用@Value* */@Value("com.mysql.jdbc.Driver")private String driver;@Value("jdbc:mysql://localhost:3306/ssmdb")private String url;@Value("root")private String username;@Value("123456")private String password;// 1、定義一個方法獲得要管理的對象// 2、添加@Bean表示當前方法的返回值是一個bean@Bean/** 引用類型的依賴注入:主需要為bean定義方法設置形參即可,容器會根據 類型 自動裝配對象。* */public DataSource dataSource(BookDao bookDao) {System.out.println(bookDao);DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;} }public class MyBatisConfig {/*** SqlSessionFactoryBean專門產生SqlSessionFactory* @return*/@Beanpublic SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();ssfb.setTypeAliasesPackage("com.clp.domain"); // 設置實體類別名ssfb.setDataSource(dataSource); // 設置數據源return ssfb;}/*** 加載Dao層的映射信息* @return*/@Beanpublic MapperScannerConfigurer mapperScannerConfigurer() {MapperScannerConfigurer msc = new MapperScannerConfigurer();msc.setBasePackage("com.clp.dao"); // 設置映射配置所在的包return msc;} }@Configuration @Import({JdbcConfig.class, MyBatisConfig.class}) @ComponentScan("com.clp") @PropertySource("classpath:jdbc.properties") public class SpringConfig { }8、Spring整合Junit
8.1、原始Junit測試Spring的問題
在測試類中,每個測試方法都有以下兩行代碼:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = ac.getBean("accountService",IAccountService.class);這兩行代碼的作用是獲取容器,如果不寫的話,直接回提示空指針異常,所以又不能輕易刪掉。
8.2、上述問題解決思路
- 讓SpringJunit負責創建Spring容器,但是需要將配置文件的名稱告訴它。
- 將需要進行測試Bean直接在測試類中進行注入。
8.3、Spring整合Junit步驟
9、Spring集成Web環境
9.1、ApplicationContext應用上下文獲取方式
應用上下文是通過new ClasspathXmlApplicationContext(spring配置文件)方式獲取的,但是每次從容器中獲得Bean時都要編寫new ClasspathXmlApplicationContext(spring配置文件),這樣的弊端是配置文件加載多次,應用上下文對象創建多次。
在Web項目中,可以使用ServletContextListener監聽Web應用的啟動,我們可以在Web應用啟動時,就加載Spring的配置文件,創建應用上下文對象ApplicationContext,再將其存儲到最大的域servletContext域中,這樣就可以再任意位置從域中獲得應用上下文ApplicationContext對象了。
9.2、Spring提供獲取應用上下文的工具
Spring提供了一個監聽器ContextLoaderListener,該監聽器內部加載Spring配置文件,創建應用上下文對象,并存儲到ServletContext域中,提供了一個客戶端工具WebApplicationContextUtils供使用者獲得應用上下文對象。
所以我們需要做的只有兩件事:
在web.xml中配置ContextLoaderListener監聽器(要導入spring-web坐標);
使用WebApplicationContextUtils獲得應用上下文對象ApplicationContext。
web.xml:<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--全局初始化參數--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--配置監聽器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener></web-app> package listener;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService;import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener;@WebListener public class ContextLoaderListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {//將Spring的應用上下文對象存儲到ServletContext域中ServletContext servletContext = sce.getServletContext();String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");ApplicationContext app = new ClassPathXmlApplicationContext(contextConfigLocation);servletContext.setAttribute("app",app);System.out.println("spring容器創建完畢");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {} } package web;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import service.UserService;import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@WebServlet public class UserServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req,resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ServletContext servletContext = req.getServletContext();//ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);UserService userService = app.getBean(UserService.class);userService.save();} }9.3、Spring集成web環境步驟
10、Spring JdbcTemplate的基本使用
10.1、JdbcTemplate概述
它是spring框架中提供的一個對象,是對原始繁瑣的Jdbc API對象的簡單封裝。spring框架為我們提供了很多的操作模板類。例如:操作關系型數據的JdbcTemplate和HibernateTemplate,操作nosql數據庫的RedisTemplate,操作消息隊列的JmsTemplate等等。
10.2、JdbcTemplate開發步驟
10.3、Spring產生JdbcTemplate對象
我們可以將JdbcTemplate的創建權交給Spring,將數據源DataSource的創建權也交給Spring,在Spring容器內部將數據源DataSource注入到JdbcTemplate模板對象中,配置如下:
jdbc.properties:jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db>characterEncoding=utf-8 jdbc.username=root jdbc.password=123456 applicationContext.xml:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!--配置數據源對象--> <!-- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">--> <!-- <property name="driverClass" value="com.mysql.jdbc.Driver" />--> <!-- <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/db?characterEncoding=utf-8" />--> <!-- <property name="user" value="root" />--> <!-- <property name="password" value="123456" />--> <!-- </bean>--><!--加載外部的jdbc.properties--><context:property-placeholder location="classpath:jdbc.properties" /><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driver}"/><property name="jdbcUrl" value="${jdbc.url}"/><property name="user" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--配置jdbc模板對象--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource" /></bean></beans> @Test//測試spring產生jdbcTemplate模板對象public void test2() {ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);int row = jdbcTemplate.update("insert into account values(?,?)", "王五", 30);System.out.println(row);app.close();} package com.test;import com.domain.Account; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class JdbcTemplateCRUDTest {@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void test1() {jdbcTemplate.update("update account set money=? where name=?",10000,"張三");}@Testpublic void test2() {List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));System.out.println(accountList);}@Testpublic void test3() {Account account = jdbcTemplate.queryForObject("select * from account where name=?",new BeanPropertyRowMapper<Account>(Account.class), "張三");System.out.println(account);}@Testpublic void test4() {Long count = jdbcTemplate.queryForObject("select count(*) from account",Long.class);System.out.println(count);} }11、Spring AOP
11.1、什么是AOP
AOP(Aspect Oriented Programming)的縮寫,意思為面向切面編程,是通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。
AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間耦合度降低,提高程序的可重用性,同時提高了開發的效率。
10.2、AOP的作用及其優勢
作用:在程序運行期間,在不修改源碼的情況下對方法進行功能增強。
優勢:減少重復代碼,提高開發效率,并且易于維護。
10.3、AOP的底層實現
實際上,AOP的底層是通過Spring提供的動態代理技術實現的。在運行期間,Spring通過動態代理技術動態地生成代理對象,代理對象方法執行時進行增強功能地接入,再去調用目標對象地方法,從而完成功能的增強。
10.4、AOP的動態代理技術
常用的動態代理技術:
- JDK代理:基于接口的動態代理技術。
- cglib代理:基于父類的動態代理技術。
10.4.1、基于JDK的動態代理
package com.proxy.jdk;public class Advance {public void before() {System.out.println("前置增強..");}public void after() {System.out.println("后置增強..");} } package com.proxy.jdk;public interface TargetInterface {public void save(); } package com.proxy.jdk;public class Target implements TargetInterface{@Overridepublic void save() {System.out.println("save running...");} } package com.proxy.jdk;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;public class ProxyTest {public static void main(String[] args) {//創建目標對象final Target target = new Target();//創建增強對象final Advance advance = new Advance();//返回值就是動態生成的代理對象TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(//目標對象的類加載器target.getClass().getClassLoader(),//目標對象相同的字節碼對象數組target.getClass().getInterfaces(),new InvocationHandler() {@Override//調用代理對象的任何方法,實質執行的都是invoke方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//前置增強advance.before();//執行目標方法Object invoke = method.invoke(target, args);//后置增強advance.after();return invoke;}});//調用代理對象的方法proxy.save();} } 結果:前置增強.. save running... 后置增強..10.4.2、基于cglib的動態代理
package com.proxy.cglib;import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class ProxyTest {public static void main(String[] args) {//創建目標對象final Target target = new Target();//創建增強對象final Advance advance = new Advance();//返回值就是動態生成的代理對象,基于cglib//1、創建增強器Enhancer enhancer = new Enhancer();//2、設置父類(目標)enhancer.setSuperclass(Target.class);//3、設置回調enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//執行前置advance.before();//執行目標Object invoke = method.invoke(target, args);//執行后置advance.after();return invoke;}});//4、創建代理對象Target proxy = (Target) enhancer.create();proxy.save();} }10.5、AOP相關概念
Spring的AOP實現底層就是對上面的動態代理的代碼進行了封裝,封裝后我們只需要對需要關注的部分進行代碼編寫,并通過配置的方式完成指定目標的方法增強。
AOP常用的術語如下:
- Target(目標對象):原始功能去掉共性功能對應的類產生的對象,這種對象是無法直接完成最終工作的。
- Proxy(代理對象):目標對象無法直接完成工作,需要對其進行功能回填,通過原始對象的代理對象實現。SpringAOP的核心本質是采用代理模式實現的。
- Joinpoint(連接點):程序執行過程中的任意位置,在SpringAOP中,這些點指的是方法,因為Spring只支持方法類型的連接點。
- Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義(匹配連接點的式子)。在SpringAOP中,一個切入點可以只描述一個具體方法,也可以匹配多個方法:① 一個具體方法:com.itheima.dao包下的BookDao接口中的無形參無返回值的save()方法;② 匹配多個方法:所有的save()方法,所有的get開頭的方法,所有以Dao結尾的接口中的任意方法,所有帶有一個參數的方法。注意:連接點包含切入點,切入點一定在連接點中。
- Advice(通知/增強):所謂通知是指攔截到Joinpoint(切入點)之后所要做的操作,也就是共性功能。在SpringAOP中,功能最終以方法的形式呈現。通知類:定義通知的類。
- Aspect(切面):是切入點和通知(引介)的結合。
- Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程。Spring采用動態代理織入,而AspectJ采用編譯期織入和類裝載期織入。
10.5.1、AOP切入點表達式
- 切入點:要進行增強的方法。
- 切入點表達式:要進行增強的方法的描述形式。
1、切點表達式的寫法:
- 切入點表達式標準格式:動作關鍵字execution(訪問修飾符 返回值類型 包名.類/接口名.方法名(參數) 異常名)例:execution(public User com.itheima.service.UserService.findById(int))動作關鍵字:描述切入點的行為動作,例如execution表示執行到指定切入點。訪問修飾符:public,private等。訪問修飾符可以省略;返回值類型:可以使用星號*代表任意;包名:可以使用星號*代表任意。包名與類名之間一個點.代表當前包下的類;兩個點..表示當前包及其子包下的類;類/接口名:可以使用星號*代表任意;方法名:可以使用星號*代表任意;參數:參數列表可以使用兩個點..表示任意個數,任意類型的參數列表。異常名:方法定義中拋出指定異常,可以省略。- 可以使用通配符描述切入點,快速描述*:單個獨立的任意符號,可以獨立實現,也可以作為前綴或者后綴的匹配符出現。例:execution(public * com.itheima.*.UserService.find* (*))匹配com.itheima包下的任意包中的UserService類或接口中所有find開頭的帶有一個參數的方法。..:多個連續的任意符號,可以獨立出現,常用語簡化包名與參數的書寫。例:execution(public User com..UserService.findById (..))匹配com包下的任意包中的UserService類或接口中所有名稱為findById的方法+:專用于匹配子類類型。例:execution(* *..*Service+.*(..))2、切入點表達式的書寫技巧
- 所有代碼按照標準規范開發,否則以下技巧全部失效;
- 描述切入點通常描述接口,而不描述實現類;
- 訪問控制修飾符針對接口開發均采用public描述(可省略訪問控制修飾符描述);
- 返回值類型對于增刪改類使用精準類型加速匹配,對于查詢類使用*通配快速描述;
- 包名書寫盡量不使用..匹配,效率過低,常用*做單個包描述匹配,或精準匹配;
- 接口名/類名書寫名稱與模塊相關的采用*匹配,例如UserService書寫成*Service,綁定業務層接口名;
- 方法名書寫以動詞進行精準匹配,名詞采用*匹配,例如getByI書寫成getBy*,selectAll書寫成selectAll;
- 參數規則較為復雜,根據業務方法靈活調整;
- 通常不使用異常作為匹配規則。
10.5.2、AOP通知類型
AOP通知描述了抽取的共性功能,根據共性功能抽取的位置不同,最終運行代碼時要將其加入到合理的位置。
AOP通知功分為5種類型:
- 前置通知;
- 后置通知;
- 環繞通知(重點);
- 返回后通知(了解);
- 拋出異常后通知(了解)。
10.6、AOP開發明確的事項
10.6.1、需要編寫的內容
- 編寫核心業務代碼(目標類的目標方法);
- 編寫切面類,切面類中有通知(增強功能方法);
- 在配置文件中,配置織入關系,即將哪些通知與哪些連接點進行結合。
10.6.2、AOP技術實現的內容
Spring框架監控切入點方法的執行,一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完成的代碼邏輯運行。
10.6.3、AOP底層使用哪種代理方式
在Spring中,框架會根據目標類是否實現了接口來決定采用哪種動態代理的方式。
10.7、AOP工作流程
10.8、基于XML的AOP開發
10.8.1、快速入門
10.8.2、XML配置AOP詳解
1、通知的類型:
通知的配置語法:
??????? <aop:通知類型 method="切面類中方法名" pointcunt="切點表達式"></aop:通知類型>
| 前置通知 | <aop:before> | 用于配置前置通知。指定增強的方法在切入點方法之前執行。 |
| 后置通知 | <aop:after-returning> | 用于配置后置通知。指定增強的方法在切入點方法之后執行。 |
| 環繞通知 | <aop:around> | 用于配置環繞通知。指定增強的方法在切入點方法之前和之后都執行。 |
| 異常拋出通知 | <aop:throwing> | 用于配置異常拋出通知。指定增強的方法在出現異常時執行。 |
| 最終通知 | <aop:after> | 用于配置最終通知。無論增強方式執行是否有異常都會執行。 |
2、切點表達式的抽取
當多個增強的切點表達式相同時,可以將切點表達式進行抽取,在增強中使用pointcut-ref屬性代替pointcut屬性來引用抽取后的切點表達式。
applicationContext.xml:...<!--配置織入:告訴Spring框架,哪些方法(切點)需要進行哪些增強(前置、后置)--><aop:config><!--聲明切面--><aop:aspect ref="myAspect"><!--切面:切點+通知--><aop:pointcut id="myPointcut" expression="execution(* com.aop.*.*(..))"/><aop:before method="before" pointcut-ref="myPointcut"></aop:before></aop:aspect></aop:config>...10.9、基于注解的AOP開發
10.9.1、快速入門
在applicationContext.xml中配置的方式代碼案例:
applicationContext.xml:...<!--配置組件掃描--><context:component-scan base-package="com.anno" /><!--AOP自動代理--><aop:aspectj-autoproxy />... package com.anno;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component;@Component("myAspect") @Aspect //標注當前MyAspect是一個切面類 public class MyAspect {//配置前置增強@Before("execution(* com.anno.*.*(..))")public void before() {System.out.println("前置增強...");}public void afterReturning() {System.out.println("后置增強...");}//ProceedingJoinPoint :正在執行的連接點, 即 切點public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("環繞前增強...");//切點方法Object proceed = pjp.proceed();System.out.println("環繞后增強...");return proceed;}public void afterThrowing() {System.out.println("異常拋出增強...");}private void after() {System.out.println("最終增強...");} }10.9.2、注解配置AOP詳解
1、切點表達式的抽取
同xml配置aop一樣,我們可以將切點表達式抽取。抽取方式是在切面內定義方法,在該方法上使用@Pointcut注解定義切點表達式,然后再在增強注解中進行引用。
@Component("myAspect")@Aspectpublic class MyAspect {@Before("MyAspect.myPoint()")public void before() {System.out.println("前置代碼增強..");}@Pointcut("execution(* com.anno.*.*(..))")public void myPoint() {}}2、AOP注解通知的類型
通知的配置語法:@通知注解("切點表達式")
| @Before | 方法注解 | 通知方法定義的上方 | 設置當前通知方法與切入點之間的綁定關系,當前通知方法在原始切入點方法前運行 | value(默認):切入點方法名,格式為類名.方法名() |
| @After | 方法注解 | 通知方法定義的上方 | 設置當前通知方法與切入點之間的綁定關系,當前通知方法在原始切入點方法后運行 | value(默認):切入點方法名,格式為類名.方法名() |
| @Around | 方法注解 | 通知方法定義的上方 | 設置當前通知方法與切入點之間的綁定關系,當前通知方法在原始切入點方法前后運行 | |
| @AfterReturning | 方法注解 | 通知方法定義的上方 | 設置當前通知方法與切入點之間的綁定關系,當前通知方法在原始切入點方法正常執行完畢后運行 | value(默認):切入點方法名,格式為類名.方法名() |
| @AfterThrowing | 方法注解 | 通知方法定義的上方 | 設置當前通知方法與切入點之間的綁定關系,當前通知方法在原始切入點方法運行拋出異常后執行 |
@Around注解使用注意事項:
- 環繞通知必須依賴形參ProceedingJoinPoint才能實現對原始方法的調用,進而實現原始方法調用前后同時添加通知;
- 通知中如果未使用ProceedingJoinPoint對原始方法進行調用將跳過原始方法的執行;
- 對原始方法的調用可以不接收返回值,通知方法設置成void即可,如果接收返回值,必須設定為Object類型;
- 原始方法的返回值如果是void類型,通知方法的返回值類型可以設置成void,也可以設置成Object;
- 由于無法預知原始方法運行后是否會拋出異常,因此環繞通知方法必須拋出Throwable對象。
代碼演示:
@Configuration @ComponentScan("com.clp") @EnableAspectJAutoProxy // 告訴Spring,容器中有注解開發的AOP public class SpringConfig { }@Repository("bookDaoImpl") public class BookDaoImpl implements BookDao {@Overridepublic void update() {System.out.println("book dao update is running ...");}@Overridepublic int select() {System.out.println("book dao select is running ...");return 100;} }/*** 切面類*/ @Component // 該類受Spring管理 @Aspect // 說明該類作為AOP處理 public class MyAdvice {/*** 切點*/@Pointcut("execution(void com.clp.dao.BookDao.update())")private void pt() {}@Pointcut("execution(int com.clp.dao.BookDao.select())")private void pt2() {}/*** 切點 + 通知 = 切面*/@Before("pt()")public void before() {System.out.println("before advice ...");}@After("pt()")public void after() {System.out.println("after advice ...");}@After("pt2()")public void afterSelect() {System.out.println("after advice ...");}@Around("pt()")public void around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("around before advice ...");pjp.proceed(); // 表示對原始操作的調用System.out.println("around after advice ...");}@Around("pt2()")public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {System.out.println("around before advice ...");int res = (int) pjp.proceed();// 表示對原始操作的調用System.out.println("around after advice ...");return res + 111;}/*** 切點方法正常執行完才會執行該通知*/@AfterReturning("pt2()")public void afterReturning() {System.out.println("after returning advice ...");}/*** 切點方法拋出異常才會執行該通知*/@AfterThrowing("pt2()")public void afterThrowing() {System.out.println("afterThrowing advice ...");} }10.9.3、AOP通知獲取數據
獲取切入點方法的參數:
- JoinPoint:適用于前置、后置、返回后、拋出異常后通知;
- ProceedingJoinPoint:適用于環繞通知。
獲取切入點方法返回值:
- 返回后通知;
- 環繞通知。
獲取切入點方法運行異常信息:
- 拋出異常后通知;
- 環繞通知。
10.10、AOP案例
10.10.1、案例1——測量業務層接口萬次執行效率
需求:任意業務層接口執行均可顯示其執行效率(執行時長)。
分析:
- 業務功能:業務層接口執行前后分別記錄時間,求差值得到執行效率;
- 通知類型選擇前后均可以增強的類型——環繞通知。
補充說明:當前測試的接口執行效率僅僅是一個理論值,并不是一次完整的執行過程。
代碼演示:
public class JdbcConfig {/** 簡單類型的依賴注入使用@Value* */@Value("com.mysql.jdbc.Driver")private String driver;@Value("jdbc:mysql://localhost:3306/ssmdb")private String url;@Value("root")private String username;@Value("123456")private String password;// 1、定義一個方法獲得要管理的對象// 2、添加@Bean表示當前方法的返回值是一個bean/** 引用類型的依賴注入:主需要為bean定義方法設置形參即可,容器會根據 類型 自動裝配對象。* */@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;} }/*********************************************************************************/ public class MyBatisConfig {/*** SqlSessionFactoryBean專門產生SqlSessionFactory* @return*/@Beanpublic SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();ssfb.setTypeAliasesPackage("com.clp.domain"); // 設置實體類別名ssfb.setDataSource(dataSource); // 設置數據源return ssfb;}/*** 加載Dao層的映射信息* @return*/@Beanpublic MapperScannerConfigurer mapperScannerConfigurer() {MapperScannerConfigurer msc = new MapperScannerConfigurer();msc.setBasePackage("com.clp.dao");return msc;} }/*********************************************************************************/ @Configuration @Import({JdbcConfig.class, MyBatisConfig.class}) @ComponentScan("com.clp") @PropertySource("classpath:jdbc.properties") @EnableAspectJAutoProxy // 告訴Spring,容器中有注解開發的AOP public class SpringConfig { }/*********************************************************************************/ @Component @Aspect public class ProjectAdvice {/*** 匹配業務層的所有方法*/@Pointcut("execution(* com.clp.service.*Service.*(..))")private void servicePc() {}@Around("ProjectAdvice.servicePc()")public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {// 獲取執行的簽名信息Signature signature = pjp.getSignature();// 通過簽名獲取執行類型(接口名)String className = signature.getDeclaringTypeName();// 通過簽名獲取執行操作名稱(方法名)String name = signature.getName();// 記錄時間long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {pjp.proceed();}long end = System.currentTimeMillis();System.out.println("萬次執行:"+ className + "." + name + " ----> " + (end - start) + "ms");} }/*********************************************************************************/ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest {@Autowiredprivate AccountService accountService;@Testpublic void testFindById() {Account account = accountService.findById(3);}@Testpublic void testFindAll() {accountService.findAll();} }結果: 萬次執行:com.clp.service.AccountService.findAll---->1651ms10.10.2、案例2——百度網盤密碼數據兼容處理
分析:
11、Spring的事務控制
事務作用:在數據層保障一系列的數據庫操作同時成功同時失敗。
Spring事務作用:在數據層或業務層保障一系列的數據庫操作同時成功同時失敗。
11.1、事務相關定義
11.1.1、事務角色
- 事務管理員:發起事務方,在Spring中通常指代業務層開啟事務的方法;
- 事務協調員:加入事務方,在Spring中通常指代數據層方法,也可以是業務層方法。
11.1.2、事務的隔離級別
設置隔離級別,可以解決事務并發產生的問題,如臟讀、不可重復讀和虛讀。
- ISOLATION_DEFAULT
- ISOLATION_READ_UNCOMMITTED
- ISOLATION_READ_COMMITTED
- ISOLATION_REPEATABLE_READ
- ISOLATION_SERIALIZABLE
11.1.3、事務的傳播行為
事務傳播行為:事務協調員對事務管理員所攜帶事務的處理態度。
| REQUIRED(默認) | 開啟T | 加入T |
| 未開啟T | 新建T2 | |
| REQUIRES_NEW | 開啟T | 新建T2 |
| 未開啟T | 新建T2 | |
| SUPPORTS | 開啟T | 加入T |
| 未開啟T | 無 | |
| NOT_SUPPORTED | 開啟T | 無 |
| 未開啟T | 無 | |
| MANDATORY | 開啟T | 加入T |
| 未開啟T | ERROR | |
| NEVER | 開啟T | ERROR |
| 未開啟T | 無 | |
| NESTED | 設置savePoint,一旦事務回滾,事務將回滾到savePoint處,交由客戶端響應提交/回滾 | |
11.2、編程式事務控制相關對象
11.2.1、PlatformTransactionManager
PlatformTransactionManager接口是Spring的事務管理器,它里面提供了我們常用的操作事務的方法。
| TransactionStatus getTransaction(TransactionDefination defination) | 獲取事務的狀態信息 |
| void commit(TransactionStatus status) | 提交事務 |
| void rollback(TransactionStatus) | 回滾事務 |
注意:PlatformTransactionManager是接口類型,不同的Dao層技術則有不同的實現類,例如:Dao層技術是jdbc或mybatis時:org.springframework.jdbc.datasource.DataSourceTransactionManager;Dao層技術是hibernate時:org.springframework.orm.hibernate5.HibernateTransactionManager。
11.2.2、TransactionDefinition
TransactionDefinition是事務的定義信息對象,里面有如下方法:
| int getIsolationLevel() | 獲得事務的隔離級別 |
| int getPropogationBehavior() | 獲得事務的傳播行為 |
| int getTimeout() | 獲得超時時間 |
| boolean isReadOnly() | 是否只讀 |
11.2.3、TransactionStatus
TransactionStatus接口提供的是事務具體的運行狀態,方法介紹如下:
| boolean hasSavepoint() | 是否存儲回滾點 |
| boolean isCompleted() | 事務是否完成 |
| boolean isNewTransaction() | 是否是新事務 |
| boolean isRollbackOnly() | 事務是否回滾 |
11.3、基于XML的聲明式事務控制
11.3.1、什么是聲明式事務控制
Spring 的聲明式事務控制顧名思義就是采用聲明的方式來處理事務。這里所說的聲明,就是指在配置文件中聲明,用在Spring配置文件中聲明式地處理事務來代替代碼式的處理事務。
聲明式事務處理的作用:
- 事務管理不侵入開發的組件。具體來說,業務邏輯對象不會意識到正在事務管理之中,事實上也應該如此,因為事務管理是屬于系統層面的服務,而不是業務邏輯的一部分,如果要改變事務管理策劃的話,也只需要在定義文件中重新配置即可。
- 在不需要事務管理的時候,只要在設定文件上修改一下,即可移去事務管理服務,無需改變代碼重新編譯,這樣維護起來極其方便。
注意:Spring聲明式事務控制底層就是AOP。
11.3.2、聲明式事務控制的實現
聲明式事務控制明確事項:
- 誰是切點?
- 誰是通知?
- 配置切面?
其中,<tx:method>代表切點方法的事務參數的配置。
- name:切點方法的名稱。
- isolation:事務的隔離級別。
- propagation:事務的傳播行為。
- timeout:超時時間。
- read-only:是否只讀。
11.4、基于注解的聲明式事務控制
11.4.1、使用步驟
步驟:
11.4.2、注解配置聲明式事務控制解析
- 使用@Transactional在需要進行事務控制的類或者方法上修飾,注解可用的屬性同xml配置方式,例如:隔離級別、傳播行為等。
- 注解使用在類上,那么該類下的所有方法都使用同一套注解參數配置。
- 使用在方法上,不同的方法可以采用不同的事務參數配置。
- XML配置文件(applicationContext.xml)中要開啟事務的注解驅動<tx:annotation-driven />
注解聲明式事務控制的配置要點:
- 平臺事務管理器配置(xml方式/配置類方式);
- 事務通知的配置(@Transactional注解配置);
- 事務注解驅動的配置<tx:annotation-driven/>。
11.5、Spring事務相關配置
| readOnly | 設置是否為只讀事務 | readOnly=true(只讀事務) |
| timeout | 設置事務超時時間 | timeout=-1(永不超時) |
| rollbackFor | 設置事務回滾異常(class) | rollbackFor=如NullPointException.class |
| rollbackForClassName | 設置事務回滾異常(String) | 同上,格式為字符串 |
| noRollbackFor | 設置事務不回滾異常(class) | noRollbackFor=如NullPointException.class |
| noRollbackForClassName | 設置事務不回滾異常(String) | 同上,格式為字符串 |
| propagation | 設置事務傳播行為 | ... |
11.5、Spring事務案例
11.5.1、案例1——銀行賬戶轉賬
模擬銀行賬戶間轉賬業務。
需求:實現任意兩個賬戶間轉賬操作。
需求微縮:A賬戶減錢,B賬戶加錢。
分析:
- 數據層提供基礎操作,指定賬戶減錢(outMoney),指定賬戶加錢(inMoney);
- 業務層提供轉賬操作(transfer),調用減錢與加錢的操作;
- 提供2個賬號和操作金額執行轉賬操作;
- 基于Spring整合MyBatis環境搭建上述操作。
結果分析:
- 程序正常執行時,賬戶金額A減B加,沒有問題;
- 程序出現異常后,轉賬失敗,但是異常之前操作成功,異常之后操作失敗,整體業務失敗。
11.5.2、案例2——轉賬業務追加日志
需求:實現任意兩個賬戶間轉賬操作,并對每次轉賬操作在數據庫進行留痕。
需求微縮:A賬戶減錢,B賬戶加錢,數據庫記錄日志。
分析:
- 基于轉賬操作案例添加日志模塊,實現數據庫中記錄日志;
- 業務層轉賬操作(transfer),調用減錢、加錢與記錄日志功能。
實現效果預期:無論轉賬是否成功,均進行轉賬操作的日志留痕。
步驟:
總結
以上是生活随笔為你收集整理的JavaWeb开发框架——Spring的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenGL--天空盒
- 下一篇: JavaWeb项目框架