1. 程式人生 > 其它 >Sping:五、自動裝配Bean和使用註解開發

Sping:五、自動裝配Bean和使用註解開發

7、自動裝配Bean

Spring 中有三種裝配方式:

  1. XML 顯式配置:使用配置檔案;
  2. Java 顯式配置:實現零配置檔案;
  3. 自動裝配:自動注入屬性值
    • 可以減少 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

匹配到 idpetBean 並裝配。

  • 如果去掉 idpetBean,即沒有與屬性同名的Bean,則不會裝配。

3、byType

匹配到多個 Bean ,報錯。

  • 只保留一個 Bean,則自動裝配相應的Bean:

4、constructor

Person 中沒有 Pet 的構造器。

  • Person 類中增加一個構造器

    public Person(Pet pet) {
        this.pet = pet;
    }  
    
    • 如果只匹配到一個Bean,則自動裝配;
    • 如果匹配到0個到多個Bean,則不裝配。

7.2、註解自動裝配

在使用註解之前,需要引入以下配置。

  1. 匯入 context 配置;

  2. 設定註解支援:<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

  1. Spring 查詢容器中是否存在屬性型別Bean
  2. 如果容器中只存在一個匹配的 Bean ,即 Bean 只在 XML 中配置了一次,則自動裝配;
  3. 如果容器中存在多個匹配的 Bean,即 BeanXML 中配置了多次,則匹配屬性名
    • 存在與屬性同名的 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;
        }
    }
    
  • 測試結果:

    1. pet 屬性不為 null,自動裝配成功;
    2. pettype 值為 myPet ,說明注入了 idpetBean 物件;

進一步測試

  1. 測試byType:刪除 idpetpet1Bean ,保留 idpet2Bean

    • 結果:注入了 idpet2Bean

  2. 測試byName:刪除 idpetBean ,保留 idpet1pet2Bean

    • 匹配到兩個 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、關於註解

註解實現原理:反射機制和動態代理方法

  1. 註解注入比 XML 注入先執行,因此 XML 配置會覆蓋註解配置;

  2. Spring4 後,要使用註解開發,必須要有 aopjar 包支援

  3. 要使用註解開發,需要設定配置資訊

    <?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、註解的型別

  1. 類級別的註解:新增在類上面。
    • @Component
    • 元註解為 @Component 的註解
      • @Repository
      • @Controller
      • @Service
      • 自定義註解
    • Java EE6@ManagedBean@Named
  2. 類內部的註解:新增在類內部的屬性或方法上。
    • @Autowired
    • @Resource
    • @Value
    • ...

8.1.2、工作原理

IOC 容器根據 XML 配置,進行以下操作:

  1. 掃描.class檔案,將包含類級別註解Bean 註冊到 BeanFactory 中;
  2. 註冊相應註解的後置處理器(post-processor,註冊的同時將其例項化,用於處理類內部的註解
  3. 將其處理器放到 BeanFactorybeanPostProcessors 列表中;
  4. 建立 Bean 的過程中,屬性注入或者初始化 Bean 時,呼叫相應的處理器進行處理。

8.1.3、後置處理器

後置處理器(post-processor)主要用於處理類內部的註解。

  1. 要使用註解的話,必須要在 XML 中註冊相應的處理器,作為支援;
  2. 基於第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 才會被註冊到容器中。

  1. base-package 屬性:用於指定掃描的包路徑;
  2. 使用該標籤,隱式啟用了<context:annotation-config/>標籤。即使用當前標籤時,不再需要引入<context:annotation-config/>標籤。

8.1.4、註解小結

  1. 註解有2種類型:類級別、類內部;
  2. IOC容器會自動註冊類級別註解Bean,前提是有<context:component-scan>配置支援;
  3. 要使用類內部的註解,需要有相應處理器的支援,處理器需要在 XML 中註冊;
  4. 通過<context:annotation-config/>標籤來隱式註冊處理器;
  5. 要讓類級別註解生效,就要<context:component-scan>配置支援,而這個標籤又隱式啟動了<context:annotation-config/>
  6. 在使用時,我們只需要引入相關配置,以及<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 屬性設定 BeanName

  • @Component:註冊 Java Bean
  • 元註解為 @Component 的註解
    • @Repository:註冊 DAO
    • @Controller:註冊 Controller
    • @Service:註冊 Service

8.3.1、@Component

Java Bean上方新增 @Component 註解,將其註冊到容器中。

@Component
public class Student {
	...
}
  1. 自動註冊的 Bean 名:開頭小寫的類名,即 student

    Student student = context.getBean("student", Student.class);
    
  2. 可以通過 value 屬性,顯式設定 Bean 名,value可以省略:

    @Component(value = "myStudent")
    public class Student {
        ...
    }
    
    @Component("myStudent") // 直接填寫Bean名
    public class Student {
        ...
    }
    
    Student student = context.getBean("myStudent", Student.class);
    
  3. 結果

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名

  1. 預設 Bean 名:開頭小寫的非限定類名

    StudentDao dao = context.getBean("studentDaoImpl", StudentDaoImpl.class);
    
    StudentController controller = context.getBean("studentController", StudentController.class;
                                                   
    StudentService service = context.getBean("studentServiceImpl", StudentService.class);
    
  2. 可以通過 value 屬性,顯式設定 Bean。也可以省略 value,直接填寫 Bean 名:

    @Component(value = "daoImpl")
    public class Student {
        ...
    }
    
    @Controller("controller") // 直接填寫Bean名
    public class StudentController {
        ...
    }
    
    @Component(value = "daoImpl") // 直接填寫Bean名
    public class Student {
        ...
    }
    
  3. 顯式設定 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() {
    ...
}
  1. 自動註冊的 Bean 名:方法名,即 getMajor1

    Major major = context.getBean("getMajor1", Major.class);
    
  2. 可以通過 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 屬性注入 idpet1Bean

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;

也可以通過 @AutowiredRequired = false實現。

@Autowired(required = false)
private Pet pet;

8.6、小結

XML與註解

  1. XML 適用於任何場合,維護簡單;
  2. 註解在一定程度上可以替代 XML 配置檔案,但是維護相對複雜;
  3. 最佳實踐:
    • XML 管理 Bean:即手動在 XML 中註冊 Bean,而不是用類級別註解;
    • 註解裝配 Bean:即使用類內部註解,自動注入屬性值。