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

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.

Related

Is it possible to parameterize a JUnit Jupiter test with beans from a Spring ApplicationContext?

I would like to write a unit test which is executed for every Spring bean of a given type. JUnit5's parameterized tests offer a lot of possibilities, but I don't know how to inject beans into a method source as it has to be a static method.
Is there a way to determine the parameters of a JUnit5 test based on Spring's application context?
For starters, a factory method configured via #MethodSource does not have to be static. The second sentence in the User Guide explains that.
Factory methods within the test class must be static unless the test class is annotated with #TestInstance(Lifecycle.PER_CLASS); whereas, factory methods in external classes must always be static.
Thus, if you use #TestInstance(PER_CLASS) semantics, your #MethodSource factory method can be non-static and can therefore access the ApplicationContext injected into the test instance.
Here's an example that demonstrates that for beans of type String, with an intentional failure for the bar bean.
import java.util.stream.Stream;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
#SpringJUnitConfig
#TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {
#Autowired
ApplicationContext applicationContext;
#ParameterizedTest
#MethodSource
void stringBeans(String bean) {
assertEquals(3, bean.length());
}
Stream<String> stringBeans() {
return applicationContext.getBeansOfType(String.class).values().stream();
}
#Configuration
static class Config {
#Bean
String foo() {
return "foo";
}
#Bean
String bar() {
return "barf";
}
}
}
If you don't want to work directly with the ApplicationContext, you can simplify the solution by having the collection of all such beans of a given type (String in this example) injected directly, as follows.
#SpringJUnitConfig
#TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {
#Autowired
List<String> stringBeans;
#ParameterizedTest
#MethodSource
void stringBeans(String bean) {
assertEquals(3, bean.length());
}
Stream<String> stringBeans() {
return this.stringBeans.stream();
}
#Configuration
static class Config {
#Bean
String foo() {
return "foo";
}
#Bean
String bar() {
return "barf";
}
}
}
The usage of the #TestFactory might help.
Actually I stumbled across a post that does a pretty similar (or the same) thing as you do on github.
Let your Test run with the SpringExtenion and use the injected Beans as parameters for our Test.

Spring boot cannot find beans

i have a Spring Boot project which has some external packages i need to import as Beans in the main application.
So i have my main application in com.package.app package and some classes (among which some repositories) in com.package.commons package.
In order to take these beans i have my main class annotated as follows:
#SpringBootApplication
#ComponentScan({ "com.package.commons" ,"com.package.app"})
#EnableScheduling
#EnableAsync
public class EmanagerApplication extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(EmanagerApplication.class, args);
}
}
But when i launch the application it may occur (not always but very ofter) that the start up fails with these kind of error:
Description:
Field repository in com.package.commons.service.BrandService required a bean of type 'com.package.commons.persistence.repository.BrandRepository' that could not be found.
Action:
Consider defining a bean of type 'com.package.commons.persistence.repository.BrandRepository' in your configuration.
My BrandRepository is annotated with #Repository and the service class with #Service
The really strange thing is that if i keep launching the app at the end it stars... but there is no reason for it...
If you're using JPA, you'll also need the #EnableJpaRepositories annotation.
Also consider to use #EnableTransactionManagement to enable declarative transaction handling.
E.g. use something like the following in the same package or a parent package where you have your JPA entities and JPA repositories (untested):
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.jta.JtaTransactionManager;
#Configuration
#EntityScan
#EnableJpaRepositories
#EnableTransactionManagement
public class HibernateConfig extends JpaBaseConfiguration {
public HibernateConfig(DataSource dataSource, JpaProperties properties, ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, properties, jtaTransactionManager, transactionManagerCustomizers);
}
#Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Override
protected Map<String, Object> getVendorProperties() {
return new HashMap<>();
}
}
And don't forget to annotate your #Service classes also with #Transactional.
If you confirm that the Application which with the startup method of this application is good, and confirm the #ComponentScan is good also. And the configuration file yaml or properties of JPA also good.
How about trying extends JPA Repository like this:
public class xxxResponsitory extends JpaRepository<T, E>{
...
}
Cause JpaRepository has already annotated with #Repository annotation, T means the type of Primary Key, I always use Integer or Long, autoboxing type. E means the main type of this repository.
Make an example:
Now we have an Entity type named User, the Primary key type of User is Long, I would write the repository like this:
public class UserRepository extends JpaRepository<Long, User>{
...
}
Don't need annotated anything, then, In the service class, #Autowried UserRepository, everything is good to run. But make sure the things that I talk at the start of my answer.
Hope this can help you.

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.

How to make Spring IoC container available through out project

I feel stupid to even ask for this but I spent days looking for the answer and I'm still with nothing.
I wanna include simple Spring IoC container in my project. All I want it to do is to allow me Injecting/Autowiring some reusable objects in other classes. What I've done so far looks like this:
-> Project structure here <-
Configuration code:
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.Random;
#Configuration
#ComponentScan(basePackages = "com.example")
public class AppConfig {
#Bean
public Random rand() {
return new Random(42);
}
#Bean
public String string() {
return "Hello World!";
}
}
Main class code:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class Main {
#Autowired
Random rand;
#Autowired
String string;
public static void main(String[] args) {
// workflow
Main main = new Main();
System.out.println(main.string);
}
}
AnotherClass code:
package com.example.deeperpackage;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class AnotherClass {
#Autowired
Random rand;
#Autowired
String string;
public void methodToBeCalled() {
// TODO
System.out.println(string);
}
}
How can I make these #Autowired annotations work? Do I have to instantiate container in every single class in which I want to autowire components? I've seen in work a oracle app which used Spring and #Inject to distribute objects to numerous classes and there was no container logic in any class available for me. Just fields with #Inject annotation. How to achieve that?
Simply add the annotation #Component on the classes you want to inject :
#Component
public class AnotherClass {
...
}
But you cannot inject static attributes and when you do new Main(), no Spring context is being created. If you use Spring Boot, you should look at how to write a main with it.
https://spring.io/guides/gs/spring-boot/

Loading properties using #Value into a BeanFactory object using #Bean in Spring Boot

Can anyone help me with a Spring Boot problem?
I want to create a factory bean as part of my application context but I want to be able to instantiate it with injected property values. However it seems that Spring will load FactoryBeans before anything else as demonstrated here:
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
#EnableAutoConfiguration
public class TestClass
{
#Value("${test.value}")
String value;
#Bean
public Object test1()
{
System.out.println("test.value=" + value );
List<String> list = new ArrayList<String>();
ListFactoryBean factory = new ListFactoryBean();
factory.setSourceList(list);
return factory;
}
public static void main(String[] args)
{
SpringApplication.run(TestClass.class, args);
}
}
When run with
java -Dtest.value=HELLO -jar myTest.jar
It loads in the value correctly:
test.value=HELLO
However, when I specify that the bean to be loaded is in fact a factory bean, and run it in the same way:
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
#EnableAutoConfiguration
public class TestClass
{
#Value("${test.value}")
String value;
#Bean
public AbstractFactoryBean test1()
{
System.out.println("test.value=" + value );
List<String> list = new ArrayList<String>();
ListFactoryBean factory = new ListFactoryBean();
factory.setSourceList(list);
return factory;
}
public static void main(String[] args)
{
SpringApplication.run(TestClass.class, args);
}
}
The value is null because it hasn't been injected yet.
test.value=null
Is there any way around this?
Thanks
Spring often has to query bean definitions for the type of object they produce. Factory beans are always problematic because they can cause dependency cascades in a futile attempt to resolve all dynamic information available before asking for the type.
I think ListFactoryBean is insufficiently precise about its product type (getObjectType() can only return a non-generic List.class). You might be able to write your own factory that is parameterized with the correct generic type. Or you might get away with just declaring the #Bean to return a FactoryBean<List<String>.
Another tip is to move the #Bean definition to a separate class (e.g. a nested static one) so that it can be instantiated independently of the rest of the application context. E.g.
#EnableAutoConfiguration
public class TestClass
{
protected static class NestedConfiguration {
#Value("${test.value}")
String value;
#Bean
public FactoryBean<Properties> test1()
{
System.out.println("test.value=" + value );
// ...
return factory;
}
}
...
}
Not really a Boot question this one so you might consider changing the tags.
Take look at Empowering your apps with Spring Boot's property support
There is new annotation #EnableConfigurationProperties in Spring Boot Actuator
The Spring Environment is a collection of name-value pairs taken from (in order of decreasing precedence)
1) the command line,
2) the external configuration file,
3) System properties,
4) the OS environment.
There is also possible to define application properties (external configuration) in YAML format.

Resources