How provide a different bean implementation to #Autowire field based on annotation? - java

I have a config class that provides two implemenations of the same base bean interface. I want these to be set on the autowired fields conditionally based on an annotation on the field.
public class MyController
{
#Autowired
private MyBeanInterface base;
#Autowired
#MyAnnotation
private MyBeanInterface special;
}
This is a pesudo-code of the config class:
#Configuration
public class ConfigClass
{
#Bean
#Primary
public MyBeanInterface getNormalBeanInterface()
{
return new MyBeanInterfaceImpl();
}
#Bean
//This doesn't work
#ConditionalOnClass(MyAnnotation.class)
public MyBeanInterface getSpecialBeanInterface()
{
return new MyBeanInterfaceForMyAnnotation();
}
}
How can I make the annotated field be populated by the second bean?

Use Qualifier annotation. Example:
Controller:
Add Qualifier annotation at the injected fields with bean id as parameter:
public class MyController
{
#Autowired
#Qualifier("normalBean")
private MyBeanInterface base;
#Autowired
#Qualifier("specialBean")
private MyBeanInterface special;
}
ConfigClass
Specify bean id:
#Configuration
public class ConfigClass
{
#Bean(name="normalBean")
#Primary
public MyBeanInterface getNormalBeanInterface()
{
return new MyBeanInterfaceImpl();
}
#Bean(name="specialBean")
public MyBeanInterface getSpecialBeanInterface()
{
return new MyBeanInterfaceForMyAnnotation();
}
}

Related

Dynamically #Autowire bean depending on the #Scope in Spring

I have a service which caches the response. I use a simple Map for caching the response.
I also have two scopes: #RequestScope and #GrpcScope. #RequestScope is obviously for requests received from rest controller whereas #GrpcScope is for grpc requests.
My service doesn't know what scope it is currently running in. Both (grpc and rest) controllers uses the same service and there could be only one scope active at runtime.
I want FooService to use RequestScopedFooCache in if the the scope is #RequestScope and to use GrpcScopedFooCache if the scope is #GrpcScope. How can I do that?
// for RequestScope
#RestController
public class FooRestController{
#Autowired
FooService fooService;
#GetMapping
public BarResponse getFoo(){
return fooService.fooRequest(...)
}
}
// for #GrpcScope
#GrpcController
public class FooGrpcController{
#Autowired
FooService fooService;
public BarResponse getFoo(){
return fooService.fooRequest(...)
}
}
#Service
public class FooService {
#Autowired
FooCache cache; // doesn't know which bean to autowire however there could be only one of them at runtime
BarResponse fooRequest(String id) {
if(cache.get(id))
...
}
}
public interface FooCache {
...
}
#Component
#RequestScope
public class RequestScopedFooCache implements FooCache {
...
}
#Component
#GrpcScope
public class GrpcScopedFooCache implements FooCache {
...
}
Using SpringBoot version 2.4.+
Not exactly what you are asking for but I would probably create my own bean methods for FooService and give them qualifying names.
Changed FooService to take FooCache as a constructor:
public class FooService {
private final FooCache cache;
public FooService(FooCache cache) {
this.cache = cache;
}
BarResponse fooRequest(String id) {
if (cache.get(id))
...
}
}
Give the FooCache implementations qualifying names:
public interface FooCache {
...
}
#Component("GRPC_CACHE")
#RequestScope
public class RequestScopedFooCache implements FooCache {
...
}
#Component("REQUEST_CACHE")
#GrpcScope
public class GrpcScopedFooCache implements FooCache {
...
}
A bean manager to setup the FooService beans:
class FooServiceBeanManager {
#Bean("GRPC_SERVICE")
public FooService grpcFooService(#Qualifier("GRPC_CACHE") FooCache fooCache) {
return new FooService(fooCache);
}
#Bean("REQUEST_SERVICE")
public FooService requestFooService(#Qualifier("GRPC_CACHE") FooCache fooCache) {
return new FooService(fooCache);
}
}
Specify which bean you want in your controllers using #Qualifer:
// for RequestScope
#RestController
public class FooRestController {
private final FooService fooService;
#Autowired
public FooRestController(#Qualifier("REQUEST_SERVICE") FooService fooService) {
this.fooService = fooService;
}
#GetMapping
public BarResponse getFoo() {
return fooService.fooRequest(...)
}
}
// for #GrpcScope
#GrpcController
public class FooGrpcController {
private final FooService fooService;
#Autowired
public FooGrpcController(#Qualifier("GRPC_SERVICE") FooService fooService) {
this.fooService = fooService;
}
public BarResponse getFoo() {
return fooService.fooRequest(...)
}
}
I changed the controller classes to have #Autowired constructors instead of fields. It is the recommended usage (source: Spring #Autowire on Properties vs Constructor)
I also personally prefer to have my own #Bean methods as much as possible because it gives me much more control.

NoSuchBeanDefinitionException with Multiple JdbcTemplate and Spring Boot

I am using spring-boot-starter-jdbc and trying to use multiple Jdbc DataSources, everything worked when I used named Beans for my JdbcTemplate and then used #Qualifier to inject the right JdbcTemplate into my repository.
Application:
package my.app;
#SpringBootApplication
#EnableAutoConfiguration
public class Application implements CommandLineRunner {
#Autowired
private MyRepository repository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
List<Stuff> stuff = repository.getStuff();
}
}
Configuration:
package my.app;
#Configuration
#ComponentScan
public class AppConfig {
#Bean(name = "datasource1")
#ConfigurationProperties(prefix = "db1.datasource")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
#Bean(name = "db1")
public DataSource db1(#Qualifier("datasource1" DataSource ds) {
return new JdbcTemplate(ds);
}
#Bean(name = "datasource2")
#ConfigurationProperties(prefix = "db2.datasource")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
#Bean(name = "db2")
public DataSource db1(#Qualifier("datasource1" DataSource ds) {
return new JdbcTemplate(ds);
}
}
Repository:
package my.app;
#Repository
public class MyRepository {
private JdbcTemplate db1;
private JdbcTemplate db2;
#Autowired
public class MyRepository(#Qualifier("db1") JdbcTemplate db1, #Qualifier("db2") JdbcTemplate db2) {
this.db1 = db1;
this.db2 = db2;
}
}
When I instantiate MyRepository everything is fine.
I did a little refactoring to try to make new classes for Db1JdbcTemplate and Db2JdbcTemplate so I could inject them without qualifiers. Unfortunately when I do this I get the following exception:
Here's what I attempted to do:
Removed named JdbcTemplate Beans from AppConfig:
package my.app;
#Configuration
#ComponentScan
public class AppConfig {
#Bean(name = "datasource1")
#ConfigurationProperties(prefix = "db1.datasource")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
#Bean(name = "datasource2")
#ConfigurationProperties(prefix = "db2.datasource")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
}
Created 2 new classes for named JdbcTemplates:
package my.app;
#Component
public class Db1JdbcTemplate extends JdbcTemplate {
#Autowired
public Db1JdbcTemplate(#Qualifier("datasource1") DataSource ds1) {
super(ds1);
}
}
package my.app;
#Component
public class Db2JdbcTemplate extends JdbcTemplate {
#Autowired
public Db2JdbcTemplate(#Qualifier("datasource2") DataSource ds2) {
super(ds2);
}
}
Modified MyRepository to use those:
package my.app;
#Repository
public class MyRepository {
private Db1JdbcTemplate db1;
private Db2JdbcTemplate db2;
#Autowired
public class MyRepository(Db1JdbcTemplate db1, Db2JdbcTemplate db2) {
this.db1 = db1;
this.db2 = db2;
}
}
So I have #Component annotations on both of those so I expected to be able to be registered and be able to be Autowired. However when I run things I get the following exception:
Error creating bean with name 'repository' defined in file
[/Users/me/spring/target/classes/my/app/MyRepository.class]:
Unsatisfied dependency expressed through constructor parameter 0;
nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type 'my.app.Db1JdbcTemplate' available: expected
at least 1 bean which qualifies as autowire candidate. Dependency
annotations: {}
Any ideas what I need to change or how I can debug further? I can always go back to the working version but I'd prefer to be more explicit when possible so the compiler can help me out instead of runtime errors caused by a typo'd Qualifier name.
A more standard way of doing this and avoid typos in qualifier names is to avoid using qualifier names, and instead use Qualifier annotations:
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Qualifier
public #interface DB1 {
}
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Qualifier
public #interface DB2 {
}
and then, when defining your beans:
#Configuration
#ComponentScan
public class AppConfig {
#Bean
#ConfigurationProperties(prefix = "db1.datasource")
#DB1
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
#Bean
#DB1
public JdbcTemplate db1(#DB1 DataSource ds) {
return new JdbcTemplate(ds);
}
#Bean
#ConfigurationProperties(prefix = "db2.datasource")
#DB2
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
#Bean
#DB2
public JdbcTemplate db1(#DB2 DataSource ds) {
return new JdbcTemplate(ds);
}
}
And finally when injecting your beans:
public class MyRepository(#DB1 JdbcTemplate db1, #DB2 JdbcTemplate db2) {
...
}
If the main concern is typos, just define your names as constants (something like public static final String DATASOURCE1 = "datasource1";) and consistently use those in your qualifier annotations instead of string literals. No need to add new classes or interfaces.

spring multiple transactionmanagers for similar datasources

I have two datasources that are equal in their structure but not in their Data.
My application has to deal with both of them at the same time.
I have controller, servie, dao structure that looks like this.
Controller Modell:
#Controller
public abstract class MyFancyControllerModell{
private MyService service;
public MyFancyControllerModell (MyService service){
this.service = service;
}
#RequestMapping(....)
public void editSomeData(String data){....}
}
Controller implementation:
#Controller
#RequestMapping(...)
public class MyControllerImpl1 extends MyFancyControllerModell{
#Autowired
public MyControllerImpl1(#Qualifier("myServiceInstance1") MyService service){
super(service);
}
}
#Controller
#RequestMapping(...)
public class MyControllerImpl2 extends MyFancyControllerModell{
#Autowired
public MyControllerImpl2(#Qualifier("myServiceInstance2") MyService service){
super(service);
}
}
And the Service:
public class MyService{
private MyDao myDao;
public MyService(MyDao myDao){
this.myDao = myDao;
}
#Transactional
public void editSomeData(String data){...}
}
I create the Beans in my configuration class like this:
private DataSource lookupDataSource(final String jndiName) {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
dsLookup.setResourceRef(true);
return dsLookup.getDataSource(jndiName);
}
#Bean
public DataSource dataSource1(){
return lookUpDataSource("dataSource1");
}
#Bean
public DataSource dataSource2(){
return lookUpDataSource("dataSource2");
}
#Bean
public MyService myServiceInstance1(#Qualifier("dataSource1") DataSource dataSource){
return new(MyService(new MyDao(dataSource))));
}
#Bean
public MyService myServiceInstance1(#Qualifier("dataSource1") DataSource dataSource){
return new(MyService(new MyDao(dataSource)));
}
My question is, is it possible to create transactionmanagers for both datasources without the need to declare in the service layer which transactionmanager is used?
I tried creating them as bean just like the services but that did not work.
Check out here the answers for previous quesion related to transaction manager...
https://stackoverflow.com/a/1961853/7504001

Not injected bean in class that extends abstract class in Spring Boot

I have trouble with initializing bean and injecting JPA repository into one particular bean. No idea why it doesn't work...
There is a interface defining key service:
public interface KeyService {
Store getKeyStore();
Store getTrustStore();
}
and abstract class that implements this interface:
public abstract class DefaultKeyService implements KeyService {
abstract KeyRecord loadKeyStore();
abstract KeyRecord loadTrustStore();
/* rest omitted... */
}
and base class that extends abstract class:
#Service
public class DatabaseKeyService extends DefaultKeyService {
#Autowired
private KeyRecordRepository keyRecordRepository;
#Override
protected KeyRecord loadKeyStore() {
return extract(keyRecordRepository.findKeyStore());
}
#Override
protected KeyRecord loadTrustStore() {
return extract(keyRecordRepository.findTrustStore());
}
/* rest omitted... */
}
And bean initialization:
#Bean
public KeyService keyService() {
return new DatabaseKeyService();
}
This is a KeyRecordRepository repository:
public interface KeyRecordRepository extends Repository<KeyRecord, Long> {
KeyRecord save(KeyRecord keyRecord);
#Query("SELECT t FROM KeyRecord t WHERE key_type = 'KEY_STORE' AND is_active = TRUE")
Iterable<KeyRecord> findKeyStore();
#Query("SELECT t FROM KeyRecord t WHERE key_type = 'TRUST_STORE' AND is_active = TRUE")
Iterable<KeyRecord> findTrustStore();
KeyRecord findById(long id);
}
Question: is there some reason why keyRecordRepository in DatabaseKeyService class is still null? Really I have no idea why only this this field is not injected. Other beans and repositories works perfectly fine.
Couldn't be a problem because parent class is an abstract class?
DatabaseKeyService must be annotated with #Component to be a Spring managed bean.
Your problem is related with having 2 beans for class DatabaseKeyService. One from configuration class - #Bean annotation and second from #Service annotation.
Probably when you remove
#Bean
public KeyService keyService() {
return new DatabaseKeyService();
}
injecting with #Service will be work.
If you want use #Bean you must add KeyRecordRepository. I prefer using constructor injection so firstly create it in DatabaseKeyService
public DatabaseKeyService(KeyRecordRepository keyRecordRepository) {
this.keyRecordRepository = keyRecordRepository;
}
Then in your configuration file
//other
#Autowired
private KeyRecordRepository keyRecordRepository;
#Bean
public KeyService keyService() {
return new DatabaseKeyService(keyRecordRepository);
}

SpringJUnit4ClassRunner with declarative and annotated beans

I have a declarative spring config
#Configuration
public class SpringConfig {
#Bean
public someBean() {
return new Bean1();
}
}
and a #Component annotated Bean
#Component
public class Bean2 {
}
Now I would like to use both of then in my UnitTest
#RunWith(SpringJUnit4ClassRunner.class)
public void UnitTest {
#Autowired Bean1 bean1;
#Autowired Bean2 bean2;
}
but I have no idea how to do it.
You can do this:
#ContextConfiguration(classes = {SpringConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
public void UnitTest {
#Autowired Bean1 bean1;
#Autowired Bean2 bean2;
}
For class Bean2, you can add the #ComponentScan annotation:
#Configuration
#ComponentScan("com....package.of.bean2")
public class SpringConfig {
#Bean
public someBean() {
return new Bean1();
}
}
If you don't want to add the ComponentScan to your SpringConfig class, you can add an additional test config class with the ComponentScan annotation and add it to the ContextConfiguration annotation:
#ContextConfiguration(classes = {SpringConfig.class, SpringTestConfig.class})

Categories