Can't make Spring's ImportAware to work - spring

I am trying to create my own #EnableXxx-style annotation (namely #EnableCustomizedPropertySources). For this the annotation imports the class CustomizedPropertySourcesConfiguration which in turn implements ImportAware, in order to have access to attributes of the #EnableCustomizedPropertySources annotation.
Annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(CustomizedPropertySourcesConfiguration.class)
public #interface EnableCustomizedPropertySources {
String externalFolderName();
String propertiesFileName();
(...)
}
Imported configuration class:
#Configuration
public class CustomizedPropertySourcesConfiguration implements ImportAware {
protected AnnotationAttributes enableCustomizedPropertySourcesAttributes;
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> attributes = importMetadata.getAnnotationAttributes(EnableCustomizedPropertySources.class.getName(), false);
this.enableCustomizedPropertySourcesAttributes = AnnotationAttributes.fromMap(attributes);
}
#Bean
public PropertySourcesPlaceholderConfigurer propertySource() {
return (...);
}
}
The problem is, the method setImportMetadata is not invoked by Spring when I annotate some #Configuration class with my #EnableCustomizedPropertySources annotation, so I cannot access the annotations attributes.

The ImportAware class (CustomizedPropertySourcesConfiguration) needs both:
#Configuration
#Component

Related

How to use #TestConfiguration

How to override #Configuation which is present under src/main/java with #TestConfiguration during unit tests?
#Configuration
public class AppConfig {
#Bean
public EmployeeService employeeService(){
return new EmployeeService();
}
}
#Component
public class ServerStartSetup implements CommandLineRunner {
#Autowired
private EmployeeService employeeService;
public void run(String... args) {
// do something with employee service
}
}
I would like to override the above bean with some below custom bean for testing purposes.
#TestConfiguration
public class TestAppConfig {
#Bean
public EmployeeService employeeService(){
return new FakeEmployeeService();
}
}
#SpringBootTest
#Import(TestAppConfig.class)
public class UnitTest {
}
However AppConfig does not seem to be skipped. That is , it throws an error saying that there is a bean with same name employeeService. If I rename bean method name in the TestAppConfig, it injects the bean created via AppConfig.
How to fix this.?
Note: One possible solution is using #Profile. I am looking for anything other than using Profiles.
I tested locally and found that changing the method name or #Bean to #Bean("fakeEmployeeService") and adding the #Primary annotation works.
#SpringBootTest
class DemoApplicationTests {
#Autowired
private EmployeeService employeeService;
#TestConfiguration
static class TestConfig {
//#Bean("fakeEmployeeService")
#Bean
#Primary
public EmployeeService employeeServiceTest() {
return new EmployeeService() {
#Override
public void doSomething() {
System.out.println("Do something from test...");
}
};
}
}
...
}
If we want to override a bean definition in #TestConfiguration, we need:
To use the same name as the overridden bean. (Otherwise it would be an "additional" bean and we could get conflict/'d have to qualify/primary)
Since spring-boot:2.1: spring.main.allow-bean-definition-overriding=true (set this in tests ONLY!plz!)
#ref
Then, with:
#TestConfiguration
public class TestAppConfig {
#Bean // when same name, no #Primary needed
public EmployeeService employeeService(){ // same name as main bean!
return new FakeEmployeeService();
}
}
We can do that:
#Import(TestAppConfig.class)
#SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
public class UnitTest {
... // EmployeeService will be "fake", the rest is from "main config"
You can mock the AppConfig bean in your test like this:
#MockBean
private AppConfig config;
Or, like you said, just use profiles.

Defining constructor in prototype bean

Using SpringBoot, I have a Component bean that is defined as #Scope("protoype"). It also injects another prototype bean
The class is defined as
#Component
#Scope("prototype")
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(DataObj data) {
this.injectedBean = beanFactory.getBean(InjectedBean.class, data)
}
}
However, IntelliJ complains about the data field on the constructor: Could not autowire. No beans of 'DataObj' type found.. But DataObj is a POJO. I pass it in at runtime in order to create the bean. Am I defining the constructor incorrectly?
Update
Had the same problem doing it this way. It still wants to treat DataObj as a bean on the factory constructor class. Doesn't matter if I annotate the class with #Component or #Configuration
#Component
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
#Bean
#Scope("prototype")
public MyClass myClass(DataObj data) {
InjectedBean injectedBean = beanFactory.getBean(InjectedBean.class, data)
return new MyClass(injectedBean);
}
}
Also tried this example from that same link:
#Configuration
public class ServiceConfig {
#Bean
public Function<DataObj, MyClass> thingFactory() {
return data-> myClass(data); //
}
#Bean
#Scope(value = "prototype")
public MyClass myClass(DataObj data) {
return new MyClass(data);
}
}
Update
I think I resolved this with some information in Spring Java Config: how do you create a prototype-scoped #Bean with runtime arguments?. Part of my problem is that I tried to put the factory bean in the Component itself, which doesn't work
In other words
#Component
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
#Bean
#Scope("prototype")
public MyClass myClass(DataObj data) {
InjectedBean injectedBean = beanFactory.getBean(InjectedBean.class, data)
return new MyClass(injectedBean);
}
}
In this cass, Spring tries to create a MyClass bean because of the #Component annotation, but another MyClass bean due to the #Bean annotation.
So I moved the #Bean to another class
#Configuration
public class ServiceConfig {
#Bean
public Function<DataObj, MyClass> thingFactory() {
return data-> myClass(data); //
}
#Bean
#Scope(value = "prototype")
public MyClass myClass(DataObj data) {
return new MyClass(data);
}
}
This appears to work, but IntelliJ still complains about DataObj. This might be an Intellij issue

how to create annotation in spring

#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Import({
testMethod.class
})
public #interface test{
public String value() default "";
}
#Component
public class testMethod{
...
}
in my controller, I want to use the annotation I created
#test
#RequestMapping(...)
public response getAll(){
...}
I put break point in the testMethod, and it could not hit the break point. It seems like it could not find the testMethod component.
You need to import a Configuration class. As documented #Import annotation used to import a configuration as follows.
Indicates one or more {#link Configuration #Configuration} classes to import.
public class TestBean {
public TestBean() {
}
}
#Configuration
public class TestMethod {
#Bean
public TestBean testBean() {
return new TestBean(); //put break-point here
}
}

MethodValidationPostProcessor breaks #Transactional

I have a Controller which calls a Service which has #Transactional annotation.
But when I declare a bean MethodValidationPostProcessor, no transaction is created (could not initialize proxy - no Session).
#EnableWebMvc
#ComponentScan(basePackages = {"my"})
public class Application extends WebMvcConfigurerAdapter {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
Controller bean:
#RestController
#RequestMapping(path = "/my", produces = APPLICATION_JSON_VALUE)
public class MyController {
#Autowired
private TransactionalService transactionalService;
#RequestMapping(method = POST)
public void post(#SafeHtml #RequestBody String hey) {
transactionalService.doStuff(hey);
}
}
Service bean:
#Service
public class TransactionalService {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void doStuff(String hey) {
Item h = entityManager.find(Item.class, hey);
h.getParent(); // could not initialize proxy - no Session
}
}
I'd like to understand why #Transactional doesn't work when I declare MethodValidationPostProcessor. Thanks !
Note: If I add #Transactional on my Controller, it works. But it's not what I want to do.
Thanks to #Kakawait, I got a work-around: declaring my bean MethodValidationPostProcessor. Needs to be static so that #Transactional still work properly.
/**
* This bean must be static, to be instantiated before the other MethodValidationPostProcessors.
* Otherwise, some are not instantiated.
*/
#Bean
public static MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}

JUnit Mocking Bean which is #autowired with userdefined Annotation

I have a user defined annotation class as follows.
#Target({ TYPE, METHOD, PARAMETER, FIELD })
#Retention(RUNTIME)
#Qualifier
public #interface Message
{
Dest value();
public static enum Target { DEFAULT, TEST }
}
I use this annotation in the following way.
#Component
public class ProcessorBean implements Processor
{
#Autowired #Message(Message.Target.DEFAULT) Producer<Object, Object> messageProducer;
#Autowired
MessageConfig messageConfig;
Not sure, how to create a bean of ProcessorBean and inject Producer.
#Bean(name="DEFAULT")
public Producer<Object, Object> producer() {
return mock(Producer.class);
}
I tried the above one and it is throwing dependency error.
Thanks
I found the solution myself. Hope, this will be helpful for others.
#Message(Message.Target.DEFAULT)
#Bean
public Producer<Object, Object> producer() {
return mock(Producer.class);
}

Resources