I have something like this:
#Configuration
public class SpringConfigUtil {
private static final Logger logger = Logger.getLogger(SpringConfigUtil.class);
#Value("#{ systemProperties['activemq.username'] }")
private String userName;
#Value("#{ systemProperties['activemq.password'] }")
private String password;
#Value("#{ systemProperties['activemq.URL'] }")
private String brokerURL;
#Value("#{ systemProperties['activemq.queueName'] }")
private String queueName;
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean(name="producer")
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setDefaultDestination(new ActiveMQQueue(queueName));
jmsTemplate.setConnectionFactory(connectionFactory());
return jmsTemplate;
}
#Bean
public ActiveMQConnectionFactory connectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(brokerURL);
activeMQConnectionFactory.setUserName(userName);
activeMQConnectionFactory.setPassword(password);
return activeMQConnectionFactory;
}
}
It works fine.
Let's say I also have an applicationContext.xml file which has loaded some beans.
How do I refer to those beans here in #Configuration?
I don't want to create beans programmatically again as they are already created by loading applicationContext.xml.
Let's have I have 50+ properties. Is there a better way to refer to them than defining something like the following for every property?
#Value("#{ systemProperties['activemq.URL'] }")
private String brokerURL;
Thanks for your time!
How do I refer to those beans here in #Configuration?
According to http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch06.html, please try:
...
#Configuration
#AnnotationDrivenConfig // enable the #Autowired annotation (??)
#ImportXml("classpath:<*path*/to/your/*>applicationContext.xml")
public class SpringConfigUtil { ...
Let's have I have 50+ properties. Is there a better way to refer to them than defining something like the following for every property?
None, that I can imagine :-( ... how could you "better access" 50+ properties, than "by their name" - "by index", array-like!? Though some properties can be coupled, at the end of the day" each has (should have) its special meaning and purpose - and for that touched/wired/configured individually. (the best practice tip is just to do it as rare as possible)
Related
I would like to achieve not-trivial bean injection implementation.
I have a custom properties file:
#Getter
#Setter
#ConfigurationProperties
public class DatabaseProperties {
private String url;
private String username;
private String password;
}
I Here is the configuration file:
#Configuration
#EnableConfigurationProperties(DatabaseProperties.class)
public class DBConfig {
#Bean
#ConfigurationProperties(prefix = "datasource.database1")
public JdbcTemplate jdbcTemplateDatabase1(DatabaseProperties databaseProperties) {
DataSource dataSource = new DriverManagerDataSource(
databaseProperties.getUrl(),
databaseProperties.getUsername(),
databaseProperties.getPassword());
return new JdbcTemplate(dataSource);
}
#Bean
#ConfigurationProperties(prefix = "datasource.database2")
public JdbcTemplate jdbcTemplateDatabase2(DatabaseProperties databaseProperties) {
DataSource dataSource = new DriverManagerDataSource(
databaseProperties.getUrl(),
databaseProperties.getUsername(),
databaseProperties.getPassword());
return new JdbcTemplate(dataSource);
}
}
The goal I want to achieve is to instantiate a new DatabaseProperties instance based on prefix.
There are two possible solutions:
create two beans of type DatabaseProperties using corresponding prefixes and two JdbcTemplate beans where parameter is qualified DatabaseProperties bean accordingly.
in each JdbcTemplate bean provide 3 parameters (String url, String username, String password) and inject them through #Value
BUT Is it possible to get rid of creating DatabaseProperties beans for each JdbcTemplate or using #Value ?
You don't need create a DatabaseProperties. Spring already does this for us on datasources and proprierties variable
#Configuration
public class ConfigDataSource {
#Bean("datasource-1") // this name will qualify on #autowired
#ConfigurationProperties(prefix="spring.datasource.yourname-datasource-1") // this is the name for the prefix for datasource on .properties settings
public DataSource dataSourcePostgres() {
return DataSourceBuilder.create().build();
}
#Bean("datasource-2") // this name will qualify on #autowired
#ConfigurationProperties(prefix="spring.datasource.yourname-datasource-2") // this is the name for the prefix for datasource on .properties settings
public DataSource dataSourceMySql() {
return DataSourceBuilder.create().build();
}
}
.properties
# Its required use the same name declared in bean
spring.datasource.yourname-datasource-1.url=...
spring.datasource.yourname-datasource-1.jdbcUrl=${spring.datasource.yourname-datasource-1}
spring.datasource.yourname-datasource-1.username=user
spring.datasource.yourname-datasource-1.password=pass
spring.datasource.yourname-datasource-1.driver-class-name=your.driver
spring.datasource.yourname-datasource-2.url=...
spring.datasource.yourname-datasource-2.jdbcUrl=${spring.datasource.yourname-datasource-2}
spring.datasource.yourname-datasource-2.username=user
spring.datasource.yourname-datasource-2.password=pass
spring.datasource.yourname-datasource-2.driver-class-name=your.driver
using on services
#Awtowired
#Qualifier("datasource-1")
private DataSource dataSource1;
#Awtowired
#Qualifier("datasource-2")
private DataSource dataSource2;
public testJdbcTemplate(){
// You can qualifier JdbcTemplate below on bean and not necessary need instance on service
JdbcTemplate jdbcTemplateDatasource1 = new JdbcTemplate(dataSource1);
JdbcTemplate jdbcTemplateDatasource2 = new JdbcTemplate(dataSource2);
}
To my knowledge, there is no way around the fact that if you want to have access to multiple databases Spring will not be able to do the magic for you. You will need to create the two JdbcTemplate Spring-managed beans and then inject them where needed using the #Qualifier annotation.
This approach has two benefits:
It actually work;
You are explicit about what you are doing. A Spring application has already a good load of magic happening in there, so we might want to avoid some additional one when we need something a little bit more custom and that is not that complex to achieve.
I have something like :
#Configuration
#EnableTransactionManagement
public class JdbcTemplateConfig{
#Bean("JdbcTemplateOne")
public NamedParameterJdbcTemplate (#Qualifier(firstDataSource final DataSource ds)){
return new NamedParameterJdbcTemplate(ds);
}
#Bean("JdbcTemplateTwo")
public NamedParameterJdbcTemplate (#Qualifier(secondDataSource final DataSource ds)){
return new NamedParameterJdbcTemplate(ds);
}
#Bean("JdbcTemplateThree")
public NamedParameterJdbcTemplate (#Qualifier(thirdDataSource final DataSource ds)){
return new NamedParameterJdbcTemplate(ds);
}
}
and now I need a list of above templates. To get a one by one (for example in an IT-Test) I can make something like:
#SpringBootTest
public class SomeITCase{
#Autowired
#Qualifier("JdbcTemplateOne")
private NamedParameterJdbcTemplate jdbcTemplate1;
#Autowired
#Qualifier("JdbcTemplateTwo")
private NamedParameterJdbcTemplate jdbcTemplate2;
#Autowired
#Qualifier("JdbcTemplateThree")
private NamedParameterJdbcTemplate jdbcTemplate3;
???
List<NamedParameterJdbcTemplate> myList = ???
}
Question: How to get all templates in a list without to have declaring them one by one?
What you a re looking for is a straight forward #Autowired annotation.
Let's suppose we have several beans defined like in your above example.
Now, we create a test like below:
#SpringBootTest
#Slf4j
public class SomeITCase {
#Autowired
#Qualifier("JdbcTemplateTwo")
private NamedParameterJdbcTemplate jdbcTemplate2;
#Autowired
List<NamedParameterJdbcTemplate> allTemplates;
#Test
public void testMyList() {
assertThat(allTemplates)
.hasSize(3)
.contains(jdbcTemplate2);
allTemplates.forEach(template -> {
log.info(template.toString());
});
}
}
Executing this test should make it really obvious: Spring injects all beans of matching type in your list. The test ensures that the size is three and at least the one known bean reference is contained.
For further information on getting started with #Autowired, maybe take a look at Injecting collections - Injecting Bean references.
To get list of all the beans from the Application context, you can do:
public void beanNames(ApplicationContext ctx) {
String[] beanNames = ctx.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}
You can also look into Spring boot acutator. It provides endpoints like /bean to get all the beans registered with Spring.
If you just want a list of all NamedParameterJdbcTemplate beans in your application, you could just ask spring to autowire it for you. It allows you to autowire a list of all beans of a specific type:
{
...
#Autowired
private List<NamedParameterJdbcTemplate> allJdbcTemplates;
...
}
Lets say I have a config class (JmsQueueConfig, see below). In this class, I want to configure multiple queues for my entire application. For one queue, there is no problem. However when I add a second queue and try to use one of these queues from a service (MemberService), then Spring-boot tells me
Parameter 1 of constructor in
com.example.notification.application.jms.JmsEventPublisher
required a single bean, but 2 were found:
- queueAccountToNotification: defined by method 'queueAccountToNotification' in class path resource
[com/example/notification/application/jms/JmsQueueConfig.class]
- queueNotificationToAccount: defined by method 'queueNotificationToAccount' in class path resource
[com/example/notification/application/jms/JmsQueueConfig.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer
to accept multiple beans, or using #Qualifier to identify the bean
that should be consumed
Here is my Config-Class:
#Configuration
#EnableJms
#ImportAutoConfiguration(classes = {
JmsAutoConfiguration.class,
ActiveMQAutoConfiguration.class
})
public class JmsQueueConfig {
#Value("${APP_QUEUE_ACCOUNT_TO_NOTIFICATION}")
private String queueAccountToNotificationName;
#Value("${APP_QUEUE_NOTIFICATION_TO_ACCOUNT}")
private String queueNotificationNameToAccount;
#Bean
#Qualifier("q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
#Bean
#Qualifier("q2")
public Queue queueNotificationToAccount() {
return new ActiveMQQueue(queueNotificationNameToAccount);
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#Bean
#Qualifier("p1")
public EventPublisher eventPublisher(JmsTemplate jmsTemplate) {
return new JmsEventPublisher(jmsTemplate, new ActiveMQQueue(queueAccountToNotificationName));
}
#Bean
public MessageConverter messageConverter() {
return new JmsMessageConverter();
}
}
My Service:
#Service
#FieldDefaults(level = AccessLevel.PRIVATE)
#AllArgsConstructor
#Slf4j
public class MemberService {
#Autowired
#Qualifier("q1")
Queue q;
#Qualifier("p1")
EventPublisher eventPublisher;
public void createMemberSubscription(final Member member) {
final MembershipSubscriptionEvent event = new MembershipSubscriptionEvent(UUID.randomUUID().toString(), member.getEmail());
//eventPublisher.publish(event);
log.info("createMemberSubscription");
}
public void removeMemberSubscription(final Member member) {
final MembershipRemovalEvent event = new MembershipRemovalEvent(UUID.randomUUID().toString());
//eventPublisher.publish(event);
log.info("removeMemberSubscription");
}
}
I am new to Spring ecosystem and I might not have understand well the #Autowired and bindings. Any good documentation or example would be much appreciated.
On Spring and SoF, I haven't found any such documentation.
Updated:
JmsEventPublisher class
#Component
#FieldDefaults(level = AccessLevel.PRIVATE)
#Slf4j
#AllArgsConstructor
public class JmsEventPublisher implements EventPublisher {
final JmsTemplate jmsTemplate;
final Destination destination;
#Override
public void publish(DomainEvent event) {
jmsTemplate.convertAndSend(destination, event);
log.trace("Sent event. [destination={}, event={}]", destination, event);
}
}
To correct the accepted answer,your understanding for the use of #Qualifier is correct. It can be used in two contexts. It can be used with a #Bean configuration method to provide a qualifier for the bean. If not provided this defaults to the bean name.
It can also be used at an injection target i.e method or field with #Autowired or #Inject annotation. At this context it assist Spring autowiring infrastructure to filter the bean candidates matching the injection target based on the qualifier(provided with #Bean method) if multiple beans are found
The cause of the error was due to
#AllArgsConstructor
public class JmsEventPublisher implements EventPublisher
The #AllArgsConstructor will generate the following constructor
public JmsEventPublisher(JmsTemplate jmsTemplate, Destination destination){
//body
}
Spring will attempt to autowire JmsEventPublisher by constructor since it has one constructor which is not a no argument constructor. However the parameter of type Destination matches two beans of type Queue.
The solution is to either use an explicit constructor. ie remove #AllArgsConstructor and define constrctor as follows
public JmsEventPublisher(JmsTemplate jmsTemplate, #Qualifier("q1")Destination destination){
//body
}
or use field or setter injection instead i.e remove #AllArgsConstructor and inject fields or setter methods
public class JmsEventPublisher implements EventPublisher {
private JmsTemplate jmsTemplate;
#Qualifier("q1")
private Destination destination;
}
I think you have misunderstood #Qualifier
From the documentation, "This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring."
In your case #Qualifier is of no meaning.
#Bean
#Qualifier("q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
Instead you should be doing like this
#Bean(name = "q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
#Bean(name = "q2")
public Queue queueNotificationToAccount() {
return new ActiveMQQueue(queueNotificationNameToAccount);
}
Similarly remove #Qualifier on eventPublisher(...)
That doesn't solve all the problem. :)
As the exception indicates, spring is not able to autowire Destination field in JmsEventPublisher. Because it has two beans of type Destination(q1 and q2).
To solve this what you can do is .
Put #Primary on one of the bean declaration and then use #Qualifier.
#Primary
#Bean(name = "q1")
public Queue queueAccountToNotification() {
return new ActiveMQQueue(queueAccountToNotificationName);
}
public class JmsEventPublisher implements EventPublisher {
final JmsTemplate jmsTemplate;
#Qualifier("q1")
final Destination destination;
..........
}
Bottom line is For #Qualifier to work in case of multiple beans of same type, you need to put #Primary
Another option is instead of using #Primary, you can name the variables exactly as Bean names, then spring will automagically inject correct beans for you. i.e.
public class JmsEventPublisher implements EventPublisher {
final JmsTemplate jmsTemplate;
final Destination q1; // q1 or q2
.....
}
similiarly in MemberService
public class MemberService {
#Autowired
Queue q1; // q1 or q2
.....
}
Change final Destination destination to final Destination q1 and it should work. I had the same problem and it worked for me.
I've the following configuration file
#Configuration
#ComponentScan(basePackages = "com.foo")
#EnableTransactionManagement
public class AppSpringConfiguration {
#Autowired
private Environment env;
#Autowired
private ApplicationContext appContext;
#Value("#{cvlExternalProperties['dbDriverClassName']}")
private String dbDriverName;
#Bean
public PropertiesFactoryBean cvlExternalProperties() {
PropertiesFactoryBean res = new PropertiesFactoryBean();
res.setFileEncoding("UTF-8");
res.setLocation(new FileSystemResource(env.resolvePlaceholders("${MY_ENV_VAR}") + "external.properties"));
return res;
}
#Bean
public BasicDataSource datasource() {
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName("myDriverClassName");
basicDataSource.setUrl("MyDbUrl");
basicDataSource.setUsername("myUser");
basicDataSource.setPassword("myPass");
return basicDataSource;
}
}
And in the external properties file I've
dbUrl=jdbc:mysql://localhost:3306/someDb
dbUser=someUser
dbPassword=somePass
dbDriverClassName=com.mysql.jdbc.Driver
In which way I can use the cvlProperties inside the datasource() method?
I've tried
env.getProperty("dbDriverClassName")
env.getProperty("#cvlProperties['dbDriverClassName']")
But I'm not able to retrieve the properties.
The field dbDriverName is correctly filled, so that means the bean declaration is ok.
I want to use the PropertyFactoryBean class because in this way I can specify the encoding to use.
If I use the the following annotation on the top of the configuration class
#PropertySource("file:${MY_ENV_VAR}/external.properties")
I'm able to retrieve the properties with this piece of code
env.getProperty("dbDriverClassName")
But the encoding used by the PropertySource annotation is the windows default, and for me is not correct.
Can you help me?
At the moment the solution(that I don't love so much) is to declare the properties by using the annotation #Value
#Value("#{cvlExternalProperties['dbDriverClassName']}")
private String dbDriverClassName;
and then using it inside the java class
I am using Spring 3 and also heavily utilize the well known #Autowire annotation. I would like to create a new annotation, let's call it #Property that autowires Java properties from set by .property files or vm arguments.
Considering the following class
class A {
#Property("my.a")
private int a;
}
if the property my.a is present, the property A.a is set.
Is such an annotation maybe already existing? If not I am aiming to create one, as mentioned above. Are the utilities given by spring to achieve my goal? I think about creating a BeanPostProcessor ...
Thanks for your hints!
There's already such an annotation - #Value
You should just define a PropertyPlaceHolderConfigurer, and configure it to resolve system properties.
Refer http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/
you can use #ImportResource to import XML configuration files. Then use context:property-placeholder to load properties
#Configuration
#ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
private #Value("${jdbc.url}") String url;
private #Value("${jdbc.username}") String username;
private #Value("${jdbc.password}") String password;
public #Bean DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}