I have this project hierarchy:
A SpringConfiguration.java file with one Bean (SoapServiceBean).
A LoggerUtilsConfiguration.java with two beans (ConfigManager and LoggerManager).
SpringConfiguration imports LoggerUtilsConfigueration with an #Import.
The code:
#Configuration
#Import({com.myApp.LoggerUtilsConfiguration.class})
public class SpringConfiguration {
#Lazy
#Bean(name = "soapServiceBean")
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SoapServiceBean soapServiceBeanInit(
#Qualifier("configManager") ConfigManager configManager ,
#Qualifier("loggerManager") LoggerManager loggerManager)
throws ServiceException, SystemException {
SoapServiceBean service = new SoapServiceBean();
service.setConfigManager(configManager);
service.setLoggerManager(loggerManager);
return service;
}
}
#Configuration
public class LoggerUtilsConfiguration {
#Bean(name = "configManager")
#Scope(BeanDefinition.SINGLETON)
public ConfigManager configManagerInit() {
ConfigManager cManager = new ConfigManager();
return cManager;
}
#Bean(name = "loggerManager")
#Scope(BeanDefinition.SINGLETON)
public LoggerManager loggerManagerInit() {
LoggerManager cManager = new LoggerManager();
return cManager;
}
}
My problema is that, "configManager" is a valid bean, but loggerManager throws this Exception:
No qualifying bean of type [com.myApp.LoggerManager] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {#org.springframework.beans.factory.annotation.Qualifier(value=loggerManager)}
Is very Strange, because If I copy the loggerManager Bean method into the main #Configuration class, the app starts with no problem and loggerManager is started with no problem.
"LoggerUtilsConfiguration" is not part of my App, is a Maven dependency external jar from another dev team. I have decompiled it and I cannot see anything strange, "configManager" and "loggerManager" has the same annotations with the same values except the bean name.
Any idea?
Solved.
My problem is the WebSphere, that store an old version of the third party JAR.
\was\wasprofiles\wp_profile80\installedApps\GUUPZN00Cell\MY-APP.ear\lib\LOGGER-LIB-1.0.0.jar
\was\wasprofiles\wp_profile80\installedApps\GUUPZN00Cell\MY-APP.ear\lib\LOGGER-LIB-1.0.1.jar
The version "1.0.0" LOGGER-LIB doesn't contains the bean "loggerManager" and WebSphere is loading this version of the library.
It seems that the file was blocked when websphere try to delete it. I had to stop the server to delete it properly.
If the 2 managers are singletons you can #Autowired them in.
If there is an existing bean called loggerManager of a different type then it wouldn't find yours, but it depends on how your classpath is set up.
#Configuration
#Import({com.myApp.LoggerUtilsConfiguration.class})
public class SpringConfiguration {
#Autowired
ConfigManager configManager;
#Autowired
LoggerManager loggerManager;
#Lazy
#Bean(name = "soapServiceBean")
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SoapServiceBean soapServiceBeanInit()
throws ServiceException, SystemException {
SoapServiceBean service = new SoapServiceBean();
service.setConfigManager(configManager);
service.setLoggerManager(loggerManager);
return service;
}
}
Try to use #DependsOn:
#Lazy
#Bean(name = "soapServiceBean")
#DependsOn({ "configManager", "loggerManager" })
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SoapServiceBean soapServiceBeanInit(
#Qualifier("configManager") ConfigManager configManager ,
#Qualifier("loggerManager") LoggerManager loggerManager)
throws ServiceException, SystemException {
SoapServiceBean service = new SoapServiceBean();
service.setConfigManager(configManager);
service.setLoggerManager(loggerManager);
return service;
}
Try providing autowire annotation to properly enable constructor injection like :
#Configuration
#Import({com.myApp.LoggerUtilsConfiguration.class})
public class SpringConfiguration {
#Lazy
#Autowired
#Bean(name = "soapServiceBean")
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SoapServiceBean soapServiceBeanInit(
#Qualifier("configManager") ConfigManager configManager ,
#Qualifier("loggerManager") LoggerManager loggerManager)
throws ServiceException, SystemException {
SoapServiceBean service = new SoapServiceBean();
service.setConfigManager(configManager);
service.setLoggerManager(loggerManager);
return service;
}
}
The #Qualifier annotation is used to resolve the bean based on names while the #Autowired annotation provides constructor injection of beans. The #Bean annotation should support constructor injection without the need for #Autowired
Related
I have a configuration class like the following
#Configuration
public class Configuration {
#Autowired
private JdbcTemplate jdbcTemplate;
#Bean
SimpleJdbcCall simpleJdbcCall() {
return new SimpleJdbcCall(jdbcTemplate).withProcedureName("");
}
}
I am trying to write a unit test for this configuration class. My test class looks like the following.
#ContextConfiguration(classes = { Configuration.class })
#RunWith(SpringRunner.class)
public class ConfigurationTest {
ApplicationContextRunner context = new ApplicationContextRunner()
.withUserConfiguration(Configuration.class);
#Test
public void should_check_presence_of_example_service() {
context.run(it -> {
assertThat(it).hasSingleBean(SimpleJdbcCall.class);
});
}
}
When I am running the test in the ConfigurationTest class, I am getting an error like the one below.
Error creating bean with name 'Configuration': Unsatisfied dependency
expressed through field 'jdbcTemplate'; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type 'org.springframework.jdbc.core.JdbcTemplate'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
I tried to solve this by crating a bean jdbcTemplate in the Configuration class and passed datasource. Then the test unit test does not find the bean datasource. After that I used #TestConfiguration in the ConfigurationTest class and crated a mock(jdbcTemplate). That did not work either.
I found a solution for my problem, and I thought it might be helpful for others if someone is in the same situation. Actually my goal was to increase the test coverage because of the company requirement, and the following solution works for me.
I changed my configuration class like below
#Configuration
public class Configuration {
#Bean
SimpleJdbcCall simpleJdbcCall(DataSource dataSource) {
return new SimpleJdbcCall(dataSource).withProcedureName("");
}
}
I had to change my test class like below. Now the test class is not complaining, and I am getting 100% coverage of the Configuration class.
#SpringBootTest
public class ConfigurationTest {
#TestConfiguration
static class MyConfiguration {
#Bean
DataSource dataSource() {
return mock(DataSource.class);
}
}
#Autowired
private DataSource dataSource;
#Autowired
private Configuration configuration;
#Test
public void should_check_presence_of_simpleJdbcCall_Bean() {
SimpleJdbcCall simpleJdbcCall = configuration.simpleJdbcCall(dataSource);
Assertions.assertNotNull(simpleJdbcCall);
}
}
I'm using an remote api in my web application. Some endpoints require an authentication header in the API I'm using. Because of this need I have created two different RestTemplateBuilder bean in my configuration class.
#Configuration
public class RestTemplateConfiguration {
#Bean
#DependsOn(value = {"secureRestTemplateCustomizer"})
#Qualifier("secureRestTemplateBuilder")
public RestTemplateBuilder secureRestTemplateBuilder() {
return new RestTemplateBuilder(secureRestTemplateCustomizer());
}
#Bean
#DependsOn(value = {"publicRestTemplateCustomizer"})
#Qualifier("publicRestTemplateBuilder")
public RestTemplateBuilder publicRestTemplateBuilder() {
return new RestTemplateBuilder(publicRestTemplateCustomizer());
}
#Bean
#Qualifier("secureRestTemplateCustomizer")
public SecureRestTemplateCustomizer secureRestTemplateCustomizer() {
return new SecureRestTemplateCustomizer();
}
#Bean
#Qualifier("publicRestTemplateCustomizer")
public PublicRestTemplateCustomizer publicRestTemplateCustomizer() {
return new PublicRestTemplateCustomizer();
}
}
And these are my custom RestTemplateCustomizers
#Component
public class SecureRestTemplateCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
restTemplate.setErrorHandler(new ErrorHandler());
restTemplate.getInterceptors().add(new AuthorizationHeaderInterceptor());
}
}
PublicRestTemplateCustomizer
#Component
public class PublicRestTemplateCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
restTemplate.setErrorHandler(new ErrorHandler());
}
}
There is no problem when I want to use these RestTemplateBuilders in my api clients like below. Spring can autowire them into my api client constructor.
private RestTemplate restTemplate;
#Autowired
public LoginApiClient(#Qualifier("publicRestTemplateBuilder") RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
But in my unit tests this usage is firing an error like
Error creating bean with name 'loginApiClient' defined in file [..\LoginApiClient.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Qualifier(value=publicRestTemplateBuilder)}
#RunWith(SpringRunner.class)
#RestClientTest({LoginApiClient.class})
#Category(IUnitTest.class)
public class LoginApiClientTest {
#Autowired ILoginApiClient loginApiClient;
#Autowired private MockRestServiceServer server;
#Test
public void validateToken_returns_true_for_valid_token() throws Exception{
String token = "token";
this.server.expect(requestTo(this.validateTokenUrl))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess(objectMapper.writeValueAsString(validTokenResponse(token)), MediaType
.APPLICATION_JSON));
Boolean isValid = loginApiClient.validateToken(token);
server.verify();
assertThat(isValid,is(equalTo(true)));
}
}
How can I mock these RestTemplates truly and use in my unit test.
I think you are missing the #ComponentScan(value = "your.package") in your Main application class
You do not have context specified.
Try adding this annotation to uour test class:
#ContextConfiguration(classes=RestTemplateConfiguration.class, loader=AnnotationConfigContextLoader.class)
See this article for details: spring-3-1-m2-testing-with-configuration-classes-and-profiles - it's for Spring 3, but newer versions of Spring work similarly.
Here's a newer tutorial: junit-spring-integration-example
I have a java configuration file (class with #configuration annotation). It has one method with #Bean annotation and I would like to instantiate this bean based on some arguments. In other words I would like to get a bean by name (passed via argument) and instantiate this bean.
Is it possible to do this in #configuration class?
#Configuration
public class ApplicationConfig {
#Resource
private Config config;
#Bean
public Object application() throws ParseException {
return new SampleApp(/*get the bean by name*/);
}
}
config contains the argument and I would like to use this argument and get the bean by that name.
Something like this should work:
#Configuration
public class ApplicationConfig {
#Resource
private Config config;
#Autowired
private ApplicationContext appContext;
#Bean
public Object application() throws ParseException {
return new SampleApp(
(appContext.getBean("beanNameFromConfig"));
}
}
I am using a mock repository for my application.
Here is snippet how service looks:
#Service
public class WeatherStationService {
#Autowired
private WeatherStationRepositoryMock weatherRepository;
Here is repository code:
#Repository
public class WeatherStationRepositoryMock {
#Getter
private List<WeatherStation> stations = new ArrayList<>(
Arrays.asList(
new WeatherStation("huston", "Huston station", RandomGenerator.getRandomGeoInformation()),
new WeatherStation("colorado", "Colorado station", RandomGenerator.getRandomGeoInformation())
)
);
It works fine when I am executing main() with #SpringBootApplication.
However, when I want to run test class:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = MockConfig.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public class WeatherStationServiceTest {
#Autowired
#Real
private WeatherStationService weatherService;
It fails with the following stacktrace:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'edu.lelyak.repository.WeatherStationRepositoryMock' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Here is MockConfig content:
#Configuration
public class MockConfig {
//**************************** REAL BEANS ******************************
#Bean
#Real
public WeatherStationService weatherServiceReal() {
return new WeatherStationService();
}
Real is marker annotation for real instances:
#Retention(RUNTIME)
#Qualifier
public #interface Real {
}
I can fix it with next initialization at service:
#Service
public class WeatherStationService {
private WeatherStationRepositoryMock weatherRepository = new WeatherStationRepositoryMock();
It works fine.
Why does this happen?
How to fix autowiring for my custom repository class?
#SpringBootApplication implicitly defines #ComponentScan, which scans all subpackages for beans.
When you run a test using MockConfig's, it doesn't scan for beans.
Solution - use #ComponentScan OR define beans in MockConfig
(1) Using #ComponentScan:
#Configuration
#ComponentScan //Make sure MockConfig is below all beans to discover
public class MockConfig {
#Bean
#Real
public WeatherStationService weatherServiceReal() {
return new WeatherStationService();
}
}
(2) or define required beans:
#Configuration
public class MockConfig {
#Bean
#Real
public WeatherStationService weatherServiceReal() {
return new WeatherStationService();
}
#Bean
public WeatherStationRepositoryMock weatherStationRepository() {
return new WeatherStationRepositoryMock()
}
}
I've been using Spring with MyBatis and it's been working really well for a single database. I ran into difficulties when trying to add another database (see reproducible example on Github).
I'm using Spring Java configuration (i.e. not XML). Most of the examples I've seen show how to achieve this using XML.
I have two data configuration classes (A & B) like this:
#Configuration
#MapperScan("io.woolford.database.mapper")
public class DataConfigDatabaseA {
#Bean(name="dataSourceA")
public DataSource dataSourceA() throws SQLException {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriver(new com.mysql.jdbc.Driver());
dataSource.setUrl("jdbc:mysql://" + dbHostA + "/" + dbDatabaseA);
dataSource.setUsername(dbUserA);
dataSource.setPassword(dbPasswordA);
return dataSource;
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSourceA());
return sessionFactory.getObject();
}
}
Two mappers, and a service that autowires the mappers:
#Service
public class DbService {
#Autowired
private DbMapperA dbMapperA;
#Autowired
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
The application won't start:
Error creating bean with name 'dataSourceInitializer':
Invocation of init method failed; nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [javax.sql.DataSource] is defined:
expected single matching bean but found 2: dataSourceB,dataSourceA
I've read that it's possible to use the #Qualifier annotation to disambiguate the autowiring, though I wasn't sure where to add it.
Can you see where I'm going wrong?
If you want to use two data sources at same time and they are not primary and secondary, you should disable DataSourceAutoConfiguration by #EnableAutoConfiguration(excludes = {DataSourceAutoConfiguration.class}) on your application annotated by #SpringBootApplication. Afterwards, you can create your own SqlSessionFactory and bundle your own DataSource. If you also want to use DataSourceTransactionManager, you should do that too.
In this case, you haven't disabled DataSourceAutoConfiguration, so spring framework will try to #Autowired only one DataSource but got two, error occurs.
As what I've said before, you should disable DataSourceAutoConfiguration and configure it manually.
You can disable data source auto configuration as following:
#SpringBootApplication
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class YourApplication implements CommandLineRunner {
public static void main (String... args) {
SpringApplication.run(YourApplication.class, args);
}
}
And if you are really want to use multiple databases at same time, I suggest you to registering proper bean manually, such as:
package xyz.cloorc.boot.mybatis;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
#Configuration
public class SimpleTest {
private DataSource dsA;
private DataSource dsB;
#Bean(name = "dataSourceA")
public DataSource getDataSourceA() {
return dsA != null ? dsA : (dsA = new BasicDataSource());
}
#Bean(name = "dataSourceB")
public DataSource getDataSourceB() {
return dsB != null ? dsB : (dsB = new BasicDataSource());
}
#Bean(name = "sqlSessionFactoryA")
public SqlSessionFactory getSqlSessionFactoryA() throws Exception {
// set DataSource to dsA
return new SqlSessionFactoryBean().getObject();
}
#Bean(name = "sqlSessionFactoryB")
public SqlSessionFactory getSqlSessionFactoryB() throws Exception {
// set DataSource to dsB
return new SqlSessionFactoryBean().getObject();
}
}
#Repository
public class SimpleDao extends SqlSessionDaoSupport {
#Resource(name = "sqlSessionFactoryA")
SqlSessionFactory factory;
#PostConstruct
public void init() {
setSqlSessionFactory(factory);
}
#Override
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public <T> T get (Object id) {
return super.getSqlSession().selectOne("sql statement", "sql parameters");
}
}
In the end, we put each mapper in its own folder:
src/main/java/io/woolford/database/mapper/a/DbMapperA.java
src/main/java/io/woolford/database/mapper/c/DbMapperB.java
We then created two DataConfig classes, one for each database. The #MapperScan annotation resolved the expected single matching bean but found 2 issue.
#Configuration
#MapperScan(value = {"io.woolford.database.mapper.a"}, sqlSessionFactoryRef="sqlSessionFactoryA")
public class DataConfigDatabaseA {
It was necessary to add the #Primary annotation to the beans in one of the DataConfig classes:
#Bean(name="dataSourceA")
#Primary
public DataSource dataSourceA() throws SQLException {
...
}
#Bean(name="sqlSessionFactoryA")
#Primary
public SqlSessionFactory sqlSessionFactoryA() throws Exception {
...
}
Thanks to everyone who helped. No doubt, there's more than one way to do this. I did try #Qualifier and #EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) as recommended by #eduardlofitskyi and #GeminiKeith, but that generated some further errors.
In case it's useful, the solution that worked for us is posted here: https://github.com/alexwoolford/mybatis-spring-multiple-mysql-reproducible-example
You can use #Qualifier annotation
The problem is that you have two the same type beans in Spring container. And when you try autowire beans, Spring cannot resolve which bean inject to field
The #Qualifier annotation is the main way to work with qualifiers. It can be applied alongside #Autowired or #Inject at the point of injection to specify which bean you want to be injected.
So, your DbService should look like this:
#Service
public class DbService {
#Autowired
#Qualifier("dataSourceA")
private DbMapperA dbMapperA;
#Autowired
#Qualifier("dataSourceB")
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
I had the same issue and could not start my Spring Boot application, and by renaming the offending class and all the layers that dealt with it, strangely the application started successfully.
I have the classes UOMService, UOMServiceImpl UOMRepository and UOMRepositoryImpl. I renamed them to be UomService, UomServiceImpl, UomRepository and UomRepositoryImpl and that solved the problem!