22 March 2015

Guava

In Verify.verify’s error message, only %s is supported.

Logging

Log4j

In 1.2, %d{ISO8601} means the format yyyy-MM-dd HH:mm:ss,SSS which is wrong. The correct format is yyyy-MM-ddTHH:mm:ss,SSS as specified by wikipedia ISO 8601 page. 2.x is in consistent with wikipedia.

Without configuration file, lo4j 1.x emits the following message to stdout and no log4j log messages are printed:

log4j:WARN No appenders could be found for logger (language.Log4jLogger).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

Without configuration file, log4j 1.x with slf4j emit the following message to stdout and no log4j log messages are printed:

log4j:WARN No appenders could be found for logger (language.Slf4jLogger).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

Without configuration file, log4j 2.x emits the following message to stdout and log4j log messages are printed to stdout:

ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
19:50:47.889 [main] ERROR com.rainbow.LoggerTest - info message

Setup

Gradle:

compile "org.apache.logging.log4j:log4j-core:2.11.1"
compile "org.apache.logging.log4j:log4j-api:2.11.1"

log42.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
    <Properties>
        <Property name="log-dir">./logs</Property>
    </Properties>
    <Properties>
        <Property name="project-name">rainbow</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{ISO8601} %-5p [%t] %c{2}: %m%n"/>
        </Console>
        <RollingFile name="RollingFile" fileName="${log-dir}/${project-name}.log"
                     filePattern="${log-dir}/${project-name}-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout>
                <pattern>%d{ISO8601} %-5p [%t] %c{2}: %m%n</pattern>
            </PatternLayout>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="250 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="4"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <!--  <AppenderRef ref="RollingFile" />  -->
        </Root>
    </Loggers>
</Configuration>

Logback with slf4j

Without configuration file, logback log message are printed to stdout. For details, refer to Chapter 3: Logback configuration.

Setup

Gradle:

compile 'ch.qos.logback:logback-core:1.2.3'
compile 'ch.qos.logback:logback-classic:1.2.3'
compile 'org.slf4j:slf4j-api:1.7.25'

logback.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="false">
    <!-- Stop the annoying output INFO at start, -->
    <!--   see https://www.mkyong.com/logging/how-to-stop-logback-status-info-at-the-start-of-every-log/ -->
    <statusListener class="ch.qos.logback.core.status.NopStatusListener" />

    <property name="log-dir" value="logs"/>
    <property name="project-name" value="message-producer"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{ISO8601} %-5p [%t] %c{2}: %m%n</pattern>
        </layout>
    </appender>

    <appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log-dir}/${project-name}.log</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>${log-dir}/${project-name}-%i.log</fileNamePattern>
            <minIndex>1</minIndex>
            <maxIndex>20</maxIndex>
        </rollingPolicy>

        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>2MB</maxFileSize>
        </triggeringPolicy>
        <encoder>
            <pattern>%d{ISO8601} %-5p [%t] %c{2}: %m%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <!--<appender-ref ref="RollingFile" />-->
    </root>
</configuration>

JSON

Netty

Colorful Terminal

Build

Maven

To download a package without a POM file:

mvn dependency:get -DgroupId=org.apache.kafka -DartifactId=kafka-clients -Dversion=0.9.0.0

Note that the above command can’t be used to download Javadoc jar or source

jar for a package.

Download Javadoc Jar:

mvn dependency:resolve -Dclassifier=javadoc

Download source jar:

mvn dependency:sources

For details, refer to:

Gradle

Gretty:

In different environments, Gradle may execute a set of test cases in different orders. For example, there are test cases A and B. In environment 1, the execution order is A -> B. In environment 2, the execution order is B -> A. Test case failures related to such kind of execution order are hard to be fixed.

Gradle eclipse task downloads source jars by default.

Configuration for Gradle eclipse task:

eclipse.classpath {
  defaultOutputDir = file("build-eclipse")
}

eclipseJdt.doLast {
  File f1 = file(".settings/org.eclipse.core.resources.prefs")
  String firstLine = "eclipse.preferences.version=1\n"
  f1.write(firstLine)
  f1.append("encoding/<project>=UTF-8\n")

  File f2 = file(".settings/org.eclipse.core.runtime.prefs")
  f2.write(firstLine)
  f2.append("line.separator=\\n\n")
}

eclipseClasspath.dependsOn cleanEclipse

gradle run accepts arguments with --args. For exmaple, gradle run --args="-b localhost:9092". gradle bootRun also dose the same thing. For example, gradle bootRun --args="--server.port=8888".

JVM

Scala

  • Download Scala documentation from http://www.scala-lang.org/files/archive/

Web App

RequestDispatcher is confusing. But Introduction to Request Dispatcher explains it well.

classes places in src/test/java are not loaded by gradle jettyRun.

Error Handling in Web App

  • If no customized error handling is provided, the default error page provided by the container such as Tomcat is returned. The error page usually contains Java exception stack trace. HTTP response is a error page with a HTTP status 500.
  • Customized error handling usually return a HTTP response with status code 302. The browser will then go to the URL contained in Location header. So there are two HTTP responses: one with status code 302 and one with status code 200.

One way to do customized error handling is to use a Filter to catch the exception and does the redirect with HttpServletResponse.sendRedirect. Such a filter catches the exceptions thrown by the application code and the exceptions thrown by Web framework code. If Spring framework is used, a @ControllerAdvice which has some methods annotated with @ExceptionHandler can also be used. But such a controller advice can only catch exceptions thrown by a web controller.

404 Not Found Handling in Web App

Approaches:

  • Web container returns a 404 page by default. Only one HTTP response is produced.
  • Web container returns a customized 404 page. Only one HTTP response is produced. github.com uses this approach.
  • Web container returns a HTTP 302 response. Browser redirects to the Location header in the response. Two HTTP responses are produced: one HTTP 302 and one HTTP 200.

Servlet

The following conclusion is verified with gradle jettyRun. Filters are initialized before servlets. Unlike servlets which can be lazily initialized, filters are always initialized when the servlet contains starts up.

ORM

Tomcat

Spring

RequstMapping

If RequstMappings produces field is not specified, the content-type header of HTTP response is application/json;charset=UTF-8. If RequstMappings consumes field is not specified, the behaviour is accept requests with any content-type header.

Qualifier

Create the following three classes in a package which is component scaned.

@Service
public class Wife {
  public Wife() {
    System.out.println("construct a wife");
  }
}
@Service
public class Husband {
  public Husband() {
    System.out.println("construct a husband");
  }
}
@Service
public class Couple {

  @Autowired(required = true)
  public Couple(@Qualifier("husband") Husband husband, @Qualifier("wife") Wife wife) {
    System.out.println("construct a couple");
  }
}

A test case which knows how to load application context:

public class CoupleTest extends AbstractContextTest {
  @Autowired
  private Couple couple;

  @Test
  public void testConstruct() {
    System.out.println(couple);
  }
}

Running the test case produces:

construct a wife
construct a husband
construct a couple
com.rainbow.service.Couple@733c423e

MVC

If an interceptor’s preHandle method returns false for a HTTP GET, a response with status code 200 and an empty body may be returned.

Magic

  • spittleList for List<Spittle> for model name
  • In web.xml, a serlvet named as appServlet’s context is WEB-INF/appServlet-context.xml.
  • In a application context xml file, the / in <import resource="classpath:/service-config.xml" /> is optional. The reason is that ClassPathResource.ClassPathResource(String path) ignores it.
  • supportedMediaTypes in HttpMessageConverter can be used to set HTTP response’s Content-Type header. produces = MediaType.APPLICATION_JSON_VALUE overrides it.

REST

Allow CORS

Add the following text to web.xml:

  <!-- CORS -->
  <filter>
    <filter-name>CorsFilter</filter-name>
    <filter-class>com.rainbow.CorsFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>CorsFilter</filter-name>
    <url-pattern>/rest/*</url-pattern>
  </filter-mapping>

CorsFilter:

package com.rainbow;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
/**
 * Filter to allow CORS rest API calls.
 */
public class CorsFilter implements Filter {

  @Override
  public void doFilter(ServletRequest req, ServletResponse resp,
      FilterChain chain) throws IOException, ServletException {
    HttpServletResponse response = (HttpServletResponse) resp;

    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods",
                       "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600"); // seconds
    response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");

    chain.doFilter(req, resp);
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {}

  @Override
  public void destroy() {}
}

JDBC

Before Java 7:

Connection conn = ...;
try {
  // SQL statements
  conn.commit();
} catch (Throwable t) {
  if (conn != null) conn.rollback();
} finally {
  if (conn != null) conn.close();
  }
}

Java 7 and above:

try (Connection conn = ...;) {
  conn.commit();
}

The above code works only if Connection.close method does rollback if conn.commit() is not executed. Java 8 Connection.close method’s Javadoc discourages this way.

And remember to always invoke Connection.close() to release database resources. If a thrown exception causes Java process to terminate, the OS will terminate the Java process’s TCP connection to the remote database server. The database server can release related resources. The related database resources will not be released for the following cases:

  • The exception is swallowed or the
  • Network cable is unplugged suddenly. In this case, the OS hosting Java process does not have a change to send more TCP packages to do a graceful TCP close with the remote database server.

Be careful with JDBC url in Java code. Some unrecognized options are ignored silently. If the JDBC url is given literally in XML, & needs to be escaped. If the JDBC url is given in a properties file, don’t escape it.

Performance Tuning

Web Application Development

Not need to close input stream for HTTP request. See Is is necessary to close the input stream returned from HttpServletRequest?. Not need to close output stream for HTTP response. See Should one call .close() on HttpServletResponse.getOutputStream()/.getWriter()?.

Caliper

Invoke Caliper

public class Entry {
  public static void main(String[] args) {
    CaliperMain.main(Tutorial.Benchmark1.class, new String[0]);
  }
}

Benchmark:

@VmOptions("-XX:-TieredCompilation")
public static class Benchmark1 {
  @Benchmark
  void timeNanoTime(int reps) {
    for (int i = 0; i < reps; i++) {
      System.nanoTime();
    }
  }
}

Caliper 1.0-beta-2 is used. And guava 19.0 is required.

Concurrency

AtomicInteger.getAndIncrement:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

Unsafe.getAndAddInt:

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

Java Concurrency in Practice’s Code Listings

OpenJDK

MISC