问题
Does anyone know if there any way that I can programmatically create a bean context?
I want to be able to do something like:
ConfigurableApplicationContext c = new ConfigurableApplicationContext();
BeanDefinition bd = new BeanDefinition();
bd.setId("id");
bd.setClassName("classname");
bd.setProperty("propertyName", propertyValue");
...etc...
or better still be able to inject a ready made bean into the application context:
c.addBean("beanId", beanObject);
Or if I'm using annotations:
c.setAnnotationAware(true);
c.setAnnotationScanBasePackage("packagename");
or
c.addAnnotatedSpringClass("classnamethatisannotated");
The rationale for this is that I want to be able override bean definitions for the purpose of testing - In my test I create this new application context, configured with code in the test (not in xml) and then make this test application context have as a parent the SUT application context.
I haven't found any code in the spring libraries that can do this. Has anyone built something like this? Would it be possible to build something like this? I know the former approach is doable, I'm not 100% sure the latter approaches will work without conditions.
回答1:
Try either:
- Spring JavaConfig
- Grails BeanBuilder
- new AtUnit
JavaConfig code sample
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
BeanBuilder code sample
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 code sample
Unit test
@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);
}
}
Context file ( private for the test )
<?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>
回答2:
Why don't you just use two different contexts? one for production, one for tests... you're going about this the hard way.
回答3:
In Spring you can override bean definition as easily as making them appear again lower in the file. We use this a lot for the very purpose you described; to have a different bean definition for unit tests than for production.
This is the pattern we use for our test-context.xml
<import resource="classpath:production-context.xml">
<bean id="overriddenBean" class="com.MyClass">
....
</bean>
This means that the bean with id = overriddenBean will be wired into the classes in your production contewxts were it is referenced. Allowing you to swap the beans you need for testing in place of those that you need for production code.
Hope this helps
回答4:
Just add a bean factory post processor that can manipulate/add any bean definition
public class ABeanFactoryPostProcessor implements
BeanFactoryPostProcessor {
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinition beanDefinition=...
((BeanDefinitionRegistry)beanFactory).registerBeanDefinition(name, beanDefinition);
}
}
}
回答5:
There is brand new way to do this - 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");
}
}
回答6:
I solved this problem using bytebuddy. This builder creates javaconfig class that can be passed to spring.
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:
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.7</version>
<scope>compile</scope>
</dependency>
来源:https://stackoverflow.com/questions/974620/how-does-one-programmatically-create-a-spring-context