Java is a joy for backend work. It not only performs well, but also has simmered a long time in the industry, marking it as a battle-tested and solid platform.
What helps making Java so highly efficient and strong as a server-side language, is that it is fully customizable. You can build you application from nearly every combination of available libraries and select only the functionality you want.
This has its advantages. Though it also makes every project some sort of Lego building task. Unfortunately, it does not quite work like with off-the-shelf Lego sets. To use the feature you like or need, you need to pick a package. This gets quite into detail going so far that for a server to correctly running, you need to pick a XML/JSON parser.
To provide a step-by-step guide to your first app, we will, in this series, look at how we can
- configure a server (embedded Jetty)
- add some REST API to it (Jersey)
- provide a database (Hibernate)
- inject it with DI
- Provide a small ui with Vue.js
Create Project
As a build and dependency tool for our project, we will use Gradle. This will allow us to specify dependencies we need for our project. A project using Gradle has the following structure:
src
- main
- java
- resources
- test
- java
- main
build.gradle
settings.gradle
In the src directory we will put all our source. Here we have a subdirectory for our tests - test - **and another one for our application - main. In each of the two is a directory for Java code called **java and for additional file resources resources.
In build.gradle we store all our project configuration and in settings.gradle we will configure our project.
Here is the build.gradle used for this series:
plugins {
id 'java'
}
group 'JettyServer'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.28'
compile 'org.eclipse.jetty:jetty-server:9.4.19.v20190610'
compile 'org.eclipse.jetty:jetty-servlet:9.4.19.v20190610'
compile 'org.glassfish.jersey.inject:jersey-hk2:2.28'
compile 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2'
runtime 'org.glassfish.jaxb:jaxb-runtime:2.3.2'
compile 'org.eclipse.jetty:jetty-servlets:9.4.19.v20190610'
compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.28'
compile 'org.hibernate:hibernate-core:5.4.4.Final'
compile 'org.xerial:sqlite-jdbc:3.20.1'
compile 'com.zsoltfabok:sqlite-dialect:1.0'
}
Create a server in Java
Some of you may have already had experiences with other web technologies like Rails, Flask, Express.js, etc. In these languages the creation of the server is mostly hidden from you. You may create an object and initialize it or everything just runs out of the box. With our server, it is not like that. But let's see:
At first, we need our Main class with the main method, here we will put all our start code.
public class Main {
public static void main(String[] args) throws Exception {
}
}
First, we will need a Queue holding all the threads we will use to handle requests. Be careful, if you make it too large, it will use up all your system resources, if you make it too small, it will not handle all your load. In our case, we will give it a start capacity of 20, let it grow by 2 and limit the queue to 80. We will provide this queue to our Thread Pool, it will handle the distribution of jobs to our queue.
public static void main(String[] args) {
//Queue initialization
BlockingQueue<Runnable> threadPoolQueue = new BlockingArrayQueue<>(20, 2, 80);
// Create ThreadPool for the server to use.
// default idle time as defined in the QueuedThreadPool.
QueuedThreadPool threadPool = new QueuedThreadPool(20, 2, (int) TimeUnit.MINUTES.toMillis(1), threadPoolQueue);
threadPool.setName("jetty-test-server");
threadPool.setDaemon(true);
}
Now that we have our system resources set, we need to create a Server. We will do this by creating an instance of the Server class and a ServerConnector class. The connector will open the server to the network.
// Instance of server object - adds the threadPool to use
Server server = new Server(threadPool);
// instance of server connector with server instance to handle connections for
ServerConnector connector = new ServerConnector(server);
// set port to use
connector.setPort(8080);
// add connector to server
server.setConnectors(new Connector[]{connector});
Next we add a ServletContextHandler for our REST-Resources. This context handler is responsible to provide an environment for execution of the Request Handler methods. This includes Request, Response, Session and other important resources.
final ServletContextHandler servletContextHandler = new ServletContextHandler();
servletContextHandler.setContextPath("/");
The ContextHandler is set up. The next step is to provide a ServletHolder for the ContextHandler to operate on. A ServletHolder is used for state management and for correctly handling paths. It contains a ServletContainer that is itself just an instance of a Servlet (a resource callable by the server) that is being initialized with all Resources we want to use in our Application.
// Holds all the resources we want to register (currently, there's nothing to do)
ResourceConfig resourceConfig = new ResourceConfig();
// handles state for servlets
final ServletHolder servletHolder = new ServletHolder(new ServletContainer(resourceConfig));
servletContextHandler.addServlet(servletHolder, "/*");
When we have our ServletContextHandler initialized, it is time to provide it to our server, so that it can delegate requests to it. For this, we will use an instance of HandlerList and initialize it with our ServletContextHandler and pass it to our server.
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] {servletContextHandler});
server.setHandler(handlers);
We got everything in place (Yaaaay :D). Let's start our server:
server.start();
server.join();
It now runs on port 8080.
Top comments (0)