Spring注解方式管理Bean
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

作者?|?汪偉俊
出品?|?公眾號(hào):Java技術(shù)迷(JavaFans1024)
雖然Spring以簡(jiǎn)化開(kāi)發(fā)著稱(chēng),但在學(xué)習(xí)的過(guò)程中我們發(fā)現(xiàn),每新建一個(gè)類(lèi),就需要在配置文件中進(jìn)行配置,并且類(lèi)與類(lèi)之間的關(guān)系也需要配置在標(biāo)簽中,好像這并沒(méi)有簡(jiǎn)化我們的開(kāi)發(fā),反而增加了很多繁瑣的配置。別擔(dān)心,本篇文章我們就來(lái)學(xué)習(xí)一下用注解方式來(lái)管理Bean。



組件掃描

大家不要對(duì)組件這個(gè)詞感到陌生,在Spring中,一個(gè)類(lèi)可以被稱(chēng)為Bean,也被稱(chēng)為一個(gè)組件,回想一下,在之前,我們?nèi)绾螌⒁粋€(gè)組件注冊(cè)到IOC容器中呢?沒(méi)錯(cuò),我們需要寫(xiě)一段配置,例如:
"user" class="com.wwj.spring.demo.User">
"name" value="zs"/>
"age" value="22"/>
為了讓大家從繁瑣的配置中解脫出來(lái),Spring提供了一種基于注解的管理方式,Spring提供了以下注解用來(lái)注冊(cè)一個(gè)組件:
1.@Component2.@Controller3.@Service4.@Repository
這四個(gè)注解都可以用來(lái)注冊(cè)一個(gè)組件,不過(guò)每個(gè)注解都有其意義,比如@Controller,它是用來(lái)注冊(cè)一個(gè)前端控制器的,我們將在SpringMVC中對(duì)其進(jìn)行詳解;而@Service是用來(lái)注冊(cè)一個(gè)服務(wù)層對(duì)象的;@Repository是用來(lái)注冊(cè)一個(gè)持久層對(duì)象的。來(lái)體驗(yàn)一下它們的強(qiáng)大吧:
@Component
public class User {
private String name;
private Integer age;
}
public interface UserService {
}
@Service
public class UserServiceImpl implements UserService {
}
@Repository
public interface UserRepository {
}
我們從容器中取出所有的組件,看看注冊(cè)是否成功了:
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
當(dāng)你運(yùn)行這段測(cè)試代碼時(shí)你會(huì)發(fā)現(xiàn)控制臺(tái)沒(méi)有任何輸出,是我們獲取的方式不對(duì)嗎?不對(duì),其實(shí)我們還需要進(jìn)行一項(xiàng)配置:
base-package="com.wwj.spring.demo"/>
運(yùn)行結(jié)果:
user
userRepository
userServiceImpl
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
可以看到我們的組件確實(shí)注冊(cè)到Spring中了,剩下的是一些Spring內(nèi)置的組件,我們無(wú)需關(guān)系。
context:component-scan標(biāo)簽是用來(lái)進(jìn)行組件掃描的,其中base-package屬性用于配置需要掃描的包,一般情況下我們會(huì)掃描項(xiàng)目的頂包,即:最外層的包,這樣所有項(xiàng)目中的組件都會(huì)被掃描到并注冊(cè)。
事實(shí)上,@Component、@Controller、@Service、@Repository四個(gè)注解的作用是完全一樣的,你也可以在組件上隨意地使用它們,比如:
@Repository
public class UserServiceImpl implements UserService {
}
@Service
public interface UserRepository {
}
這是完全沒(méi)有問(wèn)題的,因?yàn)锧Service、@Controller、@Repository注解是由@Component注解衍生出來(lái)的,但為了規(guī)范,還是建議將注解添加到指定的組件上。



自動(dòng)注入

還記得Spring中的屬性注入嗎?如果不記得的話,我們來(lái)回顧一下:
public class User {
private Pet pet;
}
若是想將一個(gè)對(duì)象屬性注入進(jìn)去,我們需要進(jìn)行配置:
"pet" class="com.wwj.spring.demo.Pet"/>
"user" class="com.wwj.spring.demo.User">
"pet" ref="pet"/>
但Spring提供了一種更加便捷的注入方式,自動(dòng)注入:
public class User {
@Autowired
private Pet pet;
}
只需在User類(lèi)的對(duì)象屬性上添加@Autowired注解即可將Pet對(duì)象自動(dòng)注入進(jìn)來(lái),而且它非常智能,我們對(duì)程序進(jìn)行一些改造,首先去掉Pet類(lèi)的@Component注解:
public class Pet {
private String name;
private Integer age;
}
然后添加一個(gè)Dog類(lèi)繼承Pet,并注冊(cè):
@Component
public class Dog extends Pet{
private String name;
private Integer age;
}
來(lái)測(cè)試一下:
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
User user = context.getBean("user", User.class);
System.out.println(user.getPet().getClass());
}
運(yùn)行結(jié)果:
class com.wwj.spring.demo.Dog
這樣Dog類(lèi)就被自動(dòng)注入到User中了,但如果我們又創(chuàng)建了一個(gè)類(lèi)繼承Pet并注冊(cè):
@Component
public class Cat extends Pet{
}
此時(shí)程序就會(huì)報(bào)錯(cuò):
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.wwj.spring.demo.Pet' available: expected single matching bean but found 2: cat,dog
這是Spring中比較常見(jiàn)的一個(gè)異常,意思是期望單個(gè)匹配的Bean:Pet,但是匹配到了兩個(gè)Bean:cat、dog。錯(cuò)誤非常好理解,因?yàn)镻et的子類(lèi)有兩個(gè),所以Spring也不清楚我們到底想要哪一個(gè)Bean,所以拋出了異常。
這一問(wèn)題會(huì)在Service層中出現(xiàn),比如:
public interface UserService {
}
@Service
public class UserServiceImpl implements UserService {
}
@Service
public class EmployeeServiceImpl implements UserService {
}
現(xiàn)在我們有一個(gè)UserService接口,并且有兩個(gè)實(shí)現(xiàn)類(lèi),當(dāng)自動(dòng)注入U(xiǎn)serService時(shí)顯然會(huì)報(bào)錯(cuò),那么如何解決這一問(wèn)題呢?我們可以使用@Qualifier注解:
@Component
public class User {
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;
}
該注解的值即為需要注入的組件名,如果沒(méi)有配置組件名,則默認(rèn)是類(lèi)名且首字母小寫(xiě),當(dāng)然了,我們也可以進(jìn)行配置:
@Service("esi")
public class EmployeeServiceImpl implements UserService {
}
注入方式如下:
@Component
public class User {
@Autowired
@Qualifier("esi")
private UserService userService;
}
這一問(wèn)題也可以使用@Primary注解解決:
@Service
@Primary
public class UserServiceImpl implements UserService {
}
當(dāng)出現(xiàn)多個(gè)類(lèi)型相同的類(lèi)導(dǎo)致Spring無(wú)法選擇時(shí),如果某個(gè)類(lèi)標(biāo)注了@Primary,Spring將優(yōu)先將該組件注冊(cè)到IOC容器,不過(guò)這種方式確實(shí)不太優(yōu)雅。



@Resource注解

剛才的問(wèn)題其實(shí)可以通過(guò)換一個(gè)注解來(lái)解決,我們不妨試試看:
@Component
public class User {
@Resource
private UserService userService;
}
@Resource注解是JSR-250定義的注解,它和Spring沒(méi)有關(guān)系,但能夠?qū)崿F(xiàn)和@Autowired注解相同的功能,我們先來(lái)介紹一下這兩個(gè)注解之間的區(qū)別:
?@Autowired默認(rèn)按類(lèi)型進(jìn)行注入,若要按名稱(chēng)注入,則需要配合@Qualifier注解一起使用;@Resource既支持類(lèi)型注入,也支持名稱(chēng)注入,默認(rèn)為名稱(chēng)注入?@Autowired能夠標(biāo)注在構(gòu)造器、方法、參數(shù)、成員變量、注解上;@Resource只能標(biāo)注在類(lèi)、成員變量和方法上?@Autowired是Spring提供的注解,脫離了Spring框架則無(wú)法使用;@Resource是JSR-250定義的注解,可以脫離任何框架使用
現(xiàn)在問(wèn)題就解決了嗎?其實(shí)并沒(méi)有,當(dāng)你運(yùn)行測(cè)試代碼時(shí)程序仍然會(huì)拋出異常,這是因?yàn)殡m然@Resource默認(rèn)為名稱(chēng)注入,但是在使用名稱(chēng)找不到組件的情況下,會(huì)繼續(xù)使用類(lèi)型注入,所以眼熟的異常就又出現(xiàn)了。
我們已經(jīng)知道,Spring在掃描組件時(shí)會(huì)將類(lèi)名且首字母小寫(xiě)作為組件的名稱(chēng)注入到IOC容器中,所以像這樣注入就是沒(méi)有問(wèn)題的:
@Component
public class User {
@Resource
private UserService userServiceImpl;
}
不過(guò)一般情況下我們不會(huì)這么寫(xiě),而是像這樣:
@Component
public class User {
@Resource(name = "employeeServiceImpl")
private UserService userService;
}
通過(guò)@Resource注解,我們就解決了@Autowired和@Qualifier兩個(gè)注解組合才能解決的問(wèn)題,至于到底用哪個(gè),還是看大家的使用習(xí)慣了。



@Value

可能有同學(xué)有疑問(wèn)了,我知道對(duì)象類(lèi)型的屬性如何注入了,那基本類(lèi)型數(shù)據(jù)如何注入呢?@Value注解能夠幫助到你,使用方法如下:
@Component
public class User {
@Value("zs")
private String name;
@Value("20")
private Integer age;
}
不過(guò)一般情況下,我們都不會(huì)把數(shù)據(jù)這樣寫(xiě)死,都會(huì)將其放到配置文件中:
jdbc.url=jdbc:mysql:///test
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root
此時(shí)需要借助一個(gè)新注解@PropertySource將值注入到指定的組件中:
@Component
@PropertySource("classpath:jdbc.properties")
public class User {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
}
@Value還能夠注入操作系統(tǒng)屬性:
@Component
public class User {
@Value("#{systemProperties['os.name']}")
private String name;
}
還可以注入表達(dá)式計(jì)算后的結(jié)果:
@Component
public class User {
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private String result;
}本文作者:汪偉俊?為Java技術(shù)迷專(zhuān)欄作者?投稿,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載
????
往 期 推 薦
1、來(lái)自谷歌的開(kāi)發(fā)心得:所有SQL和代碼,都沒(méi)必要藏著掖著
2、用了這么久的?Chrome,你不會(huì)還沒(méi)掌握這個(gè)功能吧?
點(diǎn)分享
點(diǎn)收藏
點(diǎn)點(diǎn)贊
點(diǎn)在看





