From 75021aaf869272a8828fb6325c50e23bed01985d Mon Sep 17 00:00:00 2001 From: Steven Gantz Date: Thu, 19 Sep 2024 18:24:41 -0400 Subject: [PATCH 1/6] add new posts --- ...-09-17-Java-Streams-Gather-Jdk-23.markdown | 58 ++++ ...sy-Spring-Rest-Client-with-OAuth2.markdown | 247 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 _posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown create mode 100644 _posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown diff --git a/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown b/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown new file mode 100644 index 00000000..aa16454a --- /dev/null +++ b/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown @@ -0,0 +1,58 @@ +--- +layout: post +title: "Jdk 23 Streams - Using Gather" +toc: true +date: 2024-09-17 12:00:00 -0500 +categories: +- software +- java +--- + +# It's finally here! JDK 23 +TODO - make small XXXTODOXXX + +Just kidding, this isn't an LTS release + +The next LTS release that will include all of the changes in JDK 23 is XXXTODOXXX and +that won't be available until XXXTODOXXX + +The link to the release notes for Jdk23 are right here: https://openjdk.org/projects/jdk/23/ + +But this article is going to focus on a pretty powerful addition for anyone that's been +leveraging Java Streams heavily for stream processing! + +Specifically Gatherers, the JEP is right over here: https://openjdk.org/jeps/473 + +## JEP-473 + +Stream Gatherers is a preview API delivered in JDK 22 that's in preview again +in JDK 23. Gatherers allow for stream pipelines to transform +data through intermediate operations instead of needing to perform +a terminating operation and generate a new stream. + +Today, if we want to process a list of elements, then perform an operation on the entire list, +and then continue processing the list, we'd have to do it like this: + +```java +TODO - add an example here +``` +With Gatherers, we're able to do this in a cleaner way by +doing our extra processing in an intermediate method! + +```java +TODO - add an example here +``` + +Here's the full example + +```bash + $ + $ javac demo.java --enable-preview -source 23 + $ java --enable-preview demo +``` + +TODO - find real life example +- distinctBy +- grouping and applying a change to the whole collection + +[soby-chako]: https://github.com/sobychacko diff --git a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown new file mode 100644 index 00000000..ce78758b --- /dev/null +++ b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown @@ -0,0 +1,247 @@ +--- +layout: post +title: "Easy Spring Rest Client w/ OAuth2" +toc: true +date: 2024-09-19 12:00:00 -0500 +categories: +- software +- spring boot +- java +--- + +Structure: + +- overview of oauth2 + +- note about updating on release +- webclient in latest spring boot + +- spring security milestone version +- spring boot milestone version + +- full example of restclient + +# Brief + +The purpose of the post is to explain the RecordRecoverableProcessor which is a new addition +to the Spring Cloud Streams library of functions available to the Kafka Streams collection of stream functions. + +If you'd like to view the notes here on the RecordRecoverableProcessor, you can simple scroll +down to the "Solution" section, or check out the [Spring Cloud Stream Samples][scs-samples] under the `kafka-recoverable` +sub-project. + +## Spring Cloud Streams + +[Spring Cloud Stream][spring-cloud-stream-documentation] is a framework for building rich event processing applications +with minimal code and relatively straightforward configurations. + +In my professional career, I've used spring cloud streams extensively to create +tons of messaging applications using the supported message processing softwares such as +Kafka, RabbitMq, Solace, etc. + +There are a great deal of sample projects available in the [Spring Cloud Stream Samples][scs-samples] repository. + +Here's a quick snippet of how simple this framework makes the process of wiring up +event driven applications to set the stage for why we would use this framework over others. + +##### Minimal Configuration + +{% highlight yaml %} +spring: + cloud: + function: + # Enable the bean by name + definition: readAndPublish + stream: + bindings: + # The configuration for the function IN binding + readAndPublish-in-0: + destination: my.in.topic + # The configuration for the function OUT binding + readAndPublish-out-0: + destination: my.out.topic +{% endhighlight %} + +##### Minimal Java Code + +{% highlight java %} + +@SpringBootApplication +public class SpringCloudStreamExample { + + public static void main(String[] args) { + SpringApplication.run(SpringCloudStreamExample.class, args); + } + + /** + * This stream we've defined simple checks if the incoming value is not equal to null. + * If it isn't null, then it gets passed to the BeanName-out-0 configuration from our properties. + * + * The name of this bean is what gets mapped to the configuration, where "in-0" is the input configuration + * and "out-0" is the output configuration for the function that we return here. + **/ + @Bean + public Function, KStream> readAndPublish() { + return input -> input.filter(value -> value != null); + } +} +{% endhighlight %} + +This minimal code example does a simple null check and passes the message from the in-0 configuration +to the out-0 configuration. The framework comes with a TON of sensible default values, so a basic configuration is all +that's needed to set up our simple example. + +However, my team noticed a relatively large gap when building incredibly +complex pipelines using Spring Cloud Streams, specifically when working with +Kafka Streams using the [Spring Cloud Stream Kafka Streams Binder][kafka-streams-binder-documentation] + +## Problem + +Before the introduction of RecordRecoverableProcessor, handling errors in Kafka Streams was a real headache. The framework primarily focused on [deserialization errors][deserialization-error-scs-ks], leaving developers to fend for themselves when it came to processing logic. + +To manage exceptions, we were forced to wrap every processing step in a try-catch block and manually handle errors. This was not only tedious but also error-prone. To make matters worse, Spring Cloud Streams' default behavior was to kill the consumer thread upon any exception, preventing message acknowledgments and leading to endless restart loops. + +A common workaround was to return Optional values from processing components and filter out errors. However, this approach introduced its own set of challenges, including type inference issues and a less-than-ideal developer experience. + +{% highlight java %} +// Pseudocode +@Bean +public Function, KStream> readAndPublish() { + return input -> input + // This method has a complicated try-catch with logging and DLQ + // and then ends in Optional.empty() if an error occurred + .process(someMethod::returnsOptional) + .filter(optional -> optional.isPresent()) + .process(someOtherMethod::returnsOptional2) + .filter(optional -> optional.isPresent()) + .process(sometOtherOtherMethod::returnsOptional3) + .filter(optional -> optional.isPresent()) +} +{% endhighlight %} + +Implementing a Dead Letter Queue (DLQ) was also a manual nightmare. While the framework offered DLQ capabilities for deserialization errors, there was no out-of-the-box solution for processing errors. + +It wasn't until after raising [Issue #2779][spring-cloud-stream-issues-2779] with the Spring Cloud Streams team that the RecordRecoverableProcessor was introduced. This marked a significant improvement in the framework's error handling capabilities. +(Special thanks to [Soby Chako][soby-chako]) + +Attempting to implement a dead-letter-queue process (which is fully available via configuration during deserialization) +was an extremely manual process too. + +## Solution + +The solution, the RecordRecoverableProcessor and the DltAwareProcessor. + +These two Processor components support supplying a function that is applied to the +incoming data and the result pushed to the outgoing data sink. They also can receive an error handling +function that gets applied to any exception thrown from inside the supplied function. + +The new section of the documentation for these capabilities is [available here][non-deserialization-error-scs-ks]. + +There are two new components that can be used as KStream processor objects. + +Both operate on a similar concept. + +This is the main body of the [RecordRecoverableProcessor][rrp]: + +{% highlight java %} +try { + // The delegate function is the function passed into the processor + Record downstreamRecord = this.delegateFunction.apply(record); + this.context.forward(downstreamRecord); +} +catch (Exception exception) { + // The processorRecordRecoverer is the error function supplied. + if (this.processorRecordRecoverer == null) { + this.processorRecordRecoverer = defaultProcessorRecordRecoverer(); + } + this.processorRecordRecoverer.accept(record, exception); +} +{% endhighlight %} + +Both rely on the `this.context.forward` call to propagate the record downstream. + +If `forward` is not called on the message, it is simply consumed on the spot and does not continue +through the stream, and no exception is propagated. + +Below are examples of each of the two new components: + +#### DltAwareProcessor + +{% highlight java %} + +@Bean +public Function, KStream> dltAwareExample( + DltPublishingContext dltPublishingContext) { + return input -> input + .process(() -> new DltAwareProcessor<>(myRecord -> { + throw new RuntimeException("Something went wrong, Error"); + }, "my.dead-letter-queue.topic", dltPublishingContext)); +} + +{% endhighlight %} + +#### RecordRecoverableProcessor + +Here is a sample RecordRecoverableProcessor, fully laid out. An IDE will suggest turning many parts +of this example into lambda functions, making it significantly cleaner. I will link both here to show what exactly +is happening with the expanded version, but also how short it can be with the cleaned up version. + +{% highlight java %} + +// Expanded version +@Bean +public Function, KStream> rrpDemo(){ + return input -> input + .process(new ProcessorSupplier() { + @Override + public Processor get() { + return new RecordRecoverableProcessor<>( + new Function, Record>() { + @Override + public Record apply(Record stringStringRecord) { + return stringStringRecord; + } + }, + new BiConsumer, Exception>() { + @Override + public void accept(Record stringStringRecord, Exception e) { + log.error(e.getMessage()); + } + } + ); + } + } + ); +} + +// Collapsed Version +@Bean +public Function, KStream> rrpDemo(){ + return input -> input + .process(() -> new RecordRecoverableProcessor<>( + stringStringRecord -> stringStringRecord, + (stringStringRecord, e) -> log.error(e.getMessage() + ) + )); +} + +{% endhighlight %} + + + +### Finally + +With Spring Cloud Streams, we can easily create rich and powerful pipelines connecting any number +of message processing systems. + +I'm proud to have had a hand in closing what I see as one of the largest gaps in this framework +and look forward to the continued development of something so useful to the spring community! + +[scs-samples]: https://github.com/spring-cloud/spring-cloud-stream-samples +[spring-cloud-stream-documentation]: https://spring.io/projects/spring-cloud-stream +[kafka-streams-binder-documentation]: https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html#_kafka_streams_binder +[deserialization-error-scs-ks]: https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html#_handling_deserialization_exceptions +[non-deserialization-error-scs-ks]: https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html#_handling_non_deserialization_exceptions +[soby-chako]: https://github.com/sobychacko +[spring-cloud-stream-issues-2779]: https://github.com/spring-cloud/spring-cloud-stream/issues/2779 +[rrp]: https://github.com/spring-cloud/spring-cloud-stream/blob/main/binders/kafka-binder/spring-cloud-stream-binder-kafka-streams/src/main/java/org/springframework/cloud/stream/binder/kafka/streams/RecordRecoverableProcessor.java#L84 From 55b0ac8c6be2c951dc54e6959c7161bd14998204 Mon Sep 17 00:00:00 2001 From: Steven Gantz Date: Thu, 19 Sep 2024 18:31:37 -0400 Subject: [PATCH 2/6] add format and sample --- ...-09-17-Java-Streams-Gather-Jdk-23.markdown | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown b/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown index aa16454a..4c4566fa 100644 --- a/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown +++ b/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown @@ -43,10 +43,25 @@ doing our extra processing in an intermediate method! TODO - add an example here ``` -Here's the full example +Here's the full example code (and we don't need a default class anymore with JDK 23!) + +```java +import java.util.function.Predicate; +import java.util.stream.Gatherer; +import java.util.stream.Stream; + +void main(String[] args) { + + var result = Stream.of(1, 2, 3) // The Stream + .filter((integer -> integer > 0)) // Intermediate Operation + // TODO - gather example + .toList(); // The Terminal Operation + System.out.println(result); +} + +``` ```bash - $ $ javac demo.java --enable-preview -source 23 $ java --enable-preview demo ``` From ab178c40b263e10dffee9994bb46b3294b68c920 Mon Sep 17 00:00:00 2001 From: Steven Gantz Date: Thu, 19 Sep 2024 18:56:10 -0400 Subject: [PATCH 3/6] add structure for the restclient piece --- ...sy-Spring-Rest-Client-with-OAuth2.markdown | 300 ++++++------------ 1 file changed, 91 insertions(+), 209 deletions(-) diff --git a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown index ce78758b..38e495f9 100644 --- a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown +++ b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown @@ -16,232 +16,114 @@ Structure: - note about updating on release - webclient in latest spring boot -- spring security milestone version -- spring boot milestone version +## The Setup (Currently a Milestone Release but this will be updated!) -- full example of restclient +Here's everything you need to get RestClient working with OAuth2! -# Brief +build.gradle +```groovy +plugins { + id 'org.springframework.boot' version '3.3.3' -The purpose of the post is to explain the RecordRecoverableProcessor which is a new addition -to the Spring Cloud Streams library of functions available to the Kafka Streams collection of stream functions. +... -If you'd like to view the notes here on the RecordRecoverableProcessor, you can simple scroll -down to the "Solution" section, or check out the [Spring Cloud Stream Samples][scs-samples] under the `kafka-recoverable` -sub-project. +implementation 'org.springframework.boot:spring-boot-starter-actuator' +implementation 'org.springframework.boot:spring-boot-starter-security' +implementation 'org.springframework.boot:spring-boot-starter-web' +implementation 'org.springframework.security:spring-security-oauth2-client:6.4.0-M3' +``` -## Spring Cloud Streams - -[Spring Cloud Stream][spring-cloud-stream-documentation] is a framework for building rich event processing applications -with minimal code and relatively straightforward configurations. - -In my professional career, I've used spring cloud streams extensively to create -tons of messaging applications using the supported message processing softwares such as -Kafka, RabbitMq, Solace, etc. - -There are a great deal of sample projects available in the [Spring Cloud Stream Samples][scs-samples] repository. - -Here's a quick snippet of how simple this framework makes the process of wiring up -event driven applications to set the stage for why we would use this framework over others. - -##### Minimal Configuration - -{% highlight yaml %} +application.yaml +```yaml spring: - cloud: - function: - # Enable the bean by name - definition: readAndPublish - stream: - bindings: - # The configuration for the function IN binding - readAndPublish-in-0: - destination: my.in.topic - # The configuration for the function OUT binding - readAndPublish-out-0: - destination: my.out.topic -{% endhighlight %} - -##### Minimal Java Code - -{% highlight java %} - -@SpringBootApplication -public class SpringCloudStreamExample { - - public static void main(String[] args) { - SpringApplication.run(SpringCloudStreamExample.class, args); - } + security: + oauth2: + client: + registration: + my-oauth-client: + client-id: ${oauthClientId} + client-secret: ${oauthClientSecret} + provider: my-oauth-provider + authorization-grant-type: client_credentials + scope: openid + provider: + my-oauth-provider: + token-uri: ${oauth2ServerUri}/protocol/openid-connect/token + issuer-uri: ${oauth2ServerUri} +``` + +application-local.yaml +```yaml +oauth2ServerUri: http://myServerUri:9090 +oauthClientId: clientId +oauth2ClientSecret: mySecretSecret +``` + +ConfigurationClass.java +```java +@Configuration +public class RestClientConfiguration +{ + // This needs to match the YAML configuration + private static final String CLIENT_REGISTRATION_ID = "my-oauth-client"; - /** - * This stream we've defined simple checks if the incoming value is not equal to null. - * If it isn't null, then it gets passed to the BeanName-out-0 configuration from our properties. - * - * The name of this bean is what gets mapped to the configuration, where "in-0" is the input configuration - * and "out-0" is the output configuration for the function that we return here. - **/ @Bean - public Function, KStream> readAndPublish() { - return input -> input.filter(value -> value != null); + public OAuth2AuthorizedClientManager authorizedClientManager ( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientService authorizedClientService + ){ + // We create a manager using the autowired clientRegistrations from YAML and connect it to the service + AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = + new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); + + // Setting the clientManager to look for a clientCredentials configuration + authorizedClientManager.setAuthorizedClientProvider(OAuth2AuthorizedClientProviderBuilder.builder() + .clientCredentials() + .build()); + return authorizedClientManager; } -} -{% endhighlight %} - -This minimal code example does a simple null check and passes the message from the in-0 configuration -to the out-0 configuration. The framework comes with a TON of sensible default values, so a basic configuration is all -that's needed to set up our simple example. - -However, my team noticed a relatively large gap when building incredibly -complex pipelines using Spring Cloud Streams, specifically when working with -Kafka Streams using the [Spring Cloud Stream Kafka Streams Binder][kafka-streams-binder-documentation] - -## Problem - -Before the introduction of RecordRecoverableProcessor, handling errors in Kafka Streams was a real headache. The framework primarily focused on [deserialization errors][deserialization-error-scs-ks], leaving developers to fend for themselves when it came to processing logic. - -To manage exceptions, we were forced to wrap every processing step in a try-catch block and manually handle errors. This was not only tedious but also error-prone. To make matters worse, Spring Cloud Streams' default behavior was to kill the consumer thread upon any exception, preventing message acknowledgments and leading to endless restart loops. - -A common workaround was to return Optional values from processing components and filter out errors. However, this approach introduced its own set of challenges, including type inference issues and a less-than-ideal developer experience. - -{% highlight java %} -// Pseudocode -@Bean -public Function, KStream> readAndPublish() { - return input -> input - // This method has a complicated try-catch with logging and DLQ - // and then ends in Optional.empty() if an error occurred - .process(someMethod::returnsOptional) - .filter(optional -> optional.isPresent()) - .process(someOtherMethod::returnsOptional2) - .filter(optional -> optional.isPresent()) - .process(sometOtherOtherMethod::returnsOptional3) - .filter(optional -> optional.isPresent()) -} -{% endhighlight %} - -Implementing a Dead Letter Queue (DLQ) was also a manual nightmare. While the framework offered DLQ capabilities for deserialization errors, there was no out-of-the-box solution for processing errors. - -It wasn't until after raising [Issue #2779][spring-cloud-stream-issues-2779] with the Spring Cloud Streams team that the RecordRecoverableProcessor was introduced. This marked a significant improvement in the framework's error handling capabilities. -(Special thanks to [Soby Chako][soby-chako]) - -Attempting to implement a dead-letter-queue process (which is fully available via configuration during deserialization) -was an extremely manual process too. - -## Solution - -The solution, the RecordRecoverableProcessor and the DltAwareProcessor. -These two Processor components support supplying a function that is applied to the -incoming data and the result pushed to the outgoing data sink. They also can receive an error handling -function that gets applied to any exception thrown from inside the supplied function. - -The new section of the documentation for these capabilities is [available here][non-deserialization-error-scs-ks]. - -There are two new components that can be used as KStream processor objects. - -Both operate on a similar concept. - -This is the main body of the [RecordRecoverableProcessor][rrp]: - -{% highlight java %} -try { - // The delegate function is the function passed into the processor - Record downstreamRecord = this.delegateFunction.apply(record); - this.context.forward(downstreamRecord); -} -catch (Exception exception) { - // The processorRecordRecoverer is the error function supplied. - if (this.processorRecordRecoverer == null) { - this.processorRecordRecoverer = defaultProcessorRecordRecoverer(); + @Bean + public RestClient oauth2RestClient( + OAuth2AuthorizedClientManager authorizedClientManager, + LogbookClientHttpRequestInterceptor logbookClientHttpRequestInterceptor) { + + // This is the new class!!! We instantiate a new one and provide it the client registration to match + OAuth2ClientHttpRequestInterceptor oAuth2ClientHttpRequestInterceptor = + new OAuth2ClientHttpRequestInterceptor(authorizedClientManager, request -> CLIENT_REGISTRATION_ID); + + // From here we simply return the client with any custom configuration, and we're good to go! + return RestClient.builder() + .baseUrl("http://myBaseUrl:8080") + .requestInterceptor(oAuth2ClientHttpRequestInterceptor) + .build(); } - this.processorRecordRecoverer.accept(record, exception); } -{% endhighlight %} - -Both rely on the `this.context.forward` call to propagate the record downstream. - -If `forward` is not called on the message, it is simply consumed on the spot and does not continue -through the stream, and no exception is propagated. - -Below are examples of each of the two new components: +``` -#### DltAwareProcessor +Bonus: Setting up HttpServiceProxyFactory (not required but useful!) -{% highlight java %} +```java +public interface MyHttpService { -@Bean -public Function, KStream> dltAwareExample( - DltPublishingContext dltPublishingContext) { - return input -> input - .process(() -> new DltAwareProcessor<>(myRecord -> { - throw new RuntimeException("Something went wrong, Error"); - }, "my.dead-letter-queue.topic", dltPublishingContext)); + @PostExchange("api/my/path") + SomeResponse post(@RequestBody MyPostBody request); } +``` -{% endhighlight %} - -#### RecordRecoverableProcessor - -Here is a sample RecordRecoverableProcessor, fully laid out. An IDE will suggest turning many parts -of this example into lambda functions, making it significantly cleaner. I will link both here to show what exactly -is happening with the expanded version, but also how short it can be with the cleaned up version. - -{% highlight java %} - -// Expanded version -@Bean -public Function, KStream> rrpDemo(){ - return input -> input - .process(new ProcessorSupplier() { - @Override - public Processor get() { - return new RecordRecoverableProcessor<>( - new Function, Record>() { - @Override - public Record apply(Record stringStringRecord) { - return stringStringRecord; - } - }, - new BiConsumer, Exception>() { - @Override - public void accept(Record stringStringRecord, Exception e) { - log.error(e.getMessage()); - } - } - ); - } - } - ); -} - -// Collapsed Version -@Bean -public Function, KStream> rrpDemo(){ - return input -> input - .process(() -> new RecordRecoverableProcessor<>( - stringStringRecord -> stringStringRecord, - (stringStringRecord, e) -> log.error(e.getMessage() - ) - )); +```java +@Configuration +public class HttpServiceFactory +{ + @Bean + public MyHttpService getMyHttpService(RestClient oauth2RestClient) { + // We're simply injecting our restClient into the factory and creating a concrete instance of the interface + HttpServiceProxyFactory factory = HttpServiceProxyFactory + .builderFor(RestClientAdapter.create(oauth2RestClient)) + .build(); + return factory.createClient(MyHttpService.class); + } } +``` -{% endhighlight %} - - - -### Finally - -With Spring Cloud Streams, we can easily create rich and powerful pipelines connecting any number -of message processing systems. - -I'm proud to have had a hand in closing what I see as one of the largest gaps in this framework -and look forward to the continued development of something so useful to the spring community! - -[scs-samples]: https://github.com/spring-cloud/spring-cloud-stream-samples -[spring-cloud-stream-documentation]: https://spring.io/projects/spring-cloud-stream -[kafka-streams-binder-documentation]: https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html#_kafka_streams_binder -[deserialization-error-scs-ks]: https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html#_handling_deserialization_exceptions -[non-deserialization-error-scs-ks]: https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html#_handling_non_deserialization_exceptions [soby-chako]: https://github.com/sobychacko -[spring-cloud-stream-issues-2779]: https://github.com/spring-cloud/spring-cloud-stream/issues/2779 -[rrp]: https://github.com/spring-cloud/spring-cloud-stream/blob/main/binders/kafka-binder/spring-cloud-stream-binder-kafka-streams/src/main/java/org/springframework/cloud/stream/binder/kafka/streams/RecordRecoverableProcessor.java#L84 From 4a27eb9db82da8d3e07a9b6817625a6e67aff0cb Mon Sep 17 00:00:00 2001 From: Steven Gantz Date: Thu, 19 Sep 2024 19:09:26 -0400 Subject: [PATCH 4/6] Add article --- ...sy-Spring-Rest-Client-with-OAuth2.markdown | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown index 38e495f9..552c2bc5 100644 --- a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown +++ b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown @@ -9,12 +9,49 @@ categories: - java --- -Structure: +# Brief -- overview of oauth2 +My goal is to make posts like this to SIMPLEST place on the internet to learn how to do things +that caused me trouble. That way, if this is found, someone doesn't have to do the same digging I had to do. -- note about updating on release -- webclient in latest spring boot +# What is OAuth2? + +OAuth2 is a popular authorization framework that allows users to grant +third-party applications access to their data without revealing their +credentials. It's often used for services like social media logins, API integrations, and more. + +### Key Components of OAuth2 + +- Authorization Server: Issues access tokens to clients after user authorization. +- Resource Server: Protects resources that can only be accessed with valid access tokens. +- Client: The application requesting access to resources. +- User: The person granting or denying access. + +### How OAuth2 Works + +- User Authorization: The user grants permission to the client to access specific resources. +- Token Exchange: The client exchanges an authorization code for an access token. +- Resource Access: The client uses the access token to access protected resources. + +### Why RestClient? + +While **RestTemplate** has been a staple for many years, its limitations and the +introduction of more modern alternatives have led to its deprecation in recent +versions of Spring Boot. Let's delve into the key differences between WebClient +and RestClient and why RestTemplate is being phased out. + +**WebClient** is built on top of Project Reactor, a reactive +programming framework. This means it can handle asynchronous operations efficiently, +making it well-suited for scenarios where concurrent requests and non-blocking I/O +are essential. + +However, with RestTemplate's deprecation, the only real Spring alternative is WebClient. +This requires including the spring-webflux dependencies and calling `.block()` when making +blocking API calls. It feels shoe-horned into place. + +In comes RestClient, a client written in the same functional style as WebClient, but supports +synchronous and asynchronous out of the box. This lets us remove the spring-webflux +dependency and use spring-web-mvc as the primary HTTP dependency for server and client applications. ## The Setup (Currently a Milestone Release but this will be updated!) @@ -59,7 +96,7 @@ oauthClientId: clientId oauth2ClientSecret: mySecretSecret ``` -ConfigurationClass.java +RestClientConfiguration.java ```java @Configuration public class RestClientConfiguration From 1c1a5ab5ed496114dd26d3d2d4657bbd88061e77 Mon Sep 17 00:00:00 2001 From: Steven Gantz Date: Thu, 19 Sep 2024 19:11:27 -0400 Subject: [PATCH 5/6] Add outro --- ...-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown index 552c2bc5..6bc4a07a 100644 --- a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown +++ b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown @@ -64,7 +64,6 @@ plugins { ... -implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.security:spring-security-oauth2-client:6.4.0-M3' @@ -163,4 +162,13 @@ public class HttpServiceFactory } ``` +TODO - add links to things + +## Summary + +The new RestClient is already a popular alternative for developers in the Spring ecosystem. +The lack of an OAuth2 component has been a sore spot for new users converting over from WebClient. So with +this new feature releasing in Spring Boot 3.4.0, it can now take it's rightful place as the default, non-webflux +HTTP Client for Spring MVC! + [soby-chako]: https://github.com/sobychacko From 0c43b1b8c971e53acf9aed233c4b4ab3d90b8530 Mon Sep 17 00:00:00 2001 From: Steven Gantz Date: Fri, 20 Sep 2024 17:06:41 -0400 Subject: [PATCH 6/6] Fix typos --- ...-09-17-Java-Streams-Gather-Jdk-23.markdown | 0 ...sy-Spring-Rest-Client-with-OAuth2.markdown | 37 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) rename {_posts => _drafts}/2024-09-17-Java-Streams-Gather-Jdk-23.markdown (100%) diff --git a/_posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown b/_drafts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown similarity index 100% rename from _posts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown rename to _drafts/2024-09-17-Java-Streams-Gather-Jdk-23.markdown diff --git a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown index 6bc4a07a..9b4e7504 100644 --- a/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown +++ b/_posts/2024-09-19-Easy-Spring-Rest-Client-with-OAuth2.markdown @@ -11,7 +11,7 @@ categories: # Brief -My goal is to make posts like this to SIMPLEST place on the internet to learn how to do things +My goal is to make posts like this the SIMPLEST place on the internet to learn how to do things that caused me trouble. That way, if this is found, someone doesn't have to do the same digging I had to do. # What is OAuth2? @@ -37,7 +37,7 @@ credentials. It's often used for services like social media logins, API integrat While **RestTemplate** has been a staple for many years, its limitations and the introduction of more modern alternatives have led to its deprecation in recent -versions of Spring Boot. Let's delve into the key differences between WebClient +versions of Spring. Let's dive into the key differences between WebClient and RestClient and why RestTemplate is being phased out. **WebClient** is built on top of Project Reactor, a reactive @@ -49,8 +49,8 @@ However, with RestTemplate's deprecation, the only real Spring alternative is We This requires including the spring-webflux dependencies and calling `.block()` when making blocking API calls. It feels shoe-horned into place. -In comes RestClient, a client written in the same functional style as WebClient, but supports -synchronous and asynchronous out of the box. This lets us remove the spring-webflux +In comes [RestClient][restClientBlogAnnouncement], a client written in the same functional style as WebClient, but supports +synchronous and asynchronous operations out of the box. This lets us remove the spring-webflux dependency and use spring-web-mvc as the primary HTTP dependency for server and client applications. ## The Setup (Currently a Milestone Release but this will be updated!) @@ -61,12 +61,16 @@ build.gradle ```groovy plugins { id 'org.springframework.boot' version '3.3.3' +} -... + // ... The rest of the stuff, this is just what's required -implementation 'org.springframework.boot:spring-boot-starter-security' -implementation 'org.springframework.boot:spring-boot-starter-web' -implementation 'org.springframework.security:spring-security-oauth2-client:6.4.0-M3' + dependencies { + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.security:spring-security-oauth2-client:6.4.0-M3' + } +} ``` application.yaml @@ -95,7 +99,7 @@ oauthClientId: clientId oauth2ClientSecret: mySecretSecret ``` -RestClientConfiguration.java +RestClientConfiguration.java using the new [OAuth2ClientHttpRequestInterceptor][oAuth2ClientHttpRequestInterceptor] ```java @Configuration public class RestClientConfiguration @@ -137,7 +141,9 @@ public class RestClientConfiguration } ``` -Bonus: Setting up HttpServiceProxyFactory (not required but useful!) +### Bonus: Setting up HttpServiceProxyFactory (not required but useful!) + +[HttpServiceProxyFactory][httpServiceProxyFactory] is new in [Spring 6][httpServiceProxyFactoryJavadoc]! ```java public interface MyHttpService { @@ -162,8 +168,6 @@ public class HttpServiceFactory } ``` -TODO - add links to things - ## Summary The new RestClient is already a popular alternative for developers in the Spring ecosystem. @@ -171,4 +175,13 @@ The lack of an OAuth2 component has been a sore spot for new users converting ov this new feature releasing in Spring Boot 3.4.0, it can now take it's rightful place as the default, non-webflux HTTP Client for Spring MVC! +## Update Note + +Once this is available in the official spring release, I'll update this from milestone versions and +hook up the JavaDoc instead of the originating Github Issue! + +[restClientBlogAnnouncement]: https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient +[oAuth2ClientHttpRequestInterceptor]: https://github.com/spring-projects/spring-security/issues/13588 +[httpServiceProxyFactoryJavadoc]: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/service/invoker/HttpServiceProxyFactory.html +[httpServiceProxyFactory]: https://www.baeldung.com/spring-6-http-interface [soby-chako]: https://github.com/sobychacko