I'm trying to integrate Spring in a standalone Swing application.
The Swing application asks for login details at start-up, which should then be used to create a singleton DataSource Bean.
However I can't come up with a way to pass those login info (as Java object) to the Spring ApplicationContext during initialization (which would then be passed down to the #Bean producer method).
Any ideas?
Possible solution:
#SpringBootApplication
public class DemoSwingApplication {
public static void main(final String[] args) {
...
final var loginInfo = buildLoginInfo();
try (final var context = new AnnotationConfigApplicationContext()) {
context.getBeanFactory().registerSingleton("loginInfo", loginInfo);
context.register(DemoSwingApplication.class);
context.refresh();
}
}
}
There are multiple ways in which you can do this,
Using BeanDefinitionRegistryPostProcessor - Create a bean which will implement BeanDefinitionRegistryPostProcessor and then store the BeanDefinitionRegistry instance and dynamically register your bean.
#Component
public class DbConfigurer implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
private BeanDefinitionRegistry beanDefinitionRegistry;
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
public void registerDataSourceBean() {
beanDefinitionRegistry.registerBeanDefinition("dataSource", new RootBeanDefinition(DataSource.class,
BeanDefinition.SCOPE_SINGLETON, yourDataSourceBeanSupplier));
}
}
Using BeanFactoryAware - This is similar to implementation that you provided but by implementing BeanFactoryAware interface but downside of this is to check for BeanFactory instance -
#Component
public class DbConfigurer implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory; // Need to cast
}
}
And then in your UI component, inject this And and register bean when config properties are available -
#Component
public class MainWindow extends JFrame {
private final DbConfigurer dbConfigurer;
// register bean once user provides config properties
}
and start your application using headless mode disabled -
#SpringBootApplication
public class DesktopApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DesktopApplication.class).headless(false).run(args);
}
}
Related
I cannot get the bean by adding #Component on it.
I have created a class as below for getting bean in cases where instances are not injected by #autowired.
#Component
public class SpringApplicationContext implements ApplicationContextAware{
private static ApplicationContext CONTEXT;
public static Object getBean(String beanName) {
return CONTEXT.getBean(beanName);
}
#Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
CONTEXT = context;
}
}
Then I created a AppProperties class for reading the decrypt token from application.properties.
#Component
public class AppProperties {
#Autowired
private Environment env;
public String getTokenSecret() {
return env.getProperty("tokenSecret");
}
}
Then, I try to get the AppProperties instance as bean like this, which SecurityConstants.getTokenSecret() are used as parameter by manually inject in another method like this:
public class SecurityConstants {
...
public static String getTokenSecret() {
//Fail to get bean in this line
AppProperties appProperties = (AppProperties)SpringApplicationContext.getBean("AppProperties");
return appProperties.getTokenSecret();
}
}
but fail with below exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'AppProperties' available
After that, I add getAppProperties() with #Bean to make it register as a bean and it worked.
#SpringBootApplication
public class RegAppApplication {
public static void main(String[] args) {
SpringApplication.run(RegAppApplication.class, args);
}
#Bean(name="AppProperties")
public AppProperties getAppProperties() {
return new AppProperties();
}
}
The question is:
Why SpringApplicationContext is run on application start and AppProperties does not?
Why cannot get AppProperties as bean? Isn't adding #Component above the class will make it a component scan target, thus being treated as a bean?
How adding getAppProperties() with #Bean making it different with adding #Component above the class?
Thank you.
I have a custom spring-boot starter project which is used by rest controller, the auto configuration class is used for creating several(according to config value in application.yml) Storage Context instance as spring singleton, so I have to create them dynamically in setBeanFactory method of BeanFactoryAware by :
#Import(StorageContextProperties.class)
public class StorageAutoConfiguration implements BeanFactoryAware {
......
#Override
public void setBeanFactory(BeanFactory factory) throws BeansException {
......
StorageContextProperties config = beanFactory.getBean(StorageContextProperties.class);
config.getProfiles().stream().forEach(p -> {
......
((SingletonBeanRegistry) beanFactory).registerSingleton(profile.name, instance);
the problem is that the method is not been called before the controller #autowired event, so it will complain there is no StorageContext instance, I have also tried BeanFactoryPostProcessor and InitializingBean interface, neither of them works.
But, I notice if I just add some special #Bean method into the auto config class, let's say :
#Import(StorageContextProperties.class)
public class StorageAutoConfiguration implements BeanFactoryAware{
#Bean
public MethodValidationPostProcessor validationPostProcessor2() {
return new MethodValidationPostProcessor();
}
......
#Override
public void setBeanFactory(BeanFactory factory) throws BeansException {
then the setBeanFactory() will be called before the controller, it seems that spring needs MethodValidationPostProcessor instance, so it created shared instance of singleton bean: StorageAutoConfiguration, also create StorageContextProperties instance and call setBeanFactory().
the code works if I add above #Bean method. well things also goes well in this way, but I don't like the style since I actually have no need for MethodValidationPostProcessor.
is there any elegant(without #Bean method) way to achieve it?
my requirements are :
before the controller creating event.
1 create StorageContextProperties ( it's a #ConfigurationProperties class by #Import)
2 some callback I can call registerSingleton() to create my Storage Context instances
Thanks!
[UPDATED1]
I still don't find any way to make it works, but I changed the code as :
#Configuration
#Import(StorageContextProperties.class)
#ConditionalOnProperty(prefix = "avalon.thiton.storage.config", name = "masterKey")
public class StorageAutoConfiguration implements BeanFactoryAware {
private static Logger log = LoggerFactory.getLogger(StorageAutoConfiguration.class);
#Bean
#Order(Ordered.LOWEST_PRECEDENCE)
#ConditionalOnMissingBean(MethodValidationPostProcessor.class)
public MethodValidationPostProcessor placeholder() {
return new MethodValidationPostProcessor();
}
......
It makes me feel..... better.
Hi I've had similar problem when I was trying to initialize custom beans using BeanFactoryAware interface - The #Configuration annotated class was like:
#Configuration
#EnableConfigurationProperties(SomeProps.class)
class MyConfiguration implements BeanFactoryAware {
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
...
...
}
#Bean
MyBean myBean(){
return new MyBean();
}
}
It did not run this configuration class code before MyBean was needed and NoSuchBeanDefinitionException were thrown.
To fix this I've changed BeanFactoryAware interface to extending AbstractBeanFactoryAwareAdvisingPostProcessor like that:
#Configuration
#EnableConfigurationProperties(SomeProps.class)
class MyConfiguration extends AbstractBeanFactoryAwareAdvisingPostProcessor {
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
...
...
}
#Bean
MyBean myBean(){
return new MyBean();
}
}
I have spring boot, hibernate application and android application for client side. Also I am using java.net.Socket api for socket connection.
Before I was creating server socket like this new Server(12346); and everything was good enough. But now I need access to database from socket class e.g. with #Autowired UsersDao field, but of course it is null because Socket class is not visible by Spring Framework.
So how do I make dependency injection on Socket class using port as constructor argument and make UserDao non-null?
You can access the Spring Application Context from static method and use this static method to load your repository bean in your Server class instead of autowiring it.
You need to create the following classes (found here):
ApplicationContextProvider
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
}
SpringConfiguration
#Configuration
public class SpringConfiguration {
#Bean
public static ApplicationContextProvider contextProvider() {
return new ApplicationContextProvider();
}
}
And then your non-Spring managed Server class:
public class Server {
//your code
public void doUsersDaoStuff() {
UsersDao usersDao = (UsersDao) SpringConfiguration.contextProvider().getApplicationContext().getBean("UsersDao");
// Do your own stuff with UsersDao here...
}
}
For the past 2 weeks things have been going great in my application. Last night I login remotely to work to find out that when I run my application my ApplicationContextProvider class no longer has knowledge of the Application Context. I've run Maven clean & build in addition to rebooting my PC. Can't seem to shake it...
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext (ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
My Main class:
public static void main(String[] args) throws IOException {
System.setProperty("java.util.logging.SimpleFormatter.format", "%4$s: %5$s%n");
final HttpServer server = HttpServer.createSimpleServer(".", 80);
WebappContext ctx = new WebappContext("ProductionQueue", "/");
//enable annotation configuration
ctx.addContextInitParameter("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
ctx.addContextInitParameter("contextConfigLocation", "com.production");
//allow spring to do all of it's stuff
ctx.addListener("org.springframework.web.context.ContextLoaderListener");
....
ctx.deploy(server);
server.start();
//start the production process
Production.init();
System.in.read();
server.stop();
My Production class:
public class Production {
private static final Logger logger = Logger.getLogger(Production.class.getName());
/* A list of active workflows */
private static List<Workflow> workflowList = new ArrayList<Workflow>();
private static ProductionService productionService;
/**
* Initialize the production line
*/
public static void init() {
logger.info("Initializing production workflows...");
ApplicationContext context = ApplicationContextProvider.getApplicationContext(); //THIS IS NULL
productionService = (ProductionService) context.getBean("productionService");
No configuration has been modified at all. Within my config class I do have a bean for it...
#Configuration
#ComponentScan(basePackages = {
"com.production"
})
#PropertySource(value= {
"classpath:/application.properties",
"classpath:/environment-${FETTER_ENVIRONMENT}.properties"
})
#EnableJpaRepositories("com.production.repository")
#EnableTransactionManagement
public class Config {
#Value("${db.url}")
String PROPERTY_DATABASE_URL;
#Value("${db.user}")
String PROPERTY_DATABASE_USER;
#Value("${db.password}")
String PROPERTY_DATABASE_PASSWORD;
#Value("${persistenceUnit.default}")
String PROPERTY_DEFAULT_PERSISTENCE_UNIT;
#Value("${hibernate.dialect}")
String PROPERTY_HIBERNATE_DIALECT;
#Value("${hibernate.format_sql}")
String PROPERTY_HIBERNATE_FORMAT_SQL;
#Value("${hibernate.show_sql}")
String PROPERTY_HIBERNATE_SHOW_SQL;
#Value("${entitymanager.packages.to.scan}")
String PROPERTY_ENTITYMANAGER_PACKAGES_TO_SCAN;
#Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
I'd say its mudsoup between the try to have it static and use it as a bean.
You creating a new instance of the ApplicationContextProvider as a spring bean. This is ApplicationContextAware and so gets the AC injected. But THEN you do not use said bean, you use its static getter to read the field, yet this, static thing never received the AC in the first place. You're never using your actual bean.
I'd say scratch that provider completly, and rely soley on the ApplicationContextAware interface, it does what you want, ie it was designed to do exactly that, why use a delegating bean?
I do not know if
#Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
the ApplicationContextAware interface.
Try to add #Component at ApplicationContextProvider class and then remove the #Bean. I hope that the ApplicationContextAware` interface is taken in account if this class is found by your normal component scan.
Turns out there was a buried exception my logging was preventing me from getting access to. Thanks for help.
I am trying to find a better what to do this. In Spring a lot of my classes need to load beans (objects) from XmlBeanFactory. So I put the following line into most of my classes
private static XmlBeanFactory beanFactory = new XmlBeanFactory(
new ClassPathResource("config.xml"));
Does anyone know of a better what for me to do this so I don't have to have this in most of my classes?
You can make your class implement BeanFactoryAware that will give you instance of the bean factory, so you could call one of BeanFactory.getBean(..) methods directly.
public class MyFactoryBean implements BeanFactoryAware {
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void someMethod() {
MyBean myBean = beanFactory.getBean("myBean", MyBean.class);
...
}
}