javascript
SSM框架-Spring(一)
目錄
1 Spring啟示錄
1.1 OCP開閉原則
1.2 依賴倒置原則DIP
1.3 控制反轉(zhuǎn)IoC
2 Spring概述
2.1 Spring簡(jiǎn)介
2.2 Spring8大模塊
2.3 Spring特點(diǎn)
2.4 本次學(xué)習(xí)使用軟件版本
3 Spring入門程序
3.1 Spring下載
3.2 第一個(gè)Spring程序
3.3 第一個(gè)spring程序的細(xì)節(jié)
3.4?Spring6啟用Log4j2日志框架
4 spring對(duì)IoC的實(shí)現(xiàn)
4.1 set注入
4.2 構(gòu)造注入
4.3 set注入專題
4.3.1 注入內(nèi)部bean和外部bean
4.3.2 注入簡(jiǎn)單類型
4.3.3 級(jí)聯(lián)屬性賦值
4.3.4 數(shù)組注入
4.3.5 集合注入
4.3.6 注入null和空字符串
4.3.7 特殊字符的注入
4.4 p命名空間注入
4.5 c命名空間注入
4.6 util命名空間
4.7 基于XML的自動(dòng)裝配
4.7.1 根據(jù)名稱自動(dòng)裝配
4.7.2 根據(jù)類型自動(dòng)裝配
4.8?Spring引入外部屬性配置文件
5 Bean的作用域
5.1 singleton
5.2 prototype
5.3 其它scope
6 GoF工廠設(shè)計(jì)模式
6.1 工廠模式三種形態(tài)
6.2 簡(jiǎn)單工廠模式
6.3 工廠方法模式
6.4 抽象工廠模式(了解)
7 Bean的實(shí)例化方式
7.1 通過(guò)構(gòu)造方法實(shí)例化
7.2 通過(guò)簡(jiǎn)單工廠模式實(shí)例化
7.3 通過(guò)factory-bean實(shí)例化
7.4 通過(guò)FactoryBean接口實(shí)例化
7.5 BeanFactory和FactoryBean的區(qū)別
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的循環(huán)依賴問(wèn)題
9.1 什么是Bean的循環(huán)依賴
9.2 singleton下的set注入產(chǎn)生的循環(huán)依賴
9.3 prototype下的set注入產(chǎn)生的循環(huán)依賴
9.4 singleton下的構(gòu)造注入產(chǎn)生的循環(huán)依賴
10 回顧反射機(jī)制
10.1 分析方法四要素
10.2 使用反射機(jī)制調(diào)用方法
10.3 假設(shè)知道屬性名
1 Spring啟示錄
我們之前學(xué)過(guò)mvc設(shè)計(jì)模式,這種模式可以有效的降低代碼的耦合度,提高擴(kuò)展力,我們?cè)賹懸粋€(gè)這樣的模式,代碼如下:
在創(chuàng)建maven項(xiàng)目前我們可以先設(shè)置如下
package com.itzw.spring6.dao.impl;import com.itzw.spring6.dao.UserDao;public class UserDaoImplForMySQL implements UserDao {public void select() {System.out.println("正在連接數(shù)據(jù)庫(kù)。。。");} } 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();} }以上大概就是我們之前學(xué)的mvc架構(gòu)模式,分為表示層、業(yè)務(wù)邏輯層、持久層。而其中UserServlet依賴了具體的UserServiceImpl,UserServiceImpl依賴了具體的UserDaoImplForMySQL。
假如我們不想連接mysql數(shù)據(jù)庫(kù)了,我們想連接Oracle數(shù)據(jù)庫(kù),我們就要修改UserServiceImpl中的代碼。
1.1 OCP開閉原則
什么是OCP?
- OCP是軟件七大開發(fā)原則當(dāng)中最基本的一個(gè)原則。對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉
- 其中OCP原則是最核心的最基本的,其它六個(gè)原則都是為了這個(gè)原則服務(wù)的
- OCP開閉原則的核心是:當(dāng)我們?cè)跀U(kuò)展系統(tǒng)功能的時(shí)候,沒(méi)有修改以前寫好的代碼那么就是符合OCP原則的,反之不符合這個(gè)原則
如上圖可以很明顯的看出上層是依賴下層的,下面改動(dòng)上面必然改動(dòng),這樣同樣違背了另一個(gè)開發(fā)原則:依賴倒置原則
1.2 依賴倒置原則DIP
依賴倒置原則(Dependence Inversion Principe),倡導(dǎo)面向接口編程,面向抽象編程,不要面向具體編程,讓上層不再依賴下層,下層改動(dòng)上層不需要改動(dòng),這樣大大降低耦合度,耦合度低了,擴(kuò)展力就強(qiáng)了,同時(shí)代碼復(fù)用性也會(huì)增強(qiáng)(軟件七大開發(fā)原則都在為解耦合服務(wù))
那我們可能有疑問(wèn),這不就是面向接口編程的嗎?確實(shí),是這樣的,但是不完全是,我們雖然都是調(diào)用接口中的方法,但是我們是通過(guò)new 對(duì)象,new一個(gè)具體的接口實(shí)現(xiàn)類,如下:
如下才是完全面向接口,完全符合依賴倒置原則:
但是如果這樣編程userDao是null,那么就會(huì)出現(xiàn)空指針異常,確實(shí)是這樣。這也就是我們接下來(lái)要解決的問(wèn)題。
1.3 控制反轉(zhuǎn)IoC
控制反轉(zhuǎn)(Inversion of Control),是面向?qū)ο缶幊痰囊环N設(shè)計(jì)思想,可以用來(lái)降低代碼的耦合度,符合依賴倒置原則??刂品崔D(zhuǎn)的核心是:將對(duì)象的創(chuàng)建權(quán)交出去,將對(duì)象和對(duì)象之間的關(guān)系管理權(quán)交出去,由第三方容器負(fù)責(zé)創(chuàng)建與維護(hù)。
我們要學(xué)的Spring框架實(shí)現(xiàn)了控制反轉(zhuǎn)這種思想,Spring框架可以幫我們new 對(duì)象,還可以幫我們維護(hù)對(duì)象與對(duì)象之間的關(guān)系
控制反轉(zhuǎn)常用的實(shí)現(xiàn)方法:依賴注入(Dependency Injection,簡(jiǎn)稱DI)
依賴注入DI,包括兩種常見的 方式:
- 第一種:set方法注入(執(zhí)行set方法給屬性賦值)
- 第二種:構(gòu)造方法注入
IoC是一種全新的設(shè)計(jì)模式,但是理論和時(shí)間成熟較晚,并沒(méi)有包含在GoF中(GoF是23中設(shè)計(jì)模式)
2 Spring概述
2.1 Spring簡(jiǎn)介
來(lái)自百度百科:
- Spring是一個(gè)開源框架,它由Rod Johnson創(chuàng)建。它是為了解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性而創(chuàng)建的。
- 從簡(jiǎn)單性、可測(cè)試性和松耦合的角度而言,任何Java應(yīng)用都可以從Spring中受益。
- Spring是一個(gè)輕量級(jí)的控制反轉(zhuǎn)(IoC)和面向切面(AOP)的容器框架。
- Spring最初的出現(xiàn)是為了解決EJB臃腫的設(shè)計(jì),以及難以測(cè)試等問(wèn)題。
- Spring為簡(jiǎn)化開發(fā)而生,讓程序員只需關(guān)注核心業(yè)務(wù)的實(shí)現(xiàn),盡可能的不再關(guān)注非業(yè)務(wù)邏輯代碼(事務(wù)控制,安全日志等)。
2.2 Spring8大模塊
注意:Spring5版本之后是8個(gè)模塊。在Spring5中新增了WebFlux模塊。
Spring Core模塊:這是Spring框架最基礎(chǔ)的部分,它提供了依賴注入特征來(lái)實(shí)現(xiàn)容器對(duì)Bean的管理。
2.3 Spring特點(diǎn)
- 輕量
- 控制反轉(zhuǎn)
- 面向切面
- 容器
- 框架
2.4 本次學(xué)習(xí)使用軟件版本
- 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下載
官網(wǎng)地址:Spring | Home
官網(wǎng)地址(中文):Spring 中文網(wǎng) 官網(wǎng)
以上兩個(gè)地址都可以下載,不過(guò)我們可以直接使用maven下載依賴,就像mybatis一樣
3.2 第一個(gè)Spring程序
前期準(zhǔn)備:在idea中創(chuàng)建一個(gè)maven模塊,這個(gè)我們?cè)缫言O(shè)置好,直接用即可
第一步:添加spring context依賴
<repositories><!--spring里程碑版本的倉(cāng)庫(kù)--><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
當(dāng)加入spring context依賴之后,會(huì)關(guān)聯(lián)引入其它依賴
- spring aop:面向切面編程
- spring beans:IoC核心
- spring core:spring核心工具包
- spring jcl:spring的日志包
- spring expression:spring表達(dá)式
第二步:添加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就可以創(chuàng)建idea提供的文件模板:
在配置文件中進(jìn)行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>需要注意的是:這個(gè)文件最好放在類路徑下,方便移植
bean標(biāo)簽的兩個(gè)重要屬性:id:這是bean的身份證,不能重復(fù),是唯一標(biāo)識(shí);class:必須填寫類的全路徑,全限定類名。
第五步:編寫測(cè)試程序
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容器對(duì)象//ApplicationContext是一個(gè)接口,接口下有很多實(shí)現(xiàn)類,其中有一個(gè)叫做:ClassPathXmlApplicationContext//ClassPathXmlApplicationContext 專門從類路徑下加載spring配置文件的一個(gè)spring上下文對(duì)象//運(yùn)行這行代碼就相當(dāng)于啟動(dòng)了spring容器,解析spring.xml文件,并且實(shí)例化所有的bean對(duì)象,放到spring容器當(dāng)中ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");//第二步:根據(jù)bean的id從spring容器中獲取這個(gè)對(duì)象Object userBean = applicationContext.getBean("userBean");System.out.println(userBean);}}測(cè)試結(jié)果:
3.3 第一個(gè)spring程序的細(xì)節(jié)
(1)bean的id不能重復(fù):
<bean id="userBean" class="com.itzw.spring6.bean.User"></bean><bean id="userBean" class="com.itzw.spring6.bean.Vip"></bean>(2)底層是怎樣創(chuàng)建對(duì)象的:
我們?cè)赨ser類中寫上無(wú)參構(gòu)造:
public class User {public User(){System.out.println("這是User類的無(wú)參構(gòu)造");} }測(cè)試:
如果只有有參構(gòu)造沒(méi)有無(wú)參呢?
public class User { /* public User(){System.out.println("這是User類的無(wú)參構(gòu)造");}*/public User(String name){System.out.println("這是User類的有參構(gòu)造");} }經(jīng)過(guò)測(cè)試:spring是通過(guò)調(diào)用類的無(wú)參構(gòu)造來(lái)創(chuàng)建對(duì)象的,所以要想讓spring給你創(chuàng)建對(duì)象,必須保證無(wú)參構(gòu)造方法是存在的
spring是通過(guò)反射機(jī)制調(diào)用無(wú)參構(gòu)造方法創(chuàng)建對(duì)象
(3)創(chuàng)建好的對(duì)象存儲(chǔ)在map集合中
(4)Spring配置文件的名字可以隨意改
(5)spring配置文件可以創(chuàng)建多個(gè)
我們?cè)賱?chuàng)建一個(gè)spring配置文件,配置bean的信息
我們直接在ClassPathXmlApplicationContext構(gòu)造方法參數(shù)上傳遞路徑即可,不需要再new一個(gè)ClassPathXmlApplicationContext對(duì)象,為什么呢?通過(guò)源碼查看是可以傳多個(gè)的?
(6)配置文件中的類必須是自定義的嗎
不是,可以是jdk自帶的類,如下:
<bean id="dateBean" class="java.util.Date"></bean> Object dateBean = applicationContext.getBean("dateBean");System.out.println(dateBean);可以直接輸出當(dāng)前日期
經(jīng)測(cè)試,spring配置文件中的bean可以是任意類,只要它不是抽象的并且有無(wú)參構(gòu)造
(7)執(zhí)行g(shù)etBean方法時(shí)傳入的參數(shù)不存在會(huì)報(bào)異常
(8)getBean方法返回類型問(wèn)題
默認(rèn)返回類型是Object,當(dāng)然我們可以強(qiáng)轉(zhuǎn),但是有沒(méi)有別的辦法,
User userBean1 = applicationContext.getBean("userBean", User.class);可以通過(guò)第二個(gè)參數(shù)返回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指定日志級(jí)別,從低到高的優(yōu)先級(jí):ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF--><root level="DEBUG"><appender-ref ref="spring6log"/></root></loggers><appenders><!--輸出日志信息到控制臺(tái)--><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(){//第一步:創(chuàng)建日志記錄對(duì)象//獲取Spring6Test類的日志記錄對(duì)象,也就是說(shuō)只要是Spring6Test類中的代碼記錄日志的話,就輸出日志信息Logger logger = LoggerFactory.getLogger(Spring6Test.class);//第二步:記錄日志,根據(jù)不同級(jí)別來(lái)輸出日志logger.info("我是一條信息");logger.debug("我是一個(gè)調(diào)試信息");logger.error("我是一條錯(cuò)誤信息");}4 spring對(duì)IoC的實(shí)現(xiàn)
前面我們講過(guò)我們可以使用依賴注入實(shí)現(xiàn)控制反轉(zhuǎn)??刂品崔D(zhuǎn)的思想是將對(duì)象的創(chuàng)建權(quán)交出去,交給第三方容器。
實(shí)現(xiàn)依賴注入主要有兩個(gè)方式:set注入和構(gòu)造注入
4.1 set注入
我們創(chuàng)建一個(gè)新的模塊,我們先像之前那樣創(chuàng)建一個(gè)dao文件和一個(gè)service文件,不過(guò)這次在service目錄下我們不new新的dao對(duì)象,如下:
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();} }但是這樣測(cè)試結(jié)果顯然是出錯(cuò)的,dao對(duì)象是null的
我們使用set注入的方式來(lái)實(shí)現(xiàn)控制反轉(zhuǎn):
我們需要提供一個(gè)set方法,spring會(huì)調(diào)用這個(gè)set方法給userDao屬性賦值,我們直接使用idea工具生成這個(gè)set方法即可
我們現(xiàn)在需要service調(diào)用set方法,這就要借助spring,我們?cè)赽ean標(biāo)簽配置property標(biāo)簽,其中name屬性就是用來(lái)傳set方法的,name屬性名指定格式為set方法的方法名去掉set,然后剩下的單詞首字母小寫。這就實(shí)現(xiàn)了UserService類調(diào)用set方法,但是我們還需要傳值,傳一個(gè)UserDao對(duì)象的值,因?yàn)樯厦嫖覀兣渲妙恥serDao的bean標(biāo)簽,我們直接把它的id值傳給property中的ref屬性即可這就完成了傳值。如下:
配置好后再次測(cè)試,測(cè)試成功。
4.2 構(gòu)造注入
我們多建一個(gè)dao文件,在service文件中直接用idea自動(dòng)生成構(gòu)造方法
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><!--構(gòu)造注入--><bean id="accountServiceBean" class="com.itzw.spring6.service.AccountService"><!--index屬性指定參數(shù)下標(biāo),第一個(gè)參數(shù)是0,第二個(gè)參數(shù)是1,第三個(gè)參數(shù)是3,依次。。ref指定注入的bean的id--><!--<constructor-arg index="0" ref="userDaoBean"/><constructor-arg index="1" ref="vipDaoBean"/>--><!--我們還有別的方法進(jìn)行構(gòu)造注入--><constructor-arg name="userDao" ref="userDaoBean"/><constructor-arg name="vipDao" ref="vipDaoBean"/></bean>測(cè)試即可。
4.3 set注入專題
以為set注入使用的較多,我們使用set注入來(lái)學(xué)習(xí)下面的內(nèi)容
4.3.1 注入內(nèi)部bean和外部bean
我們之前使用的set注入就是注入外部bean,那什么是注入內(nèi)部bean呢?
在property中嵌套bean標(biāo)簽就是內(nèi)部bean。這樣麻煩了一點(diǎn),我們一般不用這種方式,我們還是用之前的方式?
<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 注入簡(jiǎn)單類型
我們之前注入的數(shù)據(jù)都不是簡(jiǎn)單類型,對(duì)象屬性都是一個(gè)對(duì)象,我們現(xiàn)在注入屬性是一個(gè)數(shù)據(jù)的。
我們寫一個(gè)類,寫上屬性,創(chuàng)建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配置文件:
<!--簡(jiǎn)單類型注入--><bean id="userBean" class="com.itzw.spring6.beans.User"><property name="age" value="24"/><property name="name" value="張麻子"/></bean>在這里name屬性依然是set方法,但是我們給set方法參數(shù)傳值就只要傳簡(jiǎn)單的數(shù)據(jù)就可以了,所以我們使用value來(lái)賦值。
測(cè)試:結(jié)果如下:
那么簡(jiǎn)單類型包括哪些呢?
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類,得知簡(jiǎn)單類型有:
- 基本數(shù)據(jù)類型
- 基本數(shù)據(jù)類型對(duì)應(yīng)包裝類
- String或其他的CharSequence子類
- Number子類
- Date子類
- Enum子類
- URI
- URL
- Temporal子類
- Locale
- Class
- 另外還包括以上簡(jiǎn)單值類型對(duì)應(yīng)的數(shù)據(jù)類型
簡(jiǎn)單類型注入的經(jīng)典應(yīng)用:
給數(shù)據(jù)源的屬性賦值,比如我們經(jīng)常使用的數(shù)據(jù)庫(kù)的連接:
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;} <!--簡(jiǎn)單類型注入的應(yīng)用--><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);}你們可以把簡(jiǎn)單類型都測(cè)試一遍,那我不測(cè)了,我不打擾我走了哈哈。
4.3.3 級(jí)聯(lián)屬性賦值
也就是我們熟悉的套娃賦值,就是一個(gè)類的屬性有另一個(gè)類。我們先用我們學(xué)過(guò)的方式賦值:
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;} } <!--級(jí)聯(lián)屬性賦值--><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);}以上是我們學(xué)過(guò)的方式,測(cè)試結(jié)果:
我們使用級(jí)聯(lián)屬性賦值:
<!--級(jí)聯(lián)屬性賦值--><bean class="com.itzw.spring6.dao.Student" id="studentBean"><property name="name" value="張麻子"/><property name="age" value="34"/><property name="clazz" ref="clazzBean"/><!--級(jí)聯(lián)屬性賦值--><property name="clazz.name" value="高三二班"/></bean><bean class="com.itzw.spring6.dao.Clazz" id="clazzBean"></bean>使用這種方式我們需要給clazz屬性構(gòu)造get方法,這種方式顯得很麻煩,還不如之前的方法。
4.3.4 數(shù)組注入
首先簡(jiǎn)單類型的數(shù)組注入:
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) +'}';} } <!--數(shù)組注入--><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);}如果數(shù)組元素是非簡(jiǎn)單類型呢?
<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><!--數(shù)組注入--><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集合有序和重復(fù),set集合無(wú)序不重復(fù)
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>徐州市銅山區(qū)</value><value>徐州市云龍區(qū)</value><value>徐州市銅山區(qū)</value><value>徐州市銅山區(qū)</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集合,有鍵值對(duì):
<property name="tel"><map><!--一個(gè)entry就表示一個(gè)鍵值對(duì)--><entry key="tel1" value="110"/><entry key="tel2" value="119"/><entry key="tel3" value="120"/></map></property>properties集合本質(zhì)上也是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"><!--手動(dòng)賦值為null--><null></null></property><!--賦值為空字符串--><!--第一種方式--><!--<property name="addr" value=""/>--><!--第二種方式--><property name="addr"><value/></property></bean>4.3.7 特殊字符的注入
XML中有5個(gè)特殊字符,分別是:<、>、'、"、&。
這些字符直接出現(xiàn)在xml當(dāng)中會(huì)報(bào)錯(cuò):
解決方式有兩種:
- 第一種:使用轉(zhuǎn)義字符代替
- 第二種:將含有特殊符號(hào)的字符串放到:<![CDATA[]]> 當(dāng)中。因?yàn)榉旁贑DATA區(qū)中的數(shù)據(jù)不會(huì)被XML文件解析器解析。
特殊字符對(duì)應(yīng)的轉(zhuǎn)移字符如下:
| 特殊字符 | 轉(zhuǎn)義字符 |
| > | > |
| < | < |
| ' | ' |
| " | " |
| & | & |
4.4 p命名空間注入
使用p命名空間注入可以簡(jiǎn)化配置,使用前提是:
- 要在xml配置文件上方添加配置信息:xmlns:p="http://www.springframework.org/schema/p"
- p命名空間注入是基于set方法的,要提供set方法
其實(shí)p命名空間注入就是代替set注入的
<bean class="com.itzw.spring6.dao.Dog" id="dog" p:name="小花" p:age="2"/>4.5 c命名空間注入
c命名空間注入是用來(lái)簡(jiǎn)化構(gòu)造注入的,那么使用前提是:
- 需要在xml配置文件頭部添加信息:xmlns:c="http://www.springframework.org/schema/c"
- 需要提供構(gòu)造方法。?
注意:不管是p命名注入還是c命名注入都可以注入非簡(jiǎn)單類型
4.6 util命名空間
使用util命名空間可以讓配置復(fù)用,使用前提是:在spring配置文件頭部添加如下信息:
比如我想給多個(gè)java文件都傳輸jdbc連接的信息,它們的信息都是一樣的,這時(shí)我們就可以將這段信息使用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的自動(dòng)裝配
Spring還可以完成自動(dòng)化的注入,自動(dòng)化注入又被稱為自動(dòng)裝配。它可以根據(jù)名字進(jìn)行自動(dòng)裝配,也可以根據(jù)類型進(jìn)行自動(dòng)裝配。
4.7.1 根據(jù)名稱自動(dòng)裝配
回憶之前的業(yè)務(wù)邏輯層和持久層之間的連接,其中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>我們使用自動(dòng)裝配:
<bean class="com.itzw.spring6.dao.UserDao" id="userDao"/><bean class="com.itzw.spring6.service.OrderDao" id="orderDao" autowire="byName"/>在orderDao的bean中添加autowire,值設(shè)為byName表示通過(guò)名稱進(jìn)行自動(dòng)裝配
OrderDao類中有一個(gè)UserDao屬性,set方法為setUserDao,而UserDao的bean的id為userDao,恰好和OrderDao中的set方法對(duì)應(yīng)。滿足這些才能自動(dòng)裝配
也就是說(shuō)需要set方法名和想要注入的類的bean的id值對(duì)應(yīng)上才行
4.7.2 根據(jù)類型自動(dòng)裝配
<bean class="com.itzw.spring6.dao.UserDao"/><bean class="com.itzw.spring6.service.OrderDao" id="orderDao" autowire="byType"/>這樣連id值都不需要傳了,直接就能識(shí)別自己想要的類。但是也有缺陷,不能出現(xiàn)多個(gè)同一個(gè)類的bean,這樣它就識(shí)別不出哪個(gè)是自己需要的了。
值得注意的是:不管是根據(jù)name自動(dòng)裝配還是類型自動(dòng)裝配都是基于set注入實(shí)現(xiàn)的,也就是都需要有set方法,否則不行。
從這自動(dòng)裝配我們可以看出來(lái),尤其是根據(jù)類型自動(dòng)裝配可讀性 很差而且有限制,不如我們用原始方法,沒(méi)有方便多少反而看的 蛋疼。
4.8?Spring引入外部屬性配置文件
我們連接數(shù)據(jù)庫(kù)的時(shí)候需要配置一些信息,我們能像之前學(xué)習(xí)一樣把這些配置信息放在一個(gè)文件中然后引入到xml文件中嗎?當(dāng)然可以。
第一步:寫一個(gè)數(shù)據(jù)源類 提供相關(guān)屬性:
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標(biāo)簽引入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>但需要注意的是,${}中的值會(huì)默認(rèn)先去系統(tǒng)找對(duì)應(yīng)的值,比如username會(huì)去系統(tǒng)找,極可能輸出的結(jié)果是系統(tǒng)也就是Windows的usernam。所以我們?cè)谂渲妹Q的時(shí)候最好前面加上jdbc.
5 Bean的作用域
5.1 singleton
默認(rèn)情況下,Spring的IoC容器創(chuàng)建的Bean對(duì)象是單例的。
@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);}如上我們調(diào)用三次getBean,返回的是同一個(gè)對(duì)象
那么這個(gè)對(duì)象在什么時(shí)候創(chuàng)建的呢,我們寫上無(wú)參構(gòu)造,把getBean方法都刪除,執(zhí)行程序,發(fā)現(xiàn)無(wú)參構(gòu)造執(zhí)行了,我們得知默認(rèn)情況下,Bean對(duì)象的創(chuàng)建是在初始化Spring上下文的時(shí)候就完成的。
5.2 prototype
如果想讓spring的bean對(duì)象以多例的形式存在,可以在bean標(biāo)簽中指定scope屬性的值為:prototype,這樣spring會(huì)在每一次執(zhí)行g(shù)etBean的時(shí)候都創(chuàng)建bean對(duì)象,調(diào)用幾次就創(chuàng)建幾次
我們?cè)賵?zhí)行上段代碼:?
這時(shí)如果不調(diào)用getBean方法,那么無(wú)參構(gòu)造就不會(huì)只執(zhí)行
5.3 其它scope
scope屬性的值不止兩個(gè),它一共包括8個(gè)選項(xiàng):
- singleton:默認(rèn)的,單例。
- prototype:原型。每調(diào)用一次getBean()方法則獲取一個(gè)新的Bean對(duì)象?;蛎看巫⑷氲臅r(shí)候都是新對(duì)象。
- request:一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)Bean。僅限于在WEB應(yīng)用中使用。
- session:一個(gè)會(huì)話對(duì)應(yīng)一個(gè)Bean。僅限于在WEB應(yīng)用中使用。
- global session:portlet應(yīng)用中專用的。如果在Servlet的WEB應(yīng)用中使用global session的話,和session一個(gè)效果。(portlet和servlet都是規(guī)范。servlet運(yùn)行在servlet容器中,例如Tomcat。portlet運(yùn)行在portlet容器中。)
- application:一個(gè)應(yīng)用對(duì)應(yīng)一個(gè)Bean。僅限于在WEB應(yīng)用中使用。
- websocket:一個(gè)websocket生命周期對(duì)應(yīng)一個(gè)Bean。僅限于在WEB應(yīng)用中使用。
- 自定義scope:很少使用。
我們可以自己定義,但是沒(méi)必要,再見。
6 GoF工廠設(shè)計(jì)模式
設(shè)計(jì)模式:一種可以被重復(fù)利用的解決方案。GoF(Gang of Four),中文名——四人組。
GoF包括了23種設(shè)計(jì)模式。我們平常所說(shuō)的設(shè)計(jì)模式就是指這23種設(shè)計(jì)模式。
不過(guò)除了GoF23種設(shè)計(jì)模式之外,還有其它的設(shè)計(jì)模式,比如:JavaEE的設(shè)計(jì)模式(DAO模式、MVC模式等)。
GoF23種設(shè)計(jì)模式可分為三大類:
創(chuàng)建型(5個(gè)):解決對(duì)象創(chuàng)建問(wèn)題。
- 單例模式
- 原型模式
- 建造者模式
- 抽象工廠模式
- 工廠方法模式
結(jié)構(gòu)型(7個(gè)):一些類或?qū)ο蠼M合在一起的經(jīng)典結(jié)構(gòu)。
- 代理模式
- 橋接模式
- 外觀模式
- 享元模式
- 組合模式
- 適配器模式
- 裝飾模式
行為型(11個(gè)):解決類或?qū)ο笾g的交互問(wèn)題。
- 策略模式
- 解釋器模式
- 中介者模式
- 訪問(wèn)者模式
- 狀態(tài)模式
- 備忘錄模式
- 命令模式
- 迭代子模式
- 觀察者模式
- 責(zé)任鏈模式
- 模板方法模式
工廠模式是解決對(duì)象創(chuàng)建問(wèn)題的,所以工廠模式屬于創(chuàng)建型設(shè)計(jì)模式。這里為什么學(xué)習(xí)工廠模式呢?這是因?yàn)镾pring框架底層使用了大量的工廠模式
6.1 工廠模式三種形態(tài)
工廠模式通常有三種形態(tài):
- 第一種:簡(jiǎn)單工廠模式(Simple Factory):不屬于23種設(shè)計(jì)模式之一。簡(jiǎn)單工廠模式又叫做:靜態(tài) 工廠方法模式。簡(jiǎn)單工廠模式是工廠方法模式的一種特殊實(shí)現(xiàn)。
- 第二種:工廠方法模式(Factory Method):是23種設(shè)計(jì)模式之一。
- 第三種:抽象工廠模式(Abstract Factory):是23種設(shè)計(jì)模式之一。
6.2 簡(jiǎn)單工廠模式
簡(jiǎn)單工廠模式的角色包括三個(gè):
- 抽象產(chǎn)品 角色
- 具體產(chǎn)品 角色
- 工廠類 角色
簡(jiǎn)單工廠模式的代碼如下:
抽象產(chǎn)品角色:
package com.itzw.factory;public abstract class Weapon {public abstract void attack(); }具體產(chǎn)品角色:
package com.itzw.factory;public class Gun extends Weapon{@Overridepublic void attack() {System.out.println("機(jī)槍正在發(fā)射...");} } package com.itzw.factory;public class Plane extends Weapon{@Overridepublic void attack() {System.out.println("飛機(jī)正在扔小男孩...");} } 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("不支持該武器");}} }測(cè)試:
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();} }這種模式就是簡(jiǎn)單工廠模式,它的優(yōu)點(diǎn):客戶端程序,也就是我們這里的 測(cè)試程序不需要關(guān)系對(duì)象的創(chuàng)建細(xì)節(jié),需要哪個(gè)對(duì)象只需要向工廠索要,初步實(shí)現(xiàn)了責(zé)任的分離??蛻舳酥回?fù)責(zé)消費(fèi),工廠只負(fù)責(zé)生產(chǎn)。但 它也有缺點(diǎn):工廠類中集中了所有產(chǎn)品的創(chuàng)造邏輯,一旦出問(wèn)題整個(gè)系統(tǒng)會(huì)癱瘓;還有就是比較明顯的,不符合OCP開閉原則,我們想擴(kuò)展系統(tǒng)時(shí)也就是比如需要擴(kuò)展一個(gè)新的武器需要修改工廠類。
6.3 工廠方法模式
工廠方法模式的角色包括:
- 抽象工廠角色
- 具體工廠角色
- 抽象產(chǎn)品角色
- 具體產(chǎn)品角色
抽象產(chǎn)品角色和具體產(chǎn)品角色和上面的簡(jiǎn)單工廠模式一樣:
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("機(jī)槍正在發(fā)射...");} } package com.itzw.factory2;public class Plane extends Weapon {@Overridepublic void attack() {System.out.println("飛機(jī)正在扔小男孩...");} }抽象工廠角色:創(chuàng)建一個(gè)方法能讓它返回一個(gè)抽象產(chǎn)品角色
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();} }測(cè)試:
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();} }這時(shí)我們?cè)傧爰尤胄碌奈淦骶筒恍枰薷拇a了,直接創(chuàng)建一個(gè)具體類繼承抽象類然后創(chuàng)建具體工廠角色繼承抽象工廠即可。
顯然這種模式符合OCP開閉原則,但是每次新增一個(gè)產(chǎn)品就需要?jiǎng)?chuàng)建兩個(gè)類,在一定程度上增加了系統(tǒng)的復(fù)雜度
6.4 抽象工廠模式(了解)
抽象工廠模式可以看做上面兩種模式的結(jié)合。
7 Bean的實(shí)例化方式
或者可以說(shuō)是bean的獲取方式
Spring為Bean提供了多種實(shí)例化方式,通常包括4種方式。(也就是說(shuō)在Spring中為Bean對(duì)象的創(chuàng)建準(zhǔn)備了多種方案,目的是:更加靈活)
- 第一種:通過(guò)構(gòu)造方法實(shí)例化
- 第二種:通過(guò)簡(jiǎn)單工廠模式實(shí)例化
- 第三種:通過(guò)factory-bean實(shí)例化
- 第四種:通過(guò)FactoryBean接口實(shí)例化
7.1 通過(guò)構(gòu)造方法實(shí)例化
之前我們學(xué)習(xí)的就是通過(guò)構(gòu)造方法實(shí)例化:
package com.itzw.constructor;public class User {public User(){System.out.println("User的無(wú)參構(gòu)造執(zhí)行了");} }spring配置文件:
<bean id="user" class="com.itzw.constructor.User"/>測(cè)試:
@Testpublic void testConstructor(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");Object user = applicationContext.getBean("user", User.class);System.out.println(user);}7.2 通過(guò)簡(jiǎn)單工廠模式實(shí)例化
bean:
package com.itzw.bean;public class Vip {public Vip(){System.out.println("vip的無(wú)參構(gòu)造執(zhí)行了");} }編寫簡(jiǎn)單工廠模式中的工廠類:這里的方法是靜態(tài)方法
package com.itzw.bean;public class VipFactory {public static Vip get(){return new Vip();} }spring.xml:
我們需要在這指定調(diào)用哪個(gè)類的哪個(gè)方法獲取Bean。factory-method屬性指定的是工廠類當(dāng)中的靜態(tài)方法,也就是告訴spring框架調(diào)用這個(gè)方法可以獲取bean
<!--通過(guò)簡(jiǎn)單工廠模式實(shí)例化--><bean class="com.itzw.bean.VipFactory" id="vip" factory-method="get"/>7.3 通過(guò)factory-bean實(shí)例化
這種方式本質(zhì)上是:通過(guò)工廠方法模式進(jìn)行實(shí)例化。
和上個(gè)方法的區(qū)別就是spring配置文件的配置不同,把一個(gè)bean標(biāo)簽分解成兩個(gè),并且具體工廠類的方法是實(shí)例方法如下:
package com.itzw.bean;public class Order {public Order(){System.out.println("Order的無(wú)參構(gòu)造執(zhí)行了");} }注意這里的方法是實(shí)例方法
package com.itzw.bean;public class OrderFactory {public Order get(){return new Order();} }spring:factory-bean告訴框架調(diào)用哪個(gè)對(duì)象,factory-method告訴框架調(diào)用哪個(gè)方法。
<!--通過(guò)工廠方法模式--><bean class="com.itzw.bean.OrderFactory" id="orderFactory"/><bean factory-bean="orderFactory" factory-method="get" id="order" />7.4 通過(guò)FactoryBean接口實(shí)例化
以上三種方法,需要我們自定義factory-bean或者factory-method。在我們編寫類實(shí)現(xiàn)FactoryBean接口后這倆屬性就不需要指定了,factory-bean會(huì)自動(dòng)指向?qū)崿F(xiàn)FactoryBean接口的類,factory-method會(huì)自動(dòng)指向getObject方法
package com.itzw.bean;public class Animal {public Animal(){System.out.println("Animal構(gòu)造方法執(zhí)行了");} }具體工廠類實(shí)現(xiàn)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();} } <!--通過(guò)FactoryBean接口--><bean class="com.itzw.bean.Animal" id="animal"/>7.5 BeanFactory和FactoryBean的區(qū)別
7.5.1 BeanFactory
Spring IoC容器的頂級(jí)對(duì)象,BeanFactory被翻譯為“Bean工廠”,在Spring的IoC容器中,“Bean工廠”負(fù)責(zé)創(chuàng)建Bean對(duì)象。
BeanFactory是工廠。
7.5.2 FactoryBean
FactoryBean:它是一個(gè)Bean,是一個(gè)能夠輔助Spring實(shí)例化其它Bean對(duì)象的一個(gè)Bean。
在Spring中,Bean可以分為兩類:
- 第一類:普通Bean
- 第二類:工廠Bean(記住:工廠Bean也是一種Bean,只不過(guò)這種Bean比較特殊,它可以輔助Spring實(shí)例化其它Bean對(duì)象。)
7.6 注入自定義Date
我們?cè)谇懊婢椭v到過(guò)Date類型是簡(jiǎn)單類型可以直接使用value屬性賦值,但是對(duì)格式要求非常嚴(yán)格,只能是這樣類型的: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>以上就是我們對(duì)Date類型當(dāng)做簡(jiǎn)單類型的注入。
我們可以把Date當(dāng)做非簡(jiǎn)單類型注入:
還是那個(gè)Student類,我們創(chuàng)建Date工廠實(shí)現(xiàn)FactoryBean,并且定義屬性和構(gòu)造方法接收spring配置文件傳來(lái)的日期,我們使用SimpleDateFormat定義日期格式接收傳來(lái)的日期(比如2020-8-8),然后會(huì)返回一個(gè)日期
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;//通過(guò)構(gòu)造方法給日期賦值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工廠一個(gè)日期,會(huì)返回一個(gè)Date默認(rèn)格式的日期,然后我們?cè)偻ㄟ^(guò)Student的Bean將這個(gè)日期傳給Student。
<!--Date類型的注入(非簡(jiǎn)單類型)--><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其實(shí)就是一個(gè)管理Bean對(duì)象的工廠。它負(fù)責(zé)對(duì)象的創(chuàng)建,對(duì)象的銷毀等。
- 所謂的生命周期就是:對(duì)象從創(chuàng)建開始到最終銷毀的整個(gè)過(guò)程。
- 什么時(shí)候創(chuàng)建Bean對(duì)象?
- 創(chuàng)建Bean對(duì)象的前后會(huì)調(diào)用什么方法?
- Bean對(duì)象什么時(shí)候銷毀?
- Bean對(duì)象的銷毀前后調(diào)用什么方法?
8.2 為什么要知道Bean的生命周期
- 其實(shí)生命周期的本質(zhì)是:在哪個(gè)時(shí)間節(jié)點(diǎn)上調(diào)用了哪個(gè)類的哪個(gè)方法。
- 我們需要充分的了解在這個(gè)生命線上,都有哪些特殊的時(shí)間節(jié)點(diǎn)。
- 只有我們知道了特殊的時(shí)間節(jié)點(diǎn)都在哪,到時(shí)我們才可以確定代碼寫到哪。
- 我們可能需要在某個(gè)特殊的時(shí)間點(diǎn)上執(zhí)行一段特定的代碼,這段代碼就可以放到這個(gè)節(jié)點(diǎn)上。當(dāng)生命線走到這里的時(shí)候,自然會(huì)被調(diào)用。
8.3 Bean的聲明周期之五步
Bean生命周期可以粗略的劃分為五大步:
- 第一步:實(shí)例化Bean
- 第二步:Bean屬性賦值
- 第三步:初始化Bean
- 第四步:使用Bean
- 第五步:銷毀Bean
這五步的位置如下:
注意:我們需要自己寫初始化和銷毀方法,并且最后我們要手動(dòng)關(guān)閉銷毀方法。我們還要在spring配置文件中指定初始化方法和銷毀方法。
package com.itzw.spring6.lifecycle;public class User {private String name;public User(){System.out.println("1.實(shí)例化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");} } <!--我們自己創(chuàng)建的初始化bean和銷毀bean方法都需要在這里指定,因?yàn)槭俏覀冏约簞?chuàng)建的不可能自動(dòng)識(shí)別--><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");//我們需要手動(dòng)關(guān)閉spring容器才能執(zhí)行銷毀方法,我們還需要將applicationContext強(qiáng)轉(zhuǎn)為ClassPathXmlApplicationContextClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;context.close();}8.4 Bean生命周期之7步
在以上的5步的基礎(chǔ)上,在第3步初始化Bean前后可以各加一步,可以加入我們想加入的代碼,這一共就是七步了,可以加入“Bean后處理器”。
編寫一個(gè)類實(shí)現(xiàn)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方法執(zhí)行,即將開始初始化");return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("Bean后處理器的before方法執(zhí)行,已經(jīng)完成初始化");return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);} }我們需要在spring配置文件中配置bean后處理器,相當(dāng)于引入這個(gè)類。
<!--配置Bean后處理器。這個(gè)后處理器將作用于當(dāng)前配置文件中所有的bean。--><bean class="com.itzw.spring6.lifecycle.LogBeanPostProcessor"/>8.5 Bean生命周期之10步
在上面七步的基礎(chǔ)上,在Bean后處理器before執(zhí)行之前檢查bean是否實(shí)現(xiàn)Aware的相關(guān)接口,并設(shè)置相關(guān)依賴,在Bean后處理器before執(zhí)行之后檢查bean是否實(shí)現(xiàn)了InitialzingBean接口,并調(diào)用接口方法,在銷毀bean之前檢查bean是否實(shí)現(xiàn)了DisposableBean接口,并調(diào)用接口方法。
以上三個(gè)檢查接口并執(zhí)行相關(guān)方法或依賴一共是三步,加上前面的七步一共是十步。
那這些接口是什么意思呢?
Aware相關(guān)的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
當(dāng)Bean實(shí)現(xiàn)了BeanNameAware,Spring會(huì)將Bean的名字傳遞給Bean
當(dāng)Bean實(shí)現(xiàn)了BeanClassLoaderAware,Spring會(huì)加載該Bean的類加載器傳遞給bean
當(dāng)Bean實(shí)現(xiàn)了BeanFactoryAware,Spring會(huì)將Bean工廠對(duì)象傳遞給bean?
我們實(shí)現(xiàn)這些接口感受一下:
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.實(shí)例化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("這個(gè)bean的類加載器是:"+classLoader);}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("這個(gè)bean的工廠對(duì)象是:"+beanFactory);}@Overridepublic void setBeanName(String name) {System.out.println("這個(gè)bean的名字是:"+name);}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean接口在后處理器的before方法執(zhí)行之后執(zhí)行了");}@Overridepublic void destroy() throws Exception {System.out.println("DisposableBean接口在銷毀bean前執(zhí)行了");} }對(duì)于生命周期我們掌握七種就可以
8.6 Bean的作用域不同,管理方式不同
Spring根據(jù)bean的作用域來(lái)選擇管理方式
- 對(duì)于singleton作用域的bean,Spring能夠精確的知道該bean何時(shí)被創(chuàng)建,以及何時(shí)被銷毀
- 而對(duì)于prototype作用域的bean,spring只負(fù)責(zé)創(chuàng)建
我們把之前User類的spring.xml文件中的片配置scope設(shè)置為prototype:
只執(zhí)行了前八步
9 Bean的循環(huán)依賴問(wèn)題
9.1 什么是Bean的循環(huán)依賴
A對(duì)象有B屬性,B對(duì)象有A屬性。比如Husband對(duì)象有Wife屬性,Wife對(duì)象有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注入產(chǎn)生的循環(huán)依賴
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對(duì)象,因?yàn)檫@樣就遞歸了,我們指定輸出對(duì)應(yīng)的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);}通過(guò)測(cè)試得知:在singleton + set注入的情況下,循環(huán)依賴是沒(méi)有問(wèn)題的。Spring可以解決這個(gè)問(wèn)題。
我們簡(jiǎn)單分析一下原理:因?yàn)槭褂玫氖莝ingleton是單例模式,對(duì)象只會(huì)實(shí)例化一次,所以在spring容器加載的時(shí)候?qū)嵗痓ean,只要進(jìn)行實(shí)例化后就會(huì)立刻“曝光”【不等屬性值賦值就曝光了】。比如在給Husband對(duì)象賦值的時(shí)候需要得到WIfe對(duì)象才能完成賦值,而這時(shí)Wife對(duì)象已經(jīng)實(shí)例化已經(jīng)曝光可以得到。同樣的Wife對(duì)象在賦值的時(shí)候需要Husband對(duì)象,此時(shí)Husband對(duì)象也已經(jīng)實(shí)例化結(jié)束已經(jīng)曝光,所以不會(huì)出現(xiàn)問(wèn)題。
9.3 prototype下的set注入產(chǎn)生的循環(huán)依賴
scope改成prototype
這樣會(huì)出錯(cuò):
提示我們:請(qǐng)求的 bean 目前正在創(chuàng)建中:是否有無(wú)法解析的循環(huán)引用?
那為什么會(huì)出錯(cuò)呢?因?yàn)槭褂玫氖莗rototype。在我們給Husband賦值的時(shí)候需要注入WifeBean,這就需要new一個(gè)新的Wife對(duì)象給Husband,而new出的Wife對(duì)象需要Husband對(duì)象才行,這時(shí)又會(huì)new一個(gè)新的Husband對(duì)象...會(huì)成死循環(huán)。
但如果有一個(gè)Bean的scope是singleton就不會(huì)出錯(cuò)了。比如WIfe是singleton
當(dāng)我們給Husband賦值的時(shí)候,需要Wife對(duì)象,因?yàn)閃ife對(duì)象是單例的只有一個(gè)固定的,會(huì)曝光,Husband對(duì)象就能得到了。當(dāng)我們給Wife對(duì)象賦值的時(shí)候,需要Husband,Husband會(huì)new一個(gè)新的對(duì)象,此時(shí)Husband需要Wife對(duì)象,因?yàn)閃Ife對(duì)象是單例的,直接就可以得到了。
9.4 singleton下的構(gòu)造注入產(chǎn)生的循環(huán)依賴
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>測(cè)試結(jié)果是失敗的,錯(cuò)誤和上面的錯(cuò)一樣,為什么呢?
因?yàn)槭褂脴?gòu)造注入,這要求我們?cè)趯?shí)例化bean的時(shí)候就要給屬性值賦值,但是經(jīng)過(guò)上面那些分析我們就應(yīng)該已經(jīng)明白了,我們是做不到實(shí)例化就立刻給屬性賦值的,這會(huì)造成死循環(huán)。
小小總結(jié)一下:經(jīng)過(guò)上面測(cè)試我們發(fā)現(xiàn)只有在使用set方法注入并且scope的值為singleton的時(shí)候才能避免循環(huán)依賴。
根本原因在于,這種方式可以做到“實(shí)例化Bean”和“給Bean屬性賦值”這兩個(gè)動(dòng)作分開完成。實(shí)例化bean的時(shí)候調(diào)用無(wú)參構(gòu)造來(lái)完成,此時(shí)可以先不給屬性賦值,可以提前將該bean“曝光”給外界。
也就是說(shuō),Bean都是單例的,我們可以先把所有的單例bean實(shí)例化出來(lái),放到一個(gè)集合當(dāng)中(我們可以稱之為緩存)所有的bean都實(shí)例化完成之后,以后我們?cè)俾恼{(diào)用set方法給屬性賦值,這樣就解決了循環(huán)依賴的問(wèn)題。
10 回顧反射機(jī)制
10.1 分析方法四要素
我們隨便創(chuàng)建一個(gè)類:
package com.itzw.reflect;public class Student {private String name;private int age;public void doSome(){System.out.println("doSome無(wú)參方法執(zhí)行");}public String doSome(String name){System.out.println("doSome返回姓名的方法執(zhí)行");return name;} }我們調(diào)用這個(gè)類的方法:
package com.itzw.reflect;public class Test {public static void main(String[] args) {Student student = new Student();student.doSome();String doSome = student.doSome("王德發(fā)");System.out.println(doSome);} }我們發(fā)現(xiàn)調(diào)用一個(gè)類的方法需要四要素:類的對(duì)象;方法名;方法參數(shù);返回值
10.2 使用反射機(jī)制調(diào)用方法
還是那個(gè)類,不過(guò)我們使用反射機(jī)制的方式調(diào)用方法
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);//獲取類的對(duì)象Object obj = clazz.newInstance();//調(diào)用方法Object retValue = doSome.invoke(obj, "張麻子");//輸出返回值System.out.println(retValue);} }10.3 假設(shè)知道屬性名
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 +'}';} }假設(shè)有如上的一個(gè)類,你知道的信息是:
- 類名是:com.itzw.reflect.User
- 該類中有String類型的name屬性和int類型的age屬性
- 另外你知道該類的設(shè)計(jì)符合javabean規(guī)范
知道以上信息如何給屬性賦值呢?
我們這里給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);//獲取對(duì)象Object obj = clazz.newInstance();//調(diào)用方法setMethod.invoke(obj,12);//輸出類System.out.println(obj);} }之所以回顧反射機(jī)制是為了下面手寫spring框架
總結(jié)
以上是生活随笔為你收集整理的SSM框架-Spring(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MySQL课程超级团,值得再提一次。
- 下一篇: js 百度、高德、谷歌、火星、wgs84