There is a reason why we have not examined a web deployment context file up until now. With Java Servlet 3.1, you do not have to define a context file, because developers can choose to write Servlets using the annotations, and web container can work out how to build the metadata for the Servlet at the deployment time.
It is time to examine a sample Java Servlet and we will introduce the annotations. The code for SimpleServlet
is as follows:
package je7hb.servlets.simple; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; @WebServlet("/simple") public class SimpleServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain"); PrintWriter pwriter = resp.getWriter(); pwriter.printf("This is the class `%s' "+ "The date time is %s ", this.getClass().getName(), new Date()); } }
The preceding code is perhaps the easiest Servlet that you can develop and deploy to an application server or web container.
The SimpleServlet
subclasses the abstract base class HttpServlet
, and chooses to override the
doGet()
method, which handles the HTTP GET protocol request. The Servlet class is also annotated with @javax.servlet.annotation.WebServlet
. This is one of many new annotations since Servlet 3.0 and the Java EE 7 specifications.
The doGet()
method takes two parameters, namely HttpServletRequest
and HttpServletResponse
. With the response object, Servlets sets the content type of the response to text/plain. This should only be set once per response for a given request. The method retrieves a PrintWriter
instance for the response. The getWriter()
method is one of the many convenient methods in HttpServletReponse
. Servlet generates the output of the Servlet's full class name, and the current data and time.
By the way the HttpServletResponse
interface also has a getOutputStream()
method that returns java.io.OutputStream
. This is useful for streaming the output applications. Let me warn you to decide on using either OutputStream
or Writer
. Once the output has been pushed to the HTTP response then the Servlet framework prevents the application from changing the content type mid-flow.
Annotating a class with @WebServlet
, unfortunately, is not a free-for-all. A Java class annotated with @WebServlet
must be a subclass of HttpServlet
. The annotation serves to purely define a Servlet component in a web application. In particular, a Servlet can have a unique name and can respond to a set of URL patterns. In the SimpleServlet
code, we are declaring Servlet with the name as simple
.
In order to access this Servlet on a web container, for example, we would specify a URL like the following URL:
http://<host>[:<port>]/<web-app>/simple
Where host
is the hostname of the server, port
is the port number, and web-app
specifies the name of the web application.
A table of the attribute definitions for the
@WebServlet
annotation is as follows:
Attribute |
Type |
Description |
Default Value |
---|---|---|---|
|
|
Defines the name of Servlet. |
Empty |
|
|
Defines the URL patterns of Servlet. |
None |
|
|
Defines the URL patterns of Servlet. | |
|
|
Specifies the load-on-startup order of Servlet. |
-1 |
|
|
The initial parameters of Servlet. |
None |
|
|
Specifies if Servlet supports asynchronous operations and processing or not. See also the |
false |
|
|
The display name of Servlet |
Empty string |
|
|
The description for a Servlet |
Empty string |
|
|
Specifies a path to a small icon |
None |
|
|
Specifies a path to a large icon |
None |
According to the preceding information, we could have annotated Servlet with specific URL patterns and given it internally, a different name. Some additional usages of the @WebServlet
annotations with the same Servlet are as follows:
@WebServlet(urlPatterns = {"/simple"}, name = "simple") public class SimpleServlet extends HttpServlet {/*... */} // For example, http://localhost:8080/mywebapp/simple
This annotation declares the exact same Servlet configuration as the previous example code. SimpleServlet
will be invoked by web container with any URL pattern matching /simple
. The URL patterns are exact and therefore, the preceding pattern does not match the URL /simple/1
, which presumably would be useless in a RESTful application. If you want to match anything after the forward slash (/
) then you must use something like the following code:
@WebServlet(urlPatterns = {"/simple/*"}, name = "simple") public class SimpleServlet extends HttpServlet {/*... */} // For example, http://localhost:8080/mywebapp/simple/1 // For example, http://localhost:8080/mywebapp/simple/glasgow
What if we wanted to configure Servlet to respond to certain names? We can achieve this requirement with two URL patterns like the following:
@WebServlet(urlPatterns = {"/sarah","/paul"}, name = "simple") public class SimpleServlet extends HttpServlet {/*... */} // For example, http://localhost:8080/mywebapp/sarah// or http://localhost:8080/mywebapp/paul
Web container only invokes SimpleServlet
when the incoming URL request matches the pattern. The forward slash characters (/
) at the beginning of the patterns are significant. They serve to demarcate the URL paths.
See the Miscellaneous features section of this chapter, for further information on the URL patterns.
Now we have a Java Servlet, so what do we need to install it onto a web container? The answer is that we have to assemble it as a compiled Java class into a WAR file, a web application archive. So let's use Gradle to do it, and we are going to deploy it onto a GlassFish application server, but in this chapter, we make use of building a so-called container-less web application. This is just a fancy name for running an embedded web container or application server from the standard entry into a Java application, the JVM invokes main(String args[])
.
The Gradle build for the SimpleServlet
example is as follows:
apply plugin: 'java' apply plugin: 'war' apply plugin: 'maven' apply plugin: 'eclipse' apply plugin: 'idea' group = 'com.javaeehandbook.book1' archivesBaseName = 'ch06-servlets-basic' version = '1.0' repositories { mavenCentral() maven { url 'https://maven.java.net/content/groups/promoted' } maven { url 'http://repository.jboss.org/nexus/content/groups/public' } } dependencies { providedCompile 'org.glassfish.main.extras: glassfish-embedded-all: 4.0.1-b01' compile 'org.glassfish.main.extras: glassfish-embedded-all: 4.0.1-b01' compile 'javax: javaee-api: 7.0' testCompile 'junit: junit: 4.11' } war { // webXml = file("src/main/webapp/WEB-INF/web.xml") } // Override Gradle defaults - a force an exploded JAR view sourceSets { main { output.resourcesDir = 'build/classes/main' output.classesDir = 'build/classes/main' } test { output.resourcesDir = 'build/classes/test' output.classesDir = 'build/classes/test' } } task(run, dependsOn: 'classes', type: JavaExec) { description = 'Runs the main application' main = 'je7hb.common.webcontainer.embedded.glassfish.EmbeddedRunner' classpath = sourceSets.main.runtimeClasspath args 'Mary', 'Peter', 'Jane' standardInput = System.in }
In the build file, the most crucial dependency is glassfish-embedded-all
; it has to be the very first dependency, otherwise the execution of the embedded runner fails with a ValidationException
exception. The exception happens because the GlassFish application serve, uses the Hibernate Validator as the default Bean Validation component. Luckily, Gradle does pay attention to the order of the dependencies, because the embedded runner works from the command line and also from an IDE.
The build file adds some additional repositories, the Maven GlassFish Promoted and the JBoss Public sites.
Because we have a Servlet in the application, Gradle applies the War plugin, which generates a WAR file for ourselves in the folder build/libs
. The extra configuration of the project dependencies named as providedCompile
ensures that the embedded glassfish dependency is not included into the WAR file.
Finally, in the Gradle task named run
, that is a type of the Java execution task, we override the standard input to System.in
, because we want the embedded runner, which we will see shortly, to respond to the console input.
Let us move on to the GlassFish embedded runner.
In order to go and demonstrate the container-less application, the following example uses the GlassFish embedded API, at the time of writing. Since GlassFish is the reference implementation to Java EE 7, we should see how to invoke it, start the server, stop the server, and most importantly deploy a web application to the server.
It is possible to go container-less with other servers, such as Apache Tomcat, Jetty, and Caucho Resin. The book does not cover those approaches, however, and there is sure to documentation online that explains how to configure those containers for Java EE 7 and Servlets 3.1.
GlassFish has well documented embedded document online since Version 3.1.2 and this is the information we have used in the code. Fortunately, the API is still compatible with GlassFish 4.0.1 at the time of writing, which means we can use it.
The code for
EmbeddedRunner
is as follows:
package je7hb.common.webcontainer.embedded.glassfish; import org.glassfish.embeddable.*; import java.io.File; import java.util.Scanner; import java.util.concurrent.atomic.AtomicBoolean; public class EmbeddedRunner { private int port; private AtomicBoolean initialized = new AtomicBoolean(); private GlassFish glassfish; public EmbeddedRunner(int port) { this.port = port; } public EmbeddedRunner init() throws Exception{ if (initialized.get()) { throw new RuntimeException("runner was already initialized"); } BootstrapProperties bootstrapProperties = new BootstrapProperties(); GlassFishRuntime glassfishRuntime = GlassFishRuntime.bootstrap(bootstrapProperties); GlassFishProperties glassfishProperties = new GlassFishProperties(); glassfishProperties.setPort("http-listener", port); glassfish = glassfishRuntime.newGlassFish(glassfishProperties); initialized.set(true); return this; } private void check() { if (!initialized.get()) { throw new RuntimeException("runner was not initialised"); } } public EmbeddedRunner start() throws Exception { check(); glassfish.start(); return this; } public EmbeddedRunner stop() throws Exception { check(); glassfish.stop(); return this; } public EmbeddedRunner deployWithRename(String war, String newContext ) throws Exception { Deployer deployer = glassfish.getDeployer(); deployer.deploy(new File(war), "--name="+newContext, "--contextroot = "+newContext, "--force=true"); return this; } public static void main(String args[]) throws Exception { EmbeddedRunner runner = new EmbeddedRunner(8080).init().start(); runner.deployWithRename("build/libs/ch06-servlets-basic-1.0.war", "mywebapp"); Thread.sleep(1000); System.out.printf("**** Press the ENTER key to stop "+"the server ****"); Scanner sc = new Scanner(System.in); while(!sc.nextLine().equals("")); runner.stop(); } }
As you can see in the program,
EmbeddedRunner
looks like a barrel and stock Java application. In the main()
program, we instantiate the object with a port number 8080, initialize the runner, and start the server. The program then proceeds to deploy a WAR file from the build, it delays for one second, and then waits for the user to type ENTER
. As soon as the user does it, the program stops the server, and exits normally.
The GlassFish server is launched by initializing an instance with two types of properties, namely bootstrap
and standard
. These properties help developers to configure special GlassFish-only features. The bootstrap
properties can be used to configure an installation root, if the server or launch space already has a version of GlassFish installed. For this example, we do not use this feature. The standard properties can configure the instance root, the configuration file locally of a particular domain area, and also services and port numbers.
In EmbeddedRunner
, we configure only the HTTP listener for the embedded server, which is passed into the init()
method as 8080. Having created a GlassfishRuntime
instance, the runner invokes a new Glassfish object instance, which represents the application server.
The methods start()
and stop()
, start and stop the application server respectively.
The deployWithRename()
method is a convenient method that deploys the simple Servlet WAR file to the server, and it does a unique rename of the web context from the clumsy ch06-servlets-basic-1.0
, which by default is the base name of the WAR file without the suffix. In this case, the web application context is renamed mywebapp
.
Now that we have a Java Servlet, a Gradle build file, and an embedded runner application, we can invoke it, and run the program. Here is how, with the following Gradle commands:
$ Gradle clean
$ Gradle war
$ Gradle run
Enter in your favorite web browser URL http://localhost:8080/mywebapp/simple
. The output on the page should be something like the following:
This is the class `je7hb.servlets.simple.SimpleServlet' The date time is Sat Feb 02 16:45:52 GMT 2013
To complete this section, here is a different version of the same Servlet, but this time we configure the initialization parameters through the annotations. This Servlet is called SimpleServletWithInitParams
.
package je7hb.servlets.simple; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; // Additional imports from before are omitted @WebServlet(name = "servletWithInitParams", urlPatterns = {"/initparams"}, initParams = {@WebInitParam(name = "source", value = "East Croydon"), @WebInitParam(name = "target", value = "London Bridge"), @WebInitParam(name = "time", value = "11:57:00")}) public class SimpleServletWithInitParams extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain"); PrintWriter pwriter = resp.getWriter(); pwriter.printf("This is the class `%s' The date "+ "time is %s ", this.getClass().getName(), new Date()); for ( String name: Collections.list(this.getServletConfig().getInitParameterNames())) { pwriter.printf("init parameter: %s = %s ", name, getServletConfig().getInitParameter(name)); } } }
The extra bit of code in this example retrieves java.util.Enumeration
of the Servlet initialization parameters. In the Servlet method, we call the utility static list()
method of java.util.Collections
to turn the Enumeration
into java.util.Iterator<String>
. (This is an artifact from Java's history when the Servlet API, which was created in 1999, existed long before the release of Java SE 5, generic types, and annotations in 2005!) We then iterate through the parameters and dump their values to the Servlet's response, the output buffer, using the PrintWriter
instance.
When running the embedded runner, if you invoke this Servlet with the quasi URL http://localhost:8080/mywebapp/initparams
, you should see the output like the following:
This is the class `je7hb.servlets.simple.SimpleServletWithInitParams' The date time is Mon Feb 04 20:04:58 GMT 2013 init parameter: time = 11:57:00 init parameter: source = East Croydon init parameter: target = London Bridge
The @WebInitParam
annotation accepts three attributes: name
, value
, and description
. The name
attribute specifies the Servlet's initialization parameter name, value
is the associated value, and description
is self-explanatory.
Congratulations, this is your first Java Servlet! Best of all, you did not have to specify a web XML deployment context, better known as a web.xml
file.
3.14.142.194