Friday, April 8, 2022

Top 5 Features of Java 17 That You Must Know

Change is the only constant thing and according to Darwin’s Theory of Survival of the fittest, the individual who adapts to the evolving world will sustain itself in the long run. Looks like this theory is true for Java also, as it is continuously evolving and serving for more than two decades. From JDK 1.0 to Java 17 (LTS) it has come a long way. Java 17 is the latest long-term support (LTS) release for the SE platform.

Oracle Java, Core Java, Java 17, Oracle Java Certification, Oracle Java Career, Oracle Java Jobs, Java Skill, Oracle Java Learning

Adding features in any programming language improve its performance and thus makes code complexity easy. For example, C# was initially a Java clone but adding more features to it gave birth to C#. While there are many features that are introduced in Java 17, let’s talk about a few of them. In this article, we’re going to discuss the Top 5 features of Java 17.

1. Restricting the implementation with Sealed classes and Interfaces


Sealed classes were a preview feature in JDK 15 and now they have come as a full feature in JDK 17. When inheritance was introduced, there was a mixed opinion of people as it didn’t restrict the number of implementations. Sealed classes bring an end to this as it is a feature with which one can restrict the implementations. To cut to the chase, sealed classes give us the privilege of controlling which classes or models can implement or extend that interface or class respectively. It represents restricted class hierarchies that provide control over an inheritance. For a sealed class, all direct subclasses need to be known at compile-time and third-party clients can’t extend a sealed class in their code. To make a Java class, a sealed Java class, add the sealed modifier to its declaration, and keyword permits are placed to indicate the classes which are permitted for the given sealed class.

Sealed class Fruit specifies three permitted subclasses, Square, Rectangle, and Circle:

package com.geeksforgeeks.example.figures

public sealed class Shape permits, Square, Rectangle, Circle { }

2. Using Null in Switch Case is Now Legal


Previously, keeping selector expression as null in switch statement and expression used to throw NullPointerException and we essentially have to throw a Null Pointer Exception to be on a safer side. To resolve this issue, Java 17 has come up with a feature where we can put null as a selector expression in switch case expressions. Consider the below example where we can pass null as a selector expression.  

switch(checkNumber) {

case 1,7 -> System.out.println(“odd number”) ;

case 2,8 -> System.out.println(“even number”) ;

case null -> System.out.println(“Not defined”) ;

default -> System.out.println(“not a number”) ;

}

Here, checkNumber variable gets a number as an input. If null is passed as an input, “Not defined” is displayed as an output. Note that, for case 1,7 and case 2,8, other odd and even numbers are to be taken in selector expression as well for the proper functioning of the code. Only a few are taken to maintain the simplicity of the example.  

3. End of Guessing the Cause of Null Pointer Exception


Be it working with linked lists or just a fragment of code having reference to an object, there is always a risk of reference to a null object which can get things to ground zero if not handled well. Debugging and Java logs can help but debugging itself is a time taking task and java logs are not that good at providing details about the culprit object which caused the NullPointerException. Here, the NullPointerException guidance feature of Java 17 comes as a friend indeed as it provides the exact name of the variable that is null from the exception’s stack trace. Thus, this feature saves us from debugging hassle and ends up the guessing game of finding out the pointer which went null.  

4. Redefining Switch Statement Expressions


Forgetting a single break among multiple lines of switch-case statements is not at all a welcomed guest. Moreover, the case-break-case-break pattern doesn’t seem to be a good deal when dealing with many switch cases. Yes, now we don’t have to break with the frequent use of break! Java addressed our concern and here we present the new switch statement expressions in Java 17. The new switch expressions are less error-prone as it is cleaner and simpler now. The use of arrow symbols not only eliminates the fall-through functionality but also makes it more readable and easy to debug. 

We can include more than one value in the same block by comma separating them. One of the important features is the introduction of the yield keyword. In the code snippet, on the execution of the default statement, System.out.println() will execute and the identifyTyres variable will end up to be “Unknown Vehicle” because this is what the default is meant to yield.

String identifyTyres = switch (vehicle) {  

                                   case Car  -> “four”;  

                                   case Bike, Cycle  -> “two”;  

                                   case Autorickshaw  -> “three”;

                                   default  -> { System.out.println(“The vehicle could not be found.”);  

                                                      yield “Unknown Vehicle”;  

                                    };

5. Reducing Lines of Code with Record Classes


Record classes were previewed in Java 14. The complex and ugliest POJO code looks nicer when implemented with Records. They are both immutable and final. The fields of the Record cannot be changed after creation and the extension of the Records class is also not allowed. They are rightly the data-only classes that manage the boilerplate code of POJOs. Records are very useful when all we want is to temporarily hold immutable data. In the code snippet, Data is a record and the a and b are referred to as components of the Data record. On defining a record, we get an equals() method, hashcode() method already implemented, and a toString() method implementation to print the record components along with the component names.  

record Data(long a, long b) { }

The above Record Data is equivalent to the following lines of code:

public final class Data {

   private final long a;

   private final long b;

   Public Data(long a, long b) {

       this.a = a;

       this.b = b;

   }

   long a() { return this.a; }

   long b()  { return this.b; }

    // Implementation of equals() and hashCode(), which specify

     public boolean equals…

     public int hashCode…

    // An implementation of toString() that returns a string

     public String toString() {…}

}

Source: geeksforgeeks.org

Wednesday, April 6, 2022

Host a dark service with Java, Spring Boot, and OpenZiti

Hide your application services to protect them against hackers.

This article’s headline mentions hosting a dark service. Huh? Why would you want your application services to be dark?

Open ports are everywhere. You need open ports, right? How are your users and applications going to connect to your services if there are no open ports? Well, they can still connect—but only if they are trusted.

Oracle Java Certification, Core Java, Java Tutorial and Material, Oracle Java Learning, Oracle Java Career, Java Jobs, Java Skills

A dark service is a service that’s had zero trust (ZT) principles applied across the board and, therefore, not only to the network layer. Indeed, the principles of ZT access are baked directly into the application itself.

Dark services have no listening ports, taking them off the internet (and even off the local network) where port scanners and nefarious actors are only an IP hop away.

What is zero trust?

The zero trust phrase has been thrown around a lot lately, but what does it really mean? Here’s how the concept is defined by NIST’s Computer Security Resource Center.

Zero trust (ZT) is the term for an evolving set of cybersecurity paradigms that move defenses from static, network-based perimeters to focus on users, assets, and resources. A zero trust architecture (ZTA) uses zero trust principles to plan industrial and enterprise infrastructure and workflows. Zero trust assumes there is no implicit trust granted to assets or user accounts based solely on their physical or network location (i.e., local area networks versus the internet) or based on asset ownership (enterprise or personally owned). Authentication and authorization (both subject and device) are discrete functions performed before a session to an enterprise resource is established. Zero trust is a response to enterprise network trends that include remote users, bring your own device (BYOD), and cloud-based assets that are not located within an enterprise-owned network boundary. Zero trust focuses on protecting resources (assets, services, workflows, network accounts, etc.), not network segments, as the network location is no longer seen as the prime component to the security posture of the resource.

In practice, you can boil this down to five basic tenants that apply to everything on your network, including both human users and automated processes.

◉ Deny access by default. Nobody can connect to the network unless you let them.

◉ Authorize on connect. Only allow connections to the network that use a strong, well-defined and known identity.

◉ Use explicit access grants. Even if they can connect, actors cannot do anything unless you let them. Limiting access prevents things such as traversal attacks and information leakage.

◉ Enforce least privilege access. Granting only permissions that are absolutely required for an actor to complete its tasks limits the damage that can be done when an account is compromised.

◉ Constantly monitor for security compliance. Zero trust is not just about the initial setup. It includes constantly monitoring policies, connections, and permissions to ensure that ZT principles continue to be enforced over time.

ZT principles should not stop at the network

You can apply all the ZT principles to your network, but even after that your application is still listening on a port after that. If there is a port open, your application can be attacked. That’s why the concept of app embedded zero trust moves the edge of the network into the application. Bringing ZT into your app programmatically via an SDK has several benefits.

◉ No listening ports. Your application can become totally dark with no open ports. When there are no open ports, the application cannot be scanned or attacked from any random person on the network.

◉ Zero trust of the entire network. When ZT is in the application, it extends across all networks, including the Internet, local-area networks, and even the operating system. Not trusting the network operating system makes applications immune to network-based side-channel attacks from malicious actors or ransomware that’s trying to attack the application from a stolen or infected device.

◉ Direct access. Applications are accessed directly from clients; they are not discovered. If the client doesn’t know where the application is, it can’t access it.

◉ Portability. Once ZT is configured and becomes part of the architectural overlay, your application and your clients need only outbound, commodity internet.

◉ Encrypted data. Application data is encrypted from the client all the way to the server and back. ZT enforces this.

◉ Micro segmentation. Applications cannot talk to other applications unless explicitly authorized to do so.

In terms of portability, embedding ZT into an application can help in common use cases. If you want to change clouds or deploy your application into a new data center, no changes need to be made. Simply develop your app once and deploy it anywhere, and it just works. What about mobile clients that want access through a secure shell (ssh) from home, a client site, or the airport? No problem! It doesn’t matter where the client is; trusted connectivity works from anywhere.

How is this achieved? When using OpenZiti, every endpoint (SDK for in-app access, tunnelers for the operating system or edge routers for the network) must have an identity with provisioned certificates. The certificates allow Ziti to perform authentication and authorization before any data flows across secure communications channels. These endpoints reach out of the private network to talk to the controller (control plane) and make connections to join the network fabric mesh (data plane). Therefore, services and endpoints in your private networks only make outbound connections—and thus, no holes are opened for inbound traffic.

An example of app with embedded zero trust

This example will use OpenZiti to provide the ZT overlay network and application SDKs. Spring Boot and Tomcat will host the service. The project will take about half an hour. You will need the following:

◉ Your favorite text editor or IDE

◉ JDK 11 or later

◉ Access to a Linux environment with the bash shell—or, for Windows users, a VM or Windows Subsystem for Linux 2 (WSL 2)

If you don’t have access to a Linux environment but wish to use it, you can grab a Linux VM from the Oracle Cloud free tier.

What is OpenZiti? It’s an open source project sponsored by NetFoundry. Oracle embraces open source and zero trust security, so Oracle partnered with NetFoundry to provide zero trust connections to applications, including Java applications, running in Oracle Cloud Infrastructure (OCI). NetFoundry’s Edge Router software is staged within the OCI Marketplace for deployment within any OCI region.

Get the code. The example code can be downloaded from here. Alternatively, you can clone it using Git with the following shell command:

git clone https://github.com/netfoundry/openziti-spring-boot

As with most Spring guides, you can start from scratch and complete each step, or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

Create the test network. This example will use a very simple OpenZiti network, shown in Figure 1.

Oracle Java Certification, Core Java, Java Tutorial and Material, Oracle Java Learning, Oracle Java Career, Java Jobs, Java Skills
Figure 1. A simple OpenZiti network

For this article, it isn’t important for you to fully understand the components of the OpenZiti network; however, there are two important things to know.

◉ The controller manages the network, and it’s responsible for the configuration, authentication, and authorization of components that connect to the OpenZiti network.
◉ The router delivers traffic from the client to the server and back again.

To explore the architecture a little deeper, see “Overview of a Ziti Network” or watch this 54-minute video on YouTube.

OpenZiti provides a script that contains set of shell functions that bootstrap the OpenZiti client and network. As with any script, it is a good idea to download it and look it over before adding it to your shell. After running the following instructions, leave this terminal window open, because you’ll need it to configure the network.

# Pull the shell extensions
wget -q https://raw.githubusercontent.com/openziti/ziti/release-next/quickstart/docker/image/ziti-cli-functions.sh

# Source the shell extensions
. ziti-cli-functions.sh

# Pull the latest Ziti CLI and put it on your shell's classpath
getLatestZiti yes

The shell script above includes a few functions to initialize a network. To start the OpenZiti network overlay, run the following in the same terminal window:

expressInstall
startZitiController
waitForController
startExpressEdgeRouter

What do those functions do?

◉ expressInstall creates cryptographic material and configuration files required to run an OpenZiti network.
◉ startZitiController starts the network controller.
◉ startExpressEdgerouter starts the edge router.

Log in to the new network. The OpenZiti network is now up and running. The next step is to log in to the controller and establish the administrative session that you will use to configure the example services and identities. ziti-cli-functions has the following function to do that:

zitiLogin

Configure the new network. Use a script to configure the OpenZiti network. The code for the example contains a network directory. To configure the network, run the following command in the same terminal you used to start the OpenZiti network:

./express-network-config.sh

If the script produces errors with a lot of ziti: command not found statements, run the following shell command to put ziti in your terminal path:

getLatestZiti yes

The script will write out the two identity files (client.json and private-service.json) needed for the Java code you’ll write shortly. Note: The repository includes a file called NETWORK-SETUP.md that explains what the script is doing and why.

Reset the Ziti demo network. If you wish to start over, these are the commands that need to be run to stop the Ziti network and clean up.

stopAllEdgeRouters
stopZitiController
unsetZitiEnv
rm -rf ~/.ziti/quickstart

Host a dark service using Spring Boot


Now, you’re at the good part! There are three things that need to be done to host an OpenZiti service in a Spring Boot application.

◉ Add the OpenZiti Spring Boot dependency.

◉ Add two properties to the service to configure the service identity and service name.

◉ Add an OpenZiti Tomcat customizer to the main application component scan.

The example code contains an initial/server project. Pull that up in your favorite editor and follow along.

Add the OpenZiti Spring Boot dependency. The OpenZiti Spring Boot dependency is hosted on Maven Central.

If you are using Gradle, add the following to build.gradle:

implementation 'org.openziti:ziti-springboot:0.23.12'

If you prefer Maven, add the following to pom.xml:

<dependency>
         <groupId>org.openziti</groupId>
         <artifactId>ziti-springboot</artifactId>
         <version>0.23.12</version>
</dependency>

Add application properties. Open the application properties file: src/main/resources/application.properties. The Tomcat customizer provided by OpenZiti needs an identity and the name of the service that the identity will bind. If you followed along with the network setup above, the values will be the following:

ziti.id = ../../network/private-service.json
ziti.serviceName = demo-service

Configure the OpenZiti Tomcat customizer. The Tomcat customizer replaces the standard socket protocol with an OpenZiti protocol that knows how to bind a service to accept connections over the Ziti network. To enable this adapter, open the main application class: com.example.restservice.RestServiceApplication. Then replace

@SpringBootApplication

with

@SpringBootApplication (scanBasePackageClasses = {ZitiTomcatCustomizer.class, GreetingController.class})

Run the application. The OpenZiti Java SDK will connect to the test network, authenticate, and bind your service so that other OpenZiti overlay network clients can connect to it.

If you use Gradle, enter the following in a terminal window in your project directory:

./gradlew bootRun

If you use Maven, run the following in a terminal window in your project directory:

./mvnw spring-boot:run

Test the new Spring Boot service. The Spring Boot service you just created is now totally dark, with no listening ports. You can verify this by using the following command in a terminal window:

netstat -anp | grep 8080

You should find nothing marked as LISTENING. Now, the only way to access the service is via the OpenZiti network. Let’s write a simple client to connect to the service and check that everything is working correctly.

Create a sample Java client application


This section will use the OpenZiti Java SDK to connect to the OpenZiti network. The example source code includes a project and a class that takes care of the boilerplate stuff for you.

Connect to OpenZiti. The Java SDK needs to be initialized with an OpenZiti identity. It is polite to destroy the context once the code is done, so you will wrap it up in a try-catch construct with a finally block. Here is the code.

ZitiContext zitiContext = null;
try {
  zitiContext = Ziti.newContext(identityFile, "".toCharArray());
  long end = System.currentTimeMillis() + 10000;

  while (null == zitiContext.getService(serviceName) && System.currentTimeMillis() < end) {
    log.info("Waiting for {} to become available", serviceName);
    Thread.sleep(200);
  }

  if (null == zitiContext.getService(serviceName)) {
    throw new IllegalArgumentException(String.format("Service %s is not available on the OpenZiti network",serviceName));
  }
} catch (Throwable t) {
  log.error("OpenZiti network test failed", t);
}
finally {
  if( null != zitiContext ) zitiContext.destroy();
}

What’s going on here?

◉ Ziti.newContext loads the OpenZiti identity and starts the connection process.

◉ while() inserts a delay. It can take a little while to establish the connection with the OpenZiti network fabric. For long-running applications, this is typically not a problem, but for this little client you need to give the network some time to get everything ready.

◉ zitiContext.destroy() disposes of the context and cleans up resources locally and on the OpenZiti network.

Send a request to the service. The client now has a connection to the test OpenZiti network. Now the client can ask OpenZiti to dial the service and send some data.

Important: This client is for demonstration purposes only! You should never, ever write a raw HTTP request like this in a real app. OpenZiti has a couple of examples on GitHub that use OKHttp and Netty if you want to work up this code using a real HTTP client.

log.info("Dialing service");
ZitiConnection conn = zitiContext.dial(serviceName);
String request = "GET /greeting?name=MyName HTTP/1.1\n" +
"Accept: */*\n" +
"Host: example.web\n" +
"\n";
log.info("Sending request");
conn.write(request.getBytes(StandardCharsets.UTF_8));

Here’s an explanation of what the code does.

◉ ZitiConnection is a socket connection over the OpenZiti network fabric that can be used to exchange data with a Ziti service.

◉ zitiContext.dial opens a connection through the OpenZiti network to the service.

◉ request is used because the connection is essentially a plain socket. The request string is a plain HTTP GET command to the greeting endpoint in the Spring Boot app.

◉ conn.write sends the request over the OpenZiti network.

Read the service response. The service will respond to the request with a JSON greeting. Read the greeting and write it to the log.

byte[] buff = new byte[1024];
int i;
log.info("Reading response");
while (0 < (i = conn.read(buff,0, buff.length))) {
 log.info("=== " + new String(buff, 0, i) );
}

What’s happening? conn.read reads the data sent back from the Spring Boot service via the OpenZiti connection.

Run the client. If you use Gradle, run the following in a terminal window in the client project:

./gradlew build run

If you use Maven, run the following in a terminal window in the client project:

./mvnw package exec:java

Source: oracle.com

Tuesday, April 5, 2022

Difference between Function.andThen and Function.compose

Core Java, Oracle Java Career, Java Skills, Java Job, Java Material

Here are two different ways to mix functions in Java:

◉ using andThen

◉ using compose

It is important to understand the difference between the two.

andThen: function1.andThen(function2) will first apply function1 to the input and the result of this will be passed to the function2.

compose: function1.compose(function2) will first apply the input to the function2 and the result of this will be passed to the function1

When they are used for operations that are not commutative then you will end up with totally different results.

You can see that in the example below:

Function<Double, Double> half = (a) -> a / 2;

Function<Double, Double> twice = (a) -> a * a;

Function<Double, Double> squareAndThenCube = half.andThen(twice);

Double result = squareAndThenCube.apply(3d);

System.out.println(result);

Function<Double, Double> squareComposeCube = half.compose(twice);

result = squareComposeCube.apply(3d);

System.out.println(result);

The output for the above will be:

Core Java, Oracle Java Career, Java Skills, Java Job, Java Material
Output

Source: javacodegeeks.com

Monday, April 4, 2022

The Main Differences Between Java (Latest) and Before

Good morning, and welcome to my talk about the new features in the latest version of Java/<insert your language here>.

You’re going to notice many new things:

◉ We now support something unimportant in strings, like emojis

◉ We’ve rewritten an API you don’t use

◉ We’ve added some optional language features you may one day find useful

◉ We’ve deprecated something you were kind of dependent on

◉ We’ve introduced some subtle bugs that you may struggle to notice… at first…

◉ None of your build tools are going to support this properly for a while

I hope you enjoyed my talk. Have a nice day.

Oracle Java Certification, Core Java, Oracle Java Learning, Oracle Java Preparation, Java Guides, Java Career, Java Skill

What do you mean “Get out of the TED studios”?

Recent Java 17 Fun

Java 17 is the latest LTS version of Java, and it’s probably a good idea to adopt it. However, some recent pains:

◉ Though Amazon has released Corretto 17, they don’t actually support Java 17 in:

   ◉ AWS Lambda Runtimes

   ◉ CodeBuild Runtimes

◉ We can work around this by NOT using language level 17 on Lambdas, and by using a docker image to build in CodeBuild… which adds more pain when you’re using docker in docker for testing:

docker run -v ~/.docker:/root/.docker -v /var/run/docker.sock:/var/run/docker.sock -e CODEARTIFACT_AUTH_TOKEN=${CODEARTIFACT_AUTH_TOKEN} -e SNYK_TOKEN=${SNYK_TOKEN} -e SONAR_TOKEN=${SONAR_TOKEN} -v $(pwd):/project -w=/project amazoncorretto:17 ./gradlew clean build snyk-test sonarqube -Dsonar.branch.name="${CURRENT_BRANCH}" -i

◉ You have to update Gradle, SpotBugs and Jacoco to latest to get things to work

◉ Once you’ve installed JDK 17 on your machine, you’re kind of committed to getting every build to be compatible with the runtime, otherwise you have to jump through hoops to do builds

◉ Without the latest SystemStubs, the environment variables hacks in tests are no longer possible

◉ There’s a date bug!

Java 17 Date Bug

We use a date parser in one of our projects. As luck would have it, one of our unit tests tried to parse the date 16-Sep-2020 and started to fail on JDK 17.

We could so easily have been testing with 16-Nov-2020 and not noticed the fact that in en-GB locales, Java 17 no longer supports Sep as an abbreviation for the date format MMM. This means that DateFormatter‘s parse method is effectively broken.

Aaaaagh!

Someone on StackOverflow explained this well enough that I could fathom a fix, which is to specialise the date formatter to Locale.ENGLISH which avoid the problem. However, it makes no sense to me at all that a MMM three character month abbreviation should randomly switch from 3 to 4 characters for a single month.

One argument is that Sept is a better abbreviation for the month in natural English. Another argument is that the first argument is talking out of its a-hole.

Source: javacodegeeks.com

Saturday, April 2, 2022

Docker Compose Java Healthcheck

Oracle Java HealthCheck, Core Java, Oracle Java Exam Prep, Oracle Java Learning, Oracle Java Tutorial and Materials

Docker compose is often used to run locally a development stack. Even if I would recommend to use minikube/microk8s/…​ + Yupiik Bundlebee, it is a valid option to get started quickly.

One trick is to handle dependencies between services.

A compose descriptor often looks like:

docker-compose.yaml

version: "3.9" (1)

services: (2)

  postgres: (3)

    image: postgres:14.2-alpine

    restart: always

    ports:

      - "5432:5432"

    environment:

      POSTGRES_USERNAME: postgres

      POSTGRES_PASSWORD: postgres

  my-app-1: (4)

    image: my-app

    restart: always

    ports:

      - "18080:8080"

  my-app-2: (4)

    image: my-app

    restart: always

    depends_on: (5)

      - my-app-1

1. the descriptor version

2. the list of services (often containers if there is no replicas)

3. some external images (often databases or transversal services like gateways)

4. custom application images

5. dependencies between images

for web services it is not recommended having dependencies between services but it is insanely useful if you have a batch provisioning your database and you want it to run only when a web service is ready. It is often the case if you have a Kubernetes CronJob calling one of your Deployment/Service.

Previous descriptor works but it can happen the web service is not fully started before the second app (simulating a batch/job) is launched.

To solve that we need to add a healthcheck on the first app and depend on the state of the application in the batch. Most of the examples will use curl or wget but it has the drawback to be forced to add these dependencies – and their dependencies – to the base image – don’t forget we want the image to be light – a bit for the size but generally more for security reasons – so that it shouldn’t be there.

So the overall trick will be to write a custom main based on plain Java – since we already have a Java application.

Here is what can look like the modified docker-compose.yaml file:

"my-app-1:

        ...

        healthcheck: (1)

          test: [

            "CMD-SHELL", (2)

            "_JAVA_OPTIONS=", (3)

            "java", "-cp", "/opt/app/libs/my-jar-*.jar", (4)

            "com.app.health.HealthCheck", (5)

            "http://localhost:8080/api/health" (6)

          ]

          interval: 30s

          timeout: 10s

          retries: 5

          start_period: 5s

 

    my-app-2:

        ...

        depends_on:

          my-app-1:

            condition: service_healthy (7)

1. we register a healthcheck for the web service

2. we use CMD-SHELL and not CMD to be able to set environment variables in the command

3. we force the base image _JAVA_OPTION to be resetted to avoid to inherit the environment of the service (in particular if there is some debug option there)

4. we set the java command to use the jar containing our healthcheck main

5. we set the custom main we will write

6. we reference the local container health endpoint

7. on the batch service, we add the condition that the application must be service_healthy which means we control the state with the /health endpoint we have in the first application (and generally it is sufficient since initializations happen before it is deployed)

Now, the only remaining step is to write this main com.app.health.HealthCheck. Here is a trivial main class:

package com.app.health;

import java.io.IOException;

import java.net.URI;

import java.net.http.HttpClient;

import java.net.http.HttpRequest;

import static java.net.http.HttpResponse.BodyHandlers.discarding;

public final class HealthCheck {

    private HealthCheck() {

        // no-op

    }

    public static void main(final String... args)

        throws IOException, InterruptedException {

        final var builder = HttpRequest.newBuilder()

                .GET()

                .uri(URI.create(args[0]));

        for (int i = 1; i < 1 + (args.length - 1) / 2; i++) {

            final var base = 2 * (i - 1) + 1;

            builder.header(args[base], args[base + 1]);

        }

        final var response = HttpClient.newHttpClient()

            .send(builder.build(), discarding());

        if (response.statusCode() < 200 || response.statusCode() > 299) {

            throw new IllegalStateException("Invalid status: HTTP " + response.statusCode());

        }

    }

}

Nothing crazy there, we just do a GET request on the based on the args of the main. What is important to note there is you control that logic since you code the healthcheck so you can also check a file is present for example.

Last but not least you have to ensure the jar containing this class is in your docker image (generally the class can be included in a app-common.jar) which will enable to reference it as classpath in the healthcheck command.

Indeed you can use any dependency you want if you also add them in the classpath of the healthcheck, but generally just using the JDK is more than sufficient and enables a simpler healthcheck command.

you can also build a dedicated healthcheck-main.jar archive and add it in your docker to use it directly. This option enables to set in the jar the Main-Class which provides your the facility to use java -jar healthcheck-main.jar <url>

Source: javacodegeeks.com

Friday, April 1, 2022

Using Byte Buddy for proxy creation

Core Java, Oracle Java Exam Prep, Oracle Java Learning, Oracle Java Preparation, Oracle Java Career, Java Skills, Java Jobs

With the increasing adoption of Java 17 and its strict encapsulation, several unmaintained libraries that rely on internal JVM APIs have stopped working. One of these libraries is cglib, the code generation library, which allows to create and load proxy classes during the runtime of a JVM process. And while there are alternatives to cglib that support Java 17, migration is not always straight-forward. To ease such migration, this article discusses how Byte Buddy can be used for proxy creation and what concept changes need to be considered during a migration.

General concept

Other than cglib, Byte Buddy does not offer an API that is dedicated to the creation of proxies. Instead, Byte Buddy offers a generic API for defining classes. While this might feel less convenient at first, it typically aids the evolution of existing code over time since the proxy class generation can be adjusted without constraints.

With Byte Buddy’s general API, a proxy is therefore created by defining a subclass of the targeted class, where all methods are overridden. Since Java methods are dispatched virtually, these overridden methods will be invoked instead of the original methods. In essence, cglib defines a proxy just like that.

As an example, consider creating a proxy of the following Sample class:

public class Sample {

  public String hello() {

    return "Hello World!";

  }

}

This Sample class can be proxied with Byte Buddy by overriding the hello method. A simple way of implementing this override is by using a MethodDelegation. A method delegation requires a delegation target, typically a class that defines a single static method. To interact with the overridden method, the method declares parameters which are annotated with the expected behavior. As an example, consider the following delegation target which mimics the parameters of cglib’s MethodInterceptor:

public class Interceptor {

  @RuntimeType

  public static Object intercept(@This Object self, 

                                 @Origin Method method, 

                                 @AllArguments Object[] args, 

                                 @SuperMethod Method superMethod) throws Throwable {

    return superMethod.invoke(self, args);

  }

}

As the annotations’ names suggest, the method accepts the intercepted. This instance, a description of the Origin method, AllArguments to the methods in form of an array, and a proxy to conduct a SuperCall to the original method implementation. With the above implementation, the interception simply invokes the original code which replicates the unproxied behavior. The method itself returns a RuntimeType as the returned value is cast to the actual return type which must be a String. If any other instance was returned, a ClassCastException would occur, just as with cglib.

With this Interceptor in place, Byte Buddy can create the proxy with only a few lines of code:

Class<?> type = new ByteBuddy()

  .subclass(Sample.class)

  .method(ElementMatchers.any()).intercept(MethodDelegation.to(Interceptor.class))

  .make()

  .load(Sample.class.getClassLoader())

  .getLoaded();

The resulting class can now be instantiated using the reflection API. By default, Byte Buddy mimics all constructors that the super class is declaring. In the above case, a default constructor will be made available as Sample also declares one.

Note that Byte Buddy always requires a specification of the methods to intercept. If multiple matchers are specified, each their delegation target would be considered in the reverse order of their specification. If all methods should be intercepted, the any-matcher captures all methods. By default, Byte Buddy does however ignore the Object::finalize method. All other Object methods like hashCode, equals or toString are proxied.

Caching proxied classes

With class creation and loading being expensive operations, cglib offers a built-in cache for its proxy classes. As key for this cache, cglib considers the shape of the proxy class and recognizes if it created a class with a compatible shape previously.

While this is convenient, this cache can quickly turn into a leaky abstraction that is sensitive to minor changes. Also, the caching mechanism is performing rather poorly due to its ambitious implementation of recognizing shapes. For this reason, Byte Buddy rather offers an explicit TypeCache and requires its user to specify a mechanism for identifying a cache key. When proxying a single class, the proxied

Class typically suffices as a key:

TypeCache<Class<?>> cache = new TypeCache<>();

Class<?> type = cache.findOrInsert(Sample.class.getClassLoader(), Sample.class, () -> {

  return new ByteBuddy()

    .subclass(Sample.class)

    .method(ElementMatchers.any()).intercept(MethodDelegation.to(Interceptor.class))

    .make()

    .load(Sample.class.getClassLoader())

    .getLoaded();

});

With this cache a new proxy class is only created if no proxy class was previously stored for Sample. As an optional, additional argument, a monitor object can be provided. This monitor is then locked during class creation to avoid that the same proxy is created concurrently by different threads. This can increase contention but avoids unnecessary class generation.

If more complex caching is required, a dedicated library should of course be used instead of the cache that Byte Buddy offers.

Abstract methods and default values

Until now, we assumed that all proxied methods are implemented by the proxied class. But Byte Buddy – just as cglib – also intercepts abstract methods that do not offer a super method implementation. To support intercepting such methods, the previous interceptor must be adjusted, as it currently requires a super method proxy via its parameters. By setting a property for the SuperMethod annotation, the parameter can be considered as optional.

public class Interceptor {

  @RuntimeType

  public static Object intercept(@This Object self, 

                                 @Origin Method method, 

                                 @AllArguments Object[] args, 

                                 @SuperMethod(nullIfImpossible = true) Method superMethod,

                                 @Empty Object defaultValue) throws Throwable {

    if (superMethod == null) {

      return defaultValue;

    }

    return superMethod.invoke(self, args);

  }

}

In case of intercepting an abstract method, the proxy for the super method is set to null. Additionally, Empty injects a suitable null value for the intercepted method’s return type. For methods that return a reference type, this value will be null. For a primitive return type, the correct primitive zero is injected.

Managing instance-specific interceptor state

In the previous example, the interceptor method is static. In principle, method delegation can also delegate to an instance with a non-static method, but this would likely defeat the caching mechanism if the state would be specific for each created proxy.

cglib’s cache works around this limitation, but cannot handle several corner cases where the cache might start failing after minor changes. Byte Buddy, on the other hand, relies on the user to manage the state explicitly, typically by adding a field via the defineField step, which can then be read by the interceptor:

TypeCache<Class<?>> cache = new TypeCache<>();

Class<?> type = cache.findOrInsert(Sample.class.getClassLoader(), Sample.class, () -> {

  return new ByteBuddy()

    .subclass(Sample.class)

    .defineField(InterceptorState.class, "state", Visibility.PUBLIC)

    .method(ElementMatchers.any()).intercept(MethodDelegation.to(Interceptor.class))

    .make()

    .load(Sample.class.getClassLoader())

    .getLoaded();

});

With this changed definition, any proxy instance can contain a designated instance of InterceptorState. The value can then be set via reflection or via a method handle.

Within the interceptor, this InterceptorState is accessible via an additional parameter with the FieldValue annotation which accepts the field’s name as its property. Doing so, the generated class itself remains stateless and can remain cached.

Handling non-default constructors

Byte Buddy creates valid, verifiable Java classes. As such, any class must invoke a constructor of its super class in its own constructors. For proxies, this can be inconvenient as a class without a default constructor might not be easily constructible. Some libraries like objenesis work around this limitation, but those libraries rely on JVM-internal API and their usage should be avoided.

As mentioned before, Byte Buddy replicates all visible constructors of a proxied class by default. But this behavior can be adjusted by specifying a ConstructorStrategy as a second argument to ByteBuddy::subclass. For example, it is possible to use ConstructorStrategy.ForDefaultConstructor which creates a default constructor by invoking a super constructor with default arguments for all parameters. As an example, considering the below ConstructorSample, Byte Buddy can define a default constructor for the proxy which provides null as an argument to the proxied super class:

public class ConstructorSample {

  private final String value;

  public ConstructorSample(String value) {

    this.value = value;

  }

  public String hello() {

    return "Hello " + value;

  }

}

The dynamic type builder is now created by:

new ByteBuddy().subclass(

  ConstructorSample.class, 

  new ConstructorStrategy.ForDefaultConstructor(ElementMatchers.takesArguments(String.class)));

Note that this approach would result in the proxied method returning Hello null as a result and that this might cause an exception during a constructor’s invocation if null is not considered a valid argument.

Class loading and modules

When Byte Buddy defines a class, it does not yet consider how this class will be loaded. Without any specification, Byte Buddy loads a proxy in a dedicated class loader that is a child of the class loader that is provided to the load method. While this is often convenient, creating a class loader is however an expensive operation which should be avoided, if possible. As a cheaper alternative, proxy classes should be injected into existing class loaders; normally into the one that loaded the class that is being proxied.

With Java 9, the JVM introduced an official API for class injection via MethodHandles.Lookup, and of course Byte Buddy supports this API. If Byte Buddy is however used on Java 8 or earlier, this strategy is not yet available. Typically, users fall back to using sun.misc.Unsafe, a JVM-internal API. As Java 8 does not yet encapsulate internal API and since sun.misc.Unsafe is available on most JVM implementations, this fallback does not normally render a problem.

A caveat of using MethodHandles.Lookup is its call site sensitivity. If Java modules are used, the instance must be created and provided by the module that owns the package of the proxied class. Therefore, the instance of MethodHandles.Lookup must be provided to Byte Buddy and cannot be created from within the library which represents a module of its own.

Byte Buddy configures class loading behavior by instances of ClassLoadingStrategy which can be passed as a second argument to the load method. To support most JVMs, Byte Buddy already offers a convenience method that resolves the best available injection strategy for a given JVM via:

ClassLoadingStrategy.UsingLookup.withFallback(() -> MethodHandles.lookup());

With the above strategy, a method handle lookup is used if possible and internal API is only used as a fallback. Since the method handles lookup is resolved within a lambda, it also represents the context of the module that is using Byte Buddy, assuming that this is the right module to define the proxy class. Alternatively, this Callable has to be passed from the right place. If the module system is not used, however, the above approach is normally sufficient as all classes are likely located within the unnamed module of the same class loader.

Avoiding runtime proxies with build-time instrumentation

With a rising interest for Graal and AOT compilation of Java programs in general, the creation of runtime proxies has fallen somewhat out of fashion. Of course, when running a native program without a byte code-processing JVM, classes cannot be created during runtime. Fortunately, proxies can often be created during build time instead.

For build-time code generation, Byte Buddy offers a Maven and a Gradle plugin which allow for the application of Plugin instances that manipulate and create classes before runtime. For other build tools, Byte Buddy also offers a Plugin.Engine as part of Byte Buddy which can be invoked directly. As a matter of fact, the byte-buddy artifact even contains a manifest that allows for using the jar file as an invokable of the plugin engine.

To implement a plugin for creating proxies, the proxy creator needs to implement Byte Buddy’s Plugin and Plugin.Factory interfaces. A plugin specifies what classes to instrument and how the instrumentation should be applied. For an easy example, the following plugin creates a proxy for the Sample class and adds the name of this proxy as an assumed annotation ProxyType onto the Sample class:

public class SamplePlugin implements Plugin, Plugin.Factory {

  @Override

  public boolean matches(TypeDescription type) { 

    return type.getName().equals("pkg.Simple");

  }

  @Override

  public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, 

                                            TypeDescription typeDescription, 

                                            ClassFileLocator classFileLocator) {

    DynamicType helper = new ByteBuddy()

      .subclass(typeDescription)

      .defineField(InterceptorState.class, "state", Visibility.PUBLIC)

      .method(ElementMatchers.any()).intercept(MethodDelegation.to(Interceptor.class))

      .make();

    return builder

      .require(helper)

      .annotateType(AnnotationDescription.Builder.ofType(ProxyType.class)

        .define("value", helper.getTypeDescription().getName())

        .build());

  }

  @Override

  public void close() { }

  @Override

  public Plugin make() { return this; }

}

With the annotation in place, the runtime can now check for the existence of a build-time proxy and avoid code generation altogether in such a case:

TypeCache<Class<?>> cache = new TypeCache<>();

Class<?> type = cache.findOrInsert(Sample.class.getClassLoader(), Sample.class, () -> {

  ProxyType proxy = Sample.class.getAnnotation(ProxyType.class);

  if (proxy != null) {

    return proxy.value();

  }

  return new ByteBuddy()

    .subclass(Sample.class)

    .defineField(InterceptorState.class, "state", Visibility.PUBLIC)

    .method(ElementMatchers.any()).intercept(MethodDelegation.to(Interceptor.class))

    .make()

    .load(Sample.class.getClassLoader())

    .getLoaded();

});

An advantage of this approach is that the usage of the build-time plugin remains entirely optional. This allows for faster builds that only execute tests but do not create artifacts, and allows users that do not intend to AOT-compile their code to run their applications without an explicit build setup.

Note that a future version of Byte Buddy will likely make the use of Graal even easier by discovering and preparing runtime-generated classes when the Graal configuration agent is used. For performance reasons, using an explicit build tool is however expected to remain the most performant option. Do however note that this approach is somewhat restricted to classes of the compiled project since external dependencies are not processed by a build tool.

Inline proxy code without subclasses

With the above approach, the created proxies still require the use of reflection to create instances of the proxy. For an even more ambitious setup, Byte Buddy offers the Advice mechanism to change the code of classes directly. Advice is normally often used for the decoration of methods and a popular choice when developing Java agents. But it can also be used to emulate proxy behavior without creating a subclass.

As an example, the following advice class records the execution time of a method by declaring actions that are to be performed prior to invoking a method as well as after it. Advice offers similar annotations to MethodDelegation, be careful to not confuse those annotations as they are declared by different packages.

To emulate the previous behavior of the Interceptor, the following Decorator functions similarly to it. Note that the Decorator declares a set of proxies to recognize what instances are to be treated as proxies and which instances should function as if they were not proxied. Within the OnMethodEnter annotation, it is specified that the original code is skipped if a non-null value is returned.

public class Decorator {

  static final Set<Object> PROXIES = new HashSet<>();

  @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)

  public static Object enter(

    @Advice.This Object self,

    @Advice.Origin Method method,

    @Advice.AllArguments Object[] arguments) throws Throwable {

   if (PROXIES.contains(self)) {

     return ProxyHandler.handle(self, method, arguments);

    } else {

      return null;

    }

  }

  @Advice.OnMethodExit

  public static void exit(

      @Advice.Enter Object enter,

      @Advice.Exit(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object returned) {

    if (enter != null) {

      returned = enter;

    }

  }

}

With this code, the original method can be invoked by temporarily removing the instance from the proxy set within the ProxyHandler.

Object returned;

Decorator.PROXIES.remove(self);

try {

  returned = method.invoke(self, arguments);

} finally {

  Decorator.PROXIES.add(self);

}

Note that this is a naive approach which will fail if the proxy is used concurrently. If a proxy needs to be thread-safe, it is normally required to define a thread-local set that contains temporarily disabled proxies.

Of course, it is not normally possible to apply this decoration during a JVMs runtime, but only at build-time, unless a Java agent is used. To still allow for a fallback-implementation, Byte Buddy does however allow for Advice being used as both decorator:

new ByteBuddy().redefine(Sample.class)

  .visit(Advice.to(Decorator.class).on(ElementMatchers.isMethod()))

  .make();

and as an interceptor for creating a subclass proxy:

new ByteBuddy().subclass(Sample.class)

  .method(ElementMatchers.isMethod())

  .intercept(Advice.to(Decorator.class))

  .make();

In this case, a build-time plugin can avoid a subclass creation where this is necessary. For example, it allows for proxying final classes or methods, if this should be supported. At the same time, inline proxies cannot proxy native methods.

Replacing other cglib utilities

cglib contains a row of other class generation utilities besides the Enhancer.

The good news is that most of this functionality has become obsolete. Immutable beans are less useful today as it has become much more common to model immutable objects by for example records. And similarly other bean utilities have found better equivalents in today’s Java, especially since method and var handles have entered the stage. Especially cglib’s FastMethod and FastClass utilities are no longer useful as reflection and method handles have passed the performance that is offered by these code generation tools.

Source: javacodegeeks.com