I'm new to Spring Reactor. I've been trying to understand how the ConnectableFlux class works. I've read the docs and seen examples posted online but am stuck on an issue.
Can someone tell me why the connect() method is blocking? I don't see anything in the documentation that says it should block..especially since it returns a Disposable for later use.
Given my example code below, I never get past the connect() method.
I'm trying to basically simulate the old style Listener interface paradigm I've used many times in the past. I want to learn how to recreate a Service class & Listener architecture using Reactive streams. Where I have a simple Service class and it has a method called "addUpdateListener(Listener l)" and then when my service class "doStuff()" method it triggers some events to be passed to any listeners.
I should say that I will be writing an API for others to use, so when I say Service class I don't mean the #Service in Spring terms. It will be a plain java singleton class.
I'm just using Spring Reactor for the Reactive Streams. I was also looking at RxJava.. but wanted to see if Spring Reactor Core would work.
I was starting with a test class below just to understand the library syntax and then got stuck on the blocking issue.
I think what I'm looking for is described here: Multiple Subscribers
UPDATE: Running my code through a debugger, the code inside ConnectableFlux connect method, never returns. It hangs on the internal connect method and never returns from that method.
reactor.core.publisher.ConnectableFlux
public final Disposable connect() {
Disposable[] out = new Disposable[]{null};
this.connect((r) -> {
out[0] = r;
});
return out[0];
}
Any help would be great!
Here is my maven pom as well
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SpringReactorTest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>Bismuth-RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>classworlds:classworlds</exclude>
<exclude>junit:junit</exclude>
<exclude>jmock:*</exclude>
<exclude>*:xml-apis</exclude>
<exclude>org.apache.maven:lib:tests</exclude>
<exclude>log4j:log4j:jar:</exclude>
</excludes>
</artifactSet>
<minimizeJar>true</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Disposable;
import reactor.core.publisher.ConnectableFlux;
import reactor.core.publisher.Flux;
import java.util.concurrent.TimeUnit;
import static java.time.Duration.ofSeconds;
/**
* Testing ConnectableFlux
*/
public class Main {
private final static Logger LOG = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) throws InterruptedException {
Main m = new Main();
// Get the connectable
ConnectableFlux<Object> flux = m.fluxPrintTime();
// Subscribe some listeners
// Tried using a new thread for the subscribers, but the connect call still blocks
LOG.info("Subscribing");
Disposable disposable = flux.subscribe(e -> LOG.info("Fast 1 - {}", e));
Disposable disposable2 = flux.subscribe(e -> LOG.info("Fast 2 - {}", e));
LOG.info("Connecting...");
Disposable connect = flux.connect();// WHY does this block??
LOG.info("Connected..");
// Sleep 5 seconds
TimeUnit.SECONDS.sleep(5);
// Cleanup - Remove listeners
LOG.info("Disposing");
connect.dispose();
disposable.dispose();
disposable2.dispose();
LOG.info("Disposed called");
}
// Just create a test flux
public ConnectableFlux<Object> fluxPrintTime() {
return Flux.create(fluxSink -> {
while (true) {
fluxSink.next(System.currentTimeMillis());
}
}).doOnSubscribe(ignore -> LOG.info("Connecting to source"))
.sample(ofSeconds(2))
.publish();
}
}
Running the above code gives the following output.. it just prints the time in milliseconds until I Ctrl-C the process..
09:36:21.463 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework
09:36:21.478 [main] INFO Main - Subscribing
09:36:21.481 [main] INFO Main - Connecting...
09:36:21.490 [main] INFO Main - Connecting to source
09:36:23.492 [parallel-1] INFO Main - Fast 1 - 1589808983492
09:36:23.493 [parallel-1] INFO Main - Fast 2 - 1589808983492
09:36:25.493 [parallel-1] INFO Main - Fast 1 - 1589808985493
09:36:25.493 [parallel-1] INFO Main - Fast 2 - 1589808985493
09:36:27.490 [parallel-1] INFO Main - Fast 1 - 1589808987490
09:36:27.490 [parallel-1] INFO Main - Fast 2 - 1589808987490
09:36:29.493 [parallel-1] INFO Main - Fast 1 - 1589808989493
...
I received an answer from the Spring Reactor team and I'm just posting it here in case anyone else runs into this...
The crux of the issue is that you're entering an infinite loop in
Flux.create. The moment the flux gets subscribed, it will enter the
loop and never exit it, producing data as fast as the CPU can. With
Flux.create you should at least have a call to sink.complete() at some
point.
I suggest to experiment with eg. Flux.interval as a source for your
regular ticks, it will get rid of that extraneous complexity of
Flux.create, which puts you in charge of lower level concepts of
Reactive Streams (the onNext/onComplete/onError signals, that you'll
need to learn about, but maybe not just right now 😄 ).
As a side note, I would take into consideration that emulating a
listener-based API with Reactor (or RxJava) is not doing justice to
what reactive programming can do. It is a constrained use case that
will probably drive your focus and expectations away from the real
benefits of reactive programming
From a higher perspective:
The broad idea of ConnectableFlux#connect() is that you have a
"transient" source that you want to share between multiple
subscribers, but it gets triggered the moment someone subscribes to
it. So in order not to miss any event, you turn the source into a
ConnectableFlux, perform some set up (subscribe several subscribers)
and manually trigger the source (by calling connect()). It is not
blocking, and returns a Disposable` that represents the upstream
connection (in case you also want to manually cancel/dispose the whole
subscription).
PS: Bismuth is now clearly outdated, prefer using the latest
Dysprosium release train
Related
I'm running into an error running the native image produced by GraalVM (latest GraalVM built on JDK 11) via the Gluon Client Plugin.
javafx.fxml.LoadException: Error resolving onAction='#loginAction', either the event handler is not in the Namespace or there is an error in the script. fxml/LoginScreen.fxml:17
The compililation step works fine:
mvn clean client:build
I see the binary in a folder called "projectname/target/client/x86_64-linux/binaryname"
The above error produced when I run the executable via "./binaryname"
The FXML line of code which its complaining about on line 17 is:
<Button fx:id="_loginButton" layoutX="516.0" layoutY="174.0" mnemonicParsing="false" onAction="#loginAction" prefHeight="28.0" prefWidth="94.0" text="Login" />
The backing code logic are as follows and marked with #FXML:
#FXML
void loginAction(ActionEvent event) throws InterruptedException {
LoginService loginservice = new LoginService(_usernameTextField.getText(), _passwordTextField.getText());
According to a JavaFX common errors list the problem is usually because the onAction event does not have the same name as specified in the controller - Introduction to JavaFX
for Beginner Programmers - Pg 27 . However this is not the case, my program's naming is accurate. Using the JavaFX maven plugin (seperate from the GluonClient) using
maven javafx:run
the program starts up correctly and works as expected. If I need to post more information, please let me know.
Here is my pom.xml (I only replaced only the name of my package below)
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>com-demo-management-ui</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<client.plugin.version>0.1.26</client.plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.3</version>
<configuration>
<mainClass>com.demo.Launcher</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.gluonhq</groupId>
<artifactId>client-maven-plugin</artifactId>
<version>${client.plugin.version}</version>
<configuration>
<!-- Uncomment to run on iOS: -->
<!-- <target>ios</target> -->
<mainClass>com.demo.Launcher</mainClass>
<graalvmHome>/opt/graalvm-ce-java11-20.2.0-dev/</graalvmHome>
</configuration>
</plugin>
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>gluon-releases</id>
<url>http://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
</pluginRepository>
</pluginRepositories>
And finally, here is the code where I set the controller (This is a method call, which I exchange my views as I need them, thus controller is passed as an argument in the creating a view):
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/"+baseController.getFxmlFile()));
fxmlLoader.setController(baseController);
Parent parent = fxmlLoader.load();
Scene scene = new Scene(parent);
Stage stage = new Stage();
stage.setFullScreen(fullScreen);
stage.setMaximized(setMaximized);
stage.setScene(scene);
stage.show();
If you have a look at HelloFXML sample in the Client samples repository, you will see it uses the typical FXML file with a controller:
<AnchorPane fx:id="pane" ... fx:controller="hellofx.HelloController">
In your case, you don't have the controller in the FXML file, but you provide it like:
fxmlLoader.setController(new hellofx.HelloController());
As you know, the FXMLLoader uses reflection to instantiate controller, controls and methods that are found while parsing the FXML file.
Either way, when you click the button that triggers the loginAction method, the FXMLLoader process that with this call:
MethodHelper.invoke(method, controller, params);
that uses reflection to handle such event.
With GraalVM, reflection is an issue, and you have to "help" it a little bit, by providing the classes/methods/fields that are going to be used reflectively at some point. Find more about it here.
The Client plugin already takes care for you of adding the JavaFX core classes and methods. You can see what's added in target/client/x86_64-darwin/gvm/reflectionconfig-x86_64-darwin.json.
However, your custom classes have to be added to that file. There are two ways you can do that:
As in HelloFXML, via configuration/reflectionList, you will provide the custom class/es that will be used reflectively:
<plugin>
<groupId>com.gluonhq</groupId>
<artifactId>client-maven-plugin</artifactId>
<version>${client.plugin.version}</version>
<configuration>
<reflectionList>
<list>hellofx.HelloController</list> <!-- your custom classes -->
</reflectionList>
<mainClass>${mainClassName}</mainClass>
</configuration>
</plugin>
This has the effect of opening all class methods/fields to reflection. You will see the result in the json file as:
{
"name" : "hellofx.HelloController",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredFields" : true,
"allPublicFields" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true
}
...
This should be enough to fix your issue.
Via config files. As you can read in the Client's documentation, you can add a config file (reflectionconfig.json) to META-INF/substrate/config instead:
[
{
"name":"hellofx.HelloController",
"methods":[{"name":"loginAction","parameterTypes":["javafx.event.ActionEvent"] }]
}
]
This will fix it as well. Of course, it might require adding other methods you have in the controller too (like initialize).
This will open to reflection only this method, so it has a lower impact in memory footprint, and follows what the plugin does with the JavaFX core classes.
Using Spring Cloud Contract 2.1.3.RELEASE with spring-boot 2.1.1.RELEASE, I have added the dependency and plugin per explained in the guide:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<version>${spring-cloud-contract.version}</version>
<scope>test</scope>
</dependency>
and
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
</plugin>
I have also added under: $rootDir/src/test/resources/contracts:
Groovy file:
package contracts
import org.springframework.cloud.contract.spec.Contract
Contract.make {
name("contract_updateNodeV4")
request {
method 'PUT'
url '/v4/nodes'
headers {
header 'Content-Type': 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json'
}
body(file("updateNodeV4_request.json"))
}
response {
status OK()
body(file("updateNodeV4_response.json"))
}
}
And corresponding updateNodeV4_request.json and updateNodeV4_response.json (omitting their contents since these are large) valid JSON files.
When running mvn clean install I expected generated tests to be created (and fail for now) per the guide.
Instead I am getting the following error:
[ERROR] Failed to execute goal org.springframework.cloud:spring-cloud-contract-maven-plugin:1.0.0.RELEASE:generateStubs (default-generateStubs) on project xxx: Stubs could not be found: [C:\Users\xxx\git\xxx\target\stubs] .
[ERROR] Please make sure that spring-cloud-contract:convert was invoked
Most likely your contacts are not under the module's src/test/resources/contracts but under the root module's folder. If that's the case you need to tell the plugin that by seeing the contracts dir plugin property
I solved it by moving the plugin:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
</plugin>
From the root pom.xml to the specific module's pom.xml in which I have created the contracts in. Now it works as expected.
I have a problem with spring embedded kafka
which I want to use to test my kafka sender/receiver.
When I try to run my tests using:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#DirtiesContext
public class myTestClass {
#ClassRule
public static EmbeddedKafkaRule embeddedKafka =
new EmbeddedKafkaRule(1, true, RECEIVER_TOPIC);
#Test public void test() {
System.out.println("#Test");
}
}
I get an error:
java.io.IOException: Failed to load C:\Users\username\AppData\Local\Temp\kafka-8251150311475880576 during broker startup
...
15:29:33.135 [main] ERROR kafka.log.LogManager - Shutdown broker because none of the specified log dirs from C:\Users\username\AppData\Local\Temp\kafka-8251150311475880576 can be created or validated
Im sure that as a user I have access to this directory and when I'm running tests I can see that kafka is creating this kind of diretories (empty) on temp folder
but still not working.
This is my pom configuration:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<version>2.2.0.RC1</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.2.0.RC1</version>
<scope>test</scope>
</dependency>
appliacation.properties:
kafka.bootstrap-servers= ${spring.embedded.kafka.brokers}
The interesting thing is that I have an example of kafka embedded used for testing purposes cloned from the internet and it just works fine
but when I apply it to my project it just crushes as above.
Any suggestions what am I doing wrong?
I am building an application where Rest endpoints uses, boot bindings to send Neo4j Entitites to Neo4j Repo, and thats the pattern we repeated few times across jpa backed repositories. In current configuration;
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
JPA repositories and neo4j repositories works ok, until i add;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Erros i am facing are;
APPLICATION FAILED TO START
Description:
Constructor in org.springframework.data.neo4j.repository.config.Neo4jOgmEntityInstantiatorConfigurationBean required a single bean, but 2 were found:
- mvcConversionService: defined by method 'mvcConversionService' in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
- integrationConversionService: defined in null
:: Spring Boot :: (v2.1.0.M2)
:: Java 8
Updating project dependencies to
<spring.boot.version>2.1.0.RELEASE</spring.boot.version>
<spring.cloud.version>2.1.0.RC3</spring.cloud.version>
has fixed the issue as per #meistermeier suggestion.
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 1 year ago.
Improve this question
I need to do a junit testing for my spring application and it is done with mongo database.
I have no previous experience in embedding the junit testing with spring and mongodb.
Any reply, will great helpful to me...
Thanks with Regards.
I would start by looking at the JUnit documentation, specifically, I would start with Assertions. When testing with a dependency injection framework (e.g. Spring) a mocking framework is essential. Check out EasyMock or Mockito.
I use Spring mongo template in a Spring MVC app and JUnit 4.8.2 for unit tests.
Just create a bean for your mongoTemplate and use Autowired to inject it in your classes. As for the tests, follow these steps:
1.Create a new JUnit test case (right-click on your class in the Package Explorer, then new->JUnit Test Case). It will create a test method for each method of your class you specify.
2.Now you'll have your tests at src/test/java and yor resources at src/test/resources. It is better if you create a spring config file just for the tests so you can point the tests to a local mongodb instance and your application to perhaps a Development mongoDB instance. So create a the config file at src/test/resources and name it testSpringConfig.xml or whatever and create the bean there:
<mongo:db-factory dbname="myDB" host="localhost"
username="myDbUser" password="myPass"/>
<beans:bean id="mongoTemplate"
class="org.springframework.data.mongodb.core.MongoTemplate">
<beans:constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</beans:bean>
<beans:bean id="mongoTemplateLibrary"
class="org.springframework.data.mongodb.core.MongoTemplate">
<beans:constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</beans:bean>
3.In your test class, use annotations to reference your config file:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"/testSpringConfig.xml"})
public class MyDaoTest {
4.Once you have your test class set up, inject the mongo template
#Autowired
#Qualifier("mongoTemplate")
private MongoTemplate mongoTemplate;
5.Now yo can use it to insert/remove/find/update directly to mongo (although this would be a integration test more than a unit test)
For example, you can remove the objects you inserted in your tests using the Tear Down method:
#After
public void tearDown() {
mongoTemplate.remove(myObject);
}
pom.xml file Config as follows,
<properties>
....
<junit.version>4.10</junit.version>
....
</properties>
<dependencies>
.....
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version><!-- JUNIT CHANGES -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>1.26</version>
<scope>test</scope>
</dependency>
<!-- Mockito --><!-- JUNIT MOCKITO DEPENDENCY ADDED -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.4</version>
</dependency>
<!-- Mockito -->
......
</dependencies>
<build>
....
<plugins>
......
<!-- JUNIT PLUGIN CODE ADDED-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.14</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>2.14</version>
</dependency>
</dependencies>
</plugin>
<!-- END OF JUINT CODE -->
.....
</plugins>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/junit</testSourceDirectory>
</build>....
After setting up the pom cofigurations, do the test case code as by the following steps
Assuming as you are using IDE's like STS or else, just right click on the corresponding class and click on Junit Test Case and create class under the src/test/java path.
For implementing for spring controller classes first we initialize all the objects like initialize values from the test case code of the corresponding class in the setUp() method.
Then call the corresponding methods from the test case and do the appropriate junit assertion check-in if the corresponding method returns values like string/int/boolean etc.,
Like below:
#Test
public void testSave() {
MessageValue messageValue = saveController.saveValue(value+"::Test", model);
Query query = new Query(Criteria.where("value").is("Test").and("_id").is(id));
SaveValue saveValueThis = template.findOne(query, Save.class);
assertEquals("Success", messageValue.getMessage());
// delete the saved value at the end of test case
testDelete();
}
If it returns some UI page that is redirection to some other screen then check like below,
mockMvc = MockMvcBuilders.standaloneSetup(yourController).build();
mockMvc.perform(get("/request/url").accept(MediaType.ALL))
.andExpect(status().isOk());
Finally in the test case tearDown() method reset the initialized values.