創(chuàng)建 Maven 項目
添加 Spring 框架支持
在項目的 pom.xml 中添加 Spring 支持
如何選定版本環(huán)境:打開官網(wǎng),點擊github圖標
jdk8最后一個Spring版本是5.3.x,Spring6.0.x最低需要jdk17
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.31</version>
</dependency>
</dependencies>
版本沖突問題Maven自己處理
version : 可以選擇帶有 RELEASE結尾或者純數(shù)字結尾,這樣的版本更穩(wěn)定
添加一個啟動類
項目下創(chuàng)建一個main方法的啟動類
存儲 Bean 對象
- 存儲 Bean 之前,要先有 Bean 才行
- 將創(chuàng)建的 Bean 注冊到 Spring 容器中
這里并非實際意義上的存儲,而是類似于交給 IoC 容器進行托管
在 resources 目錄下添加 spring-config.xml 模板代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
- 從 Spring 中取出 bean 對象
取 bean 需要先得到 Spring 對象(Spring上下文)
獲取并使用 Bean 對象,分為以下3步
- 獲取Spring上下文
- 上下文獲取 Bean
- 使用 Bean
如果獲取多個 Bean 的話,重復以上2,3步驟
兩種方式獲取Spring 上下文對比
Spring 上下文可以使用 ApplicationContext 或者 BeanFactory獲取
ApplicationContext頂層接口
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanFactory頂層接口
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
繼承關系
ApplicationContext 和 BeanFactory 效果是一樣的,BeanFactory 屬于 ApplicationContext 子類,它們區(qū)別如下
-
繼承角度:Spring 容器有兩個頂級接口:BeanFactory 和 ApplicationContext
-
其中 BeanFactory 提供了基礎的訪問容器的能力,而 ApplicationContext 屬于 BeanFactory 的子類,除了 BeanFactory 的所有功能外,它還擁有獨特的特性,添加了對國際化的支持、資源訪問的支持、以及事件傳播等方面的支持
-
性能角度:ApplicationContext 是一次性加載并初始化所有的 Bean 對象「更方便」;BeanFactory 是按需加載并初始化「更輕量」
ClassPathXmlApplicationContext 屬于 ApplicationContext 的子類,擁有 ApplicationContext 的所有功能是通過 xml 的配置來獲取所有的 Bean 容器的
獲取指定的 Bean 對象
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
User user = (User) context.getBean("user");
user.sayHi();
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("Spring-config.xml"));
User user1 = (User) beanFactory.getBean("user");
user1.sayHi();
// 運行結果
你好
你好
注意事項
Bean 的 id 要一一對應
<beans>
<bean id="user" class="Beans.User"></bean>
</beans>
User user = (User) context.getBean("user");
User user1 = (User) beanFactory.getBean("user");
getBean 方法的更多用法
getBean() 方法很多重載,我們也可以使用其他方法來獲取 Bean 對象
先看一下 getBean() 源碼
getBean(String, Class): 先根據(jù) id 匹配,再根據(jù)Class匹配
當有兩個相同 id 的時候利用此方法查詢會 報錯Nouni
get(Class, String): 先根據(jù)Class匹配,再根據(jù) id 匹配
當有兩個的Bean,利用此方法,則會 報錯Nouni
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
User user = context.getBean(User.class);
User user1 = context.getBean("user1", User.class);
二者的區(qū)別
當有多個重復的對象被注冊到 Bean 中的時候,只能通過 id屬性 來獲取
<beans>
<bean id="user" class="Beans.User"></bean>
<bean id="user1" class="Beans.User"></bean>
<bean id="user2" class="Beans.User"></bean>
</beans>
如果繼續(xù)使用 context.getBean(User.class)
就會報錯。應該搭配 id 一起使用
總結Spring使用流程
- 操作容器之前,先要有容器
- 存對象
- 創(chuàng)建 Bean「普通類」
- 將 Bean 注冊「配置」到 Spring-config.xml 中
- 取對象
- 得到 Spring 上下文,并讀取到 Spring 的配置文件
- 獲取某一個 Bean 對象
- 使用 Bean 對象
配置掃描路徑配合注解進行存儲Bean對象
在上述操作過程中,我們發(fā)現(xiàn)存儲對象并沒有想象中的那么 簡單,所以就有了更簡單的操作 Bean對象 的方法
Spring 更簡單的存儲對象和讀取對象核心是使用注解
之前我們還需要在 Spring 的配置文件 spring-config.xml 中添加一行 bean
注冊內容才行
因此 Spring 中為了方便注冊,我們只需要配置一個存儲對象的掃描包即可「目錄中的所有 Bean 被添加注解后都會被注冊到 Spring 容器中」
配置掃描路徑代碼模板
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="/Beans"></content:component-scan>
</beans>
對比之前的 Spring-config.xml 發(fā)現(xiàn)多了標紅的兩行,這兩行就是注冊掃描的包
也就是說,即使添加了注解,如果不是在配置的掃描報下的類對象,也是不能被存儲的 Spring 中的
五大類注解
想把掃描包中的 Bean 添加到 Spring,由兩類注解類型可以實現(xiàn)
- 類注解:@Controller、@Service、@Repository、@Component、@Configuration
- 方法注解:@Bean「必須結合類注解才能起效」
這么多注解的原因
我們發(fā)現(xiàn)它們的功能是一樣的,都能達到 添加注解注冊Bean 的功能,那么為何還要有這么多注解類型呢?
這就和每個省市都有自己的車牌號一樣。全國的各個地區(qū)買的相同類型的車都是一樣的,但是車牌號卻不同:湖北的車有鄂A:武漢的車;鄂B:黃石的車;鄂C:十堰的車。。。這樣做的好處就是節(jié)約了車牌號以外還可以查看車輛的歸屬地方便車管局管理。
那么為什么需要這么多的類注解也是一樣的原因,就是讓程序員看到類注解之后就能直接了解當前類的用途
- @Controller:業(yè)務邏輯層
- @Service:服務層
- @Repository:持久層
- @Configuration:配置層
程序的工程分層調用流程如下:
@Component是其它注解的父類
發(fā)現(xiàn)這 4個 注解里都有一個注解 @Component,說明他們本身就是屬于 @Component子類
在看 @Component的源碼
源碼溯源就到此為止了,只需要了解它們四個實現(xiàn)了@Component接口即可
Bean 的命名源碼
通過注解注冊的bean名稱有自己的一套默認規(guī)則:
- 第一第二首字母是大寫的話就是原類名
- 其余是小駝峰方式命名
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
// 如果類名長度大于1,并且前倆字母都是大寫就直接返回原類名
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))) {
return name;
}
// 否則就是將首字母小寫再返回
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
方法注解
類注解是添加到某個類上的,而方法注解是放到某個方法上的
@Bean要搭配類注解一起使用才可以將方法存儲到 Spring 中
model層
package app.model;
public class User {
private Integer id;
private String userName;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
service層
package app.service;
import app.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void doUserService() {
System.out.println("doUserService.");
}
public User getUser() {
User user = new User();
user.setId(1);
user.setUserName("張三");
user.setAge(18);
return user;
}
}
controller層
package app.controller;
import app.model.User;
import app.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
// 也可以通過 xml 中 bean 標簽注冊
@Controller
public class UserController {
@Autowired
private UserService userService;
public void doUserController() {
System.out.println("doUserController.");
}
@Bean
public User getUser() {
return userService.getUser();
}
}
獲取@Bean對象:bean名稱采取的是方法名而非類名
// 1.獲取 Spring 上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
// @Bean方法注解
User user = beanFactory.getBean("getUser", User.class);
重命名 Bean
同一項目下類名一般不會出現(xiàn)重復但方法名有意外情況
- 不同類名出現(xiàn)同一個方法名
- 同一類中出現(xiàn)方法重載
此時如果調用會報錯,因為@Bean注解無法添加參數(shù)
查看 @Bean 源碼會發(fā)現(xiàn) name 是一個數(shù)組,因此可以填入很多bean的名稱。但一般一個就夠。
3種方式給Bean重命名。重命名之后就不能通過方法名獲取Bean對象了
獲取Bean對象(對象裝配)
回顧之前在存儲bean對象之后是如何獲取的
- new
- 先得到Spring上下文,再通過getBean獲取
- 更簡單的獲取方式:對象注入
這里就講解一下對象注入
對象裝配:獲取 Bean 對象
對象注入:把對象取出來放在某個類中
對象注入有 3 種方法
- 屬性注入
- 構造方法注入
- Setter注入
屬性注入
@Autowired: 現(xiàn)根據(jù)類型查找再根據(jù)名稱查找。所以bean的名稱隨意給都可以,這里按照標準給的是 userService
優(yōu)點:寫法簡單
缺點:
- 功能缺陷:不支持final修飾
- 通用性問題:只適用于IoC容器,非IoC容器無法使用
- 更容易違背單一設計原則(因為使用簡單,所以濫用風險更大)
Setter 注入
優(yōu)點:符合單一設計原則
缺點:
- 功能缺陷:不能諸如一個不可變對象【不能被final修飾】
- 注入對象可能會被改變
構造方法注入
final不加也可以,對于專業(yè)版IDEA會提示進行final修飾
當當前類中只有一個構造方法的時候可以省略@Autowired
優(yōu)點:
- 支持final修飾注入不可變對象
- 注入對象不會被更改
- 完全初始化
- 通用性更好
另外一種對象裝配的關鍵字:@Resource
僅支持兩種裝配方式,不支持構造方法注入。用法和@Autowired一樣
- 屬性注入
- Setter注入
@Resource支持更多的參數(shù)【JDK自帶】
經(jīng)常用的是name參數(shù),如果Spring中 @Bean 注解存儲的Bean對象制定了名稱,那么此時可以通過name屬性用于指定獲取對應名稱的bean
同一個類型注入多個Bean對象
獲取Bean對象的時候需要指定bean名稱,否則會在運行時報錯
此時因為是user1,@Autowired現(xiàn)根據(jù)type查找會發(fā)現(xiàn)有多個User類型的Bean對象然后再根據(jù)name查找就會獲取到user1的bean對象
此時對于開發(fā)而言,user1這樣的明明肯定不符合規(guī)定,因此需要一個修改一下bean的名字才行。所以引入了@Resource注解
但是@Resource注解由于不支持構造方法注入,如果仍然想用Spring的@Autowired注解使用構造方法注入就需要引入一個新注解解決名字的問題:@Qualifier
@Autowired VS @Resource
@Autowired | @Resouce | |
---|---|---|
來源 | 來自Spring | 來自 JDK |
使用時參數(shù)不同 | 搭配@Qualifier獲取指定名稱bean | 支持更多的參數(shù),可以設置 name 來獲取指定 Bean |
修飾對象不同 | 修飾屬性,構造方法,Setter | 修飾屬性,Setter |
Bean的生命周期
對象注入
對象獲取
運行結果
修改前: Dog{id=1, name='旺財', age=1}
修改后: Dog{id=1, name='喵喵', age=1}
后續(xù)獲取: Dog{id=1, name='喵喵', age=1}
會發(fā)現(xiàn)由于單例模式,導致公共的Bean對象被修改之后,后續(xù)獲取的Bean都會被修改
因此就引入Bean的作用域【從之前的代碼區(qū)域提升到現(xiàn)在框架區(qū)域】
- singleton: 單例
Spring默認作用域,通常無狀態(tài)的Bean使用該作用域無狀態(tài):表示該Bean對象的屬性狀態(tài)不需要更新
- prototype: 原型【多例】
通常有狀態(tài)的Bean使用該作用域 - request: 請求
每次http請求會創(chuàng)建新的Bean,同一次http請求中創(chuàng)建的bean是同一個Bean【限定Spring MVC中使用】 - session: 會話
同一個http session中創(chuàng)建的Bean是同一個Bean【限定Spring MVC中使用】 - application: 全局
在同一個Spring上下文中創(chuàng)建的Bean是同一個Bean【限定Spring MVC中使用】 - websocket: HTTP WebSocket作用域
在一個WebSocket的生命周期中創(chuàng)建的Bean是同一個Bean【限定Spring WebSocket中使用】
設置的方式有兩種
此時運行結果
修改前: Dog{id=1, name='旺財', age=1}
修改后: Dog{id=1, name='喵喵', age=1}
后續(xù)獲取: Dog{id=1, name='旺財', age=1}
Bean的生命周期文章來源:http://www.zghlxwxcb.cn/news/detail-820164.html
- 實例化【≠初始化,僅僅是分配內存空間】
- 設置屬性【DI依賴注入】
- 初始化
- 執(zhí)行各種通知
- 初始化的前置方法
xml:義init-method
注解:@PostConstruct注解 - 初始化方法
- 初始化后置方法
- 使用Bean
- 銷毀Bean
會發(fā)現(xiàn)注解的執(zhí)行順序先于xml文章來源地址http://www.zghlxwxcb.cn/news/detail-820164.html
Spring 主要執(zhí)行流程
- 啟動容器
- Bean初始化
配置文件中的掃描路徑,掃描路徑下的類 - 注入Bean
掃描路徑下將帶有5大類注解的類添加進Spring IoC容器中 - 使用Bean
@Bean、@Autowired、@Resource注解將對應的方法、類成員進行裝配 - 銷毀Bean
- 關閉容器
到了這里,關于Spring中Bean對象的存儲與讀取的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!