Spring boot register an instance as a bean - spring-boot

I am trying to register Datasource instance as a bean in java code(spring-boot project)
Here is what I wrote. (This code is not working.)
#Configuration
public class DatabaseConfig {
private Logger logger = Logger.getLogger(DatabaseConfig.class);
#Autowired
ApplicationContext context;
private Map<String, Map<String, String>> dsMap;
private Map<String, String> getTestDataSourceInfo () {
Map<String, String> ds = new HashMap<String, String> ();
ds.put("driverClassName", "com.mysql.jdbc.Driver");
ds.put("url", "jdbc:mysql://123.456.78.912:3306/test");
ds.put("username", "testuser");
ds.put("password", "testuser");
return ds;
}
public DatabaseConfig () {
this.dsMap = new HashMap<String, Map<String, String>>();
dsMap.put("sampleDs", getTestDataSourceInfo());
}
#PostConstruct
public void loadDataSource () {
logger.info("DS ================================ :: " + String.valueOf(this.dsMap));
this.dsMap.forEach((k,v) -> {
logger.info("value ========================== :: " + String.valueOf(v));
DataSource aSource = DataSourceBuilder.create()
.driverClassName(v.get("driverClassName"))
.url(v.get("url"))
.username(v.get("username"))
.password(v.get("password"))
.build();
// PROBLEM STARTS ..............
// Add datasource instance with name to context
context.getAutowireCapableBeanFactory().autowireBean(aSource);
});
}
}
Is there any proper way to register bean with an instance?
I could not find any fine samples for this.
FYI, What I have expected in above code is...
Spring boot application will read above class as a Configure
It will make an Java instance in its constructor
And it will add the instance as a bean to application context in loadDatasource method
However, it is not working. So I am curious about how to add an java instance as a bean to current Spring boot application context.

Actually, it could be more easier if I did not do this with DataSource.
Since, Spring-boot automatically do configure dataSource, I have to disable this settings first.
Here is what I did to achieve the goal
In #Configuration Class...
#Configuration
public class DatabaseConfig {
private Logger logger = Logger.getLogger(DatabaseConfig.class);
#Autowired
ApplicationContext context;
private Map<String, Map<String, String>> dsMap;
private Map<String, String> getTestDataSourceInfo () {
Map<String, String> ds = new HashMap<String, String> ();
ds.put("driverClassName", "${ your driverClassName }");
ds.put("url", "${ your url }");
ds.put("username", "${ your user }");
ds.put("password", "${ your password }");
return ds;
}
public DatabaseConfig () {
this.dsMap = new HashMap<String, Map<String, String>>();
dsMap.put("sampleDs1", getTestDataSourceInfo());
dsMap.put("sampleDs2", getTestDataSourceInfo());
}
#PostConstruct
public void loadDataSource () {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
this.dsMap.forEach((k,v) -> {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(BasicDataSource.class);
v.forEach((ds_key, ds_val) -> {
builder.addPropertyValue(ds_key, ds_val);
});
BeanDefinition def = builder.getBeanDefinition();
if(!registry.containsBeanDefinition(k)) registry.registerBeanDefinition(k, def);
});
}
}
In above class, I could add java instances to spring bean with BeanDefinitionRegistry and BeanDefinitionBuilder.
If this is just a bean, it would be end here, but what you are trying to add is DataSource bean, have to do some extra work.
Since, boot automatically setting DataSource, we have to disable that setting to register customized datasource.
In your #SpringbootApplication class, add #EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}).
Now, you are able to user those bean in other beans via #Autowired and #Qualifier.
Thanks.

Related

How to take a map and use it as values in spring-boot

I'm working on a spring-boot application and trying to use properties defined in a map as values that can be injected into various services. The code I am working with defines an object PropertyLoader which is able to return a map based on an environment. It looks something like this:
public interface PropertyLoader {
public Map<String, String> load(String env);
}
How can I make the entries in the map returned by this method available in #Value injections in spring components.
You can define custom application listener that will override default properties.
Old answer - below is a better way to achieve it
public class PropertyInjector implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
final ConfigurableEnvironment environment = event.getEnvironment();
final Properties props = new Properties();
//inject properties here
props.put("key", "value");
final PropertiesPropertySource propertySource = new PropertiesPropertySource("propertySource", props);
environment.getPropertySources().addFirst(propertySource);
}
}
UPDATE - I have found better way that easily works with #Value annotation
#Configuration
public class SomeConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
final Properties props = new Properties();
props.put("some_dynamic_key", "some_dynamic_value");
final PropertiesPropertySource propertySource = new PropertiesPropertySource("propertySource", props);
propertySourcesPlaceholderConfigurer.setProperties(props);
return propertySourcesPlaceholderConfigurer;
}
#Value("${some_dynamic_key}")
String property;
}
In such way value some_dynamic_key is available along all the application

Spring boot common application properties

Spring boot application properties needs to follow convention from https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html when we use any DB like cassandra/mongo. In case if we want to declare our own properties for DB setup instead of spring-boot convention, what are all the steps we need to do for setting up DB?
You can do this: Spring boot - custom variables in Application.properties
or you can just create your own property in your application.properties file like:
my.property.someDb.hostname=http://wherever.comand then reference to it in your code like:
#Value("${my.property.someDb.hostname}")
private String someDbHostname;
Update 1:
If you want to create the MongoDb with your own properties you have to define the right Java Beans in an #Configuration file. For MongoDB it could look like the following:
#Configuration
public class MyMongoConfig extends AbstractMongoConfiguration{
#Value("${my.property.someDb.hostname}")
private String someDbHostname;
#Value("${my.property.someDb.myOwnPortDefinition}")
private int myOwnPortDefinition;
#Value("${my.property.someDb.myDatabasename}")
private String myDatabasename;
#Override
protected String getDatabaseName() {
return myDatabasename;
}
#Override
#Bean
public Mongo mongo() throws Exception{
return new MongoClient(someDbHostname, myOwnPortDefinition );
}
#Bean
public MongoTemplate mongoTemplate() throws Exception{
return new MongoTemplate(mongo(), getDatabaseName());
}
}
These are the essential steps you need in order to get a data source like Jdbc, mongodb set up in Spring Boot
Need a #Configuration class that has transaction management enabled
on it
Read the environment properties for the datasource i.e. dataSource
url, username, password etc.
Create beans for datasource, session factory, transaction manager
etc.
Once all of the above setup, use this #Configuration in your
consumer to initialize the spring application context
Here are some snippets of wiring mongodb datasource in spring boot
DataSourceConfiguration.java
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {"com.example.xyz"})
public class DatabaseEntityConfiguration {
public static final String DATABASE_ENTITY_DATA_SOURCE = "databaseDataSource";
public static final String DATABASE_HIBERNATE_PROPERTIES = "databaseHibernateProperties";
public static final String DATABASE_ENTITY_SESSION_FACTORY = "databaseSessionFactory";
public static final String DATABASE_ENTITY_TRANSACTION_MANAGER = "databaseTransactionManager";
public static final String DATABASE_ENTITY_DB_CONFIG_DAO = "dmdatabaseDbConfigDao";
public static final String DATABASE_ENTITY_DB_CONFIG_SERVICE = "dmdatabaseDbConfigService";
private static final String ENTITY_PACKAGE = "com.example.xyz.database.entity";
#Autowired
private org.springframework.core.env.Environment environment;
#Bean(name = DATABASE_ENTITY_DATA_SOURCE)
public DataSource databaseEntitydataSource() throws PropertyVetoException {
// mongodb properties
String driverClass = environment.getProperty("databaseEntity.mongodb.driverClassName");
String mongodbUrl = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.url");
String user = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.username");
String password = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.password");
Preconditions.checkArgument(StringUtils.isNotBlank(driverClass), "The property mongodb driverClass must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(mongodbUrl), "The property mongodb mongodbUrl must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(user), "The property mongodb user must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(password), "The property mongodb password must not be null or blank");
dataSource.setDriverClass(driverClass);
dataSource.setmongodbUrl(mongodbUrl);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}
#Bean(name = DATABASE_ENTITY_SESSION_FACTORY)
public AnnotationSessionFactoryBean databaseEntitySessionFactory() throws PropertyVetoException {
AnnotationSessionFactoryBean annotationSessionFactoryBean = new AnnotationSessionFactoryBean();
annotationSessionFactoryBean.setDataSource(databaseEntitydataSource());
annotationSessionFactoryBean.setPackagesToScan(ENTITY_PACKAGE);
annotationSessionFactoryBean.setAnnotatedClasses(DBConfig.class);
annotationSessionFactoryBean.setHibernateProperties(databaseEntityHibernateProperties());
return annotationSessionFactoryBean;
}
#Bean(name = DATABASE_ENTITY_TRANSACTION_MANAGER)
public HibernateTransactionManager databaseEntityTransactionManager() throws PropertyVetoException {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(databaseEntitySessionFactory().getObject());
return transactionManager;
}
}

Singleton property on springboot

Can I create one singleton property in spring boot?
When I use this:
public class MessengerPlatformCallbackHandler {
#Scope(value = "singleton")
private Map<String, Object> conversationID = new HashMap<>();
I got the erro: #Scope not applicable to field
tks
You need to create it this way.
#Configuration
public class ConversationIDConfig {
#Bean
#Scope(value = "singleton")
public Map<String, Object> conversationId(){
private Map<String, Object> conversationID = new HashMap<>();
}
}
And later you can inject it where ever you want as below.
public class MessengerPlatformCallbackHandler {
#Autowired
private Map<String, Object> conversationID;
}
You need to create it this way.
#Configuration
public class ConversationIDConfig {
#Bean
public Map<String, Object> conversationId(){
return new HashMap<>();
}
}
And later you can inject it where ever you want as below.
public class MessengerPlatformCallbackHandler {
#Autowired
private Map<String, Object> conversationId;
}

spring boot and freemarker configure TemplateDirectiveModel and TemplateMethodModelEx

I have a project built with Spring Boot + FreeMarker, and it worked fine until tonight, but I don't think I had changed anything; however it failed. Below is my FreeMarker configuration class:
#Configuration
#Slf4j
public class FreemarkerConfiguration extends FreeMarkerAutoConfiguration.FreeMarkerWebConfiguration {
/**
* autowired all implementations of freemarker.template.TemplateDirectiveModel
*/
#Autowired
Map<String, TemplateDirectiveModel> directiveModelMap;
/**
* autowired all implementations of freemarker.template.TemplateMethodModelEx
*/
#Autowired
Map<String, TemplateMethodModelEx> methodModelExMap;
private static final String CUSTOM_DIRECTIVE_SUFFIX = "Directive";
private static final String CUSTOM_METHOD_SUFFIX = "Method";
#Override
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = super.freeMarkerConfigurer();
Map<String, Object> sharedVariables = new HashMap<String, Object>();
if (!CollectionUtils.isEmpty(directiveModelMap)) {
Map<String, Object> map = new HashMap<String, Object>();
for (Map.Entry<String, TemplateDirectiveModel> entry : directiveModelMap.entrySet()) {
map.put(StringUtils.uncapitalize(entry.getKey()).replaceAll(CUSTOM_DIRECTIVE_SUFFIX, ""), entry.getValue());
}
sharedVariables.putAll(map);
}
if (!CollectionUtils.isEmpty(this.methodModelExMap)) {
Map<String, Object> map = new HashMap<String, Object>();
for (Map.Entry<String, TemplateMethodModelEx> entry : this.methodModelExMap.entrySet()) {
map.put(StringUtils.uncapitalize(entry.getKey()).replaceAll(CUSTOM_METHOD_SUFFIX, ""), entry.getValue());
}
sharedVariables.putAll(map);
}
BeansWrapper beansWrapper = new BeansWrapperBuilder(freemarker.template.Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build();
sharedVariables.put("enums", beansWrapper.getEnumModels());
configurer.setFreemarkerVariables(sharedVariables);
return configurer;
}
}
Problem is that
#Autowired
Map<String, TemplateDirectiveModel> directiveModelMap;
#Autowired
Map<String, TemplateMethodModelEx> methodModelExMap;
I want to inject all implementations of TemplateDirectiveModel and TemplateMethodModelEx , but both Map<String ,TemplateDirectiveModel/TemplateMethodModelEx> got null. Of course, the implementations annotated with #Compoment. I don't know why, I compared the diffs but got no answers, why the Maps instantiated after
#Override
public FreeMarkerConfigurer freeMarkerConfigurer(){ .... }
Here's my boot application
#Configuration
#SpringBootApplication
#EntityScan("com.hmxx.entity")
#EnableAspectJAutoProxy
#EnableTransactionManagement
#EnableJpaRepositories(value = {"com.hmxx.service"})
public class Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(new Object[]{Application.class});
app.setWebEnvironment(true);
//app.setBannerMode(Banner.Mode.CONSOLE);
ConfigurableApplicationContext ctx = app.run(args);
Map<String, TemplateDirectiveModel> directiveModelMap = ctx.getBeansOfType(TemplateDirectiveModel.class);
Map<String, TemplateMethodModelEx> methodModelExMap = ctx.getBeansOfType(TemplateMethodModelEx.class);
}
#Autowired
DataInitService dataInitService;
#Override
public void run(String... args) throws Exception {
// dataInitService.initAdminUser();
}
}
And obviously Map<String, TemplateDirectiveModel>、Map<String, TemplateMethodModelEx> methodModelExMap both not null.
I want to know why the injection got null and hope to resolve it.

Vaadin Spring no boot : Dependency Injection issue

I'm using Vaadin 7, Spring Data JPA 1.9.4.RELEASE, and Vaadin-Spring 1.0.0 and I have some DI problemes.
I choose not to use Spring Boot because it will automatically do too many things that I cannot "see" and I have encountered some problemes that spent me too much time to understand and find the reason, so I prefer no boot.
The probleme that I encounter is that DI works at a root UI but not for a sub-window of the root UI.
RootUI.java
#SpringUI(path = "/")
public class RootUI extends UI {
#Autowired
private EntityManagerFactory entityManagerFactory; // this one works, but I cannot get EntityManager directly
#Autowired
private ClassService classService; // this one works
#Override
protected void init(VaadinRequest request) {
...
PersonForm form = new PersonForm();
CssLayout layout = new CssLayout();
layout.addComponent(form);
Window subWindow = new Window();
subWindow.setContent(layout);
...
}
}
PersonForm.java
public class PersonForm {
#Autowired
private ClassService classService; // this doesnot work,
public PersonForm(ClassService classService) {
classService.findByName();// since the #Autowired dosenot work, I have to pass the one from rootUI.
}
init() {
classService.findByName(); // null exception
}
}
DBConfig.java
#Configuration
#EnableVaadin
#EnableJpaRepositories(basePackages = {"com.example.person.repository"})
#EnableTransactionManagement
public class DBConfig {
#Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName("com.mysql.jdbc.Driver");
config.setJdbcUrl("jdbc:mysql://localhost:3306/test?autoReconnect=true&useSSL=false");
config.setUsername("root");
config.setPassword("root");
config.setMaximumPoolSize(20);
HikariDataSource dataSource = new HikariDataSource(config);
return dataSource;
}
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setDataSource(dataSource());
factory.setPackagesToScan("com.example.person");
factory.setPersistenceProviderClass(HibernatePersistenceProvider.class);
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect");
jpaProperties.put("hibernate.hbm2ddl.auto", "update");
factory.setJpaProperties(jpaProperties);
factory.afterPropertiesSet();
return factory.getObject();
}
}
Try to annotate your PersonForm with some Spring annotation like #Component. Or maybe better try to use annotation from vaadin-spring #SpringView.

Resources