Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I have Java Application that changes a game board (2D). Now I want to have a JavaFx GUI to visualize the board.
Main:
package example;
import example.common.MyService;
import example.gui.GUI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan({"example"})
public class Main implements CommandLineRunner {
#Autowired
MyService myService;
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
GUI.launchApp(GUI.class, args);
}
#Override
public void run(String... args) throws Exception {
System.out.println("gameloop or something");
System.out.println(myService.getSomething());
}
}
AbstractJavaFxApplicationSupport:
package example.gui;
import javafx.application.Application;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
#Component
public abstract class AbstractJavaFxApplicationSupport extends Application {
private static String[] savedArgs;
static ConfigurableApplicationContext applicationContext;
#Override
public void init() throws Exception {
super.init();
applicationContext = SpringApplication.run(getClass(), savedArgs);
applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
}
#Override
public void stop() throws Exception {
super.stop();
applicationContext.close();
}
public static void launchApp(Class<? extends AbstractJavaFxApplicationSupport> appClass, String[] args) {
AbstractJavaFxApplicationSupport.savedArgs = args;
Application.launch(appClass, args);
}
}
GUI:
package example.gui;
import example.common.MyService;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class GUI extends AbstractJavaFxApplicationSupport {
#Autowired
private MyService myService;
#Override
public void start(Stage primaryStage) throws Exception {
if (null == myService) {
throw new IllegalStateException("Service was not injected properly");
}
primaryStage.setTitle("Spring with JavaFX");
StackPane root = new StackPane();
root.getChildren().add(new Label("Hello World with " + myService.getSomething()));
Scene scene = new Scene(root);
scene.setFill(Color.TRANSPARENT);
primaryStage.setScene(scene);
primaryStage.show();
}
}
MyService:
package example.common;
import org.springframework.stereotype.Component;
#Component
public class MyService {
public int getSomething() {
return 42;
}
}
Most JavaFx spring boot integrations are like the shown above: They prescribe the GUI as entry point for the application. If I run this example two individual applications are booted (obviously. Because there are two SpringApplication.run calls). If you want a Standalone GUI this is fine but for my usecase it is not.
What I really want is one boot and that they share the same context. How to archive this? I would be grateful if somebody could lead me in the right direction.
You need follow fix for your AbstractJavaFxApplicationSupport:
import javafx.application.Application;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
#Component
public abstract class AbstractJavaFxApplicationSupport extends Application {
static ConfigurableApplicationContext applicationContext;
#Override
public void init() throws Exception {
super.init();
applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
}
#Override
public void stop() throws Exception {
super.stop();
applicationContext.close();
}
public static void launchApp(Class<? extends AbstractJavaFxApplicationSupport> appClass, ConfigurableApplicationContext context, String[] args) {
applicationContext = context;
Application.launch(appClass, args);
}
}
So, for your example thats enough. You just pass context created before.
But at first, I don't think that you need make your application as component of context - I don't know how can you use it. At second, I think you will use fxml for your UI, and for this purposes you can use FxmlLoader. This loader has term Controller which means that this Controller will initialize all components (in terms of JavaFx) in this class. So, for dependency injection for this Controllers you can use method FxmlLoader.setControllerFactory(context::getBean);. But it will work only for this Controller's, not for some views or panels
Related
I have specific instances that I need to use for autowiring in an AnnotationConfigWebApplicationContext. Below is the code that demonstrates the error. My case is much more complicated, of course, where the instances are determined at runtime. I discover the instances at spring configuration time which is why if the below coded worked, it would be fine. I tried but it throws NoSuchBeanDefinitionException even though ctx.getBean is able to find it. What's the right way to accomplish this?
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
class Scratch {
static class MyClass {
}
#Configuration
static class TestConfig {
#Autowired
private MyClass myClass;
}
public static void main(String[] args) {
MyClass requiredInstance = new MyClass();
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.refresh(); // Throws "BeanFactory not initialized..." on next line if this not here
ctx.getBeanFactory().registerSingleton("myClass", requiredInstance);
// Also tried: ((DefaultListableBeanFactory)ctx.getAutowireCapableBeanFactory()).registerSingleton("myClass", requiredInstance);
System.out.println(ctx.getBean(MyClass.class)); // Outputs "Scratch$MyClass#1804f60d"
ctx.register(TestConfig.class);
ctx.refresh(); // Throws "NoSuchBeanDefinitionException: No qualifying bean of type 'Scratch$MyClass' available:"
}
}
Found a solution here https://blog.pchudzik.com/201705/dynamic-beans/ . I had seen BeanFactoryPostProcessor mentioned elsewhere but they didn't show how to create the instance. This example showed using the factory mechanism that is what I needed.
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
class Scratch {
static class MyClass {
}
#Configuration
static class TestConfig {
#Autowired
private MyClass myClass;
}
public static void main(String[] args) {
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(TestConfig.class);
ctx.register(DynamicBeanFactoryProcessor.class);
ctx.refresh();
System.out.println(ctx.getBean("myClass")); // Outputs "Scratch$MyClass#1804f60d"
System.out.println(ctx.getBean(MyClass.class)); // Outputs "Scratch$MyClass#1804f60d"
}
#Configuration
public static class DynamicBeanFactoryProcessor implements BeanFactoryPostProcessor {
public <T> T createInstance(Class<T> c) {
return c.cast(new MyClass()); // Create dynamic instance here
}
#Bean
public DynamicBeanFactoryProcessor dynamicBeanFactoryProcessor() {
return new DynamicBeanFactoryProcessor();
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((BeanDefinitionRegistry)beanFactory).registerBeanDefinition("myClass",
BeanDefinitionBuilder.genericBeanDefinition(MyClass.class)
.setFactoryMethodOnBean("createInstance", "dynamicBeanFactoryProcessor")
.addConstructorArgValue(MyClass.class)
.getBeanDefinition());
}
}
}
I have this ApplicationContextProvider class defined along with the MyApplication.java (entry point where the application is run):
package com.company.my.app;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getContext() {
return applicationContext;
}
}
Have the package restapi with two classes in it (Greeting is just a class to hold data):
package com.company.my.app.restapi;
import com.company.my.app.ApplicationContextProvider;
import io.micrometer.core.instrument.Counter;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
ApplicationContextProvider acp = new ApplicationContextProvider();
ApplicationContext context = acp.getContext();
if (context == null) LOG.info("app context is NULL");
Counter bean = context.getBean(Counter.class);
bean.increment();
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Finally the MyApplication class is:
package com.company.my.app;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.MeterBinder;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
public class MyApplication {
#Bean
public MeterBinder exampleMeterBinder() {
return (meterRegistry) -> Counter.builder("my.counter")
.description("my simple counter")
.register(meterRegistry);
}
#Configuration
public class CounterConfig {
#Bean
public Counter simpleCounter(MeterRegistry registry) {
return registry.counter("my.counter");
}
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
When I run the app and call http://localhost:8081/greeting in my browser, it crashes printing app context is NULL. How do I get the application context? I need it to retrieve the simple counter bean.
tl;dr: You don't need the context; there's a better way.
ApplicationContextAware is an artifact from much older versions of Spring, before many of the now-standard features were available. In modern Spring, if you need the ApplicationContext, just inject it like any other bean. However, you almost certainly shouldn't interact with it directly, especially for getBean, which should be replaced with injecting whatever you were getting.
In general, when you need a Spring bean, you should declare it as a constructor parameter. (If you have multiple constructors, you need to annotate one with #Autowired, but if there's only a single constructor, Spring is smart enough to know to use it.) If you're using Lombok, you can use #Value to automatically write the constructor, and Groovy and Kotlin have similar features.
In the specific case of Micrometer, which you're showing here, it is not conventional to declare individual metrics as beans because they are fine-grained tools intended to apply to specific code paths. (Some services might have 10 separate metrics to track various possible scenarios.) Instead, you inject the MeterRegistry and select the counters or other metrics that you need as part of your constructor. Here, your controller class should look like this. (I've eliminated the duplicate AtomicLong, but you could add it back in as you showed if there's a specific reason you need it.)
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final Counter counter;
public GreetingController(MeterRegistry meterRegistry) {
counter = meterRegistry.counter("my.counter");
}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
counter.increment();
long count = (long) counter.count();
return new Greeting(count, String.format(template, name));
}
}
I am trying to hook into the creation of the context using a custom application listener like this
#Component
public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> {
#Override
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("Context started"); // this never happens
}
}
But the onApplicationEvent method never fires. If I use a different event such as ContextRefreshedEvent then it works just fine, but I need to hook into before it is created. Any advice? Thanks!
[Edit]
Editing answer adding more info because of the downvote.
The reason why you are not getting a callback by the listener is because you are not explicitly calling the LifeCycle start() method (JavaDoc).
This cascades down to your ApplicationContext normally via the AbstractApplicationContext on in Spring Boot case via the ConfigurableApplicationContext.
Example of working code below demonstrating how your callback would work (just explicitly call the start() method)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.stereotype.Component;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
applicationContext.start();
}
#Component
class ContextStartedListener implements ApplicationListener<ContextStartedEvent> {
#Override
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("Context started");
}
}
}
The reason why I suggested below the ContextRefreshedEvent callback instead is because behind the scenes the refresh() code is getting invoked.
If you drill down the SpringApplication#run() method you'll eventually see it.
Again here's a working example of how this would work using the ContextRefreshedEvent:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Component
class ContextStartedListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("Context refreshed");
}
}
}
[Before Edit]
Change the Generic type to ContextRefreshedEvent instead and then it should work.
For more details read this article from the Spring Blog. Just to quote the part about the ContextRefreshedEvent:
[..]This allows MyListener to be notified when the context has refreshed
and one can use that to run arbitrary code when the application
context has fully started.[..]
I'm currently working with SpringBootApplications, I have two differets #SpringBootApplication , one for Web Application, and a CommandLineRunner .
The problem is, no matter which of them I execute, it tries to run both of the applications.
package com.ws;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.Configuration;
#EnableAutoConfiguration
public class Init extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Init.class);
}
/**
* Main method.
*
* #param args String[].
* #throws Exception Exception.
*/
public static void main(String[] args) throws Exception {
SpringApplication.run(Init.class, args);
}
And this is my other InitBatch.java :
package com.batch;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class InitBatch implements CommandLineRunner {
#Autowired
private Batch batch;
#Override
public void run(String... args) throws Exception {
batch.processFiles();
}
public static void main(String[] args) throws Exception {
SpringApplication.run(InitBatch.class, args);
}
}
If I run the CommandLineRunner app, after it is executed, continues to load the Web App.
I need to be able to run each of them separately from each one. But I don't know how to configure this.
Thanks!
the spring docs say:
SpringBootApplication: This is a convenience annotation that is equivalent to declaring #Configuration, #EnableAutoConfiguration and #ComponentScan.
You should only ever add one #EnableAutoConfiguration annotation. We generally recommend that you add it to your primary #Configuration class.
so effectively you're adding 2 EnableAutoConfiguration annotations which just isn't allowed by spring boot. I would suggest using spring Profiles to achieve what you need.
This test is failing but I don't know why or how to fix it. If I hit a break point and call mockingContext.getBean(Repository.class), it does return my mock object, but for some reason the ProductionCode returned in createProductionCodeWithMock still has the real Repository autowired into it. I can only suspect that I need to do something special/extra to have an effect on autowired beans, but I don't know what it is. Why isn't this test passing, and how can I make it pass?
For what it's worth, I'm well aware of all the answers to this question. I still want to know why this test isn't working. I am using Spring 3.x.
Here's the test:
package mavensnapshot;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class ProductionCodeTest {
#Test
public void testReplaceRepositoryWithMock() {
Repository mockRepository = mock(Repository.class);
ProductionCode productionCode = createProductionCodeWithMock(mockRepository);
productionCode.doSomething();
verify(mockRepository).save();
}
private ProductionCode createProductionCodeWithMock(Repository mockRepository) {
GenericApplicationContext mockingContext = new GenericApplicationContext();
mockingContext.getBeanFactory().registerSingleton(Repository.class.getName(), mockRepository);
mockingContext.setParent(new ClassPathXmlApplicationContext("/beans.xml"));
return mockingContext.getBean(ProductionCode.class);
}
}
Here's my production code:
package mavensnapshot;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/beans.xml");
ProductionCode productionCode = context.getBean(ProductionCode.class);
productionCode.doSomething();
}
}
package mavensnapshot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class ProductionCode {
#Autowired
private Repository repository;
public void doSomething() {
repository.save();
}
}
package mavensnapshot;
import org.springframework.stereotype.Service;
#Service
public class Repository {
public void save() {
System.out.println("production code");
}
}