Lets build some services
This is a maven library, so we're going to start with a pom.xml file in the root directory.
You can read up on maven here
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <properties> <guicedee.version>1.0.15.0-jre14</guicedee.version> <maven.compiler.release>14</maven.compiler.release> </properties> </project>Now we have add the BOM (Bill of Materials) to easily reference our libraries
<dependencyManagement> <dependency> <groupId>com.guicedee</groupId> <artifactId>guicedee-bom</artifactId> <version>${guicedee.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencyManagement>
Nothing out of the ordinary. Alright - Lets create a mavbackend. If you need help configuring the maven compiler for JDK 14 take a look here
We are going to add the rest depedency into the POM file
<dependency> <groupId>com.guicedee.servlets</groupId> <artifactId>guiced-rest-services</artifactId> </dependency>
Create your module-info, referencing rest services
module my.example { requires com.guicedee.guicedservlets.rest; }
Ok. Still here? Now we make our service.
Lets put it in a package my.example?
We'll add some functionality. How about we return a hello world, as well as an object to say hi?
@Path("hello") @Produces("application/json") public class HelloResource { private final Greeter greeter; @Inject public HelloResource(final Greeter greeter) { this.greeter = greeter; } @GET @Path("{name}") public String hello(@PathParam("name") final String name) { return greeter.greet(name); } @GET @Path("helloObject/{name}") public ReturnableObject helloObject(@PathParam("name") final String name) { return new ReturnableObject().setName(name); } }
Hmm. Time for that ReturnableObject....
public class ReturnableObject { private String name; public ReturnableObject() { } public String getName() { return name; } public ReturnableObject setName(String name) { this.name = name; return this; } }
Ok Ok - How about we actually get down to an actual service?!? When are we going to run this thing?
But we still need to bind an injection for Greeter right? Inversion of Control and things...
Let's put together a binding, first we need our interface.. Greeter
public interface Greeter { public String greet(final String name); }Now our implementation of Greeter :-
public class DefaultGreeter implements Greeter { public String greet(final String name) { return "Hello " + name; } }We need a binding module to map
DefaultGreeter
to Greeter
. Notice there is no @LocalBean
or stateless.
You can read up about the differences between Guice and Spring at this location, as well as an example from baeldung over here. Maybe this gives a good idea on how to use the library if you have used something like this before?
public class HelloWorldBinding extends AbstractModule implements IGuiceModule<HelloWorldBinding> { @Override protected void configure() { bind(Greeter.class).to(DefaultGreeter.class); } }
Shew! almost there! Let's provide, and open our package
module my.example { requires com.guicedee.guicedservlets.rest; provides IGuiceModule with HelloWorldBinding; opens my.example to com.google.guice, com.fasterxml.jackson.databind, org.apache.cxf; }
Let's run this guy. We're using Jackson so we want to add it as a provider to cxf
public class BootMyFirstExample { public static void main(String... args) throws Exception { RESTContext.getProviders() .add(JacksonJsonProvider.class.getCanonicalName()); //lets not scan everything in the path shall we? GuiceContext.instance() .getConfig() .setExcludeModulesAndJars(true); //Kick her off Undertow undertow = GuicedUndertow.boot("0.0.0.0", 6003); } }Right let's run it and browse to http://localhost:6003/rest/hello/world
What about our object receiving? http://localhost:6003/rest/hello/helloObject/world
And not a single xml file.
Testing
That's not a complete example is it. I mean, we still need to unit this, and make sure what we get back is right? (yes and persistence and servlets and frontend. This library does it all)@Test public void testMyHelloWorld() { Undertow undertow = GuicedUndertow.boot("0.0.0.0", 6003); //It is deployed //Using JDK 9 and up native clients? Yeah sure why not. HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.of(5, ChronoUnit.SECONDS)) .build(); HttpResponse response = client.send(HttpRequest.newBuilder() .GET() .uri(new URI("http://localhost:6003/rest/hello/world")) .build(), HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); response = client.send(HttpRequest.newBuilder() .GET() .uri(new URI("http://localhost:6003/rest/hello/helloObject/world")) .build(), HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); undertow.stop(); }
Deploying JLink
Now we make a deployable solution, one optimized to run at maximum performance.We can create a distributable as a standalone jar, I suppose - but classpaths are slow in comparison to JRT.
I'll put both examples here.
First, a JLink zip file with a JDK 14 image
This means we need a multi-module project structure. You can see how to structure your projects here.
Let's create our distributable
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>my.examples</groupId> <artifactId>my-first-distribution</artifactId> <version>0.0.0_0-SNAPSHOT</version> </parent> <artifactId>my-example-jlink</artifactId> <packaging>jlink</packaging> <name>My Example Distribution</name> <pluginRepositories> <pluginRepository> <id>apache.snapshots</id> <url>http://repository.apache.org/snapshots/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> <dependencies> <dependency> <groupId>my.examples</groupId> <artifactId>my-example</artifactId> <version>${project.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-jlink-plugin</artifactId> <version>3.0.0-alpha-2-SNAPSHOT</version> <extensions>true</extensions> <configuration> <launcher>launch=my.example/my.example.BootMyFirstExample </launcher> <finalName>my-example-jlink.${project.version}</finalName> </configuration> <dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>8.0.1</version> </dependency> </dependencies> </plugin> </plugins> </build> </project>
Then run a clean and install on the jlink project
mvn clean install
You now have a Java Native Runtime to deploy anywhere. Cloud, local, teamcity octopus take your pick.
First time is always a bit.... with everything!
Hello World by default doesn't really do much does it?
You could have written this entire program like this... But that's how you get sunk ito complexity building solutions that aren't transparent for an enterprise environment isn't it?
@Path("hello") @Produces("application/json") public class HelloWorld { @GET @Path("{name}") public String hello(@PathParam("name") final String name) { return "Hello! " + name; } public static void main(String[] args) throws Exception { GuiceContext.instance().getConfig().setServiceLoadWithClassPath(true); GuicedUndertow.boot("0.0.0.0", 6003); } }