Spring-Boot: Dependency Injection depending on the configuration (and using interfaces) - java

I have the following question regarding the structure of my application architecture.
Assume my application consists of the following components
First of all a #ConfigurationProperties, which initializes my needed properties (here only a type to select a provider).
In addition, a bean of the type "Provider" is to be registered at this point depending on the specified type. This type is implemented as an interface and has two concrete implementations in this example (ProviderImplA & ProviderImplB).
#Configuration
#Profile("provider")
#ConfigurationProperties(prefix = "provider")
#ConditionalOnProperty(prefix = "provider", name = ["type"])
class ProviderConfiguration {
lateinit var type: String
#Bean(name = ["provider"])
fun provider(): Provider {
return when (type) {
"providerA" -> ProviderImplA()
"providerB" -> ProviderImplB()
}
}
}
Next, only the two concrete implementation of the interface.
class ProviderImplA: Provider {
#Autowired
lateinit var serviceA: ServiceA
}
class ProviderImplB: Provider {
#Autowired
lateinit var serviceA: ServiceA
#Autowired
lateinit var serviceB: ServiceB
#Autowired
lateinit var serviceC: ServiceC
}
And last but not least the interface itself.
interface Provider{
fun doSomething()
}
Now to the actual problem or better my question:
Because my concrete implementations (ProviderImplA and ProviderImplB) are no valid defined beans (missing annotation e.g. #Component), but they have to use their own #Service components, it is not possible to use #Autworie at this point. I would like to avoid different profiles/configurations if possible, therefore the initialization by property. How can I still use the individual #Service's within my implementations and still create the provider beans manually, depending on the configuration (only one provider exists at runtime)? Maybe you have other suggestions or improvements?

When instantiating objects directly, spring cannot control its life cycle, so you must create an #Bean for each 'Provider'
#Bean(name = ["providerA"])
#Lazy
fun providerA(): Provider {
return ProviderImplA()
}
#Bean(name = ["providerB"])
#Lazy
fun providerB(): Provider {
return ProviderImplB()
}
IdentityProviderConfiguration class
IdentityProviderConfiguration {
var context: ApplicationContext
...
fun provider(): Provider {
return when (type) {
"providerA" -> context.getBean("providerA")
"providerB" -> context.getBean("providerB")
}
}
}

Related

How to add #Qualifier

How can I add a qualifier to distinguish between these two beans? I know I need to use the #Qualifier annotation but I am not sure how to add it in the beans and then how to create the autowired object with reference to the appropriate bean.
#Configuration
#Slf4j
#PropertySources(PropertySource("classpath:application.properties"),
PropertySource(value = ["file:\${credentials.config}"]))
class CredentialsConfig(#Autowired private val env: Environment) {
#Bean fun getCredentials(): Credentials? {
val user: String = env.getRequiredProperty("user1")
val pass: String = env.getRequiredProperty("pass1")
return Credentials.info(user, pass)
}
#Bean fun getCredentials2(): Credentials {
val user: String = env.getRequiredProperty("user2")
val pass: String = env.getRequiredProperty("pass2")
return Credentials.info(user, pass)
}
}
In situations like this, I find it beneficial to explicitly name my beans so it is more clear which one I am picking. Otherwise, you will end up with what Spring decides to call it (based on the method name). When we want to inject a bean, but there are more than one of them, we use the #Qualifer annotation at the injection point, specifying the name of the bean we care about.
So...
// In CredentialsConfig
#Bean("firstCredentials) fun firstCredentials(): Credentials = TODO()
#Bean("secondCredentials) fun secondCredentials(): Credentials = TODO()
And when wiring in one of these, you can add a #Qualifier to pick your specific implementation (note, if you use constructor injection, you don't need #Autowired):
#Component
class MyComponent(#Qualifier("firstCredentials") creds: Credentials) { ... }
You could just add #Qualifier with bean name whenever you do an Autowire of Credentials.
#Autowired
#Qualifier("getCredentials")
Credentials credentials;

Use Spring #RefreshScope, #Conditional annotations to replace bean injection at runtime after a ConfigurationProperties has changed

I'm running a PoC around replacing bean injection at runtime after a ConfigurationProperties has changed. This is based on spring boot dynamic configuration properties support as well summarised here by Dave Syer from Pivotal.
In my application I have a simple interface implemented by two different concrete classes:
#Component
#RefreshScope
#ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'it'")
public class HelloIT implements HelloService {
#Override
public String sayHello() {
return "Ciao dall'italia";
}
}
and
#Component
#RefreshScope
#ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'us'")
public class HelloUS implements HelloService {
#Override
public String sayHello() {
return "Hi from US";
}
}
application.yaml served by spring cloud config server is:
config:
name: Default App
dynamic:
context:
country: us
and the related ConfigurationProperties class:
#Configuration
#ConfigurationProperties (prefix = "config.dynamic")
public class ContextHolder {
private Map<String, String> context;
Map<String, String> getContext() {
return context;
}
public void setContext(Map<String, String> context) {
this.context = context;
}
My client app entrypoint is:
#SpringBootApplication
#RestController
#RefreshScope
public class App1Application {
#Autowired
private HelloService helloService;
#RequestMapping("/hello")
public String hello() {
return helloService.sayHello();
}
First time I browse http://locahost:8080/hello endpoint it returns "Hi from US"
After that I change country: us in country: it in application.yaml in spring config server, and then hit the actuator/refresh endpoint ( on the client app).
Second time I browse http://locahost:8080/hello it stills returns "Hi from US" instead of "ciao dall'italia" as I would expect.
Is this use case supported in spring boot 2 when using #RefreshScope? In particular I'm referring to the fact of using it along with #Conditional annotations.
This implementation worked for me:
#Component
#RefreshScope
public class HelloDelegate implements HelloService {
#Delegate // lombok delegate (for the sake of brevity)
private final HelloService delegate;
public HelloDelegate(
// just inject value from Spring configuration
#Value("${country}") String country
) {
HelloService impl = null;
switch (country) {
case "it":
this.delegate = new HelloIT();
break;
default:
this.delegate = new HelloUS();
break;
}
}
}
It works the following way:
When first invocation of service method happens Spring creates bean HelloDelegate with configuration effective at that moment; bean is put into refresh scope cache
Because of #RefreshScope whenever configuration is changed (country property particularly in this case) HelloDelegate bean gets cleared from refresh scope cache
When next invocation happens, Spring has to create bean again because it does not exist in cache, so step 1 is repeated with new country property
As far as I watched the behavior of this implementation, Spring will try to avoid recreating RefreshScope bean if it's configuration was untouched.
I was looking for more generic solution of doing such "runtime" implementation replacement when found this question. This implementation has one significant disadvantage: if delegated beans have complex non-homogeneous configuration (e.g. each bean has it's own properties) code becomes lousy and therefore unsafe.
I use this approach to provide additional testability for artifacts. So that QA would be able to switch between stub and real integration without significant efforts. I would strongly recommend to avoid using such approach for business functionality.

Register different configurations of the same bean

Using Spring 5 on Java 9....
Not even sure this is possible. Have a very simple class:
public class Exchange {
#Autowired
private OtherService other;
#JmsListener(destination = {some.queue})
#Transactional
public void receive(String payload) {
other.send(payload)
}
}
Both Exchange and OtherService just needs a couple of configuration properties (i.e. some.queue) to work. Would like to register (either through BeanDefinitionRegistryPostProcessor or ApplicationContextInitializer) multiple instances of the Exchange bean but with prefixed configuration properties. Anyway to alter attribute definition when registering a bean?
I think you want a combination of two things, #ConfigurationProperties and #Qualifier.
ConfigurationProperties let's you supply a prefix that applies to all of the #Value properties loaded injected in tho that class.
#Qualifier allows you to specify which one of potentially many valid #Autowire targets you'd like. Specify the bean name and you are set.
Quick untested example:
#Component("FooExchange")
#ConfigurationProperties(prefix = "foo")
class FooExchange implements Exchange {
#Value("enabled") private boolean enabled;
...
}
#Component("BarExchange")
#ConfigurationProperties(prefix = "bar")
class BarExchange implements Exchange {
#Value("enabled") private boolean enabled;
...
}
And the properties you'd define are:
foo.enabled = true
bar.enabled = true
And when you inject one:
#Autowired
#Qualifier("FooExchange")
private Exchange myInjectedExchange; // Which will get FooExchange
Edit: You may beed to annotate one of your configuration classes or your main class with #EnableConfigurationProperties to enable the configuration properties.

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.

Referencing a bean with dependencies using Spring Java Config

In the following Spring Java Config:
#Configuration
#EnableAutoConfiguration
#ComponentScan("my.package")
public class Config {
#Bean
public BasicBean basicBean1() {
return new BasicBean("1");
}
#Bean
public BasicBean basicBean2() {
return new BasicBean("2");
}
#Bean
public ComplexBean complexBeanByParameters(List<BasicBean> basicBeans) {
return new ComplexBean(basicBeans);
}
#Bean
public ComplexBean complexBeanByReferences() {
return new ComplexBean(Arrays.asList(basicBean1(), basicBean2()));
}
}
I can create two ComplexBeans using either parameter injection, which is elegant, but has shortcomings if a have a few other beans of BasicBean type and only want a few (the parameters can of course be of type BasicBean and enumerate by name the beans I'm interested of, but it could turn out to be a very long list, at least for arguments sake). In case I wish to reference the beans directly I might use the complexBeanByReferences style, which could be useful in case of ordering or some other consideration.
But say I want to use the complexBeanByReference style to reference the bean complexBeanByParameters, that is something along the line of:
#Bean
public ComplexBeanRegistry complexBeanRegistry() {
return new ComplexBeanRegistry(
Arrays.asList(
complexBeanByParameters(), // but this will not work!
complexBeanByReferences()
)
);
}
How would I reference complexBeanByParameters, without having to specify a list of dependencies to complexBeanRegistry? Which, the latter in all honesty should be completely oblivious of.
There is the option to just use
public ComplexBeanRegistry complexBeanRegistry(List<ComplexBeans> complexBeans) {...}
of course, but this might not be an option in certain cases, specifically when using the CacheConfigurer from spring-context. In this case the Java Config is intended to
create the beans
by implementing CacheConfigurer, override the default instances of the CacheManager and KeyGenerator beans.
The requirement to implement CacheConfigurer means I can't change the signature to use parameter injection.
So the question is, is there a way to reference complexBeanByParameters using the "direct" reference style?
Maybe you could reference it with separation by Qualifier:
#Bean
#Qualifier("complexBeanParam")
public ComplexBean complexBeanByParameters(List<BasicBean> basicBeans) {
return new ComplexBean(basicBeans);
}
#Bean
#Qualifier("complexBeanRef")
public ComplexBean complexBeanByReferences() {
return new ComplexBean(Arrays.asList(basicBean1(), basicBean2()));
}
and for example autowire:
#Autowired
#Qualifier("complexBeanParam")
private ComplexBean beanParam;

Categories

Resources