Spring學習(五)Spring 基於註解裝配Bean
Spring 基於註解裝配Bean
前言
在 Spring 中,儘管可以使用 XML 配置檔案實現 Bean 的裝配工作,但如果應用中 Bean 的數量較多,會導致 XML 配置檔案過於臃腫,從而給維護和升級帶來一定的困難。
Java 從 JDK 5.0 以後,提供了 Annotation(註解)功能,Spring 2.5 版本開始也提供了對 Annotation 技術的全面支援,我們可以使用註解來配置依賴注入。
Spring 預設不使用註解裝配 Bean,因此需要在配置檔案中新增 <context:annotation-config/>
,啟用註解。
- 註解:就是一個類,使用@註解名稱
- 開發中:使用註解 取代 xml配置檔案。
什麼是註解
Annontation是Java5開始引入的新特徵,中文名稱叫註解。
它提供了一種安全的類似註釋的機制,用來將任何的資訊或元資料(metadata)與程式元素(類、方法、成員變數等)進行關聯。為程式的元素(類、方法、成員變數)加上更直觀更明瞭的說明,這些說明資訊是與程式的業務邏輯無關,並且供指定的工具或框架使用。Annontation像一種修飾符一樣,應用於包、型別、構造方法、方法、成員變數、引數及本地變數的宣告語句中。
Java註解是附加在程式碼中的一些元資訊,用於一些工具在編譯、執行時進行解析和使用,起到說明、配置的功能。註解不會也不能影響程式碼的實際邏輯,僅僅起到輔助性的作用。包含在 java.lang.annotation 包中。
註解的用處:
1、生成文件。這是最常見的,也是java 最早提供的註解。常用的有@param @return 等
2、跟蹤程式碼依賴性,實現替代配置檔案功能。比如Dagger 2依賴注入,未來java開發,將大量註解配置,具有很大用處;
3、在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出。
更多註解資訊點選這裡-JAVA 註解的基本原理 - Single_Yam - 部落格園 (cnblogs.com)
為什麼要使用註解裝配Bean
傳統的Spring做法是使用.xml檔案來對bean進行注入或者是配置aop、事物,這麼做有兩個缺點:
- 如果所有的內容都配置在.xml檔案中,那麼.xml檔案將會十分龐大;如果按需求分開.xml檔案,那麼.xml檔案又會非常多,總之這將導致配置檔案的可讀性與可維護性變得很低。
- 在開發中在.java檔案和.xml檔案之間不斷切換,是一件麻煩的事,同時這種思維上的不連貫也會降低開發的效率。
為了解決這兩個問題,Spring引入了註解,通過"@XXX"的方式,讓註解與Java Bean緊密結合,大大減少了配置檔案的體積,又增加了Java Bean的可讀性與內聚性。
使用Spring註解
0.啟用元件掃描
1)使用XML進行掃描
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 元件掃描,掃描含有註解的類 -->
<context:component-scan base-package="com.spring.learn2" />
</beans>
配置屬性<context:component-scan base-package="com.spring.learn2" />
其中`base-package
2) @ComponentScan註解啟用了元件掃描
package com.spring.learn2.config;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class BeanConfig {}
package com;
import com.spring.learn2.config.BeanConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(BeanConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
System.out.println("beanName: " + beanName);
}
}
}
執行效果
beanName: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
beanName: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
beanName: org.springframework.context.annotation.internalCommonAnnotationProcessor
beanName: org.springframework.context.event.internalEventListenerProcessor
beanName: org.springframework.context.event.internalEventListenerFactory
beanName: beanConfig
除了 spring 本身註冊的一些 bean 之外,可以看到最後一行,已經將 BeanConfig 這個類註冊進容器中了。
建立一個配置類,在配置類上新增 @ComponentScan 註解。該註解預設會掃描該類所在的包下所有的配置類,相當於之前的 <context:component-scan>
。
類BeanConfig通過Java程式碼定義了Spring的裝配規則。觀察可知,BeanConfig類並沒有顯式地宣告任何bean,只不過它使用了@ComponentScan註解,這個註解能夠在Spring中啟用元件掃描
如果沒有其他配置的話,@ComponentScan預設會掃描與配置類相同的包。因為BeanConfig類位於com.spring.learn2.config包中,因此Spring將會掃描這個包以及這個包下的所有子包,查詢帶有@Component註解的類。這樣的話,就能發現使用了註解的類,並且會在Spring中自動為其建立一個Bean
3)指定要掃描的包
(使用@ComponentScan 的 valule 屬性來配置)
@ComponentScan(value = "com.spring.learn2")
public class BeanConfig {
}
excludeFilters 和 includeFilters 的使用
使用 excludeFilters 來按照規則排除某些包的掃描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
@ComponentScan(value = "com.spring.learn2",
excludeFilters = {@Filter(type = FilterType.ANNOTATION,
value = {Controller.class})})
public class BeanConfig {
}
excludeFilters
的引數是一個 Filter[]
陣列,然後指定 FilterType
的型別為 ANNOTATION
,也就是通過註解來過濾,最後的 value
則是Controller
註解類。
配置之後,在 spring 掃描的時候,就會跳過 com.spring.learn2 包下,所有被 @Controller 註解標註的類。
使用 includeFilters 來按照規則只包含某些包的掃描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
@ComponentScan(value = "com.spring.learn2", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})
public class BeanConfig {
}
spring 預設會自動發現被 @Component、@Repository、@Service 和 @Controller 標註的類,並註冊進容器中。要達到只包含某些包的掃描效果,就必須將這個預設行為給禁用掉(在 @ComponentScan 中將 useDefaultFilters 設為 false 即可)。
@ComponentScan 的 useDefaultFilters 屬性,該屬性預設值為 true。
1. 定義Bean@Component
需要在類上使用註解@Component,該註解的value屬性用於指定該bean的id值。
@Component(value = "role")
public class Role {
private Long id;
private String roleName;
private String note;
}
2. Bean的作用域@Scope
需要在類上使用註解@Scope,其value屬性用於指定作用域。預設為singleton。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
...
}
3. 基本型別屬性注入@Value
需要在屬性上使用註解@Value,該註解的value屬性用於指定要注入的值。
使用該註解完成屬性注入時,類中無需setter。當然,若屬性有setter,則也可將其加到setter上。
package com.spring.learn2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "role")
public class Role {
@Value("1")
private Long id;
@Value("role_name_1")
private String roleName;
@Value("role_note_1")
private String note;
}
4.按型別注入域屬性@Autowired
@Autowired是spring的註解
需要在域屬性上使用註解@Autowired,該註解預設使用按型別自動裝配Bean的方式。
使用該註解完成屬性注入時,類中無需setter。當然,若屬性有setter,則也可將其加到setter上。
package com.spring.learn2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Autowired
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
5. 按名稱注入域屬性@Autowired與@Qualifier
需要在域屬性上聯合使用註解@Autowired與@Qualifier。
@Qualifier的value屬性用於指定要匹配的Bean的id值。同樣類中無需setter,也可加到setter上。
package com.spring.learn2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Autowired(required =false)
@Qualifier("role")
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
@Autowired還有一個屬性required,預設值為true,表示當匹配失敗後,會終止程式執行。若將其值設定為false,則匹配失敗,將被忽略,未匹配的屬性值為null。
6. 域屬性註解@Resource
@Resource是java自帶的註解
Spring提供了對JSR-250規範中定義@Resource標準註解的支援。@Resource註解既可以按名稱匹配Bean,也可以按型別匹配Bean。使用該註解,要求JDK必須是6及以上版本。
1. 按型別注入域屬性
@Resource註解若不帶任何引數,則會按照型別進行Bean的匹配注入。
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Resource
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
2. 按名稱注入域屬性
@Resource註解指定其name屬性,則name的值即為按照名稱進行匹配的Bean的id。
package com.spring.learn2;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Resource(name="role")
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
7. Bean的生命始末@PostConstruct與@PreDestroy
在方法上使用@PostConstruct,與原來的init-method等效。在方法上使用@PreDestroy,與destroy-method等效。
package com.spring.learn2;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Resource(name="role")
private Role role;
@PostConstruct
public void init(){
System.out.println("Bean初始化後執行");
}
@PreDestroy
public void destroy(){
System.out.println("Bean銷燬前執行");
}
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
Spring 中常用的註解
@Component
取代<bean class="">
。
@Component("id")
取代 <bean id="" class="">
web開發,提供3個@Component註解衍生註解(功能一樣)取代<bean class="">
@Repository
:dao層 。
@Service
:service層 。
@Controller
:web層。
@Component
可以使用此註解描述 Spring 中的 Bean,但它是一個泛化的概念,僅僅表示一個元件(Bean),並且可以作用在任何層次。使用時只需將該註解標註在相應類上即可。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "role")
public class Role {
@Value("1")
private Long id;
@Value("role_name_1")
private String roleName;
@Value("role_note_1")
private String note;
}
- 註解@Component代表Spring IoC會把這個類掃描生成Bean例項,而其中的value屬性代表這個類在Spring中的id,這就相當於XML方式定義的Bean的id,也可以簡寫成@Component("role"),甚至直接寫成@Component,對於不寫的,Spring IoC容器就預設類名,但是以首字母小寫的形式作為id,為其生成物件,配置到容器中。
- 註解@Value代表的是值的注入,這裡只是簡單注入一些值,其中id是一個long型,注入的時候Spring會為其轉化型別。
@Repository
用於將資料訪問層(DAO層)的類標識為 Spring 中的 Bean,其功能與 @Component 相同。
@Service
通常作用在業務層(Service 層),用於將業務層的類標識為 Spring 中的 Bean,其功能與 @Component 相同。
@Controller
通常作用在控制層(如 Struts2 的 Action、SpringMVC 的 Controller),用於將控制層的類標識為 Spring 中的 Bean,其功能與 @Component 相同。
@Autowired
自動裝配。
通過學習Spring IoC容器,我們知道Spring是先完成Bean的定義和生成,然後尋找需要注入的資源。也就是當Spring生成所有的Bean後,如果發現這個註解,他就會在Bean中查詢,然後找到對應的型別,將其注入進來,這樣就完成依賴注入了。
所謂自動裝配技術是一種由Spring自己發現對應的Bean,自動完成裝配工作的方式,它會應用到一個十分常用的註解@Autowired上,這個時候Spring會根據型別去尋找定義的Bean然後將其注入,這裡需要留意按型別(Role)的方式。
可以應用到 Bean 的屬性變數、屬性的 setter 方法、非 setter 方法及建構函式等,配合對應的註解處理器完成 Bean 的自動配置工作。預設按照 Bean 的型別進行裝配。
-
示例
role
package com.spring.learn2; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component(value = "role") public class Role { @Value("1") private Long id; @Value("role_name_1") private String roleName; @Value("role_note_1") private String note; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getNote() { return note; } public void setNote(String note) { this.note = note; } }
RoleService
package com.spring.learn2; public interface RoleService { public void printRoleInfo(); }
package com.spring.learn2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("RoleService") public class RoleServiceImpl implements RoleService { @Autowired private Role role; @Override public void printRoleInfo() { System.out.println("id =" + role.getId()); System.out.println("roleName =" + role.getRoleName()); System.out.println("note =" + role.getNote()); } /**** setter and getter ****/ public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } }
MainApp
package com; import com.spring.learn2.RoleService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainApp { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml"); RoleService uc = (RoleService) ctx.getBean("RoleService"); uc.printRoleInfo(); } }
Beans.xml
掃描含有註解類
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 元件掃描,掃描含有註解的類 --> <context:component-scan base-package="com.spring.learn2" /> </beans>
這裡的@Autowired註解,表示在Spring IoC定位所有的Bean後,這個欄位需要按型別注入,這樣IoC容器就會尋找資源,然後將其注入。。比如程式碼Role和程式碼RoleServiceImpl的兩個Bean,假設將其定義,那麼Spring IoC容器會為它們先生成對應的例項,然後依據@Autowired註解,按照型別找到定義的例項,將其注入。
IoC容器有時候會尋找失敗,在預設的情況下尋找失敗它就會丟擲異常,也就是說預設情況下,Spring IoC容器會認為一定要找到對應的Bean來注入這個欄位,有些時候這並不是一個真實的需要,比如日誌,有時候我們會覺得這是可有可無的,這個時候可以通過@Autowired的配置項required來改變它,比如@Autowired(required=false)。
正如之前所談到的在預設情況下是必須注入成功的,所以這裡的required的預設值為true。當把配置修改為了false時,就告訴Spring IoC容器,假如在已經定義好的Bean中找不到對應的型別,允許不注入,這樣也就沒有了異常丟擲,只是這樣這個欄位可能為空,讀者要自行校驗,以避免發生空指標異常。在大部分的情況下,都不需要這樣修改。
@Autowired除可以配置在屬性之外,還允許方法配置,常見的Bean的setter方法也可以使用它來完成注入。
@Resource
作用與 Autowired 相同,區別在於 @Autowired 預設按照 Bean 型別裝配,而 @Resource 預設按照 Bean 例項名稱進行裝配。
@Resource 中有兩個重要屬性:name 和 type。
Spring 將 name 屬性解析為 Bean 的例項名稱,type 屬性解析為 Bean 的例項型別。如果指定 name 屬性,則按例項名稱進行裝配;如果指定 type 屬性,則按 Bean 型別進行裝配。如果都不指定,則先按 Bean 例項名稱裝配,如果不能匹配,則再按照 Bean 型別進行裝配;如果都無法匹配,則丟擲 NoSuchBeanDefinitionException 異常。
另外要注意,@Resource是java自帶的註解,不是Spring中的註解。@Resource註解完整的包路徑為
import javax.annotation.Resource;
示例
@Resource(name = "userServiceImpl")
private UserService userService;
@Qualifier
與 @Autowired 註解配合使用,會將預設的按 Bean 型別裝配修改為按 Bean 的例項名稱裝配,Bean 的例項名稱由 @Qualifier 註解的引數指定。
@Autowired
@Qualifier("userServiceImp")
private UserSerevice userService;
@Service
,@Controller
這些註解要放在介面的實現類上,而不是介面上面。
@Autowired
和@Resource
是用來修飾字段,建構函式,或者設定方法,並做注入的。
而@Service
,@Controller
,@Repository
,@Component
則是用來修飾類,標記這些類要生成bean。
註解使用前提,新增名稱空間,讓spring掃描含有註解類
示例
Beans.xml
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 元件掃描,掃描含有註解的類 -->
<context:component-scan base-package="com.spring.learn1" />
</beans>
UserDao
package com.spring.learn1;
public interface UserDao {
/**
* 輸出方法
*/
public void outContent();
}
UserDaoImpl
package com.spring.learn1;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void outContent() {
System.out.println("dao方法");
}
}
package com.spring.learn1;
public interface UserService {
/**
* 輸出方法
*/
public void outContent();
}
UserServiceImpl
package com.spring.learn1;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService{
@Resource(name="userDao")
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void outContent() {
userDao.outContent();
System.out.println("service");
}
}
UserController
package com.spring.learn1;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller("userController")
public class UserController {
@Resource(name = "userService")
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void outContent() {
userService.outContent();
System.out.println("content");
}
}
MainApp
package com;
import com.spring.learn1.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
UserController uc = (UserController) ctx.getBean("userController");
uc.outContent();
}
}
執行時以下錯誤
org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
。。。。
Caused by: java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
檢查發現不存在javax.annotation這個包
在pom.xml檔案中引入
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.1</version>
</dependency>
執行如下
dao方法
service
content
@Resource vs @Autowired
@Resource的裝配順序如下:
- @Resource後面沒有任何內容,預設通過name屬性去匹配bean,找不到再按type去匹配;
- 指定了name或者type則根據指定的型別去匹配bean;
- 指定了name和type則根據指定的name和type去匹配bean,任何一個不匹配都將報錯。
然後,區分一下@Autowired和@Resource兩個註解的區別:
- @Autowired預設按照byType方式進行bean匹配,@Resource預設按照byName方式進行bean匹配
- @Autowired是Spring的註解,@Resource是J2EE的註解,這個看一下匯入註解的時候這兩個註解的包名就一清二楚了
Spring屬於第三方的,J2EE是Java自己的東西,因此,建議使用@Resource註解,以減少程式碼和Spring之間的耦合。
總結一下:
@Resource根據name和type,是先Name後Type,@Autowired是Type,一般情況下我們最好使用@Resource。
文中詳細講解了@Service、@Autowired、@Resource和@Qualifier的用法,其中重點講述了@Autowired、@Resource的區別,那麼對於@Component、@Repository、@Controller這3個註解,文中也就開頭提到,這3個註解其實和@Service一個含義,只是我們在寫程式碼時,會進行分層,比如DAO層、Service層、Action層,分別可以用@Repository、@Service、@Controller表示,其實也就字面含義不一樣,效果其實是一樣的,然後@Component可以作用在任何層次。所以看起來有7個註解,其實你可以理解只有4個。