Can Spring Autowire beans created in a BeanFactoryPostProcessor - java

I have a standard bean with some properties that need to be autowired.
#Service
public class MyServiceImpl implements MyService {
#Autowired
private FirstRepository first;
public MyServiceImpl() {
}
I use a Java Config to find the beans:
#Configuration
#ComponentScan(basePackages = "com.company", excludeFilters = { #Filter(Configuration.class) })
public class MainConfig {
}
However, the FirstRepository Bean doesn't exist so I create it in a BeanFactoryPostProcessor:
public class RepoGeneratorPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
GenericBeanDefinition jpaR = new GenericBeanDefinition();
jpaR.setBeanClass(JpaRepositoryFactoryBean.class);
jpaR.setAutowireCandidate(true);
jpaR.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
jpaR.setLazyInit(false);
jpaR.setPropertyValues(new MutablePropertyValues().add("repositoryInterface", FirstRepository.class));
RootBeanDefinition definition = new RootBeanDefinition();
definition.setBeanClass(FirstRepository.class);
definition.setAutowireCandidate(true);
definition.setFactoryBeanName("&jpaR");
definition.setFactoryMethodName("getObject");
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
definition.setLazyInit(false);
definition.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory;
registry.registerBeanDefinition("jpaR", jpaR);
registry.registerBeanDefinition("first", definition);
}
When I start my application I get the following exception which seems to suggest that Spring can't find the FirstRepository bean.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.company.FirstRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
If I remove the #Autowired annotation I can see after start up that the FirstRepository bean is properly created.
Any suggestions?

This exception is saying that there is no bean defined for the FirstRepository class when the project is being built. Which I cannot see it here either.
The simplest solution would be to have a bean definition in your application-context.xml like this:
<bean id="firstRepository" class="your.package.FirstRepository" autowire="byName"/>
In this case, at the start up, there will be that bean definition.

I don't think you need the & before the beanname in
definition.setFactoryBeanName("&jpaR");
I used something like that in my project
definition.setFactoryBeanName("jpaR");
and it worked as expected
The & is needed if you need to get the factory bean of the bean named first.
&first should return jpaR.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-extension-factorybean

Related

Spring Hadoop config - No qualifying bean of type org.apache.hadoop.conf.Configuration

I am trying to configure beans for Hadoop/Hive environment. According to documentation I need Apache Hadoop Configuration class, which should be autowired. See: http://docs.spring.io/spring-hadoop/docs/2.4.0.RELEASE/reference/html/springandhadoop-store.html (section 6.2.2 Configuring the dataset support)
Yet, when I try to run my app, I get: NoSuchBeanDefinitionException: No qualifying bean of type [org.apache.hadoop.conf.Configuration] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
My class is very simple:
#SpringBootApplication
public class HiveTestApp implements CommandLineRunner {
private
#Autowired
org.apache.hadoop.conf.Configuration hadoopConfiguration;
...
I am using Cloudera cluster, here are dependencies:
dependencies {
compile(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.data:spring-data-hadoop-hive:2.4.0.RELEASE-cdh5',
'org.apache.hive:hive-jdbc:1.1.0-cdh5.4.3',
)
Now, I might be wrong, but I can remember in the past I used autowired config, and it worked fine. Has anything changed in the latest version? Am I missing something?
OK here's the solution.
#Configuration
public class ApplicationConfiguration {
#Value("${com.domain.app.hadoop.fs-uri}")
private URI hdfsUri;
#Value("${com.domain.app.hadoop.user}")
private String user;
#Value("${com.domain.app.hadoop.hive.jdbc-uri}")
private String hiveUri;
#Autowired
private org.apache.hadoop.conf.Configuration hadoopConfiguration;
#Bean
public org.apache.hadoop.conf.Configuration hadoopConfiguration() {
return new org.apache.hadoop.conf.Configuration();
}
#Bean
public HdfsResourceLoader hdfsResourceLoader() {
return new HdfsResourceLoader(hadoopConfiguration, hdfsUri, user);
}
#Bean
public HiveTemplate hiveTemplate() {
return new HiveTemplate(() -> {
final SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new HiveDriver(), hiveUri);
return new HiveClient(dataSource);
});
}
}
Configuration file below.
com.domain.app.hadoop:
fs-uri: "hdfs://hadoop-cluster/"
user: "hdfs-user"
hive.jdbc-uri: "jdbc:hive2://hadoop-cluster:10000/hive-db"
I've made Hadoop configuration object a bean, because I need to inject it in one of the classes. If you don't need a bean, you can just create new instance by yourself.

Spring: use properties read from a file

i'm new to the spring framework and i'm having some problems trying to read and use properties from a file.
To summarize, what i want to do is to define a class which stores all the properties read, a second class that uses those properties to do something and a third class that uses the results.
The class that stores the properties is:
#Configuration
public class PropertyClass {
#Value("${propertyName")
private Integer propertyName;
#Bean(name = "propertyName")
public Integer getPropertyName() {
return propertyName;
}
}
The class that reads and uses those properties:
#Component
public class PropertyReader {
private Integer myProperty;
#Autowire
#Qualifier("propertyName")
public void setMyProperty(
Integer myProperty) {
this.myProperty = myProperty;
}
public Integer getValue() {
//do something with myProperty
return result;
}
}
And the class that uses PropertyReader:
public class Utilizer {
private PropertyReader getPropertyReader() {
ApplicationContext context = new AnnotationConfigApplicationContext(PropertyReader.class);
PropertyReader reader = (BakerStorageClassConfigHelper)context.getBean("PropertyReader");
return reader;
}
}
I've registered the classes as beans in the application-config.xml file:
<bean class="property.class.package.PropertyClass" depends-on="Environment" />
<bean class="reader.class.package.PropertyReader" />
And i have an environment.xml file where the "Environment" bean is defined with location rules to find the property files.
Now what happens that in the class "Utilizer" when i try to get the "ApplicationContext" object an exception is thrown:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'PropertyReader':
Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire method: public void reader.class.package.PropertyReader.setMyProperty(java.lang.Integer);
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.Integer]
found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
I've tried to change the annotation of PropertyReader class to #Repository or #Service and tried to add a #ComponentScan with the PropertyClass package specified but none of that worked for me..
Could someone give me some advices?
Thank you!
I do not quite get why do you need to declare propertyName as Integer.
If what you need is just get the properties from file, then you can define a PropertiesFactoryBean and autowire it to any other beans you like.
Let's say you have a myValues.properties file containing values:
key1=value1
key2=value2
Define Bean:
#Bean(name = "myProperties")
public PropertiesFactoryBean detailQueriesFactoryBean()
{
PropertiesFactoryBean pfb = new PropertiesFactoryBean();
pfb.setLocation(new ClassPathResource("com/xxx/myValues.properties"));
return pfb;
}
Now wherever you need it, do:
#Autowired
#Qualifier("myProperties")
private Properties myValuesContainer;
public void myMethod(){
//this will get you "value1"
String value1 = myValuesContainer.getProperty("key1");
}
Hope this works for you.
--------------------- For your case----------------
If it is already in the application context, you can use #Value to inject value directly in your PropertyReader and add getter/setter for them. No need a PropertyClass, right?
Or you can add a #PostConstruct method to PropertyReader. Inside the method, you can retrieve the values you need from the existing context.
#PostContstruct
public void extractValues(){
//retrieve value from context and assign to whichever var.
}

Does Spring #Autowired inject beans by name or by type?

I am reading beginning spring (wiley press) book. In chapter 2 there is an example
about Java configuration and #Autowired. It provides this #Configuration class
#Configuration
public class Ch2BeanConfiguration {
#Bean
public AccountService accountService() {
AccountServiceImpl bean = new AccountServiceImpl();
return bean;
}
#Bean
public AccountDao accountDao() {
AccountDaoInMemoryImpl bean = new AccountDaoInMemoryImpl();
//depedencies of accountDao bean will be injected here...
return bean;
}
#Bean
public AccountDao accountDaoJdbc() {
AccountDaoJdbcImpl bean = new AccountDaoJdbcImpl();
return bean;
}
}
and this regular bean class
public class AccountServiceImpl implements AccountService {
#Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
...
}
When I run the code, it works. But I expected an exception because I have defined 2 beans with the same type in the configuration.
I realized it works like this:
if Spring encounters multiple beans with same type it checks field name.
if it finds a bean with the name of the target field, it injects that bean into the field.
Isn't this wrong? Is there a bug in Spring's handling of Java configuration?
The documentation explains this
For a fallback match, the bean name is considered a default qualifier
value. Thus you can define the bean with an id "main" instead of the
nested qualifier element, leading to the same matching result.
However, although you can use this convention to refer to specific
beans by name, #Autowired is fundamentally about type-driven injection
with optional semantic qualifiers. This means that qualifier values,
even with the bean name fallback, always have narrowing semantics
within the set of type matches; they do not semantically express a
reference to a unique bean id
So, no, it's not a bug, that is the intended behavior. The bean id (name) will be used as a fallback if a by-type autowiring doesn't find a single matching bean.

Why spring can't find my bean?

I created an interface and a class:
public interface UserService {
List<User> listAll();
}
#Transactional
public class DefaultUserService implements UserService {
private String tableName;
public List<User> listAll() { someDao.listAllFromTable(tableName); }
public void setTableName(String tableName) { this.tableName = tableName; }
}
Also in my application context xml file context.xml, I defined:
<bean id="userService" class="mypackage.DefaultUserService">
<property name="tableName" value="myusers" />
</bean>
Then I want to test the DefaultUserService:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:context-test.xml"})
#TransactionConfiguration(transactionManager = "testTransactionManager")
#Transactional
public class UserServiceTest {
#Autowired
private DefaultUserService userService;
#Before
public void setup() {
userService.setTableName("mytesttable");
}
#Test
public void test() {
// test with userService;
userService.listAll();
}
}
Notice it uses context-test.xml, which imported the original context.xml:
<import resource="classpath:context.xml"/>
Unfortunately, when the test starts, spring throws exception:
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'mypackage.UserServiceTest':
Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field:
private mypackage.DefaultUserService mypackage.userService
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [mypackage.DefaultUserService] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I'm not sure where is wrong, why spring can't find the bean DefaultUserService I defined?
It's because #Transactional places the bean is behind a jdk proxy implementing UserService interface, after that the bean is only available as UserService and not DefaultUserService.
See https://stackoverflow.com/a/18875681/241986.
You can try setting the table name with a property placeholder #Value("${someprop}") and define that property in test context, or create another interface that will expose setTableName(), and autowire that helper interface into the test case.
I'm not sure there are any easy solutions of the problem, I think this task can be subsumed under the problem of bean redefinition in Spring test-context framework
Spring beans redefinition in unit test environment
Try to replace the class DefaultUserService to the interface UserService
public class UserServiceTest {
#Autowired
private UserService userService;
....
}
You have not defined the getter for your property tableName in your implementing class.Spring IOC container works on the POJO model

Autowire a bean only if it is available

#Component
public class Test {
#Autowire
private MyBean myBean;
public void sampleMethod()
{
if(myBean == null) {
myBean = BeanFactory.getDefaultBean();
}
// ...
}
}
Is it possible to autowire MyBean if it is not defined in the spring configuration xml file? I understand that in this case, it would throw No bean found of type MyBean. Can we configure something to ignore that exception and fallback on the BeanFactory to get the DeafultBean.
Something like:
#Autowire(assignNullIfBeanNotFound = true)
Let's look at the javadoc (the annotation is named Autowired, and not Autowire):
public abstract boolean required
Declares whether the annotated dependency is required.
Defaults to true.
Isn't this idea of providing documentation for classes wonderful?

Categories