Lets say I have a config class (JmsQueueConfig, see below). In this class, I want to configure multiple queues for my entire application. For one queue, there is no problem. However when I add a second queue and try to use one of these queues from a service (MemberService), then Spring-boot tells me
Parameter 1 of constructor in
com.example.notification.application.jms.JmsEventPublisher
required a single bean, but 2 were found:
- queueAccountToNotification: defined by method 'queueAccountToNotification' in class path resource
[com/example/notification/application/jms/JmsQueueConfig.class]
- queueNotificationToAccount: defined by method 'queueNotificationToAccount' in class path resource
[com/example/notification/application/jms/JmsQueueConfig.class]
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
Here is my Config-Class:
#Configuration
#EnableJms
#ImportAutoConfiguration(classes = {
JmsAutoConfiguration.class,
ActiveMQAutoConfiguration.class
})
public class JmsQueueConfig {
#Value("${APP_QUEUE_ACCOUNT_TO_NOTIFICATION}")
private String queueAccountToNotificationName;
#Value("${APP_QUEUE_NOTIFICATION_TO_ACCOUNT}")
private String queueNotificationNameToAccount;
#Bean
#Qualifier("q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
#Bean
#Qualifier("q2")
public Queue queueNotificationToAccount() {
return new ActiveMQQueue(queueNotificationNameToAccount);
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#Bean
#Qualifier("p1")
public EventPublisher eventPublisher(JmsTemplate jmsTemplate) {
return new JmsEventPublisher(jmsTemplate, new ActiveMQQueue(queueAccountToNotificationName));
}
#Bean
public MessageConverter messageConverter() {
return new JmsMessageConverter();
}
}
My Service:
#Service
#FieldDefaults(level = AccessLevel.PRIVATE)
#AllArgsConstructor
#Slf4j
public class MemberService {
#Autowired
#Qualifier("q1")
Queue q;
#Qualifier("p1")
EventPublisher eventPublisher;
public void createMemberSubscription(final Member member) {
final MembershipSubscriptionEvent event = new MembershipSubscriptionEvent(UUID.randomUUID().toString(), member.getEmail());
//eventPublisher.publish(event);
log.info("createMemberSubscription");
}
public void removeMemberSubscription(final Member member) {
final MembershipRemovalEvent event = new MembershipRemovalEvent(UUID.randomUUID().toString());
//eventPublisher.publish(event);
log.info("removeMemberSubscription");
}
}
I am new to Spring ecosystem and I might not have understand well the #Autowired and bindings. Any good documentation or example would be much appreciated.
On Spring and SoF, I haven't found any such documentation.
Updated:
JmsEventPublisher class
#Component
#FieldDefaults(level = AccessLevel.PRIVATE)
#Slf4j
#AllArgsConstructor
public class JmsEventPublisher implements EventPublisher {
final JmsTemplate jmsTemplate;
final Destination destination;
#Override
public void publish(DomainEvent event) {
jmsTemplate.convertAndSend(destination, event);
log.trace("Sent event. [destination={}, event={}]", destination, event);
}
}
To correct the accepted answer,your understanding for the use of #Qualifier is correct. It can be used in two contexts. It can be used with a #Bean configuration method to provide a qualifier for the bean. If not provided this defaults to the bean name.
It can also be used at an injection target i.e method or field with #Autowired or #Inject annotation. At this context it assist Spring autowiring infrastructure to filter the bean candidates matching the injection target based on the qualifier(provided with #Bean method) if multiple beans are found
The cause of the error was due to
#AllArgsConstructor
public class JmsEventPublisher implements EventPublisher
The #AllArgsConstructor will generate the following constructor
public JmsEventPublisher(JmsTemplate jmsTemplate, Destination destination){
//body
}
Spring will attempt to autowire JmsEventPublisher by constructor since it has one constructor which is not a no argument constructor. However the parameter of type Destination matches two beans of type Queue.
The solution is to either use an explicit constructor. ie remove #AllArgsConstructor and define constrctor as follows
public JmsEventPublisher(JmsTemplate jmsTemplate, #Qualifier("q1")Destination destination){
//body
}
or use field or setter injection instead i.e remove #AllArgsConstructor and inject fields or setter methods
public class JmsEventPublisher implements EventPublisher {
private JmsTemplate jmsTemplate;
#Qualifier("q1")
private Destination destination;
}
I think you have misunderstood #Qualifier
From the documentation, "This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring."
In your case #Qualifier is of no meaning.
#Bean
#Qualifier("q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
Instead you should be doing like this
#Bean(name = "q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
#Bean(name = "q2")
public Queue queueNotificationToAccount() {
return new ActiveMQQueue(queueNotificationNameToAccount);
}
Similarly remove #Qualifier on eventPublisher(...)
That doesn't solve all the problem. :)
As the exception indicates, spring is not able to autowire Destination field in JmsEventPublisher. Because it has two beans of type Destination(q1 and q2).
To solve this what you can do is .
Put #Primary on one of the bean declaration and then use #Qualifier.
#Primary
#Bean(name = "q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
public class JmsEventPublisher implements EventPublisher {
final JmsTemplate jmsTemplate;
#Qualifier("q1")
final Destination destination;
..........
}
Bottom line is For #Qualifier to work in case of multiple beans of same type, you need to put #Primary
Another option is instead of using #Primary, you can name the variables exactly as Bean names, then spring will automagically inject correct beans for you. i.e.
public class JmsEventPublisher implements EventPublisher {
final JmsTemplate jmsTemplate;
final Destination q1; // q1 or q2
.....
}
similiarly in MemberService
public class MemberService {
#Autowired
Queue q1; // q1 or q2
.....
}
Change final Destination destination to final Destination q1 and it should work. I had the same problem and it worked for me.
Related
An existing utility exists that I am importing to my project. It is written similarly as these two classes
#Service
public class ServiceAccessorImpl implements ServiceAccessor {
#Autowired private ServiceConfiguration serviceConfiguration;
public Response executeCall(){
return callEndPoint(serviceConfiguration.getServiceEndPoint());
}
}
#Configuration
#Data //automatically adds getters and setters
#ConfigurationProperties(prefix="config")//pulls serviceEndPoint value from external config
//Assume external config has config.serviceEndPoint = "www.endpoint1.com"
public class ServiceConfiguration {
private String serviceEndPoint;
}
In a separate project below I am importing the above into my project. I would like to have two instances of the same service with two unique and respective configuration classes. so that service1 is linked to config1 and service2 is linked to config2. My reasoning is I want one instance that only pulls the endpoint from the external configuration and another instance that I can use to set the endpoint. I have tried using things like #Qualifier but I cant figure out how to link the correct config with the correct service. I have a feeling that this may not be possible because ServiceConfiguration is privately scoped within ServiceAccessorImpl and I have no access through setters or constructors.
Controller Endpoint. The below is psuedo code of how I would like to implement my design. Autowiring in a single instance and using either endpoint works for that endpoint but not for both as shown below.
#ComponentScan(basePackageClass = ServiceAccessorImpl.class)
public class ServiceAccessorController {
#Autowired private ServiceAccessor serviceAccessor1;
#Autowired private ServiceConfiguration serviceConfiguration1;
#Autowired private ServiceAccessor serviceAccessor2;
#Autowired private ServiceConfiguration serviceConfiguration2;
Response CallEndpoint1(){
//www.endpoint1.com is already set here from external config
return serviceAccessor1.executeCall();
}
Response CallEndpoint1(){
serviceConfiguration2.setServiceEndPoint("www.endpoint2.com")
return serviceAccessor2.executeCall();
}
}
Thank you in advance
If you need multiple instances of the same implementation, it's easier to not annotate it as a bean, and have a #Configuration provide the beans instead.
public class ServiceAccessorImpl implements ServiceAccessor {
private ServiceConfiguration serviceConfiguration;
public ServiceAccessorImpl(ServiceConfiguration configuration) {
this.serviceConfiguration = configuration;
}
public Response executeCall(){
return callEndPoint(serviceConfiguration.getServiceEndPoint());
}
}
// this one should just have #ConfigurationProperties, not #Configuration
#Data
#ConfigurationProperties(prefix="config")
public class ServiceConfiguration {
private String serviceEndPoint;
}
then in your service you can have a Configuration providing both instances:
#Configuration
public class BeansConfiguration {
#Qualifier("service1")
#Primary // optional, Spring will autowire this instance by default if no qualifier is given
#Bean
public service1(#Autowired ServiceConfiguration config) {
// use the default config bean
return new ServiceAccessorImpl(config);
}
#Qualifier("service2")
#Bean
public service2() {
return new ServiceAccessorImpl(new ServiceConfiguration("www.endpoint2.com"));
}
}
then you may consume both by using the qualifiers (note that you don't have to inject the configs here):
public class ServiceAccessorController {
#Autowired
private ServiceAccessor serviceAccessor1;
#Autowired
#Qualifier("service2")
private ServiceAccessor serviceAccessor2;
Response CallEndpoint1(){
return serviceAccessor1.executeCall();
}
Response CallEndpoint2(){
return serviceAccessor2.executeCall();
}
}
Why can't I use #Autowired in this case?
#SpringBootApplication
public class Application {
#Autowired
BookingService bookingService;
public static void main(String[] args) {
bookingService.book("Alice", "Bob", "Carol");
}
}
but can use #Bean
#SpringBootApplication
public class Application {
#Bean
BookingService bookingService() {
return new BookingService();
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
BookingService bookingService = ctx.getBean(BookingService.class);
bookingService.book("Alice", "Bob", "Carol");
}
}
Aren't the two ways to generate BookingService the same thing?
#Bean and #Autowired do two very different things. The other answers here explain in a little more detail, but at a simpler level:
#Bean tells Spring 'here is an instance of this class, please keep hold of it and give it back to me when I ask'.
#Autowired says 'please give me an instance of this class, for example, one that I created with an #Bean annotation earlier'.
Does that make sense? In your first example, you're asking Spring to give you an instance of BookingService, but you're never creating one, so Spring has nothing to give you. In your second example, you're creating a new instance of BookingService, telling Spring about it, and then, in the main() method, asking for it back.
If you wanted, you could remove the two additional lines from the second main() method, and combine your two examples as below:
#SpringBootApplication
public class Application {
#Autowired
BookingService bookingService;
#Bean
BookingService bookingService() {
return new BookingService();
}
public static void main(String[] args) {
bookingService.book("Alice", "Bob", "Carol");
}
}
In this case, the #Bean annotation gives Spring the BookingService, and the #Autowired makes use of it.
This would be a slightly pointless example, as you're using it all in the same class, but it becomes useful if you have the #Bean defined in one class, and the #Autowired in a different one.
#Bean
BookingService bookingService() {
return new BookingService();
}
Annotating #Bean only registers the service as a bean(kind of an Object) in the spring application context. In simple words, it is just registration and nothing else.
#Autowired
BookingService bookingService;
Annotating a variable with #Autowired injects a BookingService bean(i.e Object) from Spring Application Context.
(i.e) The registered object with #Bean annotation will be injected to the variable annotated with #Autowired.
Hope this clears your doubt!
great answer by #DaveyDaveDave
In the example instead of
#Bean
BookingService bookingService() {
return new BookingService();
}
You can use #Service annotation on BookingService class
Contrary to what the highest voted answer here claims, they are NOT two very different things. #Bean and #Autowired and interchangeable in most cases.
Suppose you have a #Bean method that returns an instance of a Car. You can literally get rid of that bean method and add #Component on the Car class and then autowire it.
And vice versa. Whatever class you have instantiated using #Autowired, you can instantiate it inside a class with #Configuration annotation using #Bean on the method.
Places where you will use #Bean instead of #Autowired
1>You do not have access to change the class to add #Component annotation, hence you cannot autowire it.
2>You want to customize the instantiation of the class.
For example if you are instantiating a Resilience4J Circuit breaker class, if you do it inside a method with #Bean, you have the option of setting all the config using code like this
#Bean
public CircuitBreaker fooCircuitBreaker() {
CircuitBreakerConfig.Builder builder = CircuitBreakerConfig.custom().
slidingWindowSize(xxx).
failureRateThreshold(xxx).
waitDurationInOpenState(xxx)).
ignoreException(e -> {
if (e instanceof HttpStatusCodeException) {
HttpStatusCodeException httpStatusCodeException = (HttpStatusCodeException) e;
if (httpStatusCodeException.getStatusCode().is4xxClientError()) {
return true;
}
}
return false;
});
circuitBreakerRegistry.addConfiguration(xxx, builder.build());
return circuitBreakerRegistry.circuitBreaker(xxx, xxx);
}
Here's good article about #Autowired annotation: http://www.baeldung.com/spring-autowire
The #Autowired annotation can instantiate your injectables by defining #ComponentScan("namespace.with.your.components.for.inject") on config class
#Configuration
#ComponentScan("com.baeldung.autowire.sample")
public class AppConfig {}
All components must be marked by #Component annotation. It replaces the #Bean annotation.
#Bean is just for the metadata definition to create the bean(equivalent to tag). #Autowired is to inject the dependancy into a bean(equivalent to ref XML tag/attribute).
I have the below interface:
public interface MailSender {
void sender(String to, String subject,String body);
}
With 2 imlementation of it:
public class SmtpkMailSender implements MailSender {
static Log log=LogFactory.getLog(MailSender.class);
public void sender(String to, String subject,String body){
log.info("SMTP To: "+to);
log.info("SMTP Subjecy: "+subject);
log.info("SMTP body: "+body);
}
}
and the second one is:
#Primary
public class MockMailSender implements MailSender {
static Log log=LogFactory.getLog(MailSender.class);
public void sender(String to, String subject,String body){
log.info("To: "+to);
log.info("Subject: "+subject);
log.info("body: "+body);
}
}
I used the dependency injection into the controller class which is as following:
#RestController
public class MailController {
#Autowired
private MailSender smtpkMailSender;
#RequestMapping("/send")
public String send(){
smtpkMailSender.sender("Person", "Important", "Take Care");
return "mail is sent";
}
}
At the end i have a configuration class which contains my Beans:
#Configuration
public class MailConfig {
#Bean
public SmtpkMailSender getSmtpkMailSender(){
return new SmtpkMailSender();
}
#Bean
public MockMailSender getMockMailSender(){
return new MockMailSender();
}
}
Unfortunatly, when i run my application it complains with:
Description:
Field smtpkMailSender in com.example.demo.MailController required a single bean, but 2 were found:
- getSmtpkMailSender: defined by method 'getSmtpkMailSender' in class path resource [com/example/mail/MailConfig.class]
- getMockMailSender: defined by method 'getMockMailSender' in class path resource [com/example/mail/MailConfig.class]
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
As you see, although i have specified the MockMailSender as Primary the spring still complains, and cannot identify it
Since you're using java config you should mark config method with #Primary annotation, and not a class:
#Configuration
public class MailConfig {
#Bean
public SmtpkMailSender getSmtpkMailSender(){
return new SmtpkMailSender();
}
#Bean
#Primary
public MockMailSender getMockMailSender(){
return new MockMailSender();
}
}
You can use #Qualifier annotation for specify which specific type of your implementation, you want to autowire.
#RestController
public class MailController {
#Autowired
#Qualifier("smtpkMailSender")
private MailSender smtpkMailSender;
#RequestMapping("/send")
public String send(){
smtpkMailSender.sender("Person", "Important", "Take Care");
return "mail is sent";
}
}
You may simply name your bean using
#Bean(name="mockMailSender")
public MockMailSender getMockMailSender() { }
and then decorate the autowired field with
#Autowired
#Qualifier("mockMailSender")
private MailSender smtpkMailSender;
Another solution for this error is to exclude the Bean that is not used (the duplicated bean), from the autoconfiguration stage within "application.yml" file, as explained in this thread: How to exclude AutoConfiguration classes in Spring Boot JUnit tests?
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
- org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration
This is applicable if the error appears after you added a new Maven/Gradle dependency, which duplicates an existing bean that you are using. For example, when both beans come from external JARs, so the source code cannot be updated with #Primary or #Qualifier annotations to solve the error.
I am currently writing a Spring Boot autoconfiguration for Retrofit 2. What I am trying to do is to write some sort of an interface builder that is able instantiate an interface that is annotated with some annotation for autowiring just like Spring Data does it with repositories. As I cannot find any resources on how to do this (or if it can even be done with Spring), I would like to ask for your thoughts on that. Below is for an interface that I would like to instantiate.
My replacement for #Repository is #Retrofit the rest is just "ordinary" code you would write for any Retrofit repository.
The kind of interface I would like to autowire:
#Retrofit
public interface Api {
#GET("usernames")
String[] getUsernames();
}
An example for autowiring:
#SpringBootApplication
public class TestApplication {
#Autowired
private Api api;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
CommandLineRunner runner() {
return args -> {
System.out.println(api.getUsernames());
};
}
}
As I said I found a solution for my problem.
First we need an auto configuration class that is loaded by Spring Boot - as stated here - by adding the file META-INF/spring.factories with the content that is shown below. This auto configuration loads a registrar which itself searches for classes annotated with #Retrofit via a component provider. At last the registrar creates instances of RetrofitFactoryBean for each instance that could be found while this factory bean creates the Retrofit proxies itself.
The auto configuration
#Configuration
#Import(RetrofitRegistrar.class)
public class RetrofitAutoConfiguration {
#Bean
#ConditionalOnMissingBean
public Retrofit retrofit() {
return new Retrofit.Builder().build();
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
spring.retrofit.RetrofitAutoConfiguration
The imported registrar
public class RetrofitRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
#Setter
private BeanFactory beanFactory;
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory);
RetrofitComponentProvider provider = new RetrofitComponentProvider(registry);
basePackages.stream()
.map(provider::findCandidateComponents)
.flatMap(Set::stream)
.forEach(comp -> register(comp, registry));
}
private void register(BeanDefinition component, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.
rootBeanDefinition(RetrofitFactoryBean.class);
builder.addConstructorArgValue(component.getBeanClassName());
registry.registerBeanDefinition(
component.getBeanClassName().toLowerCase(), builder.getBeanDefinition());
}
}
The component provider
class RetrofitComponentProvider extends ClassPathScanningCandidateComponentProvider {
#Getter
private BeanDefinitionRegistry registry;
public RetrofitComponentProvider(BeanDefinitionRegistry registry) {
super(false);
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
this.registry = registry;
addIncludeFilter(new AnnotationTypeFilter(Retrofit.class, true, true));
}
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return true;
}
}
The factory bean
#Component
#RequiredArgsConstructor
public class RetrofitFactoryBean extends AbstractFactoryBean<Object> {
#Getter
private final Class<?> objectType;
private final retrofit2.Retrofit retrofit;
#Override
protected Object createInstance() throws Exception {
return retrofit.create(objectType);
}
}
The #Getter, #Setter and #RequiredArgsConstructor annotations are provided by ProjectLombok
Let me ask you firstly to avoid reinventing the wheel by creating a new Spring annotation (yours here is #Retrofit. However, it is absolutely okay to use retrofit with spring there is nothing to prevent it. You can simply try to use an existing Spring annotation which can be #Component as you can see in this question
you can autowire your interface without facing problems.
Hope this helps.
I have a Spring service which is checking database entries. To minimize my repository calls both find methods are "#Cacheable". But when I try to init my service bean while my configuration class has a CacheManager bean definition I get following NoSuchBeanDefinitionException:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'foo.mediacode.directory.MediaCodeDirectoryService' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:353)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1093)
at foo.mediacode.directory.MediaCodeDirectoryService.implementation(MediaCodeDirectoryService.java:63)
at foo.campaigntree.directory.CampaignTreeDirectoryService.<init>(CampaignTreeDirectoryService.java:18)
... 15 more
If I take out the CacheManager bean definition, I can init my service bean and it runs without any problems and caching!
Here is my code:
Configuration
...
#Configuration
#EnableCaching
#EnableJpaRepositories(...)
#PropertySource({...})
public class MediaCodeDirectoryServiceConfig {
private static Logger configLogger = Logger.getLogger(MediaCodeDirectoryServiceConfig.class.getName());
#Value("${jpa.loggingLevel:FINE}")
private String loggingLevel;
#Value("${mysql.databaseDriver}")
private String dataBaseDriver;
#Value("${mysql.username}")
private String username;
#Value("${mysql.password}")
private String password;
#Value("${mysql.databaseUrl}")
private String databaseUrl;
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
...
}
#Bean
public MediaCodeDirectoryService mediaCodeDirectoryService() {
return new MediaCodeDirectoryService();
}
#Bean
public CacheManager mediaCodeCacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("mediaCodeMappingRegexCache"),
new ConcurrentMapCache("mediaCodeMappingsCache")));
return cacheManager;
}
#Bean
public JpaTransactionManager transactionManager() {
...
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
...
}
public DataSource getDataSource() {
...
}
public JpaDialect getJpaDialect() {
...
}
public Properties getEclipseLinkProperty() {
...
}
public JpaVendorAdapter getJpaVendorAdapter() {
...
}
}
Service
....
public class MediaCodeDirectoryService implements MediaCodeDirectoryServiceApi {
...
#Autowired
private MediaCodeDirectoryRepository repo;
#SuppressWarnings("resource")
public static MediaCodeDirectoryServiceApi implementation() {
if (INSTANCE == null) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MediaCodeDirectoryServiceConfig.class);
INSTANCE = ctx.getBean(MediaCodeDirectoryService.class);
}
return INSTANCE;
}
...
Repository
...
#Repository
public interface MediaCodeDirectoryRepository extends CrudRepository<MediaCodeDao, Integer> {
#Cacheable("mediaCodeMappingRegexes")
#Query("SELECT m FROM #{#entityName} m WHERE (m.fooId = :fooId) AND (m.isRegex = :isRegex) ORDER BY (m.orderId DESC, m.id ASC)")
List<MediaCodeDao> findByfooIdAndIsRegexOrderByOrderIdDescAndIdAsc(#Param("fooId") int fooId, #Param("isRegex") boolean isRegex);
#Cacheable("mediaCodeMappings")
List<MediaCodeDao> findByMediaCode(String MediaCode, Pageable pageable);
}
When I debug into DefaultListableBeanFactory I can find within beanDefinitionMap my mediaCodeDirectoryService and also within beanDefinitionNames mediaCodeDirectoryService appears. But DefaultListableBeanFactory.getBean(...) cannot resolve name and namedBean in line 364 is null.
When I try to get the context via String like:
INSTANCE = (MediaCodeDirectoryService) ctx.getBean("mediaCodeDirecotryService")
I avoid the NoSuchBeanDefinitionException but I run into an other one.
Anybody here has an idea on what might be the cause of this? Did I missed something in my configuration? Thx!
Caching is applied through AOP. For AOP Spring uses a proxy based approach and the default is to create interface based proxies.
public class MediaCodeDirectoryService implements MediaCodeDirectoryServiceApi {... }
With this class definition at runtime you will get a dynamically created class (Proxy$51 or something along those lines) which implements all interfaces but it isn't a MediaCodeDirectoryService. It is however a MediaCodeDirectoryServiceApi.
You have 2 ways of fixing this, either program to interfaces (which you should have been doing anyway because you have defined interfaces) instead of concrete classes or use class based proxies.
The first option involves you changing your code in the places the directly #Autowire or get an instance of MediaCodeDirectoryService to use MediaCodeDirectoryServiceApi instead (which imho you should already do, why else define an interface). Now you will get the proxy injected and everything will work.
The second option involves you setting proxyTargetClass=true on your #EnableCaching annotation. Then instead of an interface based proxy you will get a class based proxy.
#EnableCaching(proxyTargetClass=true)