Enterprise Spring Best Practices – Part 1 – Project Config

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


About Gordon

Technology enthusiast primarily focused on Java and Open Source projects. Spring Certified Professional and Trainer. http://twitter.com/gdickens http://linkedin.com/in/gordondickens http://github.com/gordonad
This entry was posted in Eclipse, Jakarta Commons Logging, Java, Java Util Logging, Jetty, Log4J, LogBack, Logging, Maven, SLF4J, Spring, Spring Framework, Spring MVC, STS, Testing, Tomcat and tagged , , , , , , . Bookmark the permalink.

10 Responses to Enterprise Spring Best Practices – Part 1 – Project Config

  1. Hi,

    Concerning the “Never use JUL, use SLF4J instead”, I would add the things to set up to override default JUL logging (since JUL is in the JDK, we cannot bridge it easily… this is the reason why dependency is named jul-TO-slf4j and not jul-OVER-slf4j)

    More info on setting up slf4j to intercept JUL logging : http://stackoverflow.com/q/9117030

  2. Gordon says:

    Thanks Frederic!

    I will do that in the next blog.

    Regards,
    Gordon Dickens

    twitter.com/gdickens
    linkedin.com/in/gordondickens
    GitHub: github.com/gordonad

  3. Tim says:

    I think best practice is to put all this ancillary project setup in a parent pom. This way its reusable for different projects, and doesn’t bloat the actually project pom.

  4. Pingback: Enterprise Spring Best Practices – Part 2 – Application Architecture | Technophile Blog

  5. Pingback: Enterprise Spring Framework Best Practices – Part 3 – XML Config | Technophile Blog

  6. AJ says:

    Hi,

    The documentation for the enforcer plugin’s bannedDependencies tag has the following format specification for exclusions :
    “The format is groupId[:artifactId][:version][:type][:scope] where artifactId, version, type, and scope are optional. Wildcards may be used to replace an entire section.”

    If I interpret the above correctly, the following exclusion is correct:

    org.springframework:*:1.2

    but, this is not:

    org.springframework:1.*

    The wildcard seems to be meant only to replace entire sections and cannot be used to specify revision ranges.

    My experiments seem to concur with the above interpretation.

    This is what I am using currently:

    true

    org.springframework

    org.springframework:*:3.1.2

    true
    true
    true

  7. Pingback: Enterprise Spring Best Practices – Part 4 – Annotation Config | Technophile Blog

  8. Andy says:

    The jmx through localhost:8050 does not seem to work. Am I missing something or is jmx not fully supported by this config?

    BTW, thanks for a great guide of best practices!

  9. Pingback: Sawing through the Java Loggers | Technophile Blog

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>