Fine grained Spring autowiring not working (#Autowired with additional custom annotation) - java

I'm trying to implement fine grained #Autowired configuration using basically the example from the spring documentation at: http://docs.spring.io/spring/docs/3.2.0.RELEASE/spring-framework-reference/html/beans.html#beans-autowired-annotation-qualifiers.
Given the following testcase:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=ExampleConfiguration.class)
public class ExampleTest {
#Autowired #ExampleQualifier(key="x")
private ExampleBean beanWithQualifierKeyX;
#Test
public void test() {
System.out.println(this.beanWithQualifierKeyX);
}
}
and the following configuration:
#Configuration
public class ExampleConfiguration {
#Bean
#ExampleQualifier(key = "x")
public ExampleBean exampleBean1() {
return new ExampleBean();
}
#Bean
#ExampleQualifier(key = "y")
public ExampleBean exampleBean2() {
return new ExampleBean();
}
#Bean
public ExampleBean exampleBean3() {
return new ExampleBean();
}
}
with the custom qualifier annoation:
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
public #interface ExampleQualifier {
String key();
}
What I would expect is the following: The property beanWithQualifierKeyX should be autowired using the first bean from the configuration class. Both the annotation on the configuration and the annotation on the property have the key="x" setting so this should be the only match. As far as I can see this is almost the same as MovieQualifier annotation from the Spring example documentation.
However, when I execute the test I get the following error:
org.springframework.beans.factory.BeanCreationException:
Could not autowire field: private xxx.ExampleBean xxx.ExampleTest.beanWithQualifierKeyX;
nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No unique bean of type [xxx.ExampleBean] is defined:
expected single matching bean but found 2: [exampleBean1, exampleBean2]
It looks like Spring does perform a match against the annotation (since both exampleBean1 and exampleBean2 are annotated) but doesn't take into account the value for the key of the annotation - otherwise x would be a perfect match.
Did I miss something in the configuration process or why is there no match?
The Spring version I'm using is 3.2.0.RELEASE

There is/was an bug in Spring 3.2.0 Autowiring with #Qualifier and #Qualifier meta annotation fails in Spring 3.2 (fixed in 3.2.1)
Its description sound exactly like your problem.
So update to 3.2.1

Related

Error org.springframework.beans.factory.UnsatisfiedDependencyException

I have a problem that prevents me from moving forward and I don't know how to solve it. I have a class called Validator where I store validation functions and I need to do unit tests for each one and validate its functionality, but there are some of them that use the Spring Environment instance in which it accesses the properties file. If I do "normal" unit tests, whenever I call the function where this feature is implemented, it returns me Environment is null. I have tried using #Autowired instead of instantiating with new in the test class, the use of #RunWith(SpringRunner.class) and since the test classes and the function classes are in different packages I have also used the #ComponentScan and it gives me an error... What am I doing wrong or what am I doing wrong?
I have the code of the Validator class in this way:
#Component
public class Validator {
#Autowired
public Environment env;
public CodRespuestaWS validateTypeOperation(TypeOperation typeOperation, String operation) {
String response = env.getProperty(typeOperation.toString() + "." + operation);
if (OK.equalsIgnoreCase(response)) {
return new CodResponseWS();
}
return new CodResponseWS(CodResponseWS.ER100, typeOperation.toString()+" not allowed:" + operation);
}
}
And in the test I do it this way:
#ComponentScan(basePackages = "com.functions.validators")
#RunWith(SpringRunner.class)
public class ValidateRequestHigh {
RequestHigh requestHigh = new RequestHigh();
CodResponseWS response;
#Autowired Validator validator;
HighValidator highValidator = new HighValidator();
UserData userData = new UserData();
#Test
public void test() throws Exception {
response = validator.validateTypeOperation(TypeOperation.typeOperationHigh, "high");
System.out.println(response.getCodResponse());
}
}
The problem that I mentioned before the NULL, is that when executing the test it did not even reach the print that I have set, but rather it stayed on the line
String response = env.getProperty(typeOperation.toString() + "." +
operation);
And it marked the error that env was NULL and now it returns a different one
And the error that returns me now is:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name
'functiontest.unittest.ValidateRequestHighTest': Unsatisfied
dependency expressed through field 'validator'; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type 'com.functions.validators.Validator'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
I guess it's already understood, but I'll detail it more just in case, the functions are in
src/main/java/com.functions.validators/Validators.java
and the tests
src/main/test/functiontest.unittest/ValidateRequestHighTest.java
I am using version 5.1.2.RELEASE of SpringBoot and JUnit 4
Using field injection makes Validator class impossible to be unit tested because it provides no way to pass the Environment instance to it. but you can spin up the whole application context and configure it to do dependency injection stuff using #SpringBootTest which is not a unit test but integration test.
if I do "normal" unit tests, whenever I call the function where this
feature is implemented, it returns me Environment is null.
Because when you use new to instantiate a bean its not a Spring managed bean anymore and its dependencies are not resolved.
I have tried using #Autowired instead of instantiating with new in the
test class, the use of #RunWith(SpringRunner.class) and since the test
classes and the function classes are in different packages I have also
used the #ComponentScan and it gives me an error
You should also add SpringBootTest annotation for loading into application context.
If you want to really unit test your class you don't need application context instead do the following
Change the Validator class like:
#Component
public class Validator {
private Environment env;
public Validator(Environment env) {
this.env = env;
}
....
}
And for unit testing it do:
public class ValidatorTest {
Validator validator;
MockEnvironment environment;
#Before
public void setUp() {
environment = new MockEnvironment();
environment.setProperty("key1", "value1");
//...
environment.setProperty("keyn", "valuen");
validator = new Validator(environment);
}
#Test
public void test() {
// test stuff
}
}

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.

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.

Trying to inject an object into JUnit test

I have had to adapt a project I've been working on to work differently, using an injected object (documentDao) to access the methods for adding/updating/etc. records in a database. Where necessary I simply injected this object into the constructor, but of course this won't work with JUnit tests (which can only have no-argument constructors), so I'm stuck on how to get the object into the test class.
The first code snippet shows a dumbed-down version of one of the test classes. The problem is that I need to create the documentDao object so I can pass it as an argument into the BackendApiController instantiation statement.
The second snippet is the first part of the DocumentDaoImpl class, which needs to be injected.
Any suggestions would be welcomed.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class ApiBackendTests {
#Configuration
#PropertySource(value = "classpath:system.properties")
static class ContextConfiguration {
}
private static BackendApiController backendApiController = new BackendApiController(documentDao);
#Test
public void retrieveSampleStatementList() {
String response = backendApiController.genericStatementList(x,y,z);
String eStatementId = "";
if (response.indexOf("_id") > 0) {
eStatementId = response.substring(response.indexOf("<_id>") + 5, response.indexOf("</_id>"));
}
// if this test is true, then at least one statement document was found in the above search.
assertTrue(response.indexOf("_id") > 0);
}
}
#Repository
public class DocumentDaoImpl<T> implements DocumentDao<T> {
public DocumentDaoImpl() {
}
#Inject
DBCollection dbCollection;
#Inject
GridFS gridFS;
#Autowired
ObjectMapper objectMapper;
#Override
public String insert(CommonDocument document) {
There's still not enough information to say anything for sure, but I believe you can try using #Autowired to wire up your needed component:
#Autowired
private DocumentDao documentDao;
You got the error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.roler.res.test.ApiBackendTests': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.roler.res.mongodb.dao.DocumentDao
That means Spring isn't aware of the DocumentDao bean yet. There are several ways to do that, but I think the easiest way is putting this in your configuration context:
<context:component-scan base-package="package.contain.your.dao"/>
It will tell Spring to scan the package in search for components with annotation.
UPDATE: since you don't use XML configuration, #ComponentScan is the way to go

Resolve Spring #Value expression in JUnit tests

Here's a snippet of a Spring bean:
#Component
public class Bean {
#Value("${bean.timeout:60}")
private Integer timeout;
// ...
}
Now I want to test this bean with a JUnit test. I'm therefore using the SpringJUnit4ClassRunner and the ContextConfiguration annotation.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class BeanTest {
#Autowired
private Bean bean;
// tests ...
#Configuration
public static class SpringConfiguration {
#Bean
public Bean bean() {
return new Bean();
}
}
}
Unfortunately the SpringJUnit4ClassRunner can't resolve the #Value expression, even though a default value is supplied (a NumberFormatException is thrown). It seems that the runner isn't even able to parse the expression.
Is something missing in my test?
Your test #Configuration class is missing an instance of PropertyPlaceholderConfigurer and that's why Spring does not know how to resolve those expressions; add a bean like the following to your SpringConfiguration class
#org.springframework.context.annotation.Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setIgnoreResourceNotFound(true);
return ppc;
}
and move it to a separate class and use
#ContextConfiguration(classes=SpringConfiguration.class)
to be more specific when running your test.

Categories