0 前言
Spring的核心就是提供了一個IoC(Inversion of Control)容器,它可以管理所有輕量級的JavaBean元件,提供的底層服務包括元件的生命周期管理、配置群組裝服務、AOP支援,以及建立在AOP基礎上的聲明式事務服務等。
本本主要展示IoC容器對JavaBean裝配,以及依賴的注入的幾種方式。
看本文之前請務必學習JAVA基礎。
1 傳統的實作方式
先來看下面這個例子:
class Person {
private String name;
public Person(){};
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Dance {
public void do_dance(Person person){
System.out.println(person.getName()+" is dancing!");
}
}
class Sing {
public void do_sing(Person person){
System.out.println(person.getName()+" is sing!");
}
}
public class Test {
public static void main(String[] args) {
Person person=new Person("Ming");
new Sing().do_sing(person);
new Dance().do_dance(person);
}
}
Dance與Sing元件的執行個體化都依賴于Person元件的執行個體化,通過主動建構的方式實作元件的執行個體化以及依賴關系的建構。在大型項目中,這種寫法有以下缺點:
- 一個元件如果要使用另一個元件,必須先知道如何正确地建立它,管理起來麻煩。
- 随着更多的元件被引入,元件的依賴層級變深,組織起來太複雜。
- 有些元件需要銷毀以便釋放資源,但如果該元件被多個元件共享,如何確定它的使用方都已經全部被銷毀?
- 元件互相依賴,測試困難。
為解決這個問題,提出了IoC模式,在IoC模式下,控制權發生了反轉,即從應用程式轉移到了IoC容器,所有元件不再由應用程式自己建立和配置,而是由IoC容器負責,這樣,應用程式隻需要直接使用已經建立好并且配置好的元件。為了能讓元件在IoC容器中被“裝配”出來,需要某種“注入”機制,根據操作的不同可以分為下文的幾種。
2 基于XML配置的依賴注入
在XML中定義Bean并交代元件之間的依賴關系。
Pom配置如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>spring-ioc-appcontext</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
<spring.version>5.2.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
代碼結構如下:
對應的代碼如下:
Person
package model;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Dance
package model;
public class Dance {
Person person;
public void setPerson(Person person){
this.person=person;
}
public void do_dance(){
System.out.println(person.getName()+" is dancing!");
}
}
Sing
package model;
public class Sing {
Person person;
public void setPerson(Person person) {
this.person = person;
}
public void do_sing(){
System.out.println(person.getName()+" is sing!");
}
}
Dance、Sing
自己并不會建立
Person
,而是等待外部通過
set
方法來注入一個
Person
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dance" class="model.Dance">
<property name="person" ref="person"/>
</bean>
<bean id="sing" class="model.Sing">
<property name="person" ref="person"/>
</bean>
<bean id="person" class="model.Person">
<property name="name" value="Ming"/>
</bean>
</beans>
Main中通過context來通路XML檔案
import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("Application.xml");
Dance dance=context.getBean(Dance.class);
dance.do_dance();
Sing sing=context.getBean(Sing.class);
sing.do_sing();
Person person=context.getBean(Person.class);
person.setName("Tom");
Dance dance_=context.getBean(Dance.class);
dance_.do_dance();
Sing sing_=context.getBean(Sing.class);
sing_.do_sing();
}
}
運作結果
Ming is dancing!
Ming is sing!
Tom is dancing!
Tom is sing!
使用XML配置的優點是所有的Bean都能一目了然地列出來,并通過配置注入能直覺地看到每個Bean的依賴。它的缺點是寫起來非常繁瑣,每增加一個元件,就必須把新的Bean配置到XML中。
3 基于Java配置的依賴注入
檔案結構如下:
基于Java的配置檔案(AppConfig),替代了XML
Person
package model;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Dance
package model;
public class Dance {
Person person;
public Dance(Person person) {
this.person=person;
}
public void do_dance(){
System.out.println(person.getName()+" is dancing!");
}
}
Sing
package model;
public class Sing {
Person person=new Person();
public void setPerson(Person person){
this.person=person;
}
public void do_sing(){
System.out.println(person.getName()+" is sing!");
}
}
APPConfig
import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class APPConfig {
@Bean
public Person person(){
Person person=new Person();
person.setName("Ming");
return person;
}
@Bean //通過構造器的注入依賴
public Dance dance(){
return new Dance(person());
}
@Bean //通過set注入依賴
public Sing sing(){
Sing sing= new Sing();
sing.setPerson(person());
return sing;
}
}
這裡的APPConfig相當于XML,定義了每個bean,以及各個bean之間的依賴關系。
-
用于标記JavaBean對象@Bean
-
用于标記這個類是個配置類,主函數中的@Configuration
必須傳入一個标注了AnnotationConfigApplicationContext
的類名@Configuration
注意: 代碼中同時展示了兩種依賴注入的方式,一種是set方法,一種是帶參構造器。
Main方法
import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(APPConfig.class);
Dance dance=context.getBean(Dance.class);
dance.do_dance();
Sing sing=context.getBean(Sing.class);
sing.do_sing();
Person person=context.getBean(Person.class);
person.setName("Tom");
Dance dance_=context.getBean(Dance.class);
dance_.do_dance();
Sing sing_=context.getBean(Sing.class);
sing_.do_sing();
}
}
運作結果
Ming is dancing!
Ming is sing!
Tom is dancing!
Tom is sing!
從運作結果可以看出,大家使用的是同一個Person對象。
4 基于注解搜尋的依賴注入
不再需要單獨的XML或者APPConfig來告訴容器哪些是Bean以及他們之間的關系,可以通過注解在程式中定義,讓程式自動搜尋識别。
Person
package model;
import org.springframework.stereotype.Component;
@Component
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Dance
package model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Dance {
@Autowired //替代了set
Person person;
public void do_dance(){
System.out.println(person.getName()+" is dancing!");
}
}
Sing
package model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sing {
@Autowired
Person person;
public void do_sing(){
System.out.println(person.getName()+" is sing!");
}
}
-
注解就相當于定義了一個Bean,它有一個可選的名稱,預設是@Component
,即小寫開頭的類名。mailService
-
就相當于把指定類型的Bean注入到指定的字段中。@Autowired
大幅簡化了注入,因為它不但可以寫在@Autowired
方法上,還可以直接寫在字段上,甚至可以寫在構造方法中set()
Main的代碼如下:
import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "model")
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
Dance dance=context.getBean(Dance.class);
dance.do_dance();
Sing sing=context.getBean(Sing.class);
sing.do_sing();
Person person=context.getBean(Person.class);
person.setName("Tom");
Dance dance_=context.getBean(Dance.class);
dance_.do_dance();
Sing sing_=context.getBean(Sing.class);
sing_.do_sing();
}
}
運作結果:
null is dancing!
null is sing!
Tom is dancing!
Tom is sing!
注意:由于是自動裝配,person的name為null。
AppConfig
标注了
@ComponentScan
,它告訴容器,自動搜尋目前類所在的包以及子包,把所有标注為
@Component
的Bean自動建立出來,并根據
@Autowired
進行裝配。直接在Main中通過調用
@ComponentScan(value = "model")
來實作bean的自動搜尋與裝配,避免了配置檔案的書寫。
使用Annotation配合自動掃描能大幅簡化Spring的配置,我們隻需要保證:
- 每個Bean被标注為
并正确使用@Component
注入;@Autowired
- 配置類被标注為
和@Configuration
;@ComponentScan
- 所有Bean均在指定包以及子包内。
這個方法雖然友善,但不适用于第三方包加入時。
比如當需要調用某個第三方包裡的某個方法,該方法在第三方包中沒有用@Component`注解,如何告訴IoC容器建立并配置?
或者換個說法,如何建立并配置一個第三方Bean?
待續。
5 參考文獻
[1]廖雪峰的官方網站
Java教程www.liaoxuefeng.com