Search code examples
javaeclipselinkspring-data-jpaspring-cache

CacheManager bean definition in Config.class leads to NoSuchBeanDefinitionException


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!


Solution

  • 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)