Dependency Injection too late - spring

I am using JavaFX 2 in conjunction with the Spring Framework, however the injection happens to late. My Controller is instanciated by the FXML-Loader, the Spring injection for member variables of this Controller works, but it works only too late, this means that in (1) injection yet didn't happen and in (2) injection did happen:
public class MainController extends AbstractController
{
#Autowired
public StatusBarController statusbarController;
// Implementing Initializable Interface no longer required according to
// http://docs.oracle.com/javafx/2/fxml_get_started/whats_new2.htm:
private void initialize() {
BorderPane borderPane = (BorderPane)getView();
borderPane.setBottom(statusbarController.getView()); // (1) null exception!
}
// Linked to a button in the view
public void sayHello() {
BorderPane borderPane = (BorderPane)getView();
borderPane.setBottom(statusbarController.getView()); // (2) works!
}
}
Any way to let Spring inject statusbarController in an earlier state? I can't let the user have to click a button to load my GUI ;-)
My AppFactory is this:
#Configuration
public class AppFactory
{
#Bean
public MainController mainController() throws IOException
{
return (MainController) loadController("/main.fxml");
}
protected Object loadController(String url) throws IOException
{
InputStream fxmlStream = null;
try
{
fxmlStream = getClass().getResourceAsStream(url);
FXMLLoader loader = new FXMLLoader();
Node view = (Node) loader.load(fxmlStream);
AbstractController controller = (AbstractController) loader.getController();
controller.setView(view);
return controller;
}
finally
{
if (fxmlStream != null)
{
fxmlStream.close();
}
}
}
}

You should set a ControllerFactory on the FXMLLoader so that you are in charge of creating the controller instance.

Related

Injecting spring bean (service layer class) into ResourceBundle

I created a class using ResourceBundle interface as shown below. This class is dependent on another class. The implementation class for ResourceBundle (QuestionnaireSource as shown below) always has null as dependencies. No matter if I use setter or constructor injection.
Could someone please help me with this issue. I am I missing some configuration here.
#Component
public class QuestionnaireSource extends ResourceBundle {
private final QuestionnaireCache questionnaireCache;
private static final Object lockObject = new Object();
#Override
protected Object handleGetObject(String key) {
// Gets an object for the given key from this resource bundle.
// Returns null if this resource bundle does not contain an object for the given key 0
Object value = null;
try {
value = getString(key, LocaleContextHolder.getLocale());
} catch (Exception ignored) {
}
return value;
}
public Questionnaire getString(String key, Locale locale) {
Locale l = safeLocale(locale);
return getResources(l).get(key);
}
private Locale safeLocale(Locale l) {
if (l.getLanguage().equalsIgnoreCase("DE")) {
return Locale.GERMAN;
} else {
return Locale.ENGLISH;
}
}
protected Map<String, Questionnaire> getResources(Locale locale) {
synchronized (lockObject) {
return questionnaireCache.getQuestionnaireCache().get(locale.getLanguage().toUpperCase());
}
}
#Override
public Enumeration<String> getKeys() {
return null;
}
public QuestionnaireSource(QuestionnaireCache questionnaireCache) {
super();
this.questionnaireCache = questionnaireCache;
}
}
Update:
I found that even simple dependency injection in resourceBundle is failing.
UPdate2:
The way I am using in the main class is as follows:
// ResourceBundle test here
System.out.println("Test here for resource bundle");
Locale locale = new Locale("de", "DE");
ResourceBundle bundle = ResourceBundle.getBundle("com.app.util.QuestionnaireSource", locale);
System.out.println(bundle.getString("some.test.string"));
Update3
I am writing a simple example to convey the scenario:
Some service class
#Service
public class SomeServiceTest {
public String testMethod(){
return "test here and complete";
}
}
Some example implementation of resource bundle
#Component
public class MyResourceBundle extends ResourceBundle {
private final SomeServiceTest someServiceTest;
#Autowired
public MyResourceBundle(SomeServiceTest someServiceTest) {
this.someServiceTest = someServiceTest;
}
#Override
protected Object handleGetObject(String key) {
if(key.equals("test"))
return "test";
return null;
}
#Override
public Enumeration<String> getKeys() {
return null;
}
}
Main.java
main(){
// ResourceBundle test here
System.out.println("Test here for resource bundle");
Locale locale = new Locale("de", "DE");
ResourceBundle bundle = ResourceBundle.getBundle("com.app.util.MyResourceBundle", locale);
System.out.println(bundle.getString("test"));
}
Update4:
I changed the annotation on classes as mentioned by on this post https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects
but still I have the null dependency injection for SomeServiceTest class. The changes are as shown below.
SomeServiceTest.java
#Service
public class SomeServiceTest {
public String testMethod(){
return "test here and complete";
}
}
MyResourceBundle.java
#Configurable
public class MyResourceBundle extends ResourceBundle {
#Autowired
private SomeServiceTest someServiceTest;
public MyResourceBundle() {
}
#Override
protected Object handleGetObject(String key) {
if(key.equals("test"))
return someServiceTest.testMethod();
return null;
}
#Override
public Enumeration<String> getKeys() {
return null;
}
}
still SomeServiceTest class is null.
Can you please post an example on how you are using this class? Is it you (your code) or spring who instanciate it (on startup)?
#Component only works for beans which Spring instanciate. If you want to inject stuff in classes you instanciate in you code you can annotate the class with #Configurable.
Please see https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects for some examples.
Make sure you have initialized the spring context
If you are using spring boot
You can get the application context after it starts and use it to get the bean you want
For example
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(YouApplication.class, args);
MyResourceBundle resConfig = run.getBean("myResourceBundle", MyResourceBundle .class);
resConfig.handleGetObject("test");
}
Unfortunately ResourceBundle.getBundle does not initialize the spring application context

Spring Boot and Java Fx

To start my application, I'm avoiding the "implements CommandLineRunner" process to do the setup but I am facing a problem in this line
fxmlLoader.setControllerFactory(springContext::getBean);
where fxmlLoader is an instance of FxmlLoader and springContext ia an instance of ConfigurableApplicationContext. I am facing this error,
"The method setControllerFactory(Callback<Class<?>,Object>) in the
type FXMLLoader is not applicable for the arguments
(springContext::getBean)".
Can anyone help me with the exact syntax? My imported package reports an error as
"The type org.springframework.beans.factory.annotation.Autowire
cannot be resolved. It is indirectly referenced from required .class
files" .
ok, ive understood that this would require a little code, but ive found already built project with a solution similar to what ive proposed - heres example https://github.com/ruslanys/sample-spring-boot-javafx
you just to tie javafx to spring with context.getAutowireCapableBeanFactory().autowireBean(this);
in AbstractJavaFxApplicationSupport.java file
code will look like this
public abstract class AbstractJavaFxApplicationSupport extends Application {
private static String[] savedArgs;
protected ConfigurableApplicationContext context;
#Override
public void init() throws Exception {
context = SpringApplication.run(getClass(), savedArgs);
context.getAutowireCapableBeanFactory().autowireBean(this);
}
#Override
public void stop() throws Exception {
super.stop();
context.close();
}
protected static void launchApp(Class<? extends AbstractJavaFxApplicationSupport> appClass, String[] args) {
AbstractJavaFxApplicationSupport.savedArgs = args;
Application.launch(appClass, args);
}
}
and tie all you view like bean
#Configuration
public class ControllersConfiguration {
#Bean(name = "mainView")
public ViewHolder getMainView() throws IOException {
return loadView("fxml/main.fxml");
}
#Bean
public MainController getMainController() throws IOException {
return (MainController) getMainView().getController();
}
protected ViewHolder loadView(String url) throws IOException {
InputStream fxmlStream = null;
try {
fxmlStream = getClass().getClassLoader().getResourceAsStream(url);
FXMLLoader loader = new FXMLLoader();
loader.load(fxmlStream);
return new ViewHolder(loader.getRoot(), loader.getController());
} finally {
if (fxmlStream != null) {
fxmlStream.close();
}
}
}
public class ViewHolder {
private Parent view;
private Object controller;
public ViewHolder(Parent view, Object controller) {
this.view = view;
this.controller = controller;
}
public Parent getView() {
return view;
}
public void setView(Parent view) {
this.view = view;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
}
}
then in controller you may enjoy spring magic and javafx magic together
public class MainController {
#Autowired private ContactService contactService;
#FXML private TableView<Contact> table;
#FXML private TextField txtName;
#FXML private TextField txtPhone;
#FXML private TextField txtEmail;}
and just start your app like this
#SpringBootApplication
public class Application extends AbstractJavaFxApplicationSupport {
#Value("${ui.title:JavaFX приложение}")//
private String windowTitle;
#Qualifier("mainView")
#Autowired
private ControllersConfiguration.ViewHolder view;
#Override
public void start(Stage stage) throws Exception {
stage.setTitle(windowTitle);
stage.setScene(new Scene(view.getView()));
stage.setResizable(true);
stage.centerOnScreen();
stage.show();
}
public static void main(String[] args) {
launchApp(Application.class, args);
}}

Calling service layer directly from main class

I have a project structure controller -> response builder -> service layer.
Here service layer call the repository layer(database layer).
Everything is ok when follow the structure.
But,for testing i want to call the service layer directly from a java main class.
How can i do this??????
My controller:
#RestController
#RequestMapping("/ishmam")
public class IshmamAddressController {
#Autowired
#Qualifier("ishmamAddressBuilder")
IshmamAddressBuilder ishmamAddressBuilder;
#RequestMapping("/getAddress")
public IResponseDto<WebbCustomerAddressDto> getAllAddress(){
return ishmamAddressBuilder.getAllAddress();
}
}
My builder class is:
#Component("ishmamAddressBuilder")
public class IshmamAddressBuilder {
#Autowired
#Qualifier("ishmamAddressServiceImpl")
IshmamAddressInterface ishmamAddressService;
public IResponseDto<IshmamAddressResponseDto> getAllAddress(){
IResponseDto<WebbCustomerAddressDto> response=new
IResponseDto<WebbCustomerAddressDto>();
try{
//here i call the service layer
List<String> addressList=ishmamAddressService.getAllAddress();
}catch(Exception e){
throw e;
}
return addressList;
}
My service layer is:
#Service("ishmamAddressServiceImpl")
#Transactional
public class IshmamAddressServiceImpl implements IshmamAddressInterface {
#Autowired(required = true)
#Qualifier("webCustomerAddressRepository")
WebCustomerAddressRepository webCustomerAddressRepository;
#Override
public IshmamAddressResponseDto getAllAddress() {
List<WebCustomerAddress> aList = new ArrayList<WebCustomerAddress>();
List<WebbCustomerAddressDto> dtoWebCustomerAddressList = new
ArrayList<WebbCustomerAddressDto>();
IshmamAddressResponseDto ishmamAddressResponseDto=new
IshmamAddressResponseDto();
try{
aList =
address.getAllAddress(1);//Calling database layer
ishmamAddressResponseDto=//Doing something,not important for
//question
}
return ishmamAddressResponseDto;
}
Now what i want is to call the service layer directly from the main class:
public class Address{
public void getAddress(){
IshmamAddressServiceImpl i=new IshmamAddressServiceImpl();
List<String> list=i.getAllAddress();
}
public static void main(String[] args){
Address a=new Address();
a.getAddress();
}
}
This process is not working.How can i do this???????
As soon as you use spring, you must never use new to build an object that should be managed by spring.
So you could:
either do it manually, that means bootstrap an application context with the same intialization that it would have in your application and explicitely get a bean from it
ApplicationContext ctx = new AnnotationConfigApplicationContext(
Class<?>... annotatedClasses); // if it is the way you use it ...
IshmamAddressInterface i = ctx.getBean("ishmamAddressServiceImpl",
IshmamAddressInterface .class);
or use the Spring test framework that does it automatically for you in Junit tests
#WebAppConfiguration
#ContextConfiguration(classes = ...)
public class WebIntegrationTests {
#Autowired
#Qualifier("ishmamAddressServiceImpl")
IshmamAddressInterface ishmamAddressService;
...
#Test
public void testGetAddress() {
ishmamAddressService.getAddress();
...
}
}

Vaadin Dashboard demo using Spring Boot

I'm trying to put together a simple application using Vaadin + Spring Boot with just two views: login and dashboard. I'm using pieces of code from the Vaadin Dashboard demo. There is this method in the DashboardUI class:
private void updateContent() {
User user = (User) VaadinSession.getCurrent().getAttribute(
User.class.getName());
if (user != null && "admin".equals(user.getRole())) {
// Authenticated user
setContent(new MainView());
removeStyleName("loginview");
getNavigator().navigateTo(getNavigator().getState());
} else {
setContent(new LoginView());
addStyleName("loginview");
}
}
As you see the views are instantiated via the new keyword. Since I'm using Spring I would like the container take care of that. But I cannot figure out how to have the views injected by Spring.
Annotate your view with #VaadinUIScope and #VaadinComponent. And then you can use #Autowired to inject your view.
You should probably setup two UI classes i.e LoginUI.class and MainUI.class. Have Spring Security redirect unauthorized to /login and authorized to /
LoginUI.class
#Theme("valo")
#SpringUI(path = "login")
#PreserveOnRefresh
public class LoginUI extends UI {
private SpringViewProvider viewProvider;
#Autowired
public LoginUI(SpringViewProvider viewProvider) {
this.viewProvider = viewProvider;
}
#Override
protected void init(VaadinRequest request) {
Navigator navigator = new Navigator(this, this);
navigator.addProvider(viewProvider);
navigator.navigateTo(LoginView.VIEW_NAME);
}
}
MainAppUI.class
#Theme("valo")
#SpringUI
#PreserveOnRefresh
public class MainAppUI extends UI {
private SpringViewProvider viewProvider;
#Autowired
public AppUI(SpringViewProvider viewProvider) {
this.viewProvider = viewProvider;
}
#Override
protected void init(VaadinRequest request) {
getPage().setTitle("Main App");
setSizeFull();
Navigator navigator = new Navigator(this, viewDisplay);
navigator.addProvider(viewProvider);
setContent();//viewport
navigator.navigateTo(DashboardView.VIEW_NAME);
}
}
Then just use #SpringView(name = "moduleOne", ui = MainAppUI.class) on your module views for your app as suggested by Morfic to have the main navigator and module views registered only when a user has logged in to save on resources.
Note: Taking a look at that example it seems that they're not actually using views & navigator, they're somewhat faking them. If you want to proceed on the same path you can simply autowire the fake views in the UI instead of using the navigator as below.
The Vaadin-spring boot wiki offers an introduction to this by the use of:
1) #SpringUI with autowired SpringViewProvider
#Theme("valo")
#SpringUI
public class MyVaadinUI extends UI {
// we can use either constructor autowiring or field autowiring
#Autowired
private SpringViewProvider viewProvider;
#Override
protected void init(VaadinRequest request) {
// other init stuff
Navigator navigator = new Navigator(this, viewContainer);
navigator.addProvider(viewProvider);
}
}
2) #SpringView
#SpringView(name = DefaultView.VIEW_NAME)
public class DefaultView extends VerticalLayout implements View {
public static final String VIEW_NAME = "";
#PostConstruct
void init() {
addComponent(new Label("This is the default view"));
}
#Override
public void enter(ViewChangeEvent event) {
// the view is constructed in the init() method()
}
}
For the decision whether the user should be redirected to the login view, or the other ones, I usually use a ViewChangeListener, something along the lines of:
navigator.addViewChangeListener(new ViewChangeListener() {
#Override
public boolean beforeViewChange(ViewChangeEvent event) {
if (VaadinSession.getCurrent().getAttribute("user") == null) {
navigator.getDisplay().showView(loginView);
return false;
} else {
return true;
}
}
#Override
public void afterViewChange(ViewChangeEvent event) {
// meh
}
});

How to set Spring application context through setter or constructor in another class

I have a Spring class.
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager {
GenericXmlApplicationContext context;
#PersistenceContext
private EntityManager em;
public DatabaseManager(GenericXmlApplicationContext context) {
this.context = context;
}
....
} //end of class DatabaseManager
I have SpringUtil class
public class SpringUtil {
public static GenericXmlApplicationContext loadSpringContext(String springXmlFile) {
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
context.load(springXmlFile);
context.refresh();
return context;
} //end of loadSpringContext()
} //end of class SpringUtil
Now in main i am using some thing like
public class Regulator {
public static void main( String[] args ) {
Test test = new Test;
test.start();
} //end of main()
} //end of class Regulator
Here is test class
public class Test {
public void start() {
String springXmlFile = "classpath:spring/plcb-app-context-xml.xml";
GenericXmlApplicationContext context = SpringUtil.loadSpringContext(springXmlFile);
} //end of reportStudent()
} //end of class Test
But i am getting error that
Could not instantiate bean class [...DatabaseManager]: No default constructor
found; nested exception is java.lang.NoSuchMethodException:
...DatabaseManager.<init>()
I want that when DatabaseManager class created then spring context taht i am creating using SpringUtil.loadSpringContext(springXmlFile) must pass to it. How can i do it ?
Thanks
Edit
-------------------
public void switchDataSource(DatabaseType databaseType) {
DriverManagerDataSource dataSource = null;
if (databaseType == DatabaseType.LEGACY) {
dataSource = (DriverManagerDataSource)context.getBean("myLegacyDataSource");
} else if (databaseType == DatabaseType.LS360) {
dataSource = (DriverManagerDataSource)context.getBean("myLs360DataSource");
}
LocalContainerEntityManagerFactoryBean emf = context.getBean("myEmf", LocalContainerEntityManagerFactoryBean.class);
emf.setDataSource(dataSource);
}
#SuppressWarnings("unchecked")
#Transactional(readOnly=true)
public List<Object> getResultList(String query, Class mappingClass) throws Exception {
Query emQuery = em.createNativeQuery(query, mappingClass);
return emQuery.getResultList();
} //end of findTraineeFromLegacy()
Actually i have these two methods in my DatabaseManager class. I am setting context so i can get bean from the context in switchDataSource() method.
One thing that i can do is remove instance filed and change the method to
public void switchDataSource(DatabaseType databaseType, GenericXmlApplicationContext context) {
....
}
This is why i am doing this ?
Thanks
Have a no-arg constructor for DatabaseManager.
Implements ApplicationContextAware in DatabaseManager. Spring will know this bean needs to be notified of the application context:
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager implements ApplicationContextAware {
private ApplicationContext context;
public DatabaseManager() {...}
#Override
public void setApplicationContext(ApplicationContext appContext) {
this.context = appContext;
}
} //end of class DatabaseManager
however, double think if you really need that injected. In most case you are doing something wrong.
Update:
For your requirement in your update, which you want your DB Manager to switch datasource base on input type, although it doesn't seems very normal doing such thing, you can simply have your DB Manager injected with a Map and do whatever you want, instead of injecting the app context.
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager implements ApplicationContextAware {
#Resource("&emfBean")
private LocalContainerEntityManagerFactoryBean emfBean;
#Resource("dbManagerDsMap")
private Map<DatabaseType, Datasource> dsMapping;
public DatabaseManager() {...}
public void switchDataSource(DatabaseType databaseType) {
emfBean.setDatasource(dsMapping.get(databaseType));
}
} //end of class DatabaseManager
However I strongly suggest you not doing such thing. Consider having individual entityManagerFactory for each DB you are connecting to, and use the correct emf to connect to DB, instead doing this weird switching logic. I believe it is not supposed to be changed after your application start.

Resources