Spring supports @Value
methods on:
static String someVariable
@Value("{someVar}")
public void setSomeVariable(String someVar){
someVariable = someVar;
}
Executor (plain threading): allows you to define multiple threads (a pool of threads) which run atomic units of work in parallel. Depending on resources, max number of threads is limited. If on thread is blocked, it will wait until it can continue. (i.e. we have parallelism but no asynchronicity)
CompleteableFuture: allows the same as above, however now if a thread is blocked it can switch to another task while waiting for the original work to become unblocked. (i.e. now we have parallelism and asynchronicity)
Webflux: allows the same as the above, but where CompleteableFuture is eagerly executed, webflux is designed to be lazily executed (i.e. executed only when required - on subsciption). This allows the design to implement backpressure and control upstream workloads
The Spring WebClient is a reactive webclient. In general the workflow with webclient is (in kotlin):
val webClient = WebClient.create("http://x.y.z.")
.post()
.uri("/events*/_search")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(someJSON))
.exchange()
.flatMap { clientResponse -> clientResponse.bodyToMono(String::class.java) }
In the above case, webClient will be a Mono<String>
, which hasn't been executed yet (since there's no subscription). We can gather multiple such "webclients" into an array, and execute all simultaneously by using Flux.merge, for example:
val webClients = ArrayList<Mono<String>>()
webClients.add(webClient) // usually called multiple times
Flux.merge( webClients ).subscribe{ e -> println(e) } //web calls done simultaneously here
flatMapMany can be used for converting a Mono containing an iterable into a flux.
For example (in kotlin):
val demoMono : Mono<ArrayList<String>> = ...
val demoFlux = demoMono.flatMapMany {
stringArrayList -> Flux.fromIterable(stringArrayList)
}
//demoFlux will be of type Flux<String>
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#flatMapMany
Best to use GenerationType.SEQUENCE
since it is highly optimized for postgres, avoids unnecessary SELECT statements
Typically Java singletons are created using double-checked locking (i.e. check if the singleton instance is null, only if it is, go into synchronized code, check if null again and create instance). However, a more elegant way of doing this is to use static code blocks, which are also thread safe. For example:
public class ourSingleton{
public static ourSingleton INSTANCE;
private static String demo;
private ourSingleton(){
demo = "ourSingleton"; // an example of initializing a variable
this.INSTANCE = this;
System.out.println("init done");
}
static { // static code block
new ourSingleton();
}
public void printDemo(){
System.out.println(this); // show that same instance of class is used
System.out.println(demo); // get access to the initialized variable
}
}
// [...]
// note that now ourSingleton.INSTANCE can't be null due to the static code block, eliminating
// the need for the typical double-check locking
ourSingleton demo1 = ourSingleton.INSTANCE;
ourSingleton demo2 = ourSingleton.INSTANCE;
demo1.printDemo();
demo2.printDemo();
/*
outputs:
"
init done <---- init only called once
ourSingleton@5594a1b5 <---- same output for both instances of singleton
ourSingleton
ourSingleton@5594a1b5
ourSingleton
"
*/
In kotlin this is even easier by using an object
:
object ourSingleton{
init{
println("init done");
}
}
@Component
@Service
@Respository
(Incoming HTTP request) ===> Router ===> Handler ===>Response
The Handler can be a Spring component(@Component
) / service (@Service
)
In a scenario where you're dealing with an object of type Mono<Mono<String>>
(or some other similarly nested object), and you'd like to get access to the inner object, switch from using MAP
to FLATMAP
Atomic variables have special "transactional" or "atomic" operations such as getAndSet
which allow you to do two operations (like checking the value of a variable, and then setting it), in a transactional/atomic fashion - i.e. the operations (plural) are guaranteed to all succeed or all fail. Atomic variables are usually used in multithreaded sections of code which for performance reasons are avoiding locks such as synchronized blocks
Volatile
volatile int demo = 5;
Synchronized
[...] .block()
[...] .toIterable()
By default, all streams are ordered. That means that any ordered operations on both serial and parallel streams will have the same effect:
// note the use of a TreeSet - which imposes order by sorting
TreeSet<Integer> sequence = new TreeSet<>();
System.out.println("Building sequence");
for (int i=0; i < 1000000; i++) {
sequence.add(i);
}
System.out.println("Serial stream");
sequence.stream().skip(10000).findFirst().ifPresent(System.out::println);
// output 10000
System.out.println("Ordered Parallel stream");
sequence.stream().parallel().skip(10000).findFirst().ifPresent(System.out::println);
// still outputs 10000 - even though parallel used
System.out.println("Unordered Parallel stream");
sequence.stream().unordered().parallel().skip(10000).findFirst().ifPresent(System.out::println);
// doesnt always output 10000
inserting the unordered()
intermediate operation can have performance benefits for downstream parallel operators since they no longer are constrained by order
Note that if we submit a lambda that does not return a value, the compiler assumes it impelents the Future interface, but if the lambda returns a value it assumes a Callable interface