Gordon Dickens is an instructor, mentor & consultant. Gordon is currently architecting and teaching several official SpringSource courses and actively tweets about open source technology at http://twitter.com/gdickens. Gordon is active within the Spring Framework community focussed on: Spring training, Spring Roo, Spring Integration, Spring Batch and Eclipse Virgo OSGi projects. Gordon is a DZone MVB and is not an employee of DZone and has posted 39 posts at DZone. You can read more from them at their website. View Full User Profile

Enterprise Spring Best Practices – Part 1 – Project Config

07.12.2012
| 12079 views |
  • submit to reddit

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 and SLF4J 1.6 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, use SLF4J and Logback
  • Ensures no log4j, use SLF4J and Logback
  • Ensures no SLF4J 1.5, use SLF4J 1.6 and Logback
  • 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.0.1</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.6</logback.version>
...
  <slf4j.version>1.6.6</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-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>

 

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.3.v20120416</maven.jetty.plugin>
...
  <maven.tomcat.plugin>2.0-beta-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

 

 

 

Published at DZone with permission of Gordon Dickens, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)