Spring使用注解存儲(chǔ)Bean對(duì)象的方法詳解
通過(guò)配置文件注冊(cè)對(duì)象從而存儲(chǔ)到 Spring 中,這種方式其實(shí)還是挺繁瑣的。實(shí)際上,在使用學(xué)習(xí)使用Spring過(guò)程中,當(dāng)我們要實(shí)現(xiàn)一個(gè)功能的時(shí)候,先應(yīng)該考慮的是有沒(méi)有相應(yīng)的注解是實(shí)現(xiàn)對(duì)應(yīng)功能的,Spring 中很多功能的配置都是可以依靠注解實(shí)現(xiàn)的,而本篇中介紹的是使用注解來(lái)存儲(chǔ) Bean 對(duì)象。
一. 配置掃描路徑
首先還是要?jiǎng)?chuàng)建 Spring 項(xiàng)目,這里有問(wèn)題還是去看我上一篇博客。當(dāng)創(chuàng)建好項(xiàng)目后,我們的第一步就是配置掃描路徑,這一步驟非常關(guān)鍵的,這里錯(cuò)了,之后的的操作就都不會(huì)生效了。
我們?cè)?code>resources目錄下創(chuàng)建一個(gè)spring-config.xml
配置文件,用來(lái)設(shè)置掃描的路徑,在配置文件中添加如下內(nèi)容:
<?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=""></content:component-scan> </beans>
其中<content:component-scan base-package=""></content:component-scan>
里面 base-package
的值設(shè)置為你需要掃描對(duì)象的根路徑,這個(gè)路徑從java
目錄開(kāi)始,比如我在如圖中的com.tr.demo
目錄下創(chuàng)建類(lèi):
那么這個(gè)配置文件中根路徑就為com.tr.demo
,所以我們將base-package
的值設(shè)置為com.tr.demo
。
<content:component-scan base-package="com.tr.demo"></content:component-scan>
二. 使用注解儲(chǔ)存Bean對(duì)象
想要使用注解,那得先知道能使用哪些注解,在 Spring 中有五大類(lèi)注解和方法注解,分別為:
- 五大類(lèi)注解:@Controller(控制器)、@Service(服務(wù))、@Repository(倉(cāng)庫(kù))、@Component(組件)、@Configuration(配置)。
- 方法注解:@Bean。
1. 使用五大類(lèi)注解儲(chǔ)存Bean
首先,我們來(lái)了解如何使用五大類(lèi)注解來(lái)儲(chǔ)存對(duì)象,先以@Controller
注解為例,我們有如下的代碼:
package com.tr.demo; import org.springframework.stereotype.Controller; @Controller public class UserController { public void sayHi() { System.out.println("Hi, UserController~"); } }
像這樣在掃描路徑下創(chuàng)建類(lèi),并在類(lèi)上加上@Controller
注解就將Bean
存儲(chǔ)到容器當(dāng)中了。
接下來(lái)就要從 Spring 中讀取出我們的對(duì)象,這里還是先使用依賴(lài)查找的方式來(lái)獲取 Bean,使用五大類(lèi)注解,默認(rèn)情況下,Bean 的名字就是原類(lèi)名首字母小寫(xiě)(小駝峰)。
import com.tr.demo.UserController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class APP { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); //獲取對(duì)象時(shí)使用類(lèi)名的小駝峰形式作為 name 參數(shù) UserController userController = context.getBean("userController", UserController.class); userController.sayHi(); } }
運(yùn)行結(jié)果:
要注意,是使用了五大類(lèi)注解創(chuàng)建的類(lèi)且類(lèi)必須要在前面我們配置的掃描路徑下(包括子包)才能將 Bean 存儲(chǔ)到 Spring 當(dāng)中,否則是無(wú)效的,所以這個(gè)掃描路徑也叫做根路徑。
設(shè)置根路徑其實(shí)也是為了提高程序的性能,因?yàn)槿绻辉O(shè)置根路徑,Spring 就會(huì)掃描項(xiàng)目文件中所有的目錄,但并不是所有類(lèi)都需要儲(chǔ)存到 Spring當(dāng)中,這樣性能就會(huì)比較低,設(shè)置了根路徑,Spring 就只掃描該根路徑下所有的目錄就可以了,提高了程序的性能。
上面只使用了 @Controller,那么我們?cè)賮?lái)驗(yàn)證一下其他四個(gè)注解可不可以達(dá)到同樣的目的,同時(shí)為了驗(yàn)證上面的結(jié)論,我們?cè)?code>com.tr.demo目錄下再創(chuàng)建一個(gè)inner
目錄,在根路徑外在創(chuàng)建一個(gè)類(lèi)Student
使用類(lèi)注解。
package com.tr.demo.inner; import org.springframework.stereotype.Component; @Component public class UserComponent { public void sayHi() { System.out.println("Hi, UserComponent~"); } } package com.tr.demo.inner; import org.springframework.context.annotation.Configuration; @Configuration public class UserConfiguration { public void sayHi() { System.out.println("Hi, UserConfiguration~"); } } package com.tr.demo.inner; import org.springframework.stereotype.Repository; @Repository public class UserRepository { public void sayHi() { System.out.println("Hi, UserRepository~"); } } package com.tr.demo.inner; import org.springframework.stereotype.Service; @Service public class UserService { public void sayHi() { System.out.println("Hi, UserService~"); } } import com.tr.demo.UserController; import com.tr.demo.inner.UserComponent; import com.tr.demo.inner.UserConfiguration; import com.tr.demo.inner.UserRepository; import com.tr.demo.inner.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class APP { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); //獲取對(duì)象時(shí)使用類(lèi)名的小駝峰形式作為 name 參數(shù) UserController userController = context.getBean("userController", UserController.class); userController.sayHi(); UserService service = context.getBean("userService", UserService.class); service.sayHi(); UserConfiguration configuration = context.getBean("userConfiguration", UserConfiguration.class); configuration.sayHi(); UserComponent component = context.getBean("userComponent", UserComponent.class); component.sayHi(); UserRepository repository = context.getBean("userRepository", UserRepository.class); repository.sayHi(); } }
運(yùn)行結(jié)果:
五大類(lèi)注解效果都是一樣的,而不在根路徑下的Student
是無(wú)效的。
還需要知道的是使用注解存儲(chǔ)的 Bean 和使用XML
存儲(chǔ)的的 Bean 是可以一同使用的,比如我們將將剛剛有問(wèn)題的Student
重新通過(guò)XML
的方式進(jìn)行存儲(chǔ)。
運(yùn)行結(jié)果:
2. 為什么要有五大類(lèi)注解?
既然都五大類(lèi)完成的是同樣的工作,那為什么要有五大類(lèi)注解呢?
其實(shí)五大類(lèi)注解主要是為了規(guī)范 Java 項(xiàng)目的代碼,Java 項(xiàng)目的標(biāo)準(zhǔn)分層如下:
- 控制層(Controller)
- 服務(wù)層(Service)
- 數(shù)據(jù)持久層(Dao)
而五大類(lèi)注解便是對(duì)應(yīng)著不同的層級(jí)別使用的,讓程序猿看到某一個(gè)注解就可以明確這個(gè)了類(lèi)是做什么的。
- @Controller:控制器,校驗(yàn)用戶(hù)請(qǐng)求數(shù)據(jù)的正確性(安保系統(tǒng));直接和前端打交道,校驗(yàn)前端發(fā)來(lái)請(qǐng)求是參數(shù)和合法性。
- @Service:服務(wù),編排和調(diào)度具體執(zhí)行方法的(客服中心);不會(huì)直接操作數(shù)據(jù)庫(kù),根據(jù)請(qǐng)求判斷具體調(diào)用哪個(gè)方法。
- @Repository:數(shù)據(jù)持久層,直接和數(shù)據(jù)庫(kù)交互(實(shí)際業(yè)務(wù)的執(zhí)行),也叫DAO層(data access object)。
- @Component:組件(工具類(lèi)層),為整個(gè)項(xiàng)目存放一些需要使用的組件,但又和其他層沒(méi)有什么實(shí)際交互。
- @Configuration 配置項(xiàng)(項(xiàng)目中的一些配置)。
包括企業(yè)中也是按照這樣的結(jié)構(gòu)來(lái)將項(xiàng)目分層的,典型的比如阿里,它只是在標(biāo)準(zhǔn)分層在服務(wù)層(Service)做了一個(gè)擴(kuò)展,劃分的更加細(xì)致詳細(xì)了。
五大類(lèi)注解主要起到的是“見(jiàn)名知意”的作用,代碼層面上來(lái)看,作用是類(lèi)似的,我們?nèi)ゲ榭次宕箢?lèi)類(lèi)注解的源碼看一看。
可以看到五大類(lèi)的源碼中除了 @Component 以外,其他四大類(lèi)注解中都包含了 @Component 注解的功能,這四大類(lèi)注解都是基于 @Component 實(shí)現(xiàn)的,是 @Component 拓展。
3.有關(guān)獲取Bean參數(shù)的命名規(guī)則
上文中在使用依賴(lài)查找的方式獲取Bean
時(shí),getBean
方法的BeanName
是使用類(lèi)名的小駝峰形式(即類(lèi)名的首字母小寫(xiě)),這是因?yàn)槭褂米⒔鈨?chǔ)存對(duì)象時(shí),默認(rèn)會(huì)將類(lèi)名的小駝峰形式設(shè)置為 Bean 的名字,但并不是完全依照這個(gè)規(guī)則的,是有特殊情況的。
比如,我們創(chuàng)建一個(gè)類(lèi),將它的前兩個(gè)字母大寫(xiě),如UConfig
,此時(shí)來(lái)看使用類(lèi)名的小駝峰形式還能不能獲取到 Bean。
package com.tr.demo; import org.springframework.stereotype.Repository; @Repository public class UConfig { public void sayHi(){ System.out.println("Hi, UConfig~"); } }
啟動(dòng)類(lèi)
import com.tr.demo.UConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class APP2 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); UConfig uConfig= context.getBean("uConfig", UConfig.class); uConfig.sayHi(); } }
運(yùn)行結(jié)果:
可以看到程序報(bào)錯(cuò)了,說(shuō)沒(méi)有找到beanName
為uConfig
的對(duì)象,那此時(shí)的beanName
是什么呢?
此時(shí)再來(lái)試一下原本的類(lèi)名(大駝峰),看能不能獲?。?/p>
UConfig uConfig= context.getBean("UConfig", UConfig.class);
運(yùn)行結(jié)果:
此時(shí)就獲取到了,好像多少有點(diǎn)玄學(xué)在里面,我們來(lái)翻一翻源碼,看一看這是什么原因。
雙擊Shift
進(jìn)行全局搜索,上面是根于對(duì)象名稱(chēng)來(lái)找到對(duì)象的,所以我們輸入beanName
,試著搜索一下:
我們會(huì)發(fā)現(xiàn)有AnnotationBeanNameGenerator
類(lèi)與BeanNameGenerator
接口,那我們就試著點(diǎn)到AnnotationBeanNameGenerator
類(lèi)源碼看一看。
正常點(diǎn)開(kāi)后看到的應(yīng)該是 IDEA 將.class
文件反編譯出來(lái)的代碼,缺少注釋和明確的變量命名。
我們點(diǎn)擊Download Sources
將 Spring 源碼下載下來(lái)即可,此時(shí)我們?cè)谠谠创a中就能看到下面的方法,看名字也知道,用來(lái)建立默認(rèn)的BeanName
的。
返回值是Introspector.decapitalize
方法的返回值,再點(diǎn)進(jìn)去看看這個(gè)方法。
此時(shí)我們就能分析得出結(jié)論,如果類(lèi)名長(zhǎng)度大于1
并且滿(mǎn)足第一個(gè)與第二個(gè)字母為大寫(xiě),則構(gòu)造的BeanName
就為原類(lèi)名,其他正常情況為類(lèi)名的小駝峰形式,這就解釋了UConfig
類(lèi)的BeanName
為什么是原類(lèi)名了。
而且我們會(huì)發(fā)現(xiàn)這個(gè)方法所在類(lèi)是來(lái)自于jdk
的。
所以,BeanName
的規(guī)范命名規(guī)則并不是 Spring 獨(dú)創(chuàng)的,而依照 Java 標(biāo)準(zhǔn)庫(kù)的規(guī)則進(jìn)行的。
- 如果類(lèi)名不存在或類(lèi)名為空字符串,
BeanName
為原類(lèi)名。 - 如果類(lèi)名字長(zhǎng)度大于1,且第一個(gè)與第二個(gè)字符為大寫(xiě),
BeanName
為原類(lèi)名。 - 其他情況,
BeanName
為原類(lèi)名的小駝峰形式。
三. 使用方法注解儲(chǔ)存Bean對(duì)象
1. 方法注解儲(chǔ)存對(duì)象的用法
五大類(lèi)注解是添加到某個(gè)類(lèi)上的,而方法注解是放到方法上的,當(dāng)一個(gè)方法返回的是一個(gè)具體的實(shí)例對(duì)象時(shí),我們就可以使用方法注解@Bean
來(lái)將對(duì)象儲(chǔ)存到 Spring,但是單單使用一個(gè)@Bean
是不能夠成功儲(chǔ)存對(duì)象的,還需要在方法所在類(lèi)上使用五大類(lèi)注解才行,比如搭配一 個(gè)@Component 注解,方法注解是不能夠單獨(dú)使用的,@Bean
注解必須要搭配五大類(lèi)注解一起使用(Spring為了提升性能所做的規(guī)定,畢竟造方法的成本太低了,不能去掃描整個(gè)項(xiàng)目的方法吧)。
還是要注意使用必須是在根路徑下。
比如我們有一個(gè)普通文章的實(shí)體類(lèi)ArticleInfo
package com.tr.demo.model; import java.time.LocalDateTime; /** * 普通的文章實(shí)體類(lèi) */ public class ArticleInfo { private int aid; private LocalDateTime createtime; private String title; private String author; private String content; public void setAid(int aid) { this.aid = aid; } public void setCreatetime(LocalDateTime createtime) { this.createtime = createtime; } public void setTitle(String title) { this.title = title; } public void setAuthor(String author) { this.author = author; } public void setContent(String content) { this.content = content; } @Override public String toString() { return "ArticleInfo{" + "aid=" + aid + ", createtime=" + createtime + "\n" + ", title='" + title + '\'' + ", author='" + author + '\'' + "\n" + ", content='" + content + '\'' + '}'; } }
下面演示使用@Bean
方法注解儲(chǔ)存對(duì)象
package com.tr.demo; import com.tr.demo.model.ArticleInfo; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; import java.time.LocalDateTime; @Controller public class Articles { @Bean// 將當(dāng)前方法返回的對(duì)象存儲(chǔ)到 IoC 容器 public ArticleInfo getArt(){ // 偽代碼(實(shí)際上這里的 Bean 不是 new 出來(lái)的) ArticleInfo articleInfo = new ArticleInfo(); articleInfo.setAid(1); articleInfo.setCreatetime(LocalDateTime.now()); articleInfo.setTitle("夏日絕句"); articleInfo.setAuthor("李清照"); articleInfo.setContent("生當(dāng)做人杰,死亦為鬼雄。至今思項(xiàng)羽,不肯過(guò)江東。"); return articleInfo; } public void sayHi(){ System.out.println("Hi, Articles~"); } }
獲取方法注解儲(chǔ)存的對(duì)象時(shí),傳入的BeanName
參數(shù)值默認(rèn)值就是方法名,我上面的代碼中方法名為getArt
,所以獲取時(shí),就使用getArt
作為參數(shù)來(lái)進(jìn)行獲取。
import com.tr.demo.model.ArticleInfo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class APP3 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); ArticleInfo article = context.getBean("getArt", ArticleInfo.class); System.out.println(article); } }
運(yùn)行結(jié)果:
2. @Bean的重命名
獲取方法注解儲(chǔ)存的對(duì)象時(shí),傳入的BeanName
參數(shù)值默值為方法名,但像上面那樣返回對(duì)象的方法名稱(chēng)往往是getXXX
這樣式取名的,雖然在語(yǔ)法與實(shí)現(xiàn)上是沒(méi)有問(wèn)題的,但實(shí)際開(kāi)發(fā)寫(xiě)出這樣的代碼,看起來(lái)還是比較別扭的。
實(shí)際上注解 @Bean 是可以加參數(shù)的,給儲(chǔ)存的對(duì)象起別名,像下面這個(gè)樣子。
@Controller public class Articles { @Bean("article")// 將當(dāng)前方法返回的對(duì)象存儲(chǔ)到 IoC 容器 public ArticleInfo getArt(){ // 偽代碼(實(shí)際上這里的 Bean 不是 new 出來(lái)的) ArticleInfo articleInfo = new ArticleInfo(); articleInfo.setAid(1); articleInfo.setCreatetime(LocalDateTime.now()); articleInfo.setTitle("夏日絕句"); articleInfo.setAuthor("李清照"); articleInfo.setContent("生當(dāng)做人杰,死亦為鬼雄。至今思項(xiàng)羽,不肯過(guò)江東。"); return articleInfo; } public void sayHi(){ System.out.println("Hi, Articles~"); } }
也可以給 Bean 設(shè)置多個(gè)別名,總結(jié)起來(lái)有如下幾種方式:
//方式一(省略參數(shù)名的情況下默認(rèn)是name) @Bean("article1") //方式二 @Bean(name = "article2") //方式三 @Bean(value = "article3") //起多個(gè)別名 @Bean(name = {"article4", "article5"}) @Bean(value = {"article6", "article7"}) @Bean({"article8", "article9", "article10"})
我們按照第 9 行的方式設(shè)置,此時(shí)獲取方法注解儲(chǔ)存的對(duì)象就能夠使用別名來(lái)進(jìn)行獲取。
import com.tr.demo.model.ArticleInfo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class APP4 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); ArticleInfo article1 = context.getBean("article6", ArticleInfo.class); System.out.println(article1); System.out.println("-----------------------------------------------------"); ArticleInfo article2 = context.getBean("article7", ArticleInfo.class); System.out.println(article2); System.out.println("-----------------------------------------------------"); } }
運(yùn)行結(jié)果:
再想一下,當(dāng)一個(gè) Bean 有別名了,那使用之前那個(gè)方法名還能夠獲取到對(duì)象嗎?嘗試一下:
import com.tr.demo.model.ArticleInfo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class APP5 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); ArticleInfo article = context.getBean("getArt", ArticleInfo.class); System.out.println(article); } }
運(yùn)行結(jié)果:
此時(shí)就能發(fā)現(xiàn)是獲取不到的
所以使用 @Bean 存儲(chǔ)對(duì)象的beanName
命名規(guī)則是,當(dāng)沒(méi)有設(shè)置name/value
屬性時(shí),此時(shí) Bean 的默認(rèn)名字就是方法名,一旦添加了別名name/value
屬性后,就只能通過(guò)重命名的別名來(lái)獲取 Bean 了,默認(rèn)的使用方法名獲取 Bean 對(duì)象就不能使用了。
還要簡(jiǎn)單注意一下,@Bean 使用時(shí),同一類(lèi)如果多個(gè) Bean 使用相同的名稱(chēng),此時(shí)程序執(zhí)行是不會(huì)報(bào)錯(cuò)的,他會(huì)根據(jù)類(lèi)加載順序和類(lèi)中代碼從上至下的的順序,將第一個(gè) Bean 存放到 Spring 中,但第一個(gè)之后的對(duì)象就不會(huì)被存放到容器中了,也就是只有在第一次創(chuàng)建 Bean 的時(shí)候會(huì)將對(duì)象和 Bean 名稱(chēng)關(guān)聯(lián)起來(lái),后續(xù)再有相同名稱(chēng)的Bean存儲(chǔ)時(shí)候,容器會(huì)自動(dòng)忽略。
還可以通過(guò)類(lèi)注解 @Order
注解控制類(lèi)加載順序(值越小,優(yōu)先級(jí)越高),進(jìn)而影響 Bean 的存放的先后順序,這些也比較簡(jiǎn)單,就不做演示了。
以上就是Spring使用注解存儲(chǔ)Bean對(duì)象的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring注解存儲(chǔ)Bean的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
火遍全網(wǎng)的Hutool使用Builder模式創(chuàng)建線(xiàn)程池的方法
這篇文章主要介紹了火遍全網(wǎng)的Hutool使用Builder模式創(chuàng)建線(xiàn)程池的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03java中instanceof與Class的等價(jià)性代碼示例
這篇文章主要介紹了java中instanceof與Class的等價(jià)性代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01Jmeter參數(shù)化獲取序列數(shù)據(jù)實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了Jmeter參數(shù)化獲取序列數(shù)據(jù)實(shí)現(xiàn)過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07eclipse啟動(dòng)一個(gè)Springboot項(xiàng)目
本文主要介紹了eclipse啟動(dòng)一個(gè)Springboot項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08學(xué)習(xí)Java中的日期和時(shí)間處理及Java日歷小程序的編寫(xiě)
這篇文章主要介紹了學(xué)習(xí)Java中的日期和時(shí)間處理及Java日歷小程序的編寫(xiě),這個(gè)日歷小程序僅用簡(jiǎn)單的算法實(shí)現(xiàn)沒(méi)有用到date類(lèi)等,但是帶有圖形界面,需要的朋友可以參考下2016-02-02JAVA Frame 窗體背景圖片,首位相接滾動(dòng)代碼實(shí)例
這篇文章主要介紹了JAVA Frame 窗體背景圖片,首位相接滾動(dòng)代碼示例,需要的朋友可以參考下復(fù)制代碼2017-04-04java實(shí)現(xiàn)PDF轉(zhuǎn)圖片的方法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)PDF轉(zhuǎn)圖片的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07