如何以编程方式创建Spring上下文?

时间:2009-06-10 09:48:15

标签: unit-testing spring

有没有人知道我是否可以通过编程方式创建bean上下文?

我希望能够做到这样的事情:

ConfigurableApplicationContext c = new ConfigurableApplicationContext();
BeanDefinition bd = new BeanDefinition();
bd.setId("id");
bd.setClassName("classname");
bd.setProperty("propertyName", propertyValue");
...etc...

或更好仍然能够将现成的bean注入应用程序上下文:

c.addBean("beanId", beanObject);

或者如果我正在使用注释:

c.setAnnotationAware(true);
c.setAnnotationScanBasePackage("packagename");

c.addAnnotatedSpringClass("classnamethatisannotated");

这个的基本原理是我希望能够覆盖bean定义以进行测试 - 在我的测试中,我创建了这个新的应用程序上下文,在测试中配置了代码(不是在xml中),然后生成这个测试应用程序context具有SUT应用程序上下文的父级。

我没有在spring库中找到任何可以执行此操作的代码。有没有人建造这样的东西?是否有可能建立这样的东西?我知道前一种方法是可行的,我不能100%确定后一种方法可以无条件地工作。

6 个答案:

答案 0 :(得分:9)

尝试:


JavaConfig代码示例

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

BeanBuilder代码示例

def bb = new grails.spring.BeanBuilder()

bb.beans { 
  dataSource(BasicDataSource) { 
    driverClassName = "org.hsqldb.jdbcDriver" 
    url = "jdbc:hsqldb:mem:grailsDB" 
    username = "sa" 
    password = "" 
  } 

  sessionFactory(ConfigurableLocalSessionFactoryBean) { 
    dataSource = dataSource
    hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop", "hibernate.show_sql":true ] 
  }   
}

AtUnit代码示例

单元测试

@RunWith(AtUnit.class)
@Container(Container.Option.SPRING)
@MockFramework(MockFramework.Option.EASYMOCK)
public class ExampleSpringEasyMockTest {

    @Bean @Unit UserManagerImpl manager;
    @Bean("fred") User fred;
    @Bean("userDao") @Mock UserDao dao;
    @Bean("log") @Stub Logger log;

    @Test
    public void testGetUser() {
        expect(dao.load(1)).andReturn(fred);
        replay(dao);
        assertSame(fred, manager.getUser(1));
        verify(dao);
    }


}

上下文文件(测试专用)

<?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-2.0.xsd">

  <bean id="userManager" class="atunit.example.subjects.UserManagerImpl">
      <constructor-arg ref="log"/>
      <property name="userDao" ref="userDao"/>
  </bean>

  <bean id="fred" class="atunit.example.subjects.User">
      <property name="id" value="500"/>
      <property name="username" value="fred"/>
  </bean>

</beans>

答案 1 :(得分:4)

为什么不只使用两种不同的背景?一个用于生产,一个用于测试...你正在努力解决这个问题。

答案 2 :(得分:4)

在Spring中,您可以轻松覆盖bean定义,使其在文件中再次显示为低位。为了你描述的目的,我们经常使用它;单元测试的bean定义与生产不同。

这是我们用于test-context.xml的模式

<import resource="classpath:production-context.xml">

<bean id="overriddenBean" class="com.MyClass">
   ....
</bean>

这意味着id = overriddenBean的bean将连接到生成的contewxts中的类,如果它被引用的话。允许您交换测试所需的bean代替生产代码所需的bean。

希望这有帮助

答案 3 :(得分:3)

只需添加一个可以操作/添加任何bean定义的bean工厂后处理器

    public class ABeanFactoryPostProcessor implements
        BeanFactoryPostProcessor {

    public void postProcessBeanFactory(
            ConfigurableListableBeanFactory beanFactory) throws BeansException {

            if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinition beanDefinition=...

                ((BeanDefinitionRegistry)beanFactory).registerBeanDefinition(name, beanDefinition);
            }
    }

}

答案 4 :(得分:1)

有一种全新的方法 - Spring Boot

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application implements CommandLineRunner {

    private static final Logger LOG = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        LOG.info("Hello world");
    }
}

答案 5 :(得分:0)

我使用bytebuddy解决了这个问题。此构建器创建可传递给spring的javaconfig类。

public class ContextConfigBuilder {

    private List<Class<?>> imports;

    private List<Class<?>> basePackageClasses;

    //..add what you need

    public ContextConfigBuilder imports(List<Class<?>> imports) {
        this.imports = imports;
        return this;
    }

    public ModuleContextConfigBuilder basePackageClasses(List<Class<?>> basePackageClasses) {
        this.basePackageClasses = basePackageClasses;
        return this;
    }

    public Class<?> build() {
        Class<?> dynamicType = new ByteBuddy()
        .subclass(Object.class)
        .annotateType(AnnotationDescription.Builder.ofType(Configuration.class)
                .build())
        .annotateType(AnnotationDescription.Builder.ofType(Import.class)
                .defineTypeArray("value", this.imports.toArray(Class[]::new))
                .build())
        .annotateType(AnnotationDescription.Builder.ofType(ComponentScan.class)
                .defineTypeArray("basePackageClasses", this.basePackageClasses.toArray(Class[]::new))
                .build())
        .make()
        .load(getClass().getClassLoader())
        .getLoaded();
        return dynamicType;
    }
}

Bytebuddy依赖项:

         <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.10.7</version>
            <scope>compile</scope>
        </dependency>