javascript
SSM框架-Spring(一)
目錄
1 Spring啟示錄
1.1 OCP開閉原則
1.2 依賴倒置原則DIP
1.3 控制反轉IoC
2 Spring概述
2.1 Spring簡介
2.2 Spring8大模塊
2.3 Spring特點
2.4 本次學習使用軟件版本
3 Spring入門程序
3.1 Spring下載
3.2 第一個Spring程序
3.3 第一個spring程序的細節
3.4?Spring6啟用Log4j2日志框架
4 spring對IoC的實現
4.1 set注入
4.2 構造注入
4.3 set注入專題
4.3.1 注入內部bean和外部bean
4.3.2 注入簡單類型
4.3.3 級聯屬性賦值
4.3.4 數組注入
4.3.5 集合注入
4.3.6 注入null和空字符串
4.3.7 特殊字符的注入
4.4 p命名空間注入
4.5 c命名空間注入
4.6 util命名空間
4.7 基于XML的自動裝配
4.7.1 根據名稱自動裝配
4.7.2 根據類型自動裝配
4.8?Spring引入外部屬性配置文件
5 Bean的作用域
5.1 singleton
5.2 prototype
5.3 其它scope
6 GoF工廠設計模式
6.1 工廠模式三種形態
6.2 簡單工廠模式
6.3 工廠方法模式
6.4 抽象工廠模式(了解)
7 Bean的實例化方式
7.1 通過構造方法實例化
7.2 通過簡單工廠模式實例化
7.3 通過factory-bean實例化
7.4 通過FactoryBean接口實例化
7.5 BeanFactory和FactoryBean的區別
7.5.1 BeanFactory
7.5.2 FactoryBean
7.6 注入自定義Date
8 Bean的聲明周期
8.1 什么是Bean的生命周期
8.2 為什么要知道Bean的生命周期
8.3 Bean的聲明周期之五步
8.4 Bean生命周期之7步
8.5 Bean生命周期之10步
8.6 Bean的作用域不同,管理方式不同
9 Bean的循環依賴問題
9.1 什么是Bean的循環依賴
9.2 singleton下的set注入產生的循環依賴
9.3 prototype下的set注入產生的循環依賴
9.4 singleton下的構造注入產生的循環依賴
10 回顧反射機制
10.1 分析方法四要素
10.2 使用反射機制調用方法
10.3 假設知道屬性名
1 Spring啟示錄
我們之前學過mvc設計模式,這種模式可以有效的降低代碼的耦合度,提高擴展力,我們再寫一個這樣的模式,代碼如下:
在創建maven項目前我們可以先設置如下
package com.itzw.spring6.dao.impl;import com.itzw.spring6.dao.UserDao;public class UserDaoImplForMySQL implements UserDao {public void select() {System.out.println("正在連接數據庫。。。");} } package com.itzw.spring6.service.impl;import com.itzw.spring6.dao.UserDao; import com.itzw.spring6.dao.impl.UserDaoImplForMySQL; import com.itzw.spring6.service.UserService;public class UserServiceImpl implements UserService {private UserDao userDao = new UserDaoImplForMySQL();public void login() {userDao.select();} } package com.itzw.spring6.servlet;import com.itzw.spring6.service.UserService; import com.itzw.spring6.service.impl.UserServiceImpl;public class UserServlet {private UserService userService = new UserServiceImpl();public void loginRequest(){userService.login();} }以上大概就是我們之前學的mvc架構模式,分為表示層、業務邏輯層、持久層。而其中UserServlet依賴了具體的UserServiceImpl,UserServiceImpl依賴了具體的UserDaoImplForMySQL。
假如我們不想連接mysql數據庫了,我們想連接Oracle數據庫,我們就要修改UserServiceImpl中的代碼。
1.1 OCP開閉原則
什么是OCP?
- OCP是軟件七大開發原則當中最基本的一個原則。對擴展開放,對修改關閉
- 其中OCP原則是最核心的最基本的,其它六個原則都是為了這個原則服務的
- OCP開閉原則的核心是:當我們在擴展系統功能的時候,沒有修改以前寫好的代碼那么就是符合OCP原則的,反之不符合這個原則
如上圖可以很明顯的看出上層是依賴下層的,下面改動上面必然改動,這樣同樣違背了另一個開發原則:依賴倒置原則
1.2 依賴倒置原則DIP
依賴倒置原則(Dependence Inversion Principe),倡導面向接口編程,面向抽象編程,不要面向具體編程,讓上層不再依賴下層,下層改動上層不需要改動,這樣大大降低耦合度,耦合度低了,擴展力就強了,同時代碼復用性也會增強(軟件七大開發原則都在為解耦合服務)
那我們可能有疑問,這不就是面向接口編程的嗎?確實,是這樣的,但是不完全是,我們雖然都是調用接口中的方法,但是我們是通過new 對象,new一個具體的接口實現類,如下:
如下才是完全面向接口,完全符合依賴倒置原則:
但是如果這樣編程userDao是null,那么就會出現空指針異常,確實是這樣。這也就是我們接下來要解決的問題。
1.3 控制反轉IoC
控制反轉(Inversion of Control),是面向對象編程的一種設計思想,可以用來降低代碼的耦合度,符合依賴倒置原則??刂品崔D的核心是:將對象的創建權交出去,將對象和對象之間的關系管理權交出去,由第三方容器負責創建與維護。
我們要學的Spring框架實現了控制反轉這種思想,Spring框架可以幫我們new 對象,還可以幫我們維護對象與對象之間的關系
控制反轉常用的實現方法:依賴注入(Dependency Injection,簡稱DI)
依賴注入DI,包括兩種常見的 方式:
- 第一種:set方法注入(執行set方法給屬性賦值)
- 第二種:構造方法注入
IoC是一種全新的設計模式,但是理論和時間成熟較晚,并沒有包含在GoF中(GoF是23中設計模式)
2 Spring概述
2.1 Spring簡介
來自百度百科:
- Spring是一個開源框架,它由Rod Johnson創建。它是為了解決企業應用開發的復雜性而創建的。
- 從簡單性、可測試性和松耦合的角度而言,任何Java應用都可以從Spring中受益。
- Spring是一個輕量級的控制反轉(IoC)和面向切面(AOP)的容器框架。
- Spring最初的出現是為了解決EJB臃腫的設計,以及難以測試等問題。
- Spring為簡化開發而生,讓程序員只需關注核心業務的實現,盡可能的不再關注非業務邏輯代碼(事務控制,安全日志等)。
2.2 Spring8大模塊
注意:Spring5版本之后是8個模塊。在Spring5中新增了WebFlux模塊。
Spring Core模塊:這是Spring框架最基礎的部分,它提供了依賴注入特征來實現容器對Bean的管理。
2.3 Spring特點
- 輕量
- 控制反轉
- 面向切面
- 容器
- 框架
2.4 本次學習使用軟件版本
- IDEA:2021.2.3
- JDK:java17(Spring6要求JDK最低版本是java17)
- Maven:3.3.9
- Spring:6.0.0-M2
- Junit:4.13.2
3 Spring入門程序
3.1 Spring下載
官網地址:Spring | Home
官網地址(中文):Spring 中文網 官網
以上兩個地址都可以下載,不過我們可以直接使用maven下載依賴,就像mybatis一樣
3.2 第一個Spring程序
前期準備:在idea中創建一個maven模塊,這個我們早已設置好,直接用即可
第一步:添加spring context依賴
<repositories><!--spring里程碑版本的倉庫--><repository><id>repository.spring.milestone</id><name>Spring Milestone Repository</name><url>https://repo.spring.io/milestone</url></repository></repositories><dependencies><!--spring context依賴--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.0-M2</version></dependency></dependencies>注意:打包方式為jar
當加入spring context依賴之后,會關聯引入其它依賴
- spring aop:面向切面編程
- spring beans:IoC核心
- spring core:spring核心工具包
- spring jcl:spring的日志包
- spring expression:spring表達式
第二步:添加junit依賴
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency>第三步:定義bean:User
package com.itzw.spring6.bean;/*** 封裝用戶信息*/ public class User { }第四步:編寫spring配置文件spring.xml,放在類的根目錄下也就是resources目錄
我們直接右擊resources就可以創建idea提供的文件模板:
在配置文件中進行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="userBean" class="com.itzw.spring6.bean.User"></bean></beans>需要注意的是:這個文件最好放在類路徑下,方便移植
bean標簽的兩個重要屬性:id:這是bean的身份證,不能重復,是唯一標識;class:必須填寫類的全路徑,全限定類名。
第五步:編寫測試程序
package com.itzw.spring6.test;import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class Spring6Test {@Testpublic void testFirst(){//第一步:獲取spring容器對象//ApplicationContext是一個接口,接口下有很多實現類,其中有一個叫做:ClassPathXmlApplicationContext//ClassPathXmlApplicationContext 專門從類路徑下加載spring配置文件的一個spring上下文對象//運行這行代碼就相當于啟動了spring容器,解析spring.xml文件,并且實例化所有的bean對象,放到spring容器當中ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");//第二步:根據bean的id從spring容器中獲取這個對象Object userBean = applicationContext.getBean("userBean");System.out.println(userBean);}}測試結果:
3.3 第一個spring程序的細節
(1)bean的id不能重復:
<bean id="userBean" class="com.itzw.spring6.bean.User"></bean><bean id="userBean" class="com.itzw.spring6.bean.Vip"></bean>(2)底層是怎樣創建對象的:
我們在User類中寫上無參構造:
public class User {public User(){System.out.println("這是User類的無參構造");} }測試:
如果只有有參構造沒有無參呢?
public class User { /* public User(){System.out.println("這是User類的無參構造");}*/public User(String name){System.out.println("這是User類的有參構造");} }經過測試:spring是通過調用類的無參構造來創建對象的,所以要想讓spring給你創建對象,必須保證無參構造方法是存在的
spring是通過反射機制調用無參構造方法創建對象
(3)創建好的對象存儲在map集合中
(4)Spring配置文件的名字可以隨意改
(5)spring配置文件可以創建多個
我們再創建一個spring配置文件,配置bean的信息
我們直接在ClassPathXmlApplicationContext構造方法參數上傳遞路徑即可,不需要再new一個ClassPathXmlApplicationContext對象,為什么呢?通過源碼查看是可以傳多個的?
(6)配置文件中的類必須是自定義的嗎
不是,可以是jdk自帶的類,如下:
<bean id="dateBean" class="java.util.Date"></bean> Object dateBean = applicationContext.getBean("dateBean");System.out.println(dateBean);可以直接輸出當前日期
經測試,spring配置文件中的bean可以是任意類,只要它不是抽象的并且有無參構造
(7)執行getBean方法時傳入的參數不存在會報異常
(8)getBean方法返回類型問題
默認返回類型是Object,當然我們可以強轉,但是有沒有別的辦法,
User userBean1 = applicationContext.getBean("userBean", User.class);可以通過第二個參數返回bean的類型
3.4?Spring6啟用Log4j2日志框架
從spring5之后,Spring框架支持集成的日志框架是Log4j2.如何啟用日志框架:
第一步:引入log4j2的依賴:
<!--log4j2的依賴--> <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version> </dependency> <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.19.0</version> </dependency>第二步:在類的根路徑下提供log4j2.xml配置文件(文件名固定為:log4j2.xml,文件必須放到類根路徑下。)
<?xml version="1.0" encoding="UTF-8"?><configuration><loggers><!--level指定日志級別,從低到高的優先級:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF--><root level="DEBUG"><appender-ref ref="spring6log"/></root></loggers><appenders><!--輸出日志信息到控制臺--><console name="spring6log" target="SYSTEM_OUT"><!--控制日志輸出的格式--><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/></console></appenders></configuration>這樣我們的輸出信息就有日志信息了:?
但是我們自己怎么使用日志信息呢?
@Testpublic void testLog(){//第一步:創建日志記錄對象//獲取Spring6Test類的日志記錄對象,也就是說只要是Spring6Test類中的代碼記錄日志的話,就輸出日志信息Logger logger = LoggerFactory.getLogger(Spring6Test.class);//第二步:記錄日志,根據不同級別來輸出日志logger.info("我是一條信息");logger.debug("我是一個調試信息");logger.error("我是一條錯誤信息");}4 spring對IoC的實現
前面我們講過我們可以使用依賴注入實現控制反轉。控制反轉的思想是將對象的創建權交出去,交給第三方容器。
實現依賴注入主要有兩個方式:set注入和構造注入
4.1 set注入
我們創建一個新的模塊,我們先像之前那樣創建一個dao文件和一個service文件,不過這次在service目錄下我們不new新的dao對象,如下:
package com.itzw.spring6.dao;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class UserDao {private static final Logger logger = LoggerFactory.getLogger(UserDao.class);public void insert(){logger.info("正在插入信息。。");} } package com.itzw.spring6.service;import com.itzw.spring6.dao.UserDao;public class UserService {UserDao userDao;public void saveUser(){userDao.insert();} } <?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="userDaoBean" class="com.itzw.spring6.dao.UserDao"></bean><bean id="userServiceBean" class="com.itzw.spring6.service.UserService"></bean> </beans> public class SpringTest {@Testpublic void testInjectBySet(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");UserService userServiceBean = applicationContext.getBean("userServiceBean", UserService.class);userServiceBean.saveUser();} }但是這樣測試結果顯然是出錯的,dao對象是null的
我們使用set注入的方式來實現控制反轉:
我們需要提供一個set方法,spring會調用這個set方法給userDao屬性賦值,我們直接使用idea工具生成這個set方法即可
我們現在需要service調用set方法,這就要借助spring,我們在bean標簽配置property標簽,其中name屬性就是用來傳set方法的,name屬性名指定格式為set方法的方法名去掉set,然后剩下的單詞首字母小寫。這就實現了UserService類調用set方法,但是我們還需要傳值,傳一個UserDao對象的值,因為上面我們配置類userDao的bean標簽,我們直接把它的id值傳給property中的ref屬性即可這就完成了傳值。如下:
配置好后再次測試,測試成功。
4.2 構造注入
我們多建一個dao文件,在service文件中直接用idea自動生成構造方法
package com.itzw.spring6.service;import com.itzw.spring6.dao.UserDao; import com.itzw.spring6.dao.VipDao;public class AccountService {UserDao userDao;VipDao vipDao;public AccountService(UserDao userDao, VipDao vipDao) {this.userDao = userDao;this.vipDao = vipDao;}public void test(){userDao.insert();vipDao.delete();} }在spring配置文件中配置如下,和set方法差不多
<bean id="userDaoBean" class="com.itzw.spring6.dao.UserDao"></bean><bean id="vipDaoBean" class="com.itzw.spring6.dao.VipDao"></bean><!--構造注入--><bean id="accountServiceBean" class="com.itzw.spring6.service.AccountService"><!--index屬性指定參數下標,第一個參數是0,第二個參數是1,第三個參數是3,依次。。ref指定注入的bean的id--><!--<constructor-arg index="0" ref="userDaoBean"/><constructor-arg index="1" ref="vipDaoBean"/>--><!--我們還有別的方法進行構造注入--><constructor-arg name="userDao" ref="userDaoBean"/><constructor-arg name="vipDao" ref="vipDaoBean"/></bean>測試即可。
4.3 set注入專題
以為set注入使用的較多,我們使用set注入來學習下面的內容
4.3.1 注入內部bean和外部bean
我們之前使用的set注入就是注入外部bean,那什么是注入內部bean呢?
在property中嵌套bean標簽就是內部bean。這樣麻煩了一點,我們一般不用這種方式,我們還是用之前的方式?
<bean id="userDaoBean" class="com.itzw.spring6.dao.UserDao"/><bean id="orderDaoBean" class="com.itzw.spring6.service.OrderDao"><property name="userDao"><bean class="com.itzw.spring6.dao.UserDao"/></property></bean>4.3.2 注入簡單類型
我們之前注入的數據都不是簡單類型,對象屬性都是一個對象,我們現在注入屬性是一個數據的。
我們寫一個類,寫上屬性,創建set方法和toString方法:
package com.itzw.spring6.beans;public class User {private String name;private int age;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';} }編寫spring配置文件:
<!--簡單類型注入--><bean id="userBean" class="com.itzw.spring6.beans.User"><property name="age" value="24"/><property name="name" value="張麻子"/></bean>在這里name屬性依然是set方法,但是我們給set方法參數傳值就只要傳簡單的數據就可以了,所以我們使用value來賦值。
測試:結果如下:
那么簡單類型包括哪些呢?
public static boolean isSimpleValueType(Class<?> type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||CharSequence.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) ||Date.class.isAssignableFrom(type) ||Temporal.class.isAssignableFrom(type) ||URI.class == type ||URL.class == type ||Locale.class == type ||Class.class == type));}我們查看源碼分析:BeanUtils類,得知簡單類型有:
- 基本數據類型
- 基本數據類型對應包裝類
- String或其他的CharSequence子類
- Number子類
- Date子類
- Enum子類
- URI
- URL
- Temporal子類
- Locale
- Class
- 另外還包括以上簡單值類型對應的數據類型
簡單類型注入的經典應用:
給數據源的屬性賦值,比如我們經常使用的數據庫的連接:
private String driver;private String url;private String username;private String password;@Overridepublic String toString() {return "MyDataSource{" +"driver='" + driver + '\'' +", url='" + url + '\'' +", username='" + username + '\'' +", password='" + password + '\'' +'}';}public void setDriver(String driver) {this.driver = driver;}public void setUrl(String url) {this.url = url;}public void setUsername(String username) {this.username = username;}public void setPassword(String password) {this.password = password;} <!--簡單類型注入的應用--><bean id="dataSourceBean" class="com.itzw.spring6.jdbc.MyDataSource"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://172.0.0.1:3306/spring"/><property name="username" value="root"/><property name="password" value="123456"/></bean> @Testpublic void testSimple2(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");Object dataSourceBean = applicationContext.getBean("dataSourceBean");System.out.println(dataSourceBean);}你們可以把簡單類型都測試一遍,那我不測了,我不打擾我走了哈哈。
4.3.3 級聯屬性賦值
也就是我們熟悉的套娃賦值,就是一個類的屬性有另一個類。我們先用我們學過的方式賦值:
package com.itzw.spring6.dao;public class Clazz {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Clazz{" +"name='" + name + '\'' +'}';} } package com.itzw.spring6.dao;public class Student {private String name;private int age;private Clazz clazz;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", clazz=" + clazz +'}';}public void setClazz(Clazz clazz) {this.clazz = clazz;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;} } <!--級聯屬性賦值--><bean class="com.itzw.spring6.dao.Student" id="studentBean"><property name="name" value="張麻子"/><property name="age" value="34"/><property name="clazz" ref="clazzBean"/></bean><bean class="com.itzw.spring6.dao.Clazz" id="clazzBean"><property name="name" value="高三一班"/></bean> @Testpublic void testCascade(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");Student studentBean = applicationContext.getBean("studentBean", Student.class);System.out.println(studentBean);}以上是我們學過的方式,測試結果:
我們使用級聯屬性賦值:
<!--級聯屬性賦值--><bean class="com.itzw.spring6.dao.Student" id="studentBean"><property name="name" value="張麻子"/><property name="age" value="34"/><property name="clazz" ref="clazzBean"/><!--級聯屬性賦值--><property name="clazz.name" value="高三二班"/></bean><bean class="com.itzw.spring6.dao.Clazz" id="clazzBean"></bean>使用這種方式我們需要給clazz屬性構造get方法,這種方式顯得很麻煩,還不如之前的方法。
4.3.4 數組注入
首先簡單類型的數組注入:
package com.itzw.spring6.dao;import java.util.Arrays;public class Huang {private String[] hobbies;public void setHobbies(String[] hobbies) {this.hobbies = hobbies;}@Overridepublic String toString() {return "Huang{" +"hobbies=" + Arrays.toString(hobbies) +'}';} } <!--數組注入--><bean id="huang" class="com.itzw.spring6.dao.Huang"><property name="hobbies"><array><value>抽煙</value><value>喝酒</value><value>燙頭</value></array></property></bean> @Testpublic void testArray(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");Huang huang = applicationContext.getBean("huang", Huang.class);System.out.println(huang);}如果數組元素是非簡單類型呢?
<bean class="com.itzw.spring6.dao.Woman" id="w1"><property name="name" value="小花"/></bean><bean class="com.itzw.spring6.dao.Woman" id="w2"><property name="name" value="小美"/></bean><bean class="com.itzw.spring6.dao.Woman" id="w3"><property name="name" value="小麗"/></bean><!--數組注入--><bean id="huang" class="com.itzw.spring6.dao.Huang"><property name="hobbies"><array><value>抽煙</value><value>喝酒</value><value>燙頭</value></array></property><property name="womens"><array><ref bean="w1"/><ref bean="w2"/><ref bean="w3"/></array></property></bean>4.3.5 集合注入
注意的是:list集合有序和重復,set集合無序不重復
package com.itzw.spring6.dao;import java.util.List; import java.util.Set;public class Person {List names;Set addrs;public void setNames(List names) {this.names = names;}public void setAddrs(Set addrs) {this.addrs = addrs;}@Overridepublic String toString() {return "Person{" +"names=" + names +", addrs=" + addrs +'}';} } <!--集合注入set和list--><bean class="com.itzw.spring6.dao.Person" id="person"><property name="names"><list><value>張三</value><value>李四</value><value>張麻子</value><value>黃四郎</value><value>張三</value></list></property><property name="addrs"><set><value>徐州市銅山區</value><value>徐州市云龍區</value><value>徐州市銅山區</value><value>徐州市銅山區</value></set></property></bean> @Testpublic void testListAndSet(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");Person person = applicationContext.getBean("person", Person.class);System.out.println(person);}map集合,有鍵值對:
<property name="tel"><map><!--一個entry就表示一個鍵值對--><entry key="tel1" value="110"/><entry key="tel2" value="119"/><entry key="tel3" value="120"/></map></property>properties集合本質上也是map集合,但是它的注入方式不一樣
<property name="properties"><props><prop key="username">root</prop><prop key="password">1234</prop></props></property>4.3.6 注入null和空字符串
<!--注入null和空字符串--><bean class="com.itzw.spring6.dao.Man" id="man"><!--<property name="name" value="張麻子"></property>--><property name="name"><!--手動賦值為null--><null></null></property><!--賦值為空字符串--><!--第一種方式--><!--<property name="addr" value=""/>--><!--第二種方式--><property name="addr"><value/></property></bean>4.3.7 特殊字符的注入
XML中有5個特殊字符,分別是:<、>、'、"、&。
這些字符直接出現在xml當中會報錯:
解決方式有兩種:
- 第一種:使用轉義字符代替
- 第二種:將含有特殊符號的字符串放到:<![CDATA[]]> 當中。因為放在CDATA區中的數據不會被XML文件解析器解析。
特殊字符對應的轉移字符如下:
| 特殊字符 | 轉義字符 |
| > | > |
| < | < |
| ' | ' |
| " | " |
| & | & |
4.4 p命名空間注入
使用p命名空間注入可以簡化配置,使用前提是:
- 要在xml配置文件上方添加配置信息:xmlns:p="http://www.springframework.org/schema/p"
- p命名空間注入是基于set方法的,要提供set方法
其實p命名空間注入就是代替set注入的
<bean class="com.itzw.spring6.dao.Dog" id="dog" p:name="小花" p:age="2"/>4.5 c命名空間注入
c命名空間注入是用來簡化構造注入的,那么使用前提是:
- 需要在xml配置文件頭部添加信息:xmlns:c="http://www.springframework.org/schema/c"
- 需要提供構造方法。?
注意:不管是p命名注入還是c命名注入都可以注入非簡單類型
4.6 util命名空間
使用util命名空間可以讓配置復用,使用前提是:在spring配置文件頭部添加如下信息:
比如我想給多個java文件都傳輸jdbc連接的信息,它們的信息都是一樣的,這時我們就可以將這段信息使用util命名空間方式
<?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:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><util:properties id="prop"><prop key="driver">com.mysql.cj.jdbc.Driver</prop><prop key="url">jdbc:mysql://localhost:3306/spring</prop><prop key="username">root</prop><prop key="password">123</prop></util:properties><bean class="com.itzw.spring6.jdbc.MyDataSource2" id="ds2"><property name="properties" ref="prop"/></bean><bean class="com.itzw.spring6.jdbc.MyDataSource3" id="ds3"><property name="properties" ref="prop"/></bean> </beans>4.7 基于XML的自動裝配
Spring還可以完成自動化的注入,自動化注入又被稱為自動裝配。它可以根據名字進行自動裝配,也可以根據類型進行自動裝配。
4.7.1 根據名稱自動裝配
回憶之前的業務邏輯層和持久層之間的連接,其中spring配置信息如下:
<bean class="com.itzw.spring6.dao.UserDao" id="userDaoBean"></bean><bean class="com.itzw.spring6.service.OrderDao" id="orderDao"><property name="userDao" ref="userDaoBean"/></bean>我們使用自動裝配:
<bean class="com.itzw.spring6.dao.UserDao" id="userDao"/><bean class="com.itzw.spring6.service.OrderDao" id="orderDao" autowire="byName"/>在orderDao的bean中添加autowire,值設為byName表示通過名稱進行自動裝配
OrderDao類中有一個UserDao屬性,set方法為setUserDao,而UserDao的bean的id為userDao,恰好和OrderDao中的set方法對應。滿足這些才能自動裝配
也就是說需要set方法名和想要注入的類的bean的id值對應上才行
4.7.2 根據類型自動裝配
<bean class="com.itzw.spring6.dao.UserDao"/><bean class="com.itzw.spring6.service.OrderDao" id="orderDao" autowire="byType"/>這樣連id值都不需要傳了,直接就能識別自己想要的類。但是也有缺陷,不能出現多個同一個類的bean,這樣它就識別不出哪個是自己需要的了。
值得注意的是:不管是根據name自動裝配還是類型自動裝配都是基于set注入實現的,也就是都需要有set方法,否則不行。
從這自動裝配我們可以看出來,尤其是根據類型自動裝配可讀性 很差而且有限制,不如我們用原始方法,沒有方便多少反而看的 蛋疼。
4.8?Spring引入外部屬性配置文件
我們連接數據庫的時候需要配置一些信息,我們能像之前學習一樣把這些配置信息放在一個文件中然后引入到xml文件中嗎?當然可以。
第一步:寫一個數據源類 提供相關屬性:
package com.itzw.spring6.jdbc;import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger;public class MyDataSource implements DataSource {private String driver;private String url;private String username;private String password;@Overridepublic String toString() {return "MyDataSource{" +"driver='" + driver + '\'' +", url='" + url + '\'' +", username='" + username + '\'' +", password='" + password + '\'' +'}';}public void setDriver(String driver) {this.driver = driver;}public void setUrl(String url) {this.url = url;}public void setUsername(String username) {this.username = username;}public void setPassword(String password) {this.password = password;}@Overridepublic Connection getConnection() throws SQLException {return null;}//...}第二步:在類路徑下建立jdbc.properties文件并配置信息:
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/spring username=root password=123第三步:在spring配置文件中引入context命名空間
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"></beans>第四步:在spring配置文件中使用:
使用context標簽引入jdbc配置文件
<context:property-placeholder location="jdbc.properties"/><bean class="com.itzw.spring6.jdbc.MyDataSource" id="ds"><property name="driver" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></bean>但需要注意的是,${}中的值會默認先去系統找對應的值,比如username會去系統找,極可能輸出的結果是系統也就是Windows的usernam。所以我們在配置名稱的時候最好前面加上jdbc.
5 Bean的作用域
5.1 singleton
默認情況下,Spring的IoC容器創建的Bean對象是單例的。
@Testpublic void testBean(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean.xml");Customer customer1 = applicationContext.getBean("customer", Customer.class);System.out.println(customer1);Customer customer2 = applicationContext.getBean("customer", Customer.class);System.out.println(customer2);Customer customer3 = applicationContext.getBean("customer", Customer.class);System.out.println(customer3);}如上我們調用三次getBean,返回的是同一個對象
那么這個對象在什么時候創建的呢,我們寫上無參構造,把getBean方法都刪除,執行程序,發現無參構造執行了,我們得知默認情況下,Bean對象的創建是在初始化Spring上下文的時候就完成的。
5.2 prototype
如果想讓spring的bean對象以多例的形式存在,可以在bean標簽中指定scope屬性的值為:prototype,這樣spring會在每一次執行getBean的時候都創建bean對象,調用幾次就創建幾次
我們再執行上段代碼:?
這時如果不調用getBean方法,那么無參構造就不會只執行
5.3 其它scope
scope屬性的值不止兩個,它一共包括8個選項:
- singleton:默認的,單例。
- prototype:原型。每調用一次getBean()方法則獲取一個新的Bean對象?;蛎看巫⑷氲臅r候都是新對象。
- request:一個請求對應一個Bean。僅限于在WEB應用中使用。
- session:一個會話對應一個Bean。僅限于在WEB應用中使用。
- global session:portlet應用中專用的。如果在Servlet的WEB應用中使用global session的話,和session一個效果。(portlet和servlet都是規范。servlet運行在servlet容器中,例如Tomcat。portlet運行在portlet容器中。)
- application:一個應用對應一個Bean。僅限于在WEB應用中使用。
- websocket:一個websocket生命周期對應一個Bean。僅限于在WEB應用中使用。
- 自定義scope:很少使用。
我們可以自己定義,但是沒必要,再見。
6 GoF工廠設計模式
設計模式:一種可以被重復利用的解決方案。GoF(Gang of Four),中文名——四人組。
GoF包括了23種設計模式。我們平常所說的設計模式就是指這23種設計模式。
不過除了GoF23種設計模式之外,還有其它的設計模式,比如:JavaEE的設計模式(DAO模式、MVC模式等)。
GoF23種設計模式可分為三大類:
創建型(5個):解決對象創建問題。
- 單例模式
- 原型模式
- 建造者模式
- 抽象工廠模式
- 工廠方法模式
結構型(7個):一些類或對象組合在一起的經典結構。
- 代理模式
- 橋接模式
- 外觀模式
- 享元模式
- 組合模式
- 適配器模式
- 裝飾模式
行為型(11個):解決類或對象之間的交互問題。
- 策略模式
- 解釋器模式
- 中介者模式
- 訪問者模式
- 狀態模式
- 備忘錄模式
- 命令模式
- 迭代子模式
- 觀察者模式
- 責任鏈模式
- 模板方法模式
工廠模式是解決對象創建問題的,所以工廠模式屬于創建型設計模式。這里為什么學習工廠模式呢?這是因為Spring框架底層使用了大量的工廠模式
6.1 工廠模式三種形態
工廠模式通常有三種形態:
- 第一種:簡單工廠模式(Simple Factory):不屬于23種設計模式之一。簡單工廠模式又叫做:靜態 工廠方法模式。簡單工廠模式是工廠方法模式的一種特殊實現。
- 第二種:工廠方法模式(Factory Method):是23種設計模式之一。
- 第三種:抽象工廠模式(Abstract Factory):是23種設計模式之一。
6.2 簡單工廠模式
簡單工廠模式的角色包括三個:
- 抽象產品 角色
- 具體產品 角色
- 工廠類 角色
簡單工廠模式的代碼如下:
抽象產品角色:
package com.itzw.factory;public abstract class Weapon {public abstract void attack(); }具體產品角色:
package com.itzw.factory;public class Gun extends Weapon{@Overridepublic void attack() {System.out.println("機槍正在發射...");} } package com.itzw.factory;public class Plane extends Weapon{@Overridepublic void attack() {System.out.println("飛機正在扔小男孩...");} } package com.itzw.factory;public class Tank extends Weapon{@Overridepublic void attack() {System.out.println("坦克正在開炮...");} }工廠類角色:
package com.itzw.factory;public class WeaponFactory {public static Weapon get(String WeaponType){if ("GUN".equals(WeaponType)){return new Gun();}else if ("PLANE".equals(WeaponType)){return new Plane();}else if ("TANK".equals(WeaponType)){return new Tank();}else {throw new RuntimeException("不支持該武器");}} }測試:
package com.itzw.factory;public class Test {public static void main(String[] args) {Weapon tank = WeaponFactory.get("TANK");tank.attack();Weapon plane = WeaponFactory.get("PLANE");plane.attack();Weapon gun = WeaponFactory.get("GUN");gun.attack();} }這種模式就是簡單工廠模式,它的優點:客戶端程序,也就是我們這里的 測試程序不需要關系對象的創建細節,需要哪個對象只需要向工廠索要,初步實現了責任的分離??蛻舳酥回撠熛M,工廠只負責生產。但 它也有缺點:工廠類中集中了所有產品的創造邏輯,一旦出問題整個系統會癱瘓;還有就是比較明顯的,不符合OCP開閉原則,我們想擴展系統時也就是比如需要擴展一個新的武器需要修改工廠類。
6.3 工廠方法模式
工廠方法模式的角色包括:
- 抽象工廠角色
- 具體工廠角色
- 抽象產品角色
- 具體產品角色
抽象產品角色和具體產品角色和上面的簡單工廠模式一樣:
package com.itzw.factory2;public abstract class Weapon {public abstract void attack(); } package com.itzw.factory2;public class Gun extends Weapon {@Overridepublic void attack() {System.out.println("機槍正在發射...");} } package com.itzw.factory2;public class Plane extends Weapon {@Overridepublic void attack() {System.out.println("飛機正在扔小男孩...");} }抽象工廠角色:創建一個方法能讓它返回一個抽象產品角色
package com.itzw.factory2;public interface WeaponFactory {Weapon get(); }具體工廠角色:
package com.itzw.factory2;public class GunFactory implements WeaponFactory{@Overridepublic Weapon get() {return new Gun();} } package com.itzw.factory2;public class PlaneFactory implements WeaponFactory{@Overridepublic Weapon get() {return new Plane();} }測試:
package com.itzw.factory2;public class Test {public static void main(String[] args) {WeaponFactory gun = new GunFactory();gun.get().attack();WeaponFactory plane = new PlaneFactory();plane.get().attack();} }這時我們再想加入新的武器就不需要修改代碼了,直接創建一個具體類繼承抽象類然后創建具體工廠角色繼承抽象工廠即可。
顯然這種模式符合OCP開閉原則,但是每次新增一個產品就需要創建兩個類,在一定程度上增加了系統的復雜度
6.4 抽象工廠模式(了解)
抽象工廠模式可以看做上面兩種模式的結合。
7 Bean的實例化方式
或者可以說是bean的獲取方式
Spring為Bean提供了多種實例化方式,通常包括4種方式。(也就是說在Spring中為Bean對象的創建準備了多種方案,目的是:更加靈活)
- 第一種:通過構造方法實例化
- 第二種:通過簡單工廠模式實例化
- 第三種:通過factory-bean實例化
- 第四種:通過FactoryBean接口實例化
7.1 通過構造方法實例化
之前我們學習的就是通過構造方法實例化:
package com.itzw.constructor;public class User {public User(){System.out.println("User的無參構造執行了");} }spring配置文件:
<bean id="user" class="com.itzw.constructor.User"/>測試:
@Testpublic void testConstructor(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");Object user = applicationContext.getBean("user", User.class);System.out.println(user);}7.2 通過簡單工廠模式實例化
bean:
package com.itzw.bean;public class Vip {public Vip(){System.out.println("vip的無參構造執行了");} }編寫簡單工廠模式中的工廠類:這里的方法是靜態方法
package com.itzw.bean;public class VipFactory {public static Vip get(){return new Vip();} }spring.xml:
我們需要在這指定調用哪個類的哪個方法獲取Bean。factory-method屬性指定的是工廠類當中的靜態方法,也就是告訴spring框架調用這個方法可以獲取bean
<!--通過簡單工廠模式實例化--><bean class="com.itzw.bean.VipFactory" id="vip" factory-method="get"/>7.3 通過factory-bean實例化
這種方式本質上是:通過工廠方法模式進行實例化。
和上個方法的區別就是spring配置文件的配置不同,把一個bean標簽分解成兩個,并且具體工廠類的方法是實例方法如下:
package com.itzw.bean;public class Order {public Order(){System.out.println("Order的無參構造執行了");} }注意這里的方法是實例方法
package com.itzw.bean;public class OrderFactory {public Order get(){return new Order();} }spring:factory-bean告訴框架調用哪個對象,factory-method告訴框架調用哪個方法。
<!--通過工廠方法模式--><bean class="com.itzw.bean.OrderFactory" id="orderFactory"/><bean factory-bean="orderFactory" factory-method="get" id="order" />7.4 通過FactoryBean接口實例化
以上三種方法,需要我們自定義factory-bean或者factory-method。在我們編寫類實現FactoryBean接口后這倆屬性就不需要指定了,factory-bean會自動指向實現FactoryBean接口的類,factory-method會自動指向getObject方法
package com.itzw.bean;public class Animal {public Animal(){System.out.println("Animal構造方法執行了");} }具體工廠類實現FactoryBean接口
package com.itzw.bean;import org.springframework.beans.factory.FactoryBean;public class AnimalFactory implements FactoryBean<Animal> {@Overridepublic Animal getObject() throws Exception {return new Animal();}@Overridepublic Class<?> getObjectType() {return null;}@Overridepublic boolean isSingleton() {return FactoryBean.super.isSingleton();} } <!--通過FactoryBean接口--><bean class="com.itzw.bean.Animal" id="animal"/>7.5 BeanFactory和FactoryBean的區別
7.5.1 BeanFactory
Spring IoC容器的頂級對象,BeanFactory被翻譯為“Bean工廠”,在Spring的IoC容器中,“Bean工廠”負責創建Bean對象。
BeanFactory是工廠。
7.5.2 FactoryBean
FactoryBean:它是一個Bean,是一個能夠輔助Spring實例化其它Bean對象的一個Bean。
在Spring中,Bean可以分為兩類:
- 第一類:普通Bean
- 第二類:工廠Bean(記住:工廠Bean也是一種Bean,只不過這種Bean比較特殊,它可以輔助Spring實例化其它Bean對象。)
7.6 注入自定義Date
我們在前面就講到過Date類型是簡單類型可以直接使用value屬性賦值,但是對格式要求非常嚴格,只能是這樣類型的:Mon Oct 10 14:30:26 CST 2022,而這種類型不符合我們常見的格式。
package com.itzw.bean;import java.util.Date;public class Student {private Date date;public void setDate(Date date) {this.date = date;}@Overridepublic String toString() {return "Student{" +"date=" + date +'}';} } <!--Date類型的注入--><bean class="com.itzw.bean.Student" id="student"><property name="date" value="Mon Oct 10 14:30:26 CST 2022"/></bean>以上就是我們對Date類型當做簡單類型的注入。
我們可以把Date當做非簡單類型注入:
還是那個Student類,我們創建Date工廠實現FactoryBean,并且定義屬性和構造方法接收spring配置文件傳來的日期,我們使用SimpleDateFormat定義日期格式接收傳來的日期(比如2020-8-8),然后會返回一個日期
package com.itzw.bean;import org.springframework.beans.SimpleTypeConverter; import org.springframework.beans.factory.FactoryBean;import java.text.SimpleDateFormat; import java.util.Date;public class DateFactoryBean implements FactoryBean<Date> {//定義屬性接收日期private String date;//通過構造方法給日期賦值public DateFactoryBean(String date) {this.date = date;}@Overridepublic Date getObject() throws Exception {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date date = sdf.parse(this.date);return date;}@Overridepublic Class<?> getObjectType() {return null;} }傳給date工廠一個日期,會返回一個Date默認格式的日期,然后我們再通過Student的Bean將這個日期傳給Student。
<!--Date類型的注入(非簡單類型)--><bean id="dateFactoryBean" class="com.itzw.bean.DateFactoryBean"><constructor-arg name="date" value="2000-9-25"/></bean><bean class="com.itzw.bean.Student" id="student2"><property name="date" ref="dateFactoryBean"/></bean>8 Bean的聲明周期
8.1 什么是Bean的生命周期
- Spring其實就是一個管理Bean對象的工廠。它負責對象的創建,對象的銷毀等。
- 所謂的生命周期就是:對象從創建開始到最終銷毀的整個過程。
- 什么時候創建Bean對象?
- 創建Bean對象的前后會調用什么方法?
- Bean對象什么時候銷毀?
- Bean對象的銷毀前后調用什么方法?
8.2 為什么要知道Bean的生命周期
- 其實生命周期的本質是:在哪個時間節點上調用了哪個類的哪個方法。
- 我們需要充分的了解在這個生命線上,都有哪些特殊的時間節點。
- 只有我們知道了特殊的時間節點都在哪,到時我們才可以確定代碼寫到哪。
- 我們可能需要在某個特殊的時間點上執行一段特定的代碼,這段代碼就可以放到這個節點上。當生命線走到這里的時候,自然會被調用。
8.3 Bean的聲明周期之五步
Bean生命周期可以粗略的劃分為五大步:
- 第一步:實例化Bean
- 第二步:Bean屬性賦值
- 第三步:初始化Bean
- 第四步:使用Bean
- 第五步:銷毀Bean
這五步的位置如下:
注意:我們需要自己寫初始化和銷毀方法,并且最后我們要手動關閉銷毀方法。我們還要在spring配置文件中指定初始化方法和銷毀方法。
package com.itzw.spring6.lifecycle;public class User {private String name;public User(){System.out.println("1.實例化bean");}public void setName(String name) {this.name = name;System.out.println("2.給bean屬性賦值");}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}public void initBean(){System.out.println("3.初始化bean");}public void destroyBean(){System.out.println("4.銷毀bean");} } <!--我們自己創建的初始化bean和銷毀bean方法都需要在這里指定,因為是我們自己創建的不可能自動識別--><bean id="user" class="com.itzw.spring6.lifecycle.User"init-method="initBean" destroy-method="destroyBean"><property name="name" value="張麻子"/></bean> @Testpublic void testLifeCycle(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");User user = applicationContext.getBean("user", User.class);//System.out.println(user);System.out.println("4.使用bean");//我們需要手動關閉spring容器才能執行銷毀方法,我們還需要將applicationContext強轉為ClassPathXmlApplicationContextClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;context.close();}8.4 Bean生命周期之7步
在以上的5步的基礎上,在第3步初始化Bean前后可以各加一步,可以加入我們想加入的代碼,這一共就是七步了,可以加入“Bean后處理器”。
編寫一個類實現BeanPostProcessor類,并且重寫before和after方法:
package com.itzw.spring6.lifecycle;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor;public class LogBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("Bean后處理器的before方法執行,即將開始初始化");return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("Bean后處理器的before方法執行,已經完成初始化");return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);} }我們需要在spring配置文件中配置bean后處理器,相當于引入這個類。
<!--配置Bean后處理器。這個后處理器將作用于當前配置文件中所有的bean。--><bean class="com.itzw.spring6.lifecycle.LogBeanPostProcessor"/>8.5 Bean生命周期之10步
在上面七步的基礎上,在Bean后處理器before執行之前檢查bean是否實現Aware的相關接口,并設置相關依賴,在Bean后處理器before執行之后檢查bean是否實現了InitialzingBean接口,并調用接口方法,在銷毀bean之前檢查bean是否實現了DisposableBean接口,并調用接口方法。
以上三個檢查接口并執行相關方法或依賴一共是三步,加上前面的七步一共是十步。
那這些接口是什么意思呢?
Aware相關的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
當Bean實現了BeanNameAware,Spring會將Bean的名字傳遞給Bean
當Bean實現了BeanClassLoaderAware,Spring會加載該Bean的類加載器傳遞給bean
當Bean實現了BeanFactoryAware,Spring會將Bean工廠對象傳遞給bean?
我們實現這些接口感受一下:
package com.itzw.spring6.lifecycle;import org.springframework.beans.BeansException; import org.springframework.beans.factory.*;public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean,DisposableBean {private String name;public User(){System.out.println("1.實例化bean");}public void setName(String name) {this.name = name;System.out.println("2.給bean屬性賦值");}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}public void initBean(){System.out.println("4.初始化bean");}public void destroyBean(){System.out.println("7.銷毀bean");}@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {System.out.println("這個bean的類加載器是:"+classLoader);}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("這個bean的工廠對象是:"+beanFactory);}@Overridepublic void setBeanName(String name) {System.out.println("這個bean的名字是:"+name);}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean接口在后處理器的before方法執行之后執行了");}@Overridepublic void destroy() throws Exception {System.out.println("DisposableBean接口在銷毀bean前執行了");} }對于生命周期我們掌握七種就可以
8.6 Bean的作用域不同,管理方式不同
Spring根據bean的作用域來選擇管理方式
- 對于singleton作用域的bean,Spring能夠精確的知道該bean何時被創建,以及何時被銷毀
- 而對于prototype作用域的bean,spring只負責創建
我們把之前User類的spring.xml文件中的片配置scope設置為prototype:
只執行了前八步
9 Bean的循環依賴問題
9.1 什么是Bean的循環依賴
A對象有B屬性,B對象有A屬性。比如Husband對象有Wife屬性,Wife對象有Husband屬性,如下:
package com.itzw.spring6.bean;public class Husband {private String name;private Wife wife; } package com.itzw.spring6.bean;public class Wife {private String name;private Husband husband; }9.2 singleton下的set注入產生的循環依賴
package com.itzw.spring6.bean;public class Husband {private String name;private Wife wife;public String getName() {return name;}public void setName(String name) {this.name = name;}public void setWife(Wife wife) {this.wife = wife;}@Overridepublic String toString() {return "Husband{" +"name='" + name + '\'' +", wife=" + wife.getName() +'}';} } package com.itzw.spring6.bean;public class Wife {private String name;private Husband husband;public String getName() {return name;}public void setName(String name) {this.name = name;}public void setHusband(Husband husband) {this.husband = husband;}@Overridepublic String toString() {return "Wife{" +"name='" + name + '\'' +", husband=" + husband.getName() +'}';} }注意這里的toString方法里面不能直接輸出husband和wife對象,因為這樣就遞歸了,我們指定輸出對應的get方法即可
<bean class="com.itzw.spring6.bean.Husband" id="husband" scope="singleton"><property name="name" value="張麻子"/><property name="wife" ref="wife"/></bean><bean class="com.itzw.spring6.bean.Wife" id="wife" scope="singleton"><property name="name" value="馬邦德"/><property name="husband" ref="husband"/></bean> @Testpublic void testDC(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");Husband husband = applicationContext.getBean("husband", Husband.class);System.out.println(husband);Wife wife = applicationContext.getBean("wife", Wife.class);System.out.println(wife);}通過測試得知:在singleton + set注入的情況下,循環依賴是沒有問題的。Spring可以解決這個問題。
我們簡單分析一下原理:因為使用的是singleton是單例模式,對象只會實例化一次,所以在spring容器加載的時候實例化bean,只要進行實例化后就會立刻“曝光”【不等屬性值賦值就曝光了】。比如在給Husband對象賦值的時候需要得到WIfe對象才能完成賦值,而這時Wife對象已經實例化已經曝光可以得到。同樣的Wife對象在賦值的時候需要Husband對象,此時Husband對象也已經實例化結束已經曝光,所以不會出現問題。
9.3 prototype下的set注入產生的循環依賴
scope改成prototype
這樣會出錯:
提示我們:請求的 bean 目前正在創建中:是否有無法解析的循環引用?
那為什么會出錯呢?因為使用的是prototype。在我們給Husband賦值的時候需要注入WifeBean,這就需要new一個新的Wife對象給Husband,而new出的Wife對象需要Husband對象才行,這時又會new一個新的Husband對象...會成死循環。
但如果有一個Bean的scope是singleton就不會出錯了。比如WIfe是singleton
當我們給Husband賦值的時候,需要Wife對象,因為Wife對象是單例的只有一個固定的,會曝光,Husband對象就能得到了。當我們給Wife對象賦值的時候,需要Husband,Husband會new一個新的對象,此時Husband需要Wife對象,因為WIfe對象是單例的,直接就可以得到了。
9.4 singleton下的構造注入產生的循環依賴
package com.itzw.spring6.bean2;public class Husband {private String name;private Wife wife;public String getName() {return name;}public Husband(String name, Wife wife) {this.name = name;this.wife = wife;}@Overridepublic String toString() {return "Husband{" +"name='" + name + '\'' +", wife=" + wife.getName() +'}';} } package com.itzw.spring6.bean2;public class Wife {private String name;private Husband husband;public String getName() {return name;}public Wife(String name, Husband husband) {this.name = name;this.husband = husband;}@Overridepublic String toString() {return "Wife{" +"name='" + name + '\'' +", husband=" + husband.getName() +'}';} } <bean id="husband" class="com.itzw.spring6.bean2.Husband" scope="singleton"><constructor-arg name="name" value="張麻子"/><constructor-arg ref="wife"/></bean><bean id="wife" class="com.itzw.spring6.bean2.Wife" scope="singleton"><constructor-arg name="name" value="馬邦德"/><constructor-arg name="husband" ref="husband"/></bean>測試結果是失敗的,錯誤和上面的錯一樣,為什么呢?
因為使用構造注入,這要求我們在實例化bean的時候就要給屬性值賦值,但是經過上面那些分析我們就應該已經明白了,我們是做不到實例化就立刻給屬性賦值的,這會造成死循環。
小小總結一下:經過上面測試我們發現只有在使用set方法注入并且scope的值為singleton的時候才能避免循環依賴。
根本原因在于,這種方式可以做到“實例化Bean”和“給Bean屬性賦值”這兩個動作分開完成。實例化bean的時候調用無參構造來完成,此時可以先不給屬性賦值,可以提前將該bean“曝光”給外界。
也就是說,Bean都是單例的,我們可以先把所有的單例bean實例化出來,放到一個集合當中(我們可以稱之為緩存)所有的bean都實例化完成之后,以后我們再慢慢的調用set方法給屬性賦值,這樣就解決了循環依賴的問題。
10 回顧反射機制
10.1 分析方法四要素
我們隨便創建一個類:
package com.itzw.reflect;public class Student {private String name;private int age;public void doSome(){System.out.println("doSome無參方法執行");}public String doSome(String name){System.out.println("doSome返回姓名的方法執行");return name;} }我們調用這個類的方法:
package com.itzw.reflect;public class Test {public static void main(String[] args) {Student student = new Student();student.doSome();String doSome = student.doSome("王德發");System.out.println(doSome);} }我們發現調用一個類的方法需要四要素:類的對象;方法名;方法參數;返回值
10.2 使用反射機制調用方法
還是那個類,不過我們使用反射機制的方式調用方法
package com.itzw.reflect;import java.lang.reflect.Method;public class Test2 {public static void main(String[] args) throws Exception {//獲取類Class<?> clazz = Class.forName("com.itzw.reflect.Student");//獲取方法Method doSome = clazz.getDeclaredMethod("doSome", String.class);//獲取類的對象Object obj = clazz.newInstance();//調用方法Object retValue = doSome.invoke(obj, "張麻子");//輸出返回值System.out.println(retValue);} }10.3 假設知道屬性名
package com.itzw.reflect;public class User {private String name;private int age;public User(){}public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';} }假設有如上的一個類,你知道的信息是:
- 類名是:com.itzw.reflect.User
- 該類中有String類型的name屬性和int類型的age屬性
- 另外你知道該類的設計符合javabean規范
知道以上信息如何給屬性賦值呢?
我們這里給age屬性賦值:
package com.itzw.reflect;import java.lang.reflect.Method;public class Test3 {public static void main(String[] args) throws Exception{String className = "com.itzw.reflect.User";String propertyName = "age";String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);//獲取類Class<?> clazz = Class.forName(className);//獲取方法Method setMethod = clazz.getDeclaredMethod(setMethodName,int.class);//獲取對象Object obj = clazz.newInstance();//調用方法setMethod.invoke(obj,12);//輸出類System.out.println(obj);} }之所以回顧反射機制是為了下面手寫spring框架
總結
以上是生活随笔為你收集整理的SSM框架-Spring(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL课程超级团,值得再提一次。
- 下一篇: sqlite3:sqlite3_colu