Sping:五、自動裝配Bean和使用註解開發
7、自動裝配Bean
在 Spring 中有三種裝配方式:
- XML 顯式配置:使用配置檔案;
- Java 顯式配置:實現零配置檔案;
- 自動裝配:自動注入屬性值
- 可以減少 setter 和構造方法的使用;
- 可以隨著物件的發展而更新:例如需要向類增加新的依賴,無需修改配置即可自動注入依賴。
7.1、搭建環境
“一個人有一隻寵物。”
Pet類
public class Pet { private String type; public Pet() { } public Pet(String type) { this.type = type; } @Override public String toString() { return "Pet{" + "type='" + type + '\'' + '}'; } public String getType() { return type; } }
Person 類
public class Person { private String name; private Pet pet; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", pet=" + pet + '}'; } public String getName() { return name; } public Pet getPet() { return pet; } }
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"> <bean id="pet" class="indi.jaywee.pojo.Pet"/> <bean id="person" class="indi.jaywee.pojo.Person"> <property name="pet" ref="pet"/> </bean> </beans>
Test 類
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean("person", Person.class);
System.out.println(person);
}
測試結果
Person 物件建立成功,Pet 裝配成功。
7.1、XML自動裝配
使用 XML 配置元資料時,使用 Bean 元素的 autowire 屬性開啟自動裝配。
7.1.1、自動裝配模式
7.1.2、測試
去掉 ref 屬性,並增加註冊幾個 Bean,測試使用 autowire 進行自動裝配。
<?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="pet" class="indi.jaywee.pojo.Pet">
<constructor-arg name="type" value="myPet"/>
</bean>
<bean id="pet1" class="indi.jaywee.pojo.Pet">
<constructor-arg name="type" value="myPet1"/>
</bean>
<bean id="pet2" class="indi.jaywee.pojo.Pet">
<constructor-arg name="type" value="myPet2"/>
</bean>
<bean id="person" class="indi.jaywee.pojo.Person" autowire=""/>
</beans>
1、no
不會為 Person 裝配 pet 屬性。
2、byName
匹配到 id 為 pet 的 Bean 並裝配。
-
如果去掉 id 為 pet 的 Bean,即沒有與屬性同名的Bean,則不會裝配。
3、byType
匹配到多個 Bean ,報錯。
-
只保留一個 Bean,則自動裝配相應的Bean:
4、constructor
Person 中沒有 Pet 的構造器。
-
在 Person 類中增加一個構造器
public Person(Pet pet) { this.pet = pet; }
- 如果只匹配到一個Bean,則自動裝配;
- 如果匹配到0個到多個Bean,則不裝配。
7.2、註解自動裝配
在使用註解之前,需要引入以下配置。
-
匯入 context 配置;
-
設定註解支援:
<context:annotation-config/>
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> </beans>
-
相比沒有使用註解的 XML 檔案,增加了有關 context 的一個 xmlns 和兩個 xsi 標記。
7.2.1、@Autowired
自動裝配
- 可以在屬性、setter、構造器使用;
工作機制:先 byType ,再 byName
- Spring 查詢容器中是否存在屬性型別的 Bean;
- 如果容器中只存在一個匹配的 Bean ,即 Bean 只在 XML 中配置了一次,則自動裝配;
- 如果容器中存在多個匹配的 Bean,即 Bean 在 XML 中配置了多次,則匹配屬性名:
- 存在與屬性同名的 Bean,自動裝配;
- 不存在同名 Bean,可以添加註解 @Qualifier(value = "beanId") 來匹配 Bean
通過測試,來解釋以上工作機制。
測試
-
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" 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:annotation-config/> <bean id="pet" class="indi.jaywee.pojo.Pet"> <constructor-arg name="type" value="myPet"/> </bean> <bean id="pet1" class="indi.jaywee.pojo.Pet"> <constructor-arg name="type" value="myPet1"/> </bean> <bean id="person" class="indi.jaywee.pojo.Person"> <property name="name" value="jaywee"/> </bean> <bean id="person" class="indi.jaywee.pojo.Person"/> </beans>
-
在 pet 屬性上使用 @Autowired 註解
public class Person { private String name; @Autowired private Pet pet; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", pet=" + pet + '}'; } public String getName() { return name; } public Pet getPet() { return pet; } }
-
測試結果:
- pet 屬性不為 null,自動裝配成功;
- pet 的 type 值為 myPet ,說明注入了 id 為 pet 的 Bean 物件;
進一步測試
-
測試byType:刪除 id 為 pet 和 pet1 的 Bean ,保留 id 為 pet2 的 Bean
-
結果:注入了 id 為 pet2 的 Bean
-
-
測試byName:刪除 id 為 pet 的 Bean ,保留 id 為 pet1 和 pet2 的 Bean
-
匹配到兩個 Bean ,並且無法匹配屬性名。
-
解決:在 pet 屬性上使用 @Qualifier(value = "beanId") 註解,匹配容器中對應的 Bean。
@Autowired @Qualifier(value = "pet1") private Pet pet;
-
7.2.2、@Resource
Java 提供的註解,工作機制類似@Autowired,可以自行測試。
相比 @Autowired ,擁有name屬性,可以指定具體的Bean。
@Resource(name = "pet1")
private Pet pet;
8、使用註解開發
8.1、關於註解
註解實現原理:反射機制和動態代理方法。
-
註解注入比 XML 注入先執行,因此 XML 配置會覆蓋註解配置;
-
在 Spring4 後,要使用註解開發,必須要有 aop 的 jar 包支援:
-
要使用註解開發,需要設定配置資訊:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package=""/> </beans>
8.1.1、註解的型別
- 類級別的註解:新增在類上面。
- @Component
- 元註解為 @Component 的註解
- @Repository
- @Controller
- @Service
- 自定義註解
- Java EE6 的 @ManagedBean、@Named
- 類內部的註解:新增在類內部的屬性或方法上。
- @Autowired
- @Resource
- @Value
- ...
8.1.2、工作原理
IOC 容器根據 XML 配置,進行以下操作:
- 掃描
.class
檔案,將包含類級別註解的 Bean 註冊到 BeanFactory 中; - 註冊相應註解的後置處理器(post-processor),註冊的同時將其例項化,用於處理類內部的註解;
- 將其處理器放到 BeanFactory的 beanPostProcessors 列表中;
- 建立 Bean 的過程中,屬性注入或者初始化 Bean 時,呼叫相應的處理器進行處理。
8.1.3、後置處理器
後置處理器(post-processor)主要用於處理類內部的註解。
- 要使用註解的話,必須要在 XML 中註冊相應的處理器,作為支援;
- 基於第1點說明,如果要使用多種註解,相應的就要註冊很多個處理器。這樣的話非常麻煩,因此可以使用
<context:annotation-config/>
標籤來隱式註冊它們。
<context:annotation-config/>
標籤
隱式註冊了5個後置處理器,對應支援的類內部的註解才生效。
後置處理器 | 處理註解 | 參考文件 |
---|---|---|
ConfigurationClass PostProcessor |
@Configuration | ConfigurationClassPostProcessor |
AutowiredAnnotationBean PostProcessor |
@Autowired @Value @Inject |
AutowiredAnnotationBeanPostProcessor |
CommonAnnotationBean PostProcessor |
@PostConstruct、@PreDestroy @Resource @WebServiceRef @EJB |
CommonAnnotationBeanPostProcessor |
PersistenceAnnotationBean PostProcessor |
@PersistenceUnit @PersistenceContext |
PersistenceAnnotationBeanPostProcessor |
EventListenerMethod Processor |
@EventListener | EventListenerMethodProcessor |
<context:component-scan>
標籤
掃描指定包下的 Bean ,被掃描的 Bean 中包含的類級別的註解才會生效,Bean 才會被註冊到容器中。
- base-package 屬性:用於指定掃描的包路徑;
- 使用該標籤,隱式啟用了
<context:annotation-config/>
標籤。即使用當前標籤時,不再需要引入<context:annotation-config/>
標籤。
8.1.4、註解小結
- 註解有2種類型:類級別、類內部;
- IOC容器會自動註冊類級別註解的 Bean,前提是有
<context:component-scan>
配置支援; - 要使用類內部的註解,需要有相應處理器的支援,處理器需要在 XML 中註冊;
- 通過
<context:annotation-config/>
標籤來隱式註冊處理器; - 要讓類級別註解生效,就要
<context:component-scan>
配置支援,而這個標籤又隱式啟動了<context:annotation-config/>
; - 在使用時,我們只需要引入相關配置,以及
<context:component-scan>
標籤。
8.2、環境搭建
先搭建一個沒有使用註解的環境,再引入註解開發。
8.1.1、實體類
Major 類
public class Major {
/**
* 專業名
*/
private String name;
public Major() {
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Major{" +
"name='" + name + '\'' +
'}';
}
}
Student 類
public class Student {
/**
* 姓名
*/
private String name;
/**
* 專業
*/
private Major major;
public Student() {
}
@Override
public String toString() {
return "Student{" + "\n" +
"name='" + name + '\'' + "\n" +
"major=" + major + "\n" +
'}';
}
public void setName(String name) {
this.name = name;
}
public void setMajor(Major major) {
this.major = major;
}
}
8.1.2、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">
<bean id="major" class="indi.jaywee.pojo.Major">
<property name="name" value="Java"/>
</bean>
<bean id="student" class="indi.jaywee.pojo.Student">
<property name="name" value="jaywee"/>
<property name="major" ref="major"/>
</bean>
</beans>
8.1.3、測試
JUnit
@Test
public void test() {
// 例項化容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 獲取Bean
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
執行結果
Bean 物件被 IOC 容器成功建立並組裝了,基本環境搭建完成。
8.2.4、引入註解
基本環境已搭建完成,現在引入支援註解的配置,來進行註解開發。
注意掃描包的路徑
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="indi.jaywee/>
</beans>
當前執行結果:
8.3、註冊Bean
將包含類級別註解的 Bean 註冊到容器中,由容器管理。
相當於在XML檔案中使用<bean/>
標籤,可以使用 value 屬性設定 Bean 的 Name 。
- @Component:註冊 Java Bean
- 元註解為 @Component 的註解
- @Repository:註冊 DAO
- @Controller:註冊 Controller
- @Service:註冊 Service
8.3.1、@Component
在 Java Bean上方新增 @Component 註解,將其註冊到容器中。
@Component
public class Student {
...
}
-
自動註冊的 Bean 名:開頭小寫的類名,即 student。
Student student = context.getBean("student", Student.class);
-
可以通過 value 屬性,顯式設定 Bean 名,value可以省略:
@Component(value = "myStudent") public class Student { ... } @Component("myStudent") // 直接填寫Bean名 public class Student { ... }
Student student = context.getBean("myStudent", Student.class);
-
結果
8.3.2、@Component的衍生註解
即元註解為 @Component 的註解:
- @Repository:只能用於DAO
- @Controller:用於控制層
- @Service:用於業務層
添加註解
在類上方添加註解,將其註冊到容器中。
@Repository
public class StudentDaoImpl implements StudentDao{
...
}
@Controller
public class StudentController {
...
}
@Service
public class StudentServiceImpl implements StudentService{
...
}
Bean名
-
預設 Bean 名:開頭小寫的非限定類名:
StudentDao dao = context.getBean("studentDaoImpl", StudentDaoImpl.class); StudentController controller = context.getBean("studentController", StudentController.class; StudentService service = context.getBean("studentServiceImpl", StudentService.class);
-
可以通過 value 屬性,顯式設定 Bean 名。也可以省略 value,直接填寫 Bean 名:
@Component(value = "daoImpl") public class Student { ... } @Controller("controller") // 直接填寫Bean名 public class StudentController { ... } @Component(value = "daoImpl") // 直接填寫Bean名 public class Student { ... }
-
顯式設定 Bean 名後,預設的 Bean 名失效
StudentDao dao = context.getBean("daoImpl", StudentDaoImpl.class); StudentController controller = context.getBean("controller", StudentController.class); StudentDao dao = context.getBean("daoImpl", StudentDaoImpl.class);
8.3.3、@Bean
用於方法,將方法返回的物件註冊到容器中。
@Bean
public Major getMajor1() {
...
}
-
自動註冊的 Bean 名:方法名,即 getMajor1
Major major = context.getBean("getMajor1", Major.class);
-
可以通過 value 屬性,顯式設定 Bean 名:
@Bean(value = "m1") public Major getMajor1() { ... }
Major major = context.getBean("m1", Major.class);
8.4、裝配Bean
8.4.1、基本資料型別
@Value
用於基本資料型別的屬性的注入。
@Value("jaywee")
private String name;
@Value("Java")
private String name;
8.4.2、引用資料型別
@Autowired
用於引用資料型別(其他 Bean依賴)的注入。
@Autowired
private Major major;
@Resource
和 @Autowired 的效果類似,可以使用 name 屬性指定具體的 Bean。
具體有關這 2個註解的使用,參照 7、自動裝配Bean
8.5、微調註解
@Qualifier
用於屬性、構造方法和方法的引數,為其選擇一個特定的 Bean,通常與 @Autowired 結合使用。
例如:會為 pet 屬性注入 id 為 pet1 的 Bean
public class Person {
@Resource
@Qualifier(value = "pet1")
private Pet pet;
public Person(@Qualifier(value = "pet1")Pet pet) {
this.pet = pet;
}
}
等價於使用 @Resource 及其 name 屬性,並且推薦使用這種方式。
public class Person {
@Resource(name = "pet1")
private Pet pet;
public Person(@Qualifier(value = "pet1")Pet pet) {
this.pet = pet;
}
}
@Nullable
用於標記屬性或方法引數,表示該屬性可以為 null。
@Nullable
private Pet pet;
也可以通過 @Autowired 的 Required = false實現。
@Autowired(required = false)
private Pet pet;
8.6、小結
XML與註解
- XML 適用於任何場合,維護簡單;
- 註解在一定程度上可以替代 XML 配置檔案,但是維護相對複雜;
- 最佳實踐:
- XML 管理 Bean:即手動在 XML 中註冊 Bean,而不是用類級別註解;
- 註解裝配 Bean:即使用類內部註解,自動注入屬性值。