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

前言

        所谓Spring应用上下文混合配置,指的是配置一部分在xml文件,一部分在java代码。这种方式不是很常见,常见的要么是纯xml文件配置,要么是纯java编程配置。纯java编程配置后续章节我们会详细讲解。关于这两种纯配置方式的比较,我们前面的章节已经提过。

根应用上下文配置




    
    
        
    

        配置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应用上下文配置



  	

    
    
        
    

业务逻辑相关的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需要被排除。



    
    
        
    


        上述配置说明,根应用上下文排除控制器bean。但是,根据测试结果,上述配置排除了所有bean。也就是说,包com.gxz及其子包所有的bean都被排除。显然,这不是我们想要的结果。若是我们把use-default-filters设置为true,结果不排除任何bean。也就是说,包com.gxz及其子包所有的bean都纳入Spring管理,这也不是我们想要的结果。因此,我们要慎重使用排除过滤器,因为它总是没有达到我们的期望。

你可能感兴趣的:(Spring)