We will use two spring configuration files. The first will be used to configure Jetty. The second will be used to configure the application itself. The reason we split them up is to allow us to keep the Application Server setup separate from the Web Service setup. This will make it easier for us to deploy the project on a different platform in future. This post will only focus on the Jetty configuration.
Getting Started
The first step is to install the Eclipse Spring Tool Suite. This will give us auto-complete capabilities on our Spring XML files, amongst other things. Install it by typing Spring in the Eclipse market place (Help > Eclipse Marketplace) and selecting the Spring Tool Suite.Next we will create our Jetty Configuration file. We will then move the Jetty configuration out of our code. This will allow the end user to configure the desired properties of the Embedded Web Server.
Assuming the Spring Tool Suite has been installed, the following steps may be used to create a configuration file. Otherwise just create a plain xml file and copy the code segment below into it:
- Right-Click the /src/main/resources resource.
- Select File > New > Other
- Select Spring > Spring Bean Configuration File and click Next
- Enter jetty-config.xml for the filename and click Finish
We are now left with the basic Spring Configuration file.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
We also need to add the Spring Framework to our POM. The Spring Framework uses the Apache Commons Logging. We can see this in the maven site or we can look at our POM dependency tree in Eclipse. This was explained in the logging post. Remember we want to use SLF4J as our logging facade. We already have the JCL package setup, all we need to do is exclude the logging package from the Spring Framework. We need to add three Spring Framework components, Core, Beans and context. The Context component depends on Core and Beans, so we only need to add the one dependency to the POM.
The sections in bold were added to include the Spring Framework while still allowing us to use SLF4J.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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>
<groupId>net.sf.pbu.ontrack</groupId>
<artifactId>ontrack</artifactId>
<version>0.0.1</version>
<packaging>war</packaging>
<name>ontrack</name>
<url>http://sourceforge.net/projects/pbuontrack/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.apache.commons.lang3.version>3.3.2</org.apache.commons.lang3.version>
<org.apache.myfaces.core.version>2.2.7</org.apache.myfaces.core.version>
<org.springframework.version>4.1.3.RELEASE</org.springframework.version>
<org.eclipse.jetty.version>9.3.0.M1</org.eclipse.jetty.version>
<ch.qos.logback.version>1.1.2</ch.qos.logback.version>
<org.slf4j.version>1.7.6</org.slf4j.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${org.eclipse.jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${org.eclipse.jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${org.eclipse.jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${org.eclipse.jetty.version}</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-api</artifactId>
<version>${org.apache.myfaces.core.version}</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
<version>${org.apache.myfaces.core.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${ch.qos.logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${ch.qos.logback.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${org.apache.commons.lang3.version}</version>
</dependency>
</dependencies>
</project>
You will notice one other dependency made the cut. Apache Commons Lang will be used in our implementation. It is not part of the Spring Framework.
Moving Code to Configuration
We will be moving the bulk of our main configuration into the Spring configuration file. Open the Main.java file. Just a reminder, the Main class in our application looks like this:
package net.sf.pbu.ontrack.main;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
public class Main {
private static final int PORT = 8080;
public static void main(String[] args) throws Exception {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
Logger logger = LoggerFactory.getLogger(Main.class);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
webAppContext.setResourceBase("src/main/webapp/");
webAppContext.setDescriptor("src/main/webapp/WEB-INF/web.xml");
Server server = new Server(PORT);
server.setHandler(webAppContext);
logger.info("Starting server on port: " + PORT);
server.start();
logger.info("Server started. Waiting for the server to finish.");
server.join();
}
}
We will move the WebAppContext configuration completely out of the Main class. We will then load the Server from the configuration file. The corresponding jetty-config.xml configuration file is shown below. You can see that the methods of WebAppContext and Server are properties in the configuration. Using this, we can completely configure the Server in a user editable configuration file. This includes TLS and other attributes. For more information on the Spring Configuration file visit the Spring XML Configuration site.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="webAppContext" class="org.eclipse.jetty.webapp.WebAppContext">
<property name="contextPath" value="/" />
<property name="resourceBase" value="src/main/webapp/" />
<property name="descriptor" value="src/main/webapp/WEB-INF/web.xml" />
</bean>
<bean id="server" class="org.eclipse.jetty.server.Server">
<constructor-arg name="port" value="8080" />
<property name="handler" ref="webAppContext" />
</bean>
</beans>
The nice thing about the configuration above is how verbose it is. While I would not go as far as to say it documents itself, it is certainly obvious what parameters it sets and how things fit together.
Now we can change the Main class to load the Server component from the configuration file. We also throw in a bit of exception handling (pun intended).
package net.sf.pbu.ontrack.main;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import org.eclipse.jetty.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
private static final String CONFIGURATION_FILE = "jetty-config.xml";
private static Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
try {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
String configurationFile = defaultIfNull(System.getProperty("net.sf.pbu.ontrack.jetty.CONFIGURATION_PATH"), CONFIGURATION_FILE);
Server server = loadConfiguration(configurationFile);
runServer(logger, server);
} catch (Exception e) {
logger.error("Application unexpectedly terminated.", e);
}
}
private static Server loadConfiguration(String filename) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(filename);
try {
return context.getBean(Server.class);
} finally {
context.close();
}
}
private static void runServer(Logger logger, Server server) throws Exception {
try {
logger.info("Application Starting.");
server.start();
logger.info("Application Started.");
server.join();
logger.info("Application Terminated without Error.");
} finally {
server.stop();
}
}
}
For those that download the source, you will notice my POM is different to the one above. I have included some test packages. You will also notice there are extra test classes. These will not affect you if you are following the blog. If you are interested in these, download the source.
Notice that the WebAppContext and Server attributes have now been completely removed from the code. We have also allowed for the location of the configuration file to be specified via a system property. .A user may set this using the '-Dnet.sf.pbu.ontrack.jetty.CONFIGURATION_PATH' option when starting the application.
Conclusion
Although it is not perfect, the code above now has everything it needs to safely go into production. Notice that the Spring Context always gets closed if it is successfully created. The Jetty Server will always be stopped once it is started. Also notice that the try block will not allow exceptions to bubble up to the runtime environment. This means all exceptions will always appear in our logs.The source is available here.
No comments:
Post a Comment