Spring應用上下文配置:混合配置

前言

        所謂Spring應用上下文混合配置,指的是配置一部分在xml文件,一部分在java代碼。這種方式不是很常見,常見的要麼是純xml文件配置,要麼是純java編程配置。純java編程配置後續章節我們會詳細講解。關於這兩種純配置方式的比較,我們前面的章節已經提過。

根應用上下文配置

<?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-4.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.gxz" use-default-filters="false">
        <context:include-filter type="annotation"
                expression="org.springframework.stereotype.Service" />
    </context:component-scan>

</beans>
        配置context:annotation-config,表示註解配置,凡是java實現類做了以下的註解,都被認爲是需要Spring管理的bean:@Component、@Service、@Controller、@Repository等。所謂納入Spring管理的bean,指的是在恰當的時間,Spring將實例化bean併爲之注入依賴。配置context:component-scan:表示組件掃描,掃描base-package指定的包及其子包,若這些包中的實現類有註解@Component及其具體註解,都被認爲是Spring管理的bean。
        配置context:include-filter:表示掃描過濾器,一種包含的過濾器,Spring管理expression指定的bean。上述配置中,expression="org.springframework.stereotype.Service",表示Spring只管理類型爲@Service的bean,而忽略其他類型的bean,比如類型爲@Component、@Controller、@Repository的bean不納入Spring的管理。
        配置type="annotation":表示過濾器針對實現類的註解。配置use-default-filters="false":表示不再使用默認的過濾器,而是使用自定義過濾器。若是我們使用了包含過濾器或是排除過濾器,那麼,一定要配置不再使用默認過濾器。否則,包含過濾器或是排除過濾器的配置無效。包含過濾器的配置是context:include-filter,排除過濾器的配置是context:exclude-filter。關於排除過濾器,接下類的章節我們會詳細講解。
        從上述的配置我們可以看出,我們並沒有直接配置需要Spring管理哪一個具體的bean,而是通過指定範圍掃描註解的方式。在指定的範圍內,如果我們希望實現類納入Spring管理,我們需要在實現類加上註解@Component及其具體註解。這需要在xml文件配置,需要在java實現類配置(java編程配置),所以,此種配置方式稱之爲混合配置。
        通常情況下,我們創建一個根應用上下文和一個DispatcherServlet應用上下文,根應用上下文管理一組和業務邏輯相關的bean,而DispatcherServlet應用上下文管理一組和控制相關的bean。所以,在配置根應用上下文是,我們使用了包含過濾器,讓Spring只管理類型爲@Service的bean。

DispatcherServlet應用上下文配置

<?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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.0.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
  	<mvc:annotation-driven />

    <context:annotation-config />
    <context:component-scan base-package="com.gxz" use-default-filters="false">
        <context:include-filter type="annotation"
                expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
</beans>

業務邏輯相關的bean

package com.gxz;

import org.springframework.stereotype.Service;

@Service
public class StudentImp implements Student {
	@Override
	public String sayHi(String name) {
		return "Hi," + name;
	}

	public StudentImp() {
		super();
		System.out.println("StudentImp()");
	}
	
}

package com.gxz;

public interface Student {
	String sayHi(String name);
}

控制器bean

package com.gxz;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class StudentController {
	@Autowired
	private Student student;
	
	@ResponseBody
	@RequestMapping("/")
	public String sayHi() {
		return "Hi!";
	}
	
	@ResponseBody
	@RequestMapping(value="/say", params="name")
	public String say(@RequestParam("name") String name) {
		return student.sayHi(name);
	}

	public void setStudent(Student student) {
		this.student = student;
	}

	public StudentController() {
		super();
		System.out.println("StudentController()");
	}
}
        註解@Autowired:表示Spring在實例化bean StudentController時,爲之注入依賴Student,所謂的依賴Student,就是一個實現了Student接口的實現類,它必須是一個bean。可以看出,這個依賴就是bean StudentImp。依賴必須是唯一的,如果實現接口Student的實現類有兩個,分別是StudentImp、ComputerStudent,並且這兩個實現類都是受Spring管理的bean,那麼,Spring會報出異常,信息如下所示。
package com.gxz;

import org.springframework.stereotype.Service;

@Service
public class ComputerStudent implements Student {

	public String sayHi(String name) {
		return "Hi, " + name + ".Happy to see again.";
	}

}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'studentController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.gxz.Student com.gxz.StudentController.student; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.gxz.Student] is defined: expected single matching bean but found 2: computerStudent,studentImp

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.gxz.Student com.gxz.StudentController.student; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.gxz.Student] is defined: expected single matching bean but found 2: computerStudent,studentImp

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.gxz.Student] is defined: expected single matching bean but found 2: computerStudent,studentImp
          若要解決這個異常,就必須告訴Spring,Spring在實例化bean StudentController時,爲之注入依賴Student時明確指定是哪一個bean(computerStudent,studentImp二選一)。
@Autowired
@Qualifier("computerStudent")
private Student student;
        配置@Qualifier:指定注入的依賴是computerStudent,而不是studentImp。Spring實例化bean,默認取名爲類名的駱駝命名。比如實現類StudentImp,作爲bean實例化後取名爲studentImp,實現類ComputerStudent取名爲computerStudent,實現類StudentController取名爲studentController。
        現在有這樣一種場景,實現類爲com.gxz.StudentImp作爲bean實例化後,默認取名爲studentImp,實現類com.gxz.config.StudentImp作爲bean實例化後取名爲studentImp,這就導致bean名字重複,Spring報出如下異常。
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from ServletContext resource [/WEB-INF/rootContext.xml]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'studentImp' for bean class [com.gxz.StudentImp] conflicts with existing, non-compatible bean definition of same name and class [com.gxz.config.StudentImp]

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'studentImp' for bean class [com.gxz.StudentImp] conflicts with existing, non-compatible bean definition of same name and class [com.gxz.config.StudentImp]
        爲了解決上述問題,需要在配置實現類爲bean時,爲之指定實例化後的名字,以作區別。
package com.gxz.config;

import org.springframework.stereotype.Service;

import com.gxz.Student;

@Service("configStudentImp")
public class StudentImp implements Student {

	public String sayHi(String name) {
		return "Hi, " + name + ".Happy to see again.";
	}

}
        上述代碼實現類com.gxz.config.StudentImp作爲bean實例化後,取名爲configStudentImp。我們知道,實現類com.gxz.StudentImp作爲bean實例化後,默認取名爲studentImp,這樣一來,二者的名字便不再重複。
        以下註解和@Autowired的用法和作用等價:@Inject、@Resource。其中,@Autowired的全稱爲org.springframework.beans.factory.annotation.Autowired,@Inject的全稱是javax.inject.Inject,@Resource的全稱是javax.annotation.Resource。
        以下註解和@Qualifier的用法和作用等價:@Named。其中,@Qualifier的全稱是org.springframework.beans.factory.annotation.Qualifier,@Named的全稱是javax.inject.Named。

註解@Autowired配置的位置

        到目前爲止,註解@Autowired配置的位置一直在字段上。實際上,它可以位於字段的set方法上,也可以在構造函數上,該構造函數的參數之一是字段。
private Student student;
@Autowired
public void setStudent(Student student) {
	this.student = student;
}
        上述代碼在set方法上告訴Spring注入依賴student。
private Student student;
@Autowired
public StudentController(Student student) {
	super();
	this.student = student;
	System.out.println("StudentController() with argument.");
}
        上述代碼在構造方法上告訴Spring注入依賴。默認情況下,Spring實例化bean時,調用的是bean的默認構造函數,即無參構造函數。但是,若是有構造函數被備註爲@Autowired,那麼,Spring將調用該@Autowired構造函數。因爲,Spring需要調用@Autowired構造函數爲bean注入依賴。
        注意,一個bean不能有兩個或以上的@Autowired構造函數,否則,Spring不知要調用哪個構造函數, 報出如下異常。
        @Autowired
	public StudentController(Student student) {
		super();
		this.student = student;
		System.out.println("StudentController() with argument student.");
	}

	@Autowired
	public StudentController( Teacher teacher) {
		super();
		this.teacher = teacher;
		System.out.println("StudentController() with arguments teacher.");
	}
org.springframework.beans.factory.BeanCreationException: Invalid autowire-marked constructor: public com.gxz.StudentController(com.gxz.Student). Found another constructor with 'required' Autowired annotation: public com.gxz.StudentController(com.gxz.Student,com.gxz.Teacher)

關於排除過濾器存在的問題

        上面我們已經提過排除過濾器,現在談一談它存在的問題。我們有這樣的場景,根應用上下文管理一組和業務邏輯相關的bean,也就是說,控制器bean需要被排除。
<?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-4.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.gxz" use-default-filters="false">
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

</beans>
        上述配置說明,根應用上下文排除控制器bean。但是,根據測試結果,上述配置排除了所有bean。也就是說,包com.gxz及其子包所有的bean都被排除。顯然,這不是我們想要的結果。若是我們把use-default-filters設置爲true,結果不排除任何bean。也就是說,包com.gxz及其子包所有的bean都納入Spring管理,這也不是我們想要的結果。因此,我們要慎重使用排除過濾器,因爲它總是沒有達到我們的期望。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章