How to add #Qualifier - java

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;

Related

How to refer String Bean in #ConditionalOnProperty

I need to initialize a Configuration based on some other bean value that I am fetching from database,
how can we refer the bean in #ConditionalOnProperty value attribute ?
Or is there any other way to achieve the same.
// Bean that will query database
#Bean
public String checkAndCreate()
{
// Logic to Query DB
return "true";
}
Want to Refer the bean checkAndCreate in value
#ConditionalOnPropery(value = "", havingValue = "true")
#Configuration
public class MyConfiguration
{
// Some configuration Code
}
Don't use #ConditionalOnProperty, use #Conditional with a custom condition instead. The ConditionContext should give you access to this bean. You may need to use #DependsOn on to ensure that the class providing the string bean is already initialized.

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

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")
}
}
}

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 : Sharing a bean between different components

I have a bean which I've declared in my bean config as thus:
#Configuration
public class BeanConfig {
#Bean
public MemberDTO getMemberDTO() {
return new MemberDTO();
}
}
When a user calls my service, I use the username and password they've provided to call the endpoint of a different service to get the user's information:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);
private #Autowired MemberDTO memberDTO;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String loginGeniuneFailMessage = "";
boolean loginGeniuneFail = false;
try {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String endPoint = credentialsBaseUrl + "/api/login";
HttpResponse<MemberDTO> response_auth = Unirest.get(endPoint)
.basicAuth(username, password)
.header("Accept", "*/*")
.asObject(MemberDTO.class);
int status_auth = response_auth.getStatus();
if (status_auth == 200) {
if (response_auth.getBody() == null) {
LOGGER.info("account validation - could not parse response body to object");
UnirestParsingException ex = response_auth.getParsingError().get();
LOGGER.error("parsing error: ", ex);
} else {
memberDTO = response_auth.getBody();
}
}
...
} catch (Exception ex) {
...
}
}
I want to store the user's information in the memberDTO and use the memberDTO elsewhere in a different component, rather than calling the login API every time:
#Component
public class MemberLogic {
private #Autowired MemberDTO memberDTO;
public ResponseEntity<?> processMemberInformation(WrapperDTO wrapperDTO, BindingResult result) {
if (result.hasFieldErrors()) {
String errors = result.getFieldErrors().stream()
.map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
return ResponseEntity.badRequest().body("An error occured while trying to persist information: " + errors);
}
String name = memberDTO.getName();
...
}
}
The problem now is the "memberDTO.getName()" is returning null, even though this value is being set from the initial API call in CustomAuthenticationProvider.
My questions are: why isn't this working? And is this the best approach to take for something like this?
Thanks.
My questions are: why isn't this working? And is this the best approach to take for something like this?
This doesn't work because Java uses pass-by-value semantics instead of pass-by-reference semantics. What this means is that the statement memberDTO = response_auth.getBody(); does not really make the Spring container start pointing to the MemberDTO returned by response_auth.getBody(). It only makes the memberDTO reference in CustomAuthenticationProvider point to the object in the response. The Spring container still continues to refer to the original MemberDTO object.
One way to fix this would be to define a DAO class that can be used for interacting with DTO instances rather than directly creating a DTO bean :
#Configuration
public class BeanConfig {
#Bean
public MemberDAO getMemberDAO() {
return new MemberDAO();
}
}
CustomAuthenticationProvider can then set the MemberDTO in the MemberDAO by using : memberDAO.setMemberDTO(response_auth.getBody());
Finally, MemberLogic can access the MemberDTO as String name = memberDAO.getMemberDTO().getName();
Note : Instead of returning the MemberDTO from the MemberDAO, the MemberDAO can define a method called getName which extracts the name from the MemberDTO and returns it. (Tell Don't Ask principle). That said and as suggested in the comments, the best practice would be to use a SecurityContext to store the user information.
The problem is, that you can not override a spring bean "content" like this memberDTO = response_auth.getBody(); because it changes only the instance variable for the given bean. (And its also not good because its out of the spring boot context and it overrides only the field dependency for this singleton bean)
You should not use a normal spring bean for holding data (a state). All the spring beans are singleton by default and you could have some concurrency problems.
For this you should use a database, where you write your data or something like a session bean.

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.

Categories

Resources