springboot难免要用到bean,但这些bean如何导入,对于初学者时间头疼的事,本文尽量简单的例子说明springboot是如何处理bean的。
相关问题:@TestConfiguration 无法覆盖bean
@Configuration 配置不生效
bean的继承和后续手动注入
java注解bean配置类
原则1:@TestConfiguration 复写 @Configuration
即 @TestConfiguration 优先于@Configuration
原则2: xml配置优先于class配置,
即约定优于配置原则,配置改写约定
原则3:后置替换前置
同名文件后置文件替换前置文件,非同名文件,后置的bean替换前置的bean定义;
这里是替换,注意不是补充或者修正是完全替换定义。
@ImportResource(locations = {
"classpath:A.xml",
"classpath:B.xml"}) //这里test里的配置优先级是最高的
这里AB中的如果同时定义了bean1 则最终生效的是B中的bean1;如果引用(父级)包中也存在A,那么恭喜你,引用(父级)包中的A将不会再起作用,即使其中有本项目中A不同的bean定义也不会被实现,完全被本地的A所替代。
@TestConfiguration ,@Configuration,这两个是一对, Configuration用在正式的包里,TestConfiguration是用在测试的包里,是对Configuration的补充和“覆盖”。
这里需要特别注意的是如果同时存在xml和config.class包的引入,则xml会在java配置的bean后面引入,也即xml的配置会覆盖掉Configuration和TestConfiguration中的注入,也就是说TestConfiguration里的java定义的覆盖仅仅是针对java方式定义的bean的有效,xml的则需要单独引入测试用的xml才可以)。如果需要在以java方式覆盖掉xml的配置,这在springboot里是不被允许的,也即xml配置优先于java config方式的注入,以方便通过修改配置的方式升级jar包程序。但是如果想在测试的时候在不引入新的xml的情况下测试怎么覆盖呢?这就需要下面的AnnotationConfigApplicationContext和GenericApplicationContext这两个类来重新注入修改,注意这时候ioc里的是修改了的,但之前通过@Autowired创建的属性是不会自己自动更新的,需要手动更新或者自己重新获取进行测试。
注解引入bean的两种方式
@ImportResource :引入xml
@Import :引入config.class
手动创建ApplicationContext
ClassPathXmlApplicationContext :通过xml方式创建
AnnotationConfigApplicationContext:通过config.class创建
GenericApplicationContext:通过类注册bean
不多说直接上测试代码进行说明
正式包里的xmlbean配置,放在resources目录下,
foo.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd ">
<bean id="foo" class="java.lang.String" autowire="byName">
<constructor-arg index="0" type="java.lang.String" value="foo0xml" />
</bean>
</beans>
test测试包里的xml配置,放在测试的resources目录下,
foo-test.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd ">
<bean id="foo" class="java.lang.String" autowire="byName">
<constructor-arg index="0" type="java.lang.String" value="foo1xml" />
</bean>
</beans>
正式包里的config
Config.java
@Configuration
public class Config {
@Bean
public String foo() {
return "foo0";
}
@Bean
public String foo2() {
return "foo20";
}
}
测试的脚本 FooTest
import 你的package.config.Config;
import org.junit.jupiter.api.Test;//注意这里用的springboot2.4.2,是最新的测试类junit5
import org.junit.jupiter.api.extension.ExtendWith;//springboot2.4.2的测试类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.Assert.assertEquals;
@ExtendWith(SpringExtension.class)
//springboot2.4.2,它的作用就是扫描spring注解,默认启用自动扫描Configuration,和TestConfiguration
public class FooTest {
@Autowired
protected ApplicationContext applicationContext;
@Autowired
public String foo;
@Autowired
public String foo2;
@TestConfiguration()
//这里如果配置的是xml则永远都是xml优先于Configuration,TestConfiguration,在没有xml的时候它会覆盖Configuration里注入的bean
@ImportResource(locations = {
"classpath:foo.xml",
"classpath:foo-test.xml"}) //这里test里的配置优先级是最高的
@Import(Config.class)//这个可以不写,ExtendWith会自动扫描到,放在这仅仅是做对比测试
static class TestConfig {
@Bean
public String foo() {
return "foo11";
}
@Bean
public String foo2() {
return "foo21";
}
}
//这个类在ExtendWith不会被用到,是为了后面的AnnotationConfigApplicationContext 手动注入时bean用的
@Import(Config.class)
static class TestConfig2 {
@Bean
public String foo() {
return "foo12";
}
@Bean
public String foo2() {
return "foo22";
}
}
@Test //初始带xml测试
public void configTest() {
assertEquals("foo1xml",foo);//因为初始时,TestConfiguration加载了xml所以xml的值生效
assertEquals("foo21",foo2);//foo2仅存在于javaconfig里所以TestConfiguration的值生效
}
@Test //初始bean中的值测试
public void config1Test() {
String[] beans = applicationContext.getBeanDefinitionNames();//可以通过遍历这个查看所以注入的bean
assertEquals("foo1xml",applicationContext.getBean("foo"));
assertEquals("foo21",applicationContext.getBean("foo2"));
}
@Test //用ClassPathXmlApplicationContext后续加载xml,相对来说,使用注解方式方便,注解方式的还可以使@Autowired方式的属性生效,
public void config2Test() {
ApplicationContext applicationContext2=new ClassPathXmlApplicationContext(new String[]{
"classpath:foo.xml",
"classpath:foo-test.xml"},applicationContext);
//applicationContext2以applicationContext为父级创建可以集成它的所有bean
// @Autowired方式已经生成过的属性是不能自动使用新的bean的
assertEquals("foo1xml", applicationContext2.getBean("foo"));
assertEquals("foo21",applicationContext2.getBean("foo2"));
}
@Test //用config方式后续加载bean配置
public void config3Test(){
AnnotationConfigApplicationContext applicationContext3=new AnnotationConfigApplicationContext(TestConfig2.class);
(applicationContext3).setParent(applicationContext);
//applicationContext3以applicationContext为父级创建可以集成它的所有bean
// @Autowired方式已经生成过的属性是不能自动使用新的bean的
assertEquals("foo12", applicationContext3.getBean("foo"));
assertEquals("foo22",applicationContext3.getBean("foo2"));
}
@Test //单点覆盖,可以覆盖任何
public void config4Test(){
GenericApplicationContext applicationContext4=new GenericApplicationContext(applicationContext);
applicationContext4.registerBean("foo",String.class,"foo3");//这里是单点方式只能单个修改bean不能使用config.class类进行注入
applicationContext4.refresh();//注入修改完需要refresh以下才可以生效
assertEquals("foo3", applicationContext4.getBean("foo"));
assertEquals("foo21",applicationContext.getBean("foo2"));
}
}
最后,总的来看,springboot bean的加载方式,原则就是通过注解方式注入,xml的配置会在config的配置之后被加载,xml的注入会覆盖config里的而不管你是不是TestConfiguration,根xml的位置无关。如非必要还是建议按照约定的来,java方式注入的bean用TestConfiguration来修改,xml方式注入的测试的时候就用test.xml来覆盖,这样所有的@Autowired都是生效的,方便测试和系统稳定。