I am trying to understand Spring/Spring-boot. My question is, can I use a Bean instantiated/declaired by #Bean to a #Autowired field? Below is my classes, what i have defined.
#SpringBootApplication
public class SpringBootTestApplication {
#Bean(name = "TestServiceInterfaceImplBean")
TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
#Autowired
public ServiceCaller serviceCaller;
public static void main(String[] args) {
ApplicationContext appContext = new
AnnotationConfigApplicationContext(SpringBootTestApplication.class);
Arrays.asList(appContext.getBeanDefinitionNames()).forEach(beanName ->
System.out.println(beanName));
SpringApplication.run(SpringBootTestApplication.class, args);
}
}
#Component()
public class ServiceCaller {
#Autowired
#Qualifier(value = "TestServiceInterfaceImplBean")
TestServiceInterface testService;
public ServiceCaller(){
System.out.println("############################### ServiceCaller");
}
}
//Service Interface
public interface TestServiceInterface {}
//Interface Implementation Class
public class TestServiceInterfaceImpl implements TestServiceInterface {
public TestServiceInterfaceImpl() {
System.out.println("############################### TestServiceInterfaceImpl");
}
}
I know by tagging #Service/#Component to TestServiceInterfaceImpl and removing #Bean and the method getTestService(), i can have #Autowire successful but i am just tyring to understand whether i can Autowire a Bean?
In this case i am getting below exception. By looking at the exception i am not able to understand where and how the loop is created.
Exception:
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| springBootTestApplication (field public com.SpringBootTestApplication.service.ServiceCaller com.SpringBootTestApplication.SpringBootTestApplication.serviceCaller)
↑ ↓
| serviceCaller (field com.SpringBootTestApplication.service.TestServiceInterface com.SpringBootTestApplication.service.ServiceCaller.testService)
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
You'd better move below part to a Configuration (#Configuration) class:
#Bean(name = "TestServiceInterfaceImplBean")
TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
#Autowired
public ServiceCaller serviceCaller;
then do the test again. And another point, for ServiceCaller, you can even define its order after the Bean of TestServiceInterfaceImplBean created.
the 2 configuration class like:
#Configuration
#AutoConfigureAfter({ MyConfiguration2.class })
public class MyConfiguration {
public MyConfiguration() {
}
#Autowired
public ServiceCaller serviceCaller;
}
#Configuration
public class MyConfiguration2 {
public MyConfiguration2() {
}
#Bean(name = "TestServiceInterfaceImplBean")
public TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
}
Related
My service
#Service
public class StripeServiceImpl implements StripeService {
#Override
public int getCustomerId() {
return 2;
}
}
My test
public class StripeServiceTests {
#Autowired
StripeService stripeService;
#TestConfiguration
static class TestConfig {
#Bean
public StripeService employeeService() {
return new StripeServiceImpl();
}
}
#Test
public void findCustomerByEmail_customerExists_returnCustomer() {
assertThat(stripeService.getCustomerId()).isEqualTo(2);
}
}
The error: java.lang.NullPointerException. I had checked and the stripeService is actually null.
Since you are autowiring you need an applicationcontext so that Spring can manage the bean and then can get injected in your class. Therefore you are missing an annotation to create the applicationcontext for your testclass.
I have updated your code and it works now(with junit 5 on your classpath). In the case dat you are using junit 4 it should be #RunWith(SpringRunner.class) instead of #ExtendWith(SpringExtension.class):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfiguration.class)
public class StripeServiceTests {
#Autowired
StripeService stripeService;
#TestConfiguration
static class TestConfig {
#Bean
public StripeService employeeService() {
return new StripeServiceImpl();
}
}
#Test
public void findCustomerByEmail_customerExists_returnCustomer() {
assertThat(stripeService.getCustomerId()).isEqualTo(2);
}
}
Are there ways to override properties of DefaultListableBeanFactory in Spring Boot application?
For example, I want to set the DefaultListableBeanFactory.allowBeanDefinitionOverriding property to false.
EDIT
Use case.
I have pretty plain class:
#Data
#AllArgsConstructor
class MyTempComponent {
private String value;
}
Which I want use as #Bean in my application but this bean can be defined several times, for example:
#Configuration
class TestConfiguration1 {
#Bean
MyTempComponent myTempComponent() {
return new MyTempComponent("Value 1");
}
}
#Configuration
class TestConfiguration2 {
#Bean
MyTempComponent myTempComponent() {
return new MyTempComponent("Value 2");
}
}
Also there is a consumer of that bean:
#Component
class TestConfiguration3 {
private MyTempComponent myTempComponent;
#Autowired
public TestConfiguration3(MyTempComponent myTempComponent) {
this.myTempComponent = myTempComponent;
}
#PostConstruct
public void print() {
System.out.println(this.myTempComponent.getValue());
}
}
When an application starts it prints "Value 2" message, i.e. initializes myTempComponent bean from TestConfiguration2.
I want to prohibit creation of that bean (and any other beans) if there are two or more candidates.
As I realized I can reach this goal via setting DefaultListableBeanFactory.allowBeanDefinitionOverriding to false.
But how to set this property in Spring Boot application? Could you provide an example please?
You can set
private static class CustomAppCtxInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
#Override
public void initialize(GenericApplicationContext applicationContext) {
applicationContext
.getDefaultListableBeanFactory()
.setAllowBeanDefinitionOverriding(false);
}
}
and then have
public static void main(String[] args) {
try {
final SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.addInitializers(new CustomAppCtxInitializer());
I have a configuration class which creates multiple beans:
#Configuration
public class TopLevelConfig {
#Bean
public MyMapper myMapper() {
MyMapper mapper = new MyMapper();
mapper.registerModule(new MetadataModule());
return new MyMapper();
}
}
Now in MetadataModule:
#Override
public void setupModule(final SetupContext setupContext) {
final SimpleDeserializers deserializers = new SimpleDeserializers();
deserializers.addDeserializer(Payload.class, new PayloadDeserializer());
setupContext.addDeserializers(deserializers);
}
In PayloadDeserializer I'm not able to autowire the MyMapper class. I'm thinking this is because when the new Object of PayloadDeserializer is created, the bean of MyMapper hasn't been created by then. How do I allow PayloadDeserializer to get access to the bean object?
You are creating PayloadDeserializer object by yourself by calling new PayloadDeserializer(), this is the reason why MyMapper is not injected to it. To inject/autowire to work, your bean should be spring managed. To do that, you can use #Component on top of your PayloadDeserializer class like below.
#Component
public class PayloadDeserializer {
private final MyMapper mapper;
#Autowired
public PayloadDeserializer(MyMapper mapper) {
this.mapper = mapper;
}
}
#Configuration
public class TopLevelConfig {
#Bean
public MyMapper myMapper(PayloadDeserializer payloadDeserializer) {
MyMapper mapper = new MyMapper();
mapper.registerModule(metadataModule(payloadDeserializer));
return mapper;
}
#Bean
public MetadataModule metadataModule(PayloadDeserializer payloadDeserializer) {
return new MetadataModule(payloadDeserializer);
}
}
public class MetadataModule {
private final PayloadDeserializer payloadDeserializer;
public MetadataModule(PayloadDeserializer payloadDeserializer) {
this.payloadDeserializer = payloadDeserializer;
}
#Override
public void setupModule(final SetupContext setupContext) {
final SimpleDeserializers deserializers = new SimpleDeserializers();
deserializers.addDeserializer(Payload.class, payloadDeserializer);
setupContext.addDeserializers(deserializers);
}
}
I have Spring converter which uses Spring Data REST's component called EnumTranslator
#Component
public class TranslationStringToSpecificationStatusEnumConverter implements Converter<String, Specification.Status> {
private final EnumTranslator enumTranslator;
#Autowired
public TranslationStringToSpecificationStatusEnumConverter(EnumTranslator enumTranslator) {
this.enumTranslator = enumTranslator;
}
#Override
public Specification.Status convert(String source) {
return enumTranslator.fromText(Specification.Status.class, source);
}
}
Recommended way to register such converter is to subclass RepositoryRestConfigurerAdapter as follows:
#Configuration
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private final TranslationStringToSpecificationStatusEnumConverter converter;
#Autowired
public RepositoryRestConfig(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
}
When I run the Spring Boot application, it fails on the following:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| translationStringToSpecificationStatusEnumConverter defined in file ...
↑ ↓
| org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration (field java.util.List org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.configurers)
↑ ↓
| repositoryRestConfig defined in file ...
└─────┘
So there is circular bean dependency.
How can I register the converter above so that I don't introduce circular bean dependency?
To make it work:
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(String.class, Status.class, new StringToTranslatedEnumConverter<>(Status.class));
super.configureConversionService(conversionService);
}
First I created utility class that help me work with Spring beans in unmanaged objects:
#Component
public final class SpringUtils {
#Autowired private ApplicationContext ctx;
private static SpringUtils instance;
#PostConstruct
private void registerInstance() {
instance = this;
}
public static <T> T getBean(Class<T> clazz) {
return instance.ctx.getBean(clazz);
}
}
Then I created the converter:
public class StringToTranslatedEnumConverter<T extends Enum<T> & TranslatedEnum> implements Converter<String, T> {
private final ConcurrentMapCache cache;
private EnumTranslator enumTranslator;
private Class<T> type;
public StringToTranslatedEnumConverter(Class<T> type) {
this.type = type;
cache = new ConcurrentMapCache(type.getName());
}
#Override
public T convert(String from) {
if (enumTranslator == null) {
enumTranslator = SpringUtils.getBean(EnumTranslator.class);
}
Cache.ValueWrapper wrapper = cache.get(from);
if (wrapper != null) {
//noinspection unchecked
return (T) wrapper.get();
}
T translatedEnum = enumTranslator.fromText(type, from);
cache.put(from, translatedEnum);
return translatedEnum;
}
}
UPDATED
TranslatedEnum - it's interface-marker, used to mark enums which translation is only need.
public interface TranslatedEnum {
}
public enum Status implements TranslatedEnum {
CREATED, DELETED
}
The solution to this problem is Spring Core specific. In order to break circle bean dependency cycle, we have to delay setting converter in RepositoryRestConfig. It can be achieved with setter injection:
#Component
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private TranslationStringToSpecificationStatusEnumConverter converter;
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
#Autowired
public void setConverter(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
}
You can find how to solve it in this commit by Greg Turnquist: https://github.com/pmihalcin/custom-converter-in-spring-data-rest/commit/779a6477d76dc77515b3e923079e5a6543242da2
I have the situation where a protoype bean contains a singleton bean. In order to achieve it, I had to create 2 configuration classes. Is it possible to merge my 2 confgiuration classes into a single one?
Singleton class:
public class MySingleton {
}
Prototype class:
public class MyPrototype {
private MySingleton b;
public MyPrototype(MySingleton b) {
this.b = b;
}
}
Configuration class 1:
#Configuration
public class ConfigClassA {
#Bean
public MySingleton myBean() {
return new MySingleton();
}
}
Configuration class 2:
#Configuration
public class ConfigClassB {
#Autowired
public MySingleton mb;
#Bean
#Scope("prototype")
public MyPrototype myPrototype() {
return new MyPrototype(mb);
}
}
Try this:
#Configuration
public class ConfigClass {
#Bean
public MySingleton myBean() {
return new MySingleton();
}
#Bean
#Scope("prototype")
public MyPrototype myPrototype(MySingleton myBean) {
return new MyPrototype(myBean);
}
}
The BeanFactory should search for a bean of type MySingleton when creating the prototype bean and inject it into the method myPrototype.