How to force exclude Spring #Configuration picked up by recursive #ComponentScan - spring

Our top level #ComponentScan scans a package, which in turn contains #ComponentScan that picks up a #Configuration class we want to ignore. Using excludeFilters on the top level #ComponentScan doesn't seem to let us to exclude the #Configuration we want to avoid.
We are trying to add a dependency to our spring project, and with #ComponentScan, bring in the libraries we want.
However, there is one #Configuration class we want to exclude, as it contains a #Bean using deprecated GuavaCache from spring 4. We are using spring 5, so get a NoClassDefFoundError if try to use the class.
Simple to create our own implementation using the newer spring 5 cache, but can't figure out how to exclude the existing #Configuration. The base package we need to scan is not the one containing the bad class, but instead scans a package which contains other classes with #ComponentScan annotations, which causes it to get picked up.
the annotations we've tried at top level spring-boot Application class, classes in thirdparty.infrastructure.requestcontext are what then #ComponentScan to find JwtCacheConfig we want to exclude
#SpringBootApplication
#ComponentScan(basePackages = {
"com.ourpackage",
"thirdparty.infrastructure.requestcontext"},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX,
pattern = "fourthparty\\.common\\.jwtvalidation\\.domain\\.config\\..*"))
public class MyApplication
also tried:
#SpringBootApplication
#ComponentScan(basePackages = {
"com.ourpackage",
"thirdparty.infrastructure.requestcontext"},
excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = fourthparty.common.jwtvalidation.domain.config.JwtCacheConfig.class))
public class MyApplication
class to ignore:
package fourthparty.common.jwtvalidation.domain.config
import com.google.common.cache.CacheBuilder
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.Cache
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.guava.GuavaCache
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit
#Configuration
#EnableCaching
class JwtCacheConfig {
#Value('${px.cache.jwtPublicKeys.maximumSize:50}')
Integer maximumCacheSize
#Value('${px.cache.jwtPublicKeys.expireAfterWrite:1440}')
Integer expirationTime
#Bean
Cache jwtPublicKeyCache(){
return new GuavaCache('jwtPublicKeys', CacheBuilder.newBuilder()
.maximumSize(maximumCacheSize).expireAfterWrite(expirationTime, TimeUnit.MINUTES).build(), true)
}
}
The class we want to put in place
package com.ourpackage.config
import java.time.Duration
import com.github.benmanes.caffeine.cache.Caffeine
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.Cache
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.caffeine.CaffeineCache
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
#Configuration
#EnableCaching
class MyJwtCacheConfig
{
#Value('${px.cache.jwtPublicKeys.maximumSize:50}')
Integer maximumCacheSize
// cache it for 24 hours (60 min * 24 hours)
#Value('${px.cache.jwtPublicKeys.expireAfterWrite:1440}')
Integer expirationTime
#Bean
Cache myJwtPublicKeyCache(){
def cacheMap = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofMinutes(expirationTime))
.maximumSize(maximumCacheSize)
.build()
return new CaffeineCache('jwtPublicKeys', cacheMap, true)
}
}
Each time trying to start application, that class we want to exclude still gets picked up, and we get:
java.lang.NoClassDefFoundError: org/springframework/cache/guava/GuavaCache
...
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'cacheManager' defined in class path resource [org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.class]:
Unsatisfied dependency expressed through method 'cacheManager' parameter 0;
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'jwtPublicKeyCache' defined in class path resource
[fourthparty/common/jwtvalidation/domain/config/JwtCacheConfig.class]:
Bean instantiation via factory method failed;
nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cache.Cache]:
Factory method 'jwtPublicKeyCache' threw exception;
nested exception is java.lang.NoClassDefFoundError: org/springframework/cache/guava/GuavaCache

Related

How to use #Autowired in SpringBoot unit test

I am trying to unit test (using JUnit5 jupiter) a class developed in Java with Spring Boot that I would like to use the #Autowired annotation for convenience.
A very simplified version of it is as follow:
import org.springframework.stereotype.Component;
#Component
public class Demo {
public String get() {
return "hello";
}
}
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
#SpringJUnitConfig
class DemoTest {
#Autowired private Demo sut;
#Test
void Test() {
Assertions.assertEquals("hello", sut.get());
}
}
When I run the test this error occurs:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.demo.DemoTest': Unsatisfied dependency expressed through field 'sut'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.Demo' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:659)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:417)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:119)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
...
How to prevent it?
To be noted: for this test, I do not want to start the full application using #SpringBootTest
Thanks,
Usually when you use #ContextConfiguration (which is a significant part of the stereotype #SpringJUnitConfig annotation) you should specify the configuration class from which the "Demo" component will be resolved.
Otherwise spring won't know which classes to load.
So you should basically create a configuration class (annotated with #Configuration) And specify what do you want to load there, for example with #ComponentScan
Haven't tested it but something like this should work:
#SpringJUnitConfig(MyTestConfig.class)
public class MyTest {
#Autowired Demo sut;
public void test() {... sut.doSomething(...); ...}
}
#Configuration
#ComponentScan(...) // here you should specify how to
// load the Demo class and maybe other classes as well
public class MyTestConfig {
}
Of course if instead of #Component you already use Java Config and define Demo class there you won't need to create this auxiliary MyTestConfig class and can load the configuration with the Demo class right away

Spring injects a bean other than what is specified in #Configuration

Recently I've made an error while wiring beans in Spring that caused a behaviour that I'm unable to replicate. Instead of a property sourced with #Value getting injected into Stuff (see the complete demo code below) a value of another bean of type String defined in #Configuration was used when the application was deployed.
What I find puzzling is that everything works as expected when running locally (including the unit test), the output is foo not kaboom, and that this 'bean swap' happened at all when deployed rather than 'no qualifying bean' error.
The commented out line shows the fix which I think makes the configuration similar to what is in the manual.
What is the problem with my set-up? What would make the code as shown (i.e. without the fix) use kaboom String rather than foo property?
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
#SpringBootApplication
open class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
#Configuration
open class Config {
// ...beans of types other than String in original code...
#Bean
open fun beanBomb(): String {
return "kaboom"
}
#Bean
// fix:
// #Value("\${stuff}")
open fun beanStuff(stuff: String): Stuff {
return Stuff(stuff)
}
}
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
#Component
class Stuff(#Value("\${stuff}") val stuff: String)
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import javax.annotation.PostConstruct
#Component
class Init {
#Autowired
private lateinit var stuff: Stuff
#PostConstruct
fun init() {
println("stuff: " + stuff.stuff)
}
}
// application.properties
stuff=foo
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#TestPropertySource(properties = {"stuff=testFoo"})
class DemoApplicationTests {
#SpyBean
private Stuff stuff;
#Test
void test() {
assertEquals("testFoo", stuff.getStuff());
}
}
Also, is the #Value annotation in Stuff necessary once the fix has been applied? If I uncomment the fix, remove #Value from Stuff and add the following annotation to the test class the test passes:
#ContextConfiguration(classes = {Config.class})
but when I run the app it prints kaboom...
You can check the order in which the bean is being created. if the bean is created before than in my view the Spring IoC container inject the value by type i.e. kaboom and since the Bean of any type is singleton by default, the instance of Stuff won't come into effect even though it is annotated with #component.
In your test you're loading the configuration manually where the bean of Stuff defined in Config is being injected not the Stuff annotated with #component.
The problem is the annotation needs to go on the parameter not the function.
In your way Spring is looking for a bean that meets the Type of String and there is a bean of Type String produced by the function beanBomb(). If you move the annotation like this it should remove the ambiguity.
#Bean
open fun beanStuff(#Value("\${stuff}") stuff: String): Stuff {
return Stuff(stuff)
}
I would add tho, that it's a bit unusual to have a bean of Type String, but I suppose if you don't want to use property/yaml files it would allow you to change a String based on profile.

#Autowire repository works for Application yet fails in JUnitTest

I have a weird issue of #Autowire failing as the following:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'EventTargetRepositoryTest': Unsatisfied dependency expressed through field 'eventTargetRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'EventTargetRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Funnily enough, this happens only on my test which uses the exactly same #Configuration.
#Service beans autowire perfectly in the actual application.
#Repository beans autowire perfectly in the actual application.
#Service beans autowire perfectly(??) in the JUnit Test.
#Repository beans fail to autowire in the JUnit Test.
Code is as follows(package structure omitted):
Test(which fails)
import CoreConfig
import EventTarget
import org.junit.Test
import org.junit.runner.RunWith
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringRunner
#RunWith(SpringRunner::class)
#ContextConfiguration(classes = [CoreConfig::class])
class EventTargetRepositoryTest {
private val LOGGER: Logger = LoggerFactory.getLogger(EventTargetRepositoryTest::class.java)
#Autowired //not autowiring for some reason. needs fixing.
private lateinit var eventTargetRepository: EventTargetRepository
#Test
fun selectOne() {
var eventTarget = EventTarget()
eventTarget.id = 1
eventTargetRepository.selectOne(eventTarget)
LOGGER.info(eventTarget.userId)
}
}
Application(which runs fine, along with working repository)
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.runApplication
#SpringBootApplication(scanBasePackages = ["com.omittedParentName"], exclude = [DataSourceAutoConfiguration::class])
class FrontendApplication
fun main(args: Array<String>) {
runApplication<FrontendApplication>()
}
Config(which the Test and Application both use)
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.FilterType
import org.springframework.context.annotation.PropertySource
import org.springframework.stereotype.Component
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
#ComponentScan(basePackages = ["com.omittedParentName"], useDefaultFilters = false,
includeFilters = [
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Service::class]),
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Component::class]),
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Repository::class]),
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Configuration::class])
])
#Configuration
#PropertySource("classpath:properties/core.properties")
class CoreConfig
repository
import EventTarget
import org.apache.ibatis.annotations.Mapper
import org.springframework.stereotype.Repository
#Mapper
#Repository
interface EventTargetRepository {
fun selectOne(eventTarget: EventTarget): EventTarget
}
mapper xml is irrelevant, so it's omitted.
As we can clearly see, the Test and Application literally use the same #Configuration. Aside from the fact that making all tests require applicationContext is bad structure, the test should work. Other beans like #Service work in the same tests, too.
Any help would be appreciated.
Please refrain from marking as duplicate unless it's a question of #Autowire failing only exclusively on #Repository for only on #Test yet working in release.

Autowire failed in cucumber Step Def test

I have a series cucumber feature files and a list of associated step def tests in current project
in the step def tests package, I have this Hook definition
#ContextConfiguration(classes = Application.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Hooks {
....
}
and RunCukesTest
#RunWith(Cucumber.class)
#CucumberOptions(features = "src/test/resources/features", glue = { "com.myapp.test.jersey.rest.v1" })
#ContextConfiguration(classes = Application.class)
public class RunCukesTest {
....
}
above classpaths are correct.
And there is one of the step def test
package com.myapp.test.jersey.rest.v1;
....
#ContextConfiguration(classes = Application.class)
public class OrderCreateServiceTest {
....
#Autowired
private OrderRepository repository;
}
However I got follow error by Spring Boot
Exception in thread "main" java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'entityManagerFactory': Requested bean is currently in creation: Is there an unresolvable circular reference?
Then if I take out the #ContextConfiguration from step def class, like this
//#ContextConfiguration(classes = Application.class)
public class CashpointCreateServiceTest {
apparently autowire of repository object will fail by throwing NullPointerException
It's greatly appreciated if anyone can share
(1) With Hook and CukeTest configuration, how to autowire bean in the step def class?
(2) Is it ok to have #ContextConfiguration in both Hook and CukeTest class?
You don't need the ContextConfiguration on RunCukesTest class. For running cucumber tests I use the following setup.
Junit test class to launch the cucumber tests:
package mypackage.test;
import org.junit.runner.RunWith;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
#RunWith(Cucumber.class)
#CucumberOptions(features = "src/test-integration/resources/features")
public class RunFeatures {
}
Base class which all step definition classes extend. This is so that all the step classes have the same spring annotations.
package mypackage.test.steps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.ContextConfiguration;
import mypackage.Application;
#ContextConfiguration(classes = { Application.class })
#SpringBootTest(webEnvironment=WebEnvironment.DEFINED_PORT)
public class BaseSteps {
}
Then the tests steps
package mypackage.test.steps;
import mypackage.repo.SampleRepo;
import org.springframework.beans.factory.annotation.Autowired;
import cucumber.api.java.en.When;
public class SampleSteps extends BaseSteps {
#Autowired
private SampleRepo sampleRepo;
#When("^Sample repo is called$")
public void no_words_are_saved() {
sampleRepo.findAll("something");
}
I don't understand what your Hooks class is for. I've only used annotated hooks in cucumber-jvm (e.g. #Before or #After). If you need some other hook, can you explain a bit more about what you're trying to do?

Spring data repository not found at compile time

I am trying to use Spring data and repositories in a Spring Boot application, but I have an error when compiling the project.
Here is my Entity :
package fr.investstore.model;
import javax.persistence.Id;
...
#Entity
public class CrowdOperation {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
public Long id;
#Enumerated(EnumType.STRING)
public RepaymentType repaymentType;
...
}
And the corresponding Repository:
package fr.investstore.repositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import fr.investstore.model.CrowdOperation;
public interface CrowdOperationRepository extends CrudRepository<CrowdOperation, Long> {
}
I use it in a WS controller, generating a repository through the Autowired annotation:
package fr.investstore.ws;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
...
#Controller
#EnableAutoConfiguration
public class SampleController {
#Autowired
private CrowdOperationRepository crowdOperationRepository;
#RequestMapping(path = "/", method = RequestMethod.GET)
#ResponseBody
public String getOperations(#RequestParam(required=true, defaultValue="Stranger") String name) {
crowdOperationRepository.save(new CrowdOperation());
return "Hello " + name;
}
}
And the code of the application:
package fr.investstore;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import fr.investstore.ws.SampleController;
#SpringBootApplication
public class InvestStoreApplication {
public static void main(String[] args) {
SpringApplication.run(SampleController.class, args);
}
}
But when compiling the project I get:
APPLICATION FAILED TO START
Description: Field crowdOperationRepository in
fr.investstore.ws.SampleController required a bean of type
'fr.investstore.repositories.CrowdOperationRepository' that could not
be found.
Action: Consider defining a bean of type
'fr.investstore.repositories.CrowdOperationRepository' in your
configuration.
Woudn't Spring automatically generate a bean for the repository through the interface?
How can I resolve this?
EDIT: I also tried to put the Repository annotation (from org.springframework.stereotype.Repository) onto CrowdOperationRepository, but I got the same error
While creating a spring-boot application, we need to keep some point in our mind like
Always keep main class (class with `#SpringBootApplication annotation) on the top level package and other classes should lie under sub-packages.
Always mark your bean classes with proper annotation e.g. all repositories should be marked by #Repository annotation, all service implementation classes should be marked with #Service, other component classes should be marked by #Component, class which defines our beans should be marked as #Configuration
Enable the feature which you are using e.g. #EnableJpaRepositories, #EnableTransactionManagement, #EnableJpaAuditing, these annotations also provides functionality which let us define which package spring needs to scan.
So in your case, you need to mark InvestStoreApplication class with #EnableJpaRepositories annotation and CrowdOperationRepository with #Repository.
you have to tell your spring boot application to load JPA repositories.
copy this one to your application class
it will auto-scan your JPA repository and load it in your spring container even if you do not define your interface with #Repository it will wire that bean in your dependent class.
#EnableJpaRepositories(basePackages = { "fr.investstore.repositories" })
Thank to #JBNizet for his comment, that made it working.
I create this answer since he did not:
Replace SpringApplication.run(SampleController.class, args); with SpringApplication.run(InvestStoreApplication.class, args);. And remove the useless #EnableAutoConfiguration on your controller.
Annotating your entity class as shown as spring hint below to allow spring get a valid repository bean
Spring Data JPA - Could not safely identify store assignment for repository candidate interface com.xxxxx.xxxxRepository.
If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: javax.persistence.Entity, javax.persistence.MappedSuperclass (preferred),
or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository.
2022-05-06 12:32:12.623 [ restartedMain] INFO [.RepositoryConfigurationDelegate:201 ] - Finished Spring Data repository scanning in 3 ms. Found 0 JPA repository interfaces.

Resources