Problems using getBean() with constructor args, multiple application contexts - java

I'm new to spring and am working on an app that uses multiple application contexts, configured using annotation.
I have one context where I am creating 3 singleton beans, one of which I want to pass as an argument into the factory method for a prototype bean which will live in a different application context.
This other application context is created as one of the singleton beans within this original context.
The problem I am seeing is that, at the point at which I try to use getBean() to create this other bean that lives in this second context (see the 'someBean()' factory method below), I get an exception from the framework:
Error creating bean with name 'someBean' defined in class
org.imaginary.SpringAppDependencyConfiguration: Instantiation of bean
failed; nested exception is...Unsatisfied
dependency expressed through constructor argument with index 0 of type
[org.imaginary.ISomeDependency]: : No qualifying bean of type
[org.imaginary.ISomeDependency] found for dependency: expected at
least 1 bean which qualifies as autowire candidate for this
dependency.
What have I jacked up here?
The config for the original context looks like so:
#Configuration
public class SpringAppDependencyConfiguration
{
#Autowired
private ISomeDependency someDependency;
#Autowired
private AnnotationConfigApplicationContext otherSpringContext;
#Bean(destroyMethod="close")
public ISomeDependency someDependency()
{
return new SomeDependencyImpl( 13 );
}
#Bean (destroyMethod="close")
public AnnotationConfigApplicationContext otherSpringContext()
{
return new AnnotationConfigApplicationContext(OtherContextDependencyConfiguration.class);
}
#Bean
#DependsOn( { "otherSpringContext", "someDependency" } )
public ISomeBean someBean() throws Exception
{
if ( !otherSpringContext.getBeansOfType( ISomeOtherBean.class ).containsKey( "SomeOtherBean" ) )
{
throw new Exception("SpringAppDependencyConfiguration.someBean(): " +
"unable to find SomeOtherBean implementation");
}
ISomeOtherBean someOtherBean = (ISomeOtherBean) otherSpringContext.getBean( "SomeOtherBean", someDependency );
return new SomeBeanImpl( someDependency, someOtherBean );
}
}
The config for the other application context looks like so:
#Configuration
public class OtherContextDependencyConfiguration
{
#Bean
#Scope("prototype")
public ISomeOtherBean someOtherBean(ISomeDependency theDependency) throws Exception
{
return new SomeOtherBeanImpl(theDependency);
}
}

So, I think I learned that one way I can get the result I was looking for is to create this second context in such a way that it treats the initial context as a parent.
To support this required a different conceptual organization of the classes, so I will try to post an update to this question with my current approach once I've got a digestible version of it.

Related

How to use Spring ObjectProvider with more than one bean definition

I am using an ObjectProvider to create instances of a prototype scope bean using the getObject() method. Something like this
#Configuration
class Config {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
SomeType typeOne() {
return new SomeType();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
SomeType typeTwo(String param) {
return new SomeType(param);
}
}
#Service
class Service {
private ObjectProvider<SomeType> objectProvider;
public Service(
ObjectProvider<SomeType> objectProvider) {
this.objectProvider = objectProvider;
}
#Override
public String performAction() {
return getSomeType().doAction();
}
private SomeType getSomeType() {
return objectProvider.getObject();
}
}
But since there are two beans of the type that the ObjectProvider is trying to get (SomeType), I get a NoUniqueBeanDefinitionException. (And I do need the other bean of the same type, because that one I need to provide parameters using objectProvider.getObject(Object... params) )
Playing around and debugging Spring I saw that if you name your ObjectProvider exactly like your bean then it works, something like:
private ObjectProvider<SomeType> typeOne;
My question is, are there other ways to use an ObjectProvider and manage to resolve ambiguity, or is this approach the way to go?
Short answer is you just need to properly qualify the ObjectProvider you want injected, like this:
public Service(#Qualifier("typeOne") ObjectProvider<SomeType> objectProvider) {
this.objectProvider = objectProvider;
}
With Spring configuration, when you specify a bean via a method, and don't specify it's name with #Bean("NAME"), Spring uses the method name as the bean name.
Similarly, when injecting a bean that is not specified by #Qualifier("NAME"), Spring takes the injected variable as the name, if that don't exists or is not unique, you might get some exceptions informing you about this (like the NoUniqueBeanDefinitionException you facing).
So, if you match the bean name and the injected variable name you don't need to be more specific, but if you don't, #Qualifier is there to your rescue :D

Spring-Boot: Error injection multiple beans

I'm trying to get an application working in Spring-boot, but I'm running into injections errors. I have a #Service with a few #Autowire Classes. The classes our just POJO with a public setDatSource method that I need to set the DataSource via runtime. See below:
#Bean
#Qualifier("datasetDao")
public com.lexi.dao.core.DatasetDAO getDatasetDao() throws NamingException {
DatasetDAOImpl ds = new DatasetDAOImpl();
ds.setDataSource(createAuthReadDataSoure());
return ds;
}
#Bean
public LicenseDAO getLicenseDao() throws NamingException {
LicenseDAOImpl ds = new LicenseDAOImpl();
ds.setReadDataSource(createOnlineDSReadDataSoure());
ds.setWriteDataSource(createOnlineDSWriteDataSoure());
ds.setDistribDataSource(createAuthReadDataSoure());
return ds;
}
I have a Service define as this:
#Service
public class LicenseService {
#Autowired
#Qualifier("datasetDao")
private DatasetDAO datasetDao;
#Autowired
private LicenseDAO licenseDao;
However when I run the application I get this:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field datasetDao in com.wk.online.services.LicenseService required a single bean, but 3 were found:
- createAuthReadDataSoure: defined by method 'createAuthReadDataSoure' in com.wk.online.ws.OnlineWsApplication
- createOnlineDSReadDataSoure: defined by method 'createOnlineDSReadDataSoure' in com.wk.online.ws.OnlineWsApplication
- createOnlineDSWriteDataSoure: defined by method 'createOnlineDSWriteDataSoure' in com.wk.online.ws.OnlineWsApplication
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
I tried to add a #Qualifier but that didn't seem to jive wiht Spring. What am I missing, I been at this for a while and figured i'm doing something very stupid.
When defining the bean, you need to specify name, not qualifier, qualifier annotation should be used where you autowire it:
#Bean(name = "datasetDao")
public com.lexi.dao.core.DatasetDAO getDatasetDao() throws NamingException {
DatasetDAOImpl ds = new DatasetDAOImpl();
ds.setDataSource(createAuthReadDataSoure());
return ds;
}
Do you have #Bean annotation on following methods in OnlineWsApplication class?
createAuthReadDataSoure
createOnlineDSReadDataSoure
createOnlineDSWriteDataSoure
If yes get rid of them.
Full code of OnlineWsApplication would be very useful to invastigate it.
In the bean definition, instead of
#Bean
#Qualifier("datasetDao")
Try using the following:
#Bean(name="datasetDao")

Spring Boot test case doesn't use custom conversion service

I am trying to write up an integration test case with Spring Boot Test.
I customize the ConversionService to know about the new java.time types:
#Configuration
public class ConversionServiceConfiguration {
#Bean
public static ConversionService conversionService() {
final FormattingConversionService reg = new DefaultFormattingConversionService();
new DateTimeFormatterRegistrar().registerFormatters(reg);
return reg;
}
}
and then later expect it to work:
#Component
class MyServiceConfig {
#Value("${max-watch-time:PT20s}")
private Duration maxWatchTime = Duration.ofSeconds(20);
}
When running under the normal SpringApplication.run this seems to work fine. However, in my test case:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT, classes= {
MyServiceMain.class,
AttachClientRule.class
})
public class MyTest {
#Inject
#Rule
public AttachClientRule client;
#Test(expected=IllegalArgumentException.class)
public void testBad() throws Exception {
client.doSomethingIllegal();
}
}
it blows up:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AttachClientRule': Unsatisfied dependency expressed through constructor parameter 0:
Error creating bean with name 'MyServiceConfig': Unsatisfied dependency expressed through field 'maxWatchTime': Failed to convert value of type [java.lang.String] to required type [java.time.Duration];
nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.time.Duration]: no matching editors or conversion strategy found;
Peering deep into the guts of the TypeConverterDelegate that does the actual conversion, it seems to capture the ConversionService used from a field on the DefaultListableBeanFactory. Setting a watchpoint on where that field is set, I find the AbstractApplicationContext.refresh() method:
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh(); // <--- MyServiceConfig initialized here
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory); // <--- DefaultListableBeanFactory.conversionService set here!!!
// Last step: publish corresponding event.
finishRefresh();
So the #Value injection is happening before the ConversionService is applied to the BeanFactory. No bueno!
I've found what seems to be a workaround:
#Configuration
public class ConversionServiceConfiguration implements BeanFactoryPostProcessor {
#Bean
public static ConversionService conversionService() {
final FormattingConversionService reg = new DefaultFormattingConversionService();
new DateTimeFormatterRegistrar().registerFormatters(reg);
return reg;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.setConversionService(conversionService());
}
}
This forces the initialization to happen earlier on, but doesn't feel like the right solution (at least it's not documented as such).
Where have I gone wrong?
Spring 4.3.0, Spring Boot 1.4.0M3
EDIT
And now I've discovered another way for it to fail! Without making the same configuration class implement EnvironmentAware:
#Override
public void setEnvironment(Environment environment) {
((AbstractEnvironment) environment).setConversionService(conversionService());
}
I find that the PropertySourcesPropertyResolver uses the wrong (default) ConversionService. This is driving me mad!
Caused by: java.lang.IllegalArgumentException: Cannot convert value [PT15s] from source type [String] to target type [Duration]
at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:94)
at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:65)
at org.springframework.core.env.AbstractPropertyResolver.getProperty(AbstractPropertyResolver.java:143)
at org.springframework.core.env.AbstractEnvironment.getProperty(AbstractEnvironment.java:546)
at com.mycorp.DoSomething.go(DoSomething.java:103)
The Spring Boot developers have confirmed that this is poorly documented and does not work as specified: https://github.com/spring-projects/spring-boot/issues/6222
Try to remove static keyword from conversionService bean definition.

Inheritance with Spring Java Config uses different bean

The following example shows explicit wiring of dependencies using spring java config that results in a different bean being wired in while using and interface for a spring configuration class.
This seems like it shouldn't occur or at least give the normal warning that there are two beans as candidates for autowiring and it doesn't know which to select.
Any thoughts on this issue? My guess is there is no real name spacing between configuration classes as is implied by the syntax "this.iConfig.a()" Could this be considered a bug (if only for not warning about the 2 candidate beans)?
public class Main
{
public static void main( final String[] args )
{
final ApplicationContext context = new AnnotationConfigApplicationContext( IConfigImpl.class, ServiceConfig.class );
final Test test = context.getBean( Test.class );
System.out.println( test );
}
}
public class Test
{
private final String string;
public Test( final String param )
{
this.string = param;
}
public String toString()
{
return this.string;
}
}
#Configuration
public interface IConfig
{
#Bean
public String a();
}
#Configuration
public class IConfigImpl implements IConfig
{
#Bean
public String a()
{
return "GOOD String";
}
}
#Configuration
public class ServiceConfig
{
#Autowired
IConfig iConfig;
#Bean
Test test()
{
return new Test( this.iConfig.a() );
}
#Bean
String a()
{
return "BAD String";
}
}
In this case, I would expect to have "GOOD String" to be always be wired in the Test object, but flipping the order of IConfigImpl.class, ServiceConfig.class in the context loader changes which string is loaded.
Tested with Spring 4.0.7
EDIT: Further testing shows this has nothing to to with inherented configs. Same thing results if you drop the IConfig interface.
I believe this was a behavior of Spring for years.
If you redefine a bean, the one that is being loaded as last wins.
Another question would be how to control the order of bean loading when java configs are used. Check out this article http://www.java-allandsundry.com/2013/04/spring-beans-with-same-name-and.html which shows you how to do the ordering by using #Import of the other Spring java config.
The solution is actually simple - if you need to override a previously
defined bean(without say the flexibility of autowiring with a
different bean name), either use the XML bean configuration for both
the bean being overridden and the overriding bean or use the
#Configuration. XML bean configuration is the first example in this
entry, the one with #Configuration would be something like this:
#Configuration
public class Context1JavaConfig {
#Bean
public MemberService memberService() {
return new MemberSvcImpl1();
}
}
#Configuration
#Import(Context1JavaConfig.class)
public class Context2JavaConfig {
#Bean
public MemberService memberService() {
return new MemberSvcImpl2();
}
}
Stepan has mentioned the issue of order. The following is about your comment on their answer
Overriding beans of the same name makes sense, but in this case, I'm
specifically referencing the bean as specified in the iConfig
configuration. I would expect to get the one specified there.
In order to implement #Configuration and the caching of beans so that calls like
#Configuration
class Example {
#Bean
public UncaughtExceptionHandler uncaughtExceptionHandler() {
return (thread, throwable) -> System.out.println(thread + " => " + throwable.getMessage());
}
#Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Thread newThread() {
Thread thread = new Thread();
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler()); // <<<<<< allowing this
return thread;
}
}
Spring actually uses CGLIB to create a proxy subtype of the #Configuration annotated class. This proxy maintains a reference to the backing ApplicationContext and uses that to resolve a bean.
So the call in your example
return new Test(this.iConfig.a());
isn't really invoking IConfigImpl#a(). It invokes this code (as of 4.2) from the proxy interceptor. The code uses the corresponding Method to determine the target bean name and uses the ApplicationContext's BeanFactory to resolve the bean. Since the bean definition for a bean named a has already been overriden, that new bean definition gets used. That bean definition is using the ServiceConfig#a() method as its factory method.
This is described in the documentation, here
All #Configuration classes are subclassed at startup-time with CGLIB.
In the subclass, the child method checks the container first for any
cached (scoped) beans before it calls the parent method and creates a
new instance.
Could this be considered a bug [...]?
I don't believe so. The behavior is documented.

How to declare a respository populator bean using java config instead of XML?

I am working on a Spring based project that is (so-far) completely XML-free, except now I've hit a wall with the Spring JPA repository populator:
<repository:jackson-populator location="classpath:data.json" />
How would the above be expressed in a java #Configuration class?
This post suggests using the FactoryBean directly:
https://stackoverflow.com/a/13566712/1746274
I tried that and the closest I got was the following but it's not quite right.
#Bean(name="repositoryPopulator")
public RepositoryPopulator getRespositoryPopulator() throws Exception {
final JacksonRepositoryPopulatorFactoryBean factory = new JacksonRepositoryPopulatorFactoryBean();
factory.getObject().setResourceLocation("classpath:test-data.json");
factory.afterPropertiesSet();
return factory.getObject();
}
The above results in a FactoryBeanNotInitializedException with the message JacksonRepositoryPopulatorFactoryBean does not support circular references.
Any ideas?
It's straight-forward actually:
#Configuration
class ApplicationConfig {
#Bean
public JacksonRepositoryPopulatorFactoryBean repositoryPopulator() {
Resource sourceData = new ClassPathResource("test-data.json");
JacksonRepositoryPopulatorFactoryBean factory = new JacksonRepositoryPopulatorFactoryBean();
// Set a custom ObjectMapper if Jackson customization is needed
factory.setObjectMapper(…);
factory.setResources(new Resource[] { sourceData });
return factory;
}
}
By returning the FactoryBean itself, Spring will take care of invoking all the necessarry callback interfaces (i.e. setApplicationContext(…), setBeanClassLoader(…) etc.). The factory bean is an ApplicationListener and thus will listen to the ContextRefreshedEvent and trigger population when the ApplicationContext is fully initialized.

Categories

Resources