Quantcast
Channel: Technophile Blog » Tomcat
Viewing all articles
Browse latest Browse all 4

Enterprise Spring Best Practices – Part 1 – Project Config

$
0
0

Enterprise Spring Best Practices Series
In part 1, let’s review project structure and configuration.


Sections




Project Directories


Production
  • src/main/java – Java Source code packages and classes
  • src/main/resources – NON-Java Resources, such as property files and Spring configuration
Test
  • src/test/java – Test Source code packages and classes
  • src/test/resources – NON-Java Resources, such as property files and Spring configuration

Project Structure Example

── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── gordondickens
    │   │           └── sample
    │   │               ├── domain
    │   │               │   └── MyDomain.java
    │   │               ├── repository
    │   │               │   └── MyDomainRepository.java
    │   │               ├── service
    │   │               │   ├── MyDomainService.java
    │   │               │   └── internal
    │   │               │       └── MyDomainServiceImpl.java
    │   │               └── web
    │   │                   └── MyDomainController.java
    │   ├── resources
    │   │   ├── META-INF
    │   │   │   └── spring
    │   │   │       ├── applicationContext.xml
    │   │   │       └── database.properties
    │   │   ├── logback-access.xml
    │   │   └── logback.xml
    │   └── webapp
    │       ├── WEB-INF
    │       │   ├── classes
    │       │   ├── i18n
    │       │   ├── layouts
    │       │   ├── spring
    │       │   │   └── webmvc-config.xml
    │       │   ├── views
    │       │   │   ├── myDomain
    │       │   │   │   ├── create.jsp
    │       │   │   │   ├── list.jsp
    │       │   │   │   ├── show.jsp
    │       │   │   │   └── update.jsp
    │       │   │   ├── dataAccessFailure.jsp
    │       │   │   ├── index.jsp
    │       │   │   ├── resourceNotFound.jsp
    │       │   │   ├── uncaughtException.jsp
    │       │   │   └── views.xml
    │       │   └── web.xml
    │       ├── images
    │       └── styles
    ├── site
    │   ├── apt
    │   ├── fml
    │   ├── site.xml
    │   └── xdoc
    └── test
        ├── java
        │   └── com
        │       └── gordondickens
        │           └── sample
        │               └── service
        │                   └── MyDomainServiceTests.java
        └── resources
            ├── com
            │   └── gordondickens
            │       └── sample
            │           └── service
            │               └── MyDomainServiceTests-context.xml
            └── logback-test.xml



Project Dependencies



I am a big fan of Maven, it provides a consistent build structure and numerous plugins. Gradle is emerging as an alternative Groovy-based build tool, which supports the Maven structure. If you are still using Ant, I urge you to move to a more robust build tool such as Maven or Gradle. One of the challenges of enterprise build tools is managing transitive dependencies, here are some recommendations to ease these challenges.
Dependency Versions
  • DO NOT put version numbers below the <properties/> section, this will make it easier to upgrade and test newer versions.
  • DO include version numbers for ALL plugins! Do not rely on Maven’s built in Super Pom plugin versions!
Dependency Management
  • USE Maven’s <DependencyManagement> section to control implicit and explicit versions! Transitive dependencies will be resolved by those included in this section.
Enforcer Plugin

Prohibit the direct or indirect inclusion of incompatible and/or legacy jars. For example, SLF4J 1.5, 1.6 and SLF4J 1.7 do not work together, therefore we need to prohibit the project from building with mixed dependency versions. Spring is used by many open source projects, some reference older versions of Spring jars, so we want to control which Spring jar versions are inluded in our build.

Enforcer Example

  • Ensures Java 1.6
  • Ensures Maven 2.2.1 to 3.0.x
  • Ensures Spring Jars 3.1 or greater
  • Prohibits old javassist, should be org.javassist
  • Ensures no commons-logging or commons-logging-api dependencies
  • Ensures no log4j dependencies
  • Ensures no SLF4J 1.5 or 1.6 dependencies
  • Prohibits old hsqldb, should be org.hsqldb
  • Prohibits old aspectj, should be org.aspectj
<properties>
...
  <java.version>1.6</java.version>
...
  <maven.enforcer.plugin>1.2</maven.enforcer.plugin>
  <maven.version.range>[2.2.1,3.1.0)</maven.version.range>
...
</properties>

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <version>${maven.enforcer.plugin}</version>
  <executions>
    <execution>
      <id>enforce-banned-dependencies</id>
      <goals>
        <goal>enforce</goal>
      </goals>
      <configuration>
        <rules>
          <bannedDependencies>
            <searchTransitive>true</searchTransitive>
            <excludes>
              <exclude>javassist:javassist</exclude>
              <exclude>commons-logging</exclude>
              <exclude>aspectj:aspectj*</exclude>
              <exclude>hsqldb:hsqldb</exclude>
              <exclude>log4j:log4j</exclude>
              <exclude>org.slf4j:1.5*</exclude>
              <exclude>org.springframework:2.*</exclude>
              <exclude>org.springframework:3.0.*</exclude>
            </excludes>
          </bannedDependencies>
          <requireMavenVersion>
            <version>${maven.version.range}</version>
          </requireMavenVersion>
          <requireJavaVersion>
            <version>${java.version}</version>
          </requireJavaVersion>
        </rules>
        <fail>true</fail>
      </configuration>
    </execution>
  </executions>
</plugin>



Smart Logging


  • NEVER use System.out
  • NEVER use System.err
  • ALWAYS use SLF4J
  • ALWAYS use Logback
  • Prohibit Apache Commons Logging (JCL) aka Jakarta Commons Logging
  • Prohibit Java Util Logging (JUL)

Classes that use logging should include the following config for SLF4J (not log4j, not jcl, not jul, not logback):

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
public class MyClass {
  private static final Logger logger =
    LoggerFactory.getLogger(MyClass.class);
...
}

In the example below, SLF4J provides jars to route JCL and JUL logging through jcl-over-slf4j and jul-to-slf4j. Spring uses JCL, so we need to use jcl-over-slf4j to handle Spring specific logged messages.

<properties>
...
  <logback.version>1.0.10</logback.version>
...
  <slf4j.version>1.7.4</slf4j.version>
...
</properties>

...

<dependencies>
...
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
  </dependency>
...
</dependencies>

...

<dependencyManagement>
  <dependencies>
...
   <!-- Logging with SLF4J & LogBack -->
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>${slf4j.version}</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j.version}</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>${slf4j.version}</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>${logback.version}</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-access</artifactId>
    <version>${logback.version}</version>
  </dependency>
...
  </dependencies>
</dependencyManagement>
Logging Configuration Files
  • src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
    <resetJUL>true</resetJUL>
  </contextListener>

  <!-- To enable JMX Management -->
  <jmxConfigurator/>

  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%.-1level|%-40.40logger{0}|%msg%n</pattern>
    </encoder>
  </appender>

  <logger name="com.mycompany.myapp" level="debug" />
  <logger name="org.springframework" level="info" />

  <logger name="org.springframework.beans" level="debug" />

  <root level="warn">
    <appender-ref ref="console" />
  </root>
</configuration>


  • src/main/resources/logback-test.xml

Same configuration as production code, that will only be used for tests, usually for adding more log detail.


  • src/main/resources/logback-access.xml

Configuration for server access logs. HTTPRequest and HTTPResponses messages can be displayed and/or logged When used with Logback TeeFilter in web.xml – GREAT for RESTful testing.

NOTE: Using ${user.dir}, the log files will be created in the root of the project. We will want to configure this differently for production.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />

  <filter class="ch.qos.logback.access.filter.CountingFilter">
    <name>countingFilter</name>
  </filter>

  <appender name="accessfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${user.dir}/logs/app-access.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${user.dir}/logs/app-access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
    </rollingPolicy>

    <encoder>
        <pattern>combined</pattern>
    </encoder>
  </appender>

  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%n%fullRequest%n%fullResponse%n</pattern>
    </encoder>
  </appender>

  <appender name="reqrespfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${user.dir}/logs/app-req-resp.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/app-req-resp.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
    </rollingPolicy>

    <encoder>
      <pattern>%n%fullRequest%n%fullResponse%n</pattern>
    </encoder>
  </appender>

  <appender-ref ref="accessfile" />
  <appender-ref ref="reqrespfile" />
  <appender-ref ref="console" />
</configuration>

See Enterprise Spring Best Practices – XML Configuration – Part 3 for Spring configuration of java.util.logging and System.out, System.err handlers for SLF4J.



Running with Jetty and Tomcat



Developers can run Jetty or Tomcat for testing with the following Maven plugin configuration. The plugin configuration below configures the servers for JMX, SLF4J, Logback and Logback Access.
Running Jetty
    $ mvn clean install jetty:run
Running Tomcat 7
    $ mvn clean install tomcat7:run

NOTE: DO NOT use tomcat:run, this is the old Tomcat plugin.

<properties>
...
  <maven.jetty.plugin>8.1.10.v20130312</maven.jetty.plugin>
...
  <maven.tomcat.plugin>2.1</maven.tomcat.plugin>
...
</properties>

...

<plugins>
...
  <plugin>
  <groupId>org.apache.tomcat.maven</groupId>
  <artifactId>tomcat7-maven-plugin</artifactId>
  <version>${maven.tomcat.plugin}</version>
  <configuration>
    <systemProperties>
    <com.sun.management.jmxremote>true</com.sun.management.jmxremote>
    <com.sun.management.jmxremote.port>8050</com.sun.management.jmxremote.port>
    <com.sun.management.jmxremote.ssl>false</com.sun.management.jmxremote.ssl>
    <com.sun.management.jmxremote.authenticate>false</com.sun.management.jmxremote.authenticate>
    <java.util.logging.manager>org.apache.juli.ClassLoaderLogManager</java.util.logging.manager>
    <logback.ContextSelector>JNDI</logback.ContextSelector>
  </systemProperties>
  </configuration>
  <dependencies>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>${slf4j.version}</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j.version}</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>${slf4j.version}</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-access</artifactId>
    <version>${logback.version}</version>
    <scope>runtime</scope>
  </dependency>
  </dependencies>
  </plugin>
  <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>${maven.jetty.plugin}</version>
    <configuration>
      <webAppConfig>
        <contextPath>/${project.name}</contextPath>
      </webAppConfig>
      <stopPort>9966</stopPort>
      <stopKey>shutterdown</stopKey>
      <requestLog implementation="ch.qos.logback.access.jetty.RequestLogImpl">
        <fileName>./src/main/resources/logback-access.xml</fileName>
      </requestLog>
      <systemProperties>
        <systemProperty>
          <name>logback.configurationFile</name>
          <value>./src/main/resources/logback.xml</value>
        </systemProperty>
        <systemProperty>
          <name>com.sun.management.jmxremote</name>
          <value>true</value>
        </systemProperty>
        <systemProperty>
          <name>com.sun.management.jmxremote.port</name>
          <value>8050</value>
        </systemProperty>
        <systemProperty>
          <name>com.sun.management.jmxremote.ssl</name>
          <value>false</value>
        </systemProperty>
        <systemProperty>
          <name>com.sun.management.jmxremote.authenticate</name>
          <value>false</value>
        </systemProperty>
      </systemProperties>
    </configuration>
    <dependencies>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${slf4j.version}</version>
        <scope>runtime</scope>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
        <scope>runtime</scope>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jul-to-slf4j</artifactId>
        <version>${slf4j.version}</version>
        <scope>runtime</scope>
      </dependency>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
        <scope>runtime</scope>
      </dependency>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>${logback.version}</version>
        <scope>runtime</scope>
      </dependency>
    </dependencies>
  </plugin>
...
</plugins>
Logback web.xml Helpers

To see Logback status, optionally add the following Logback Status servlet.

...
  <servlet>
    <servlet-name>ViewStatusMessages</servlet-name>
    <servlet-class>ch.qos.logback.classic.ViewStatusMessagesServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>ViewStatusMessages</servlet-name>
    <url-pattern>/logbackStatus</url-pattern>
  </servlet-mapping>
...

To capture the HTTPRequest and HTTPResponse data use the Logback Tee Filter.

...
  <filter>
    <filter-name>TeeFilter</filter-name>
    <filter-class>ch.qos.logback.access.servlet.TeeFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>TeeFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
...



Spring Configuration Files



Be consistent with naming Spring xml configuration files. Start all files with the same name such as applicationConfig*.xml.

For example: applicationConfig-bootstrap.xml, applicationConfig-jpa.xml, applicationConfig-security.xml, etc.

In the next blog, I will discuss Enterprise Spring configuration best practices.

Config Directories
  • src/main/resources/META-INF/spring – Spring XML configuration directory
  • src/main/webapp/WEB-INF/spring – Spring MVC configuration directory



Complete Maven Config



The Best Practices Maven Config file is tuned for Spring application dependencies, reporting and plugin support.

Features of the Best Practices Maven Config file:

  • All versions in properties section
  • Dependency Management section controls transitive dependencies
  • All plugins defined with versions in Plugin Management section
  • Enforcer plugin stops build for incompatible dependencies
  • Maven Site plugin configured for reporting, with common reporting plugins
  • Eclipse plugin uses new Eclipse brand Maven plugin, formerly Sonatype’s
  • Idea (IntelliJ) plugin, is obsolete – not included
  • Versions plugin to check for dependency and plugin updates

BEST Practices Maven Config File



Valuable Maven Commands


Display Dependency Updates
    $ mvn versions:display-dependency-updates
Display Plugin Updates
    $ mvn versions:display-plugin-updates
Display Dependency Tree
    $ mvn dependency:tree -Ddetail
Display Dependency List
    $ mvn dependency:list
Display Effective POM
    $ mvn help:effective-pom
Display Project Settings
    $ mvn help:effective-settings
Display System and Environment Variables
    $ mvn help:system
Display Build Class Path
    $ mvn dependency:build-classpath



Further Reading




Social Me



Twitter – twitter.com/gdickens
LinkedIn – linkedin.com/in/gordondickens
GitHub: github.com/gordonad
gordon@gordondickens.com

Viewing all articles
Browse latest Browse all 4

Latest Images

Trending Articles





Latest Images