Jackson Mix-in Annotations

2020 May 20

Jackson has a feature called Mix-in annotations. With this feature, we can write cleaner code for the domain classes. Imagine we have domain classes like below.

// package com.example
public interface Item {
    ItemType getType();
}

@Value
public class FooItem implements Item{
    @NonNull String fooId;

    @Override
    public ItemType getType() {
        return ItemType.FOO;
    }
}

When implement these domain classes, no Jackson annotation is put on them. To support serialization and deserialization with Jackson for these classes, add “Mix-in” classes, for example in a separate package called com.example.jackson.

Add ItemMixin below to let Jackson be able to serialize and deserialize Item and its subclasses.

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = FooItem.class, name = "FOO")
        // ...
})
public abstract class ItemMixin {
}
// merge the Jackson annotations in the Mix-in class into the Item class,
// as if these annotations are in the Item class
objectMapper.addMixIn(Item.class, ItemMixin.class);

String json = "[{\"fooId\": \"1\", \"type\": \"FOO\"}]";
List<Item> items = mapper.readValue(json, new TypeReference<List<Item>>(){});

Note that FooItem is implemented as an immutable class using @Value from Lombok. With @Value annotated, FooItem has no default constructor, which makes Jackson unable to serialize it by default. Add FooItemMixin below to fix it.

@JsonIgnoreProperties(value={"type"}, allowGetters=true)
abstract class FooItemMixin {
    @JsonCreator
    public FooItemMixin(@JsonProperty("fooId") String fooId) {

    }
}

With help of Mix-in annotations, the domain classes don’t have to be compromised for Jackson support. All Jackson relevant annotations are in separate Mix-in classes in a separate package. Further, we could provide a simple Jackson module like below.

@Slf4j
public class MixinModule extends SimpleModule {
    @Override
    public void setupModule(SetupContext context) {
        context.setMixInAnnotations(Item.class, ItemMixin.class);
        context.setMixInAnnotations(FooItem.class, FooItemMixin.class);
        log.info("module set up");
    }
}

The consumers of the domain classes can simple register this module to take in all the Mix-in annotations.

objectMapper.registerModule(new MixinModule());

Jackson Mix-in helps especially if the domain classes are from a third party library. In this case, the source of the domain classes cannot be modified, using Mix-in is more elegant than writing custom serializers and deserializers.

Which Accessor Style? "Fluent" vs. "Java Bean"

2020 May 19

Basically, there are two styles of accessor naming convention in Java.

One is the traditional “Java Bean” style.

public class Item {
    ItemType getType();
}

Another is called the “fluent” style.

public class Item {
    ItemType type();
}

The fluent style saves some typing when writing code, also makes code a bit less verbose, item.type().

For example, the Lombok library supports this fluent style.

lombok.accessors.fluent = [true | false] (default: false)
If set to true, generated getters and setters will not be prefixed with the bean-standard ‘get, is or set; instead, the methods will use the same name as the field (minus prefixes).

Which style is better?

Actually, the verbose Java Bean style, is the better one. It’s because a lot of third party libraries are acknowledging the Java Bean style. For example, if Item is going to be serialized by the Jackson library as a JSON string, the fluent style wouldn’t work out of the box. Also, most of DTO mapping libraries are using the Java Bean style too. Therefore, using the standard Java Bean accessor style save effort when integrate our classes with other libraries and frameworks.

With the Lombok library and the auto-completion in modern IDEs, the Java Bean style doesn’t necessarily mean more typing.

The contains() Method in Java Collection Is Not "Type Safe"

2020 May 8
Currency currency;
//...
if(currency.getSupportedCountries().contains(country)) {
    //...
}

The Currency.getSupportedCountries() returns a Collection. Originally, the returned Collection was Collection<Country>. The country object in the above if-condition was of type Country. The program has been well tested and worked as expected.

However, due to whatever reason, the getSupportedCountries() is refactored to return a Collection<String>. The Java compiler complains nothing about the refactor. But the if-condition now is never true in any cases, since the equals() method of String has no idea about the equality with Country and vice versa. A bug! It’s hard to detect this kind of bug, if the code is not well covered by unit tests or end-to-end tests.

In this sense, the contains() method in Java Collection is not type safe.

How to Avoid

First, never change the behavior of an API when refactor. In the above case, the signature of the getSupportedCountries() API has changed. This is a breaking change, which usually causes the client code fails to compile. Unfortunately, in above case the client code doesn’t fail fast in the compile phase. It’s better to add new API like getSupportedCountryCodes() which returns a Collection<String>, and @Deprecated the old API, which can be further deleted some time later.

Second, make code fully covered by test cases as much as possible. Test cases can detect the bug earlier in the test phase.

Why contains() Is Not Generic

Why contains() is not designed as contains(E o), but as contains(Object o)? There are already some discussion on this design in StackOverflow, like this one and this one. It’s said it’s no harm to let methods like contains() in Collection be no generic. Being no generic, the contains() can accept a parameter of another type, which is seen as a “flexible” design. However, the above case shows that this design does have harm and cannot give developers enough confidence.

A method accepting a parameter of Object means it accepting any type, which is too “dynamic” for a “static” language.

Another question is why a static language needs a “root” Object?

The if-else Control Flow Using Optional

2020 Mar 27

Sometimes you may want to write the if-else control flow based on an Optional object.

For example, an API from a third party library declares Optional as its return type. You need to compose an if-else like control flow using that Optional object. Of course, it can be done by testing isPresent() in a traditional if-else statement.

var itemOpt = service.getItem(itemId);
if (itemOpt.isPresent()) {
    addToOrder(itemOpt.get());
} else {
    log.info("missing item {}", itemId);
    sendOutItemMissedEvent();
}

The above code doesn’t take any advantage of Optional. Actually, since Java 9 the ifPresentOrElse​(Consumer<? super T>, Runnable) method can be used to implement such control flow, which is a bit more elegant.

service.getItem(itemId).ifPresentOrElse(
    item -> {
        addToOrder(item);
    },
    () -> {
        log.info("missing item {}", itemId);
        sendOutItemMissedEvent();
    });

Print Exception Together With Parameterized Log Messages

2020 Mar 5

Often when use SLF4J, you may wonder if an exception object can be printed in the same parameterized log message together with other objects. Actually SLF4J supports it since 1.6.0.

log.error("failed to add item {}", "item-id-1", new RuntimeException("failed to add item"));

The SLF4J call above prints log message like below.

13:47:16.119 [main] ERROR example.TmpTest - failed to add item item-id-1
java.lang.RuntimeException: failed to add item
    at example.TmpTest.logErrorTest(TmpTest.java:10)
    ...
    ...

So it actually doesn’t need to log as below.

log.error("failed to add item {}", "item-id-1");
log.error("exception details:", new RuntimeException("failed to add item"));

Here is what the official FAQ says.

The SLF4J API supports parametrization in the presence of an exception, assuming the exception is the last parameter.

Use the first style to save one line.

SLF4J doesn’t clearly state this behavior in the Javadoc of Logger.error(String format, Object... arguments) (at least not in the 1.7.26 version). Maybe if a new method like below is added to Logger, programmer would find out this feature more easily.

public void error(String format, Throwable t, Object... arguments);

Java Generics, Bounded Wildcards and PECS

2019 Aug 8

With a class hierarchy below,

class Fruit {
    public void hello() {}
}
class Apple extends Fruit {}
class Banana extends Fruit {}

Lower Bounded Wildcards and Upper Bounded Wildcards

? super Fruit is called lower bounded wildcard.

? extends Fruit is called upper bounded wildcard.

Usually a class hierarchy is illustrated like,

   Object
     |
   Fruit
   /   \
Apple Banana

The base class is placed upper, subclasses placed lower.

For ? super Fruit, the lower bound of the type argument is determined, which is Fruit, so it’s called lower bounded wildcard. Similar, for type argument ? extends Fruit, its upper bound is fixed, so it’s called upper bounded wildcard.

”? super Fruit”

void func(List<? super Fruit> fruits) {
    fruits.add(new Fruit()); // ok
    fruits.add(new Apple()); // ok
    fruits.add(new Banana()); // ok
    // Error: Cannot resolve method 'hello'
    fruits.get(0).hello();  
}

The ? in the declaration List<? super Fruit> means fruits is a List of an unknown type. The ? super Fruit means the unknown type is limited to be Fruit or its super class (Object in this case). When invoke func(List<? super Fruit> ), List<Fruit> or List<Object> can be passed as the argument.

All the fruits.add(...) in the above code is ok, because no matter what type actually the type argument is Fruit, Apple, and Banana are the subtypes of that type. An Apple object is a Fruit (or Object) object, so an Apple object can be added into a List of Fruit (or Object).

Why fruits.get(0).hello() fails?

The actual type of the elements in the fruits List is undetermined when declare the method. When call a method on an element, the compiler needs to make sure the unknown type has the method. ? super Fruit in the type argument declaration sets bounds for the unknown type, one bound (“lower”) is type Fruit, the other bound (“upper”) is type Object To safely invoke methods on the unknown type, only methods of the most upper type can be invoked. Obviously, method hello is not from the most upper type (i.e. Object). If passing a List<Object> as the parameter, then obviously the elements in fruits would not have hello() method. Therefore the compiler raises the error.

”? extends Fruit”

void func(List<? extends Fruit> fruits){
    // Error: add(capture<? extends Fruit>) in List cannot be applied to add(Fruit)
    fruits.add(new Fruit()); 
    fruits.add(new Apple()); // similar error
    fruits.add(new Banana()); // similar error
    fruits.get(0).hello(); // ok
}

Similar, declaring the parameter as List<? extends Fruit> means fruits is an unknown type, and can be List<Fruit>, List<Apple> or List<Banana> when the method is invoked.

Why fruits.get(0).hello() is ok in this case?

Similar, ? extends Fruit sets bounds for the unknown type, the upper bound is determined as the type Fruit. The hello() method is from the most upper type, so the compiler knows it’s safe to call it within the func body.

Why statements like fruits.add(new Fruit()) fails?

The type of fruits is unknown, it doesn’t mean the type of fruits is “dynamic”. Being “dynamic” means the type of fruits can be List<Fruit> at some point, then be List<Apple> at some other point. It’s not possible in Java, since Java is a static language. The two types, List<Fruit> and List<Apple>, have nothing to do with each other (List<Apple> is not a subtype of List<Fruit>). The type of the fruits parameter is determined on the invocation of func. For a specific invocation, the type of fruits is determined and static. For example, with an invocation like func(new ArrayList<Apple>()), the statement fruits.add(new Fruit()) would raise compiler error (since a Fruit cannot be add()ed into a List<Apple>). To ensure all the possible invocation of func works, the compiler just can’t allow the statements like fruits.add(new Fruit()) appear in the method body.

What is “capture” (capture<? extends Fruit>)?

Inject a Method Interceptor in Guice

2018 Sep 4

I recently made a mistake to new an object (MethodInterceptor) in an plain old way in a Guice configuration, which caused the object’s @Inject-annotated fields, like Logger for example, were initialized with null value.

public class FooInterceptor implements MethodInterceptor{
  @Inject
  private Logger logger;

  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    // NPE if logger not inject correctly
  	logger.info("start to invoke"); 
  	return invocation.proceed();
  }
}

public class BarModule extends AbstractModule {
	@Override
	protected void configure() {	
    bindInterceptor(Matchers.subclassesOf(OrderApi.class), 
      Matchers.any(), 
      // new the interceptor in the plain old way
      new FooInterceptor()); 
  }
}

Solution

Guice wiki clearly states that requestInjection() should be used for injection of a “method interceptor”.

How do I inject a method interceptor?

In order to inject dependencies in an AOP MethodInterceptor, use requestInjection() alongside the standard bindInterceptor() call.

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    MethodInterceptor interceptor = new WeekendBlocker();
    // for injection of a "method interceptor"
    requestInjection(interceptor);
    bindInterceptor(any(), annotatedWith(NotOnWeekends.class), interceptor);
  }
}

Some Thoughts

Once you decide to use a dependency injection framework, like Guice here, please DO NOT new Java objects in the plain old way any more. Otherwise, you may very possibly fail to set up the objects’ dependencies correctly.

Secondly, use constructor-injection for mandatory dependency, like Logger here. It’s impossible to forget to inject a dependency using constructor-injection, even if that object is constructed in the plain old way. (However, too many dependencies injected via the constructor makes the constructor look a bit ugly.)

删除Arrays.asList返回的列表的元素会发生异常

2018 Feb 5

Arrays.asList(...)返回的List进行remove()/removeAll()操作时,会抛出UnsupportedOperationException异常。 据Arrays.asList(...)javadoc,这个方法返回的List实现类是基于数组的。

Returns a fixed-size list backed by the specified array. (Changes to the returned list “write through” to the array.)

这个List实现类是Arrays类的一个私有静态类,所有方法基本上只是简单地代理到内部的一个数组成员E[]。 数组是不支持删除操作的,所以remove()会抛异常。

实际上对所有基于数组的List实现类最好都不要进行删除操作。 ArrayList虽然支持remove(),但是remove()的实现会导致内部数组的拷贝“平移”,影响效率。

Guava Cache异步刷新的一个实现

2018 Jan 16

Guava的cache提供了refresh功能。 在指定的时间间隔后,guava可以(惰性地/lazily)更新缓存。 默认的refresh实现是同步的(synchronously),一个线程在更新缓存时,其它线程会等待。 具体见LoadingCacheCacheLoader的javadoc。

LoadingCache
void refresh(K key)
… Loading is asynchronous only if CacheLoader.reload(K, V) was overridden with an asynchronous implementation.

CacheLoader
public ListenableFuture<V> reload(K key, V oldValue) throws Exception
… This implementation synchronously delegates to load(K).

下面提供了一个异步的CacheLoader实现,使得一个线程在refresh缓存时,其它线程可以不必等待,继续使用旧的缓存值。

运行时发生IncompatibleClassChangeError异常

2017 Nov 28

执行Java程序时遇到一个IncompatibleClassChangeError异常。 IncompatibleClassChangeError的javadoc是这么说的,

Thrown when an incompatible class change has occurred to some class definition. The definition of some class, on which the currently executing method depends, has since changed.

意思是Java程序在执行时发现类的定义发生了变化。

通常,如果编译时依赖的类定义和运行时依赖的类定义不一致就有可能抛出这种异常。

比如,有如下程序,

 public class ChildType extends BaseType {
 }

在编译时,BaseType是一个类,编译顺利通过。 在运行时,classpath里的BaseType如果是一个接口,就会在运行时抛出IncompatibleClassChangeError异常。

其它会抛出IncompatibleClassChangeError异常的场景有,

  • implements a class
  • 静态成员变成非静态成员
  • 非静态成员变成了静态成员

我的Java程序抛出IncompatibleClassChangeError异常的原因是运行时的classpath里同时存在不同版本Guava jar包。 在一个早期的Guava版本里,Ticker是一个接口(且这个Ticker定义更早地在运行时被加载); 在另一个较新的Guava版本里,Ticker变成了一个类,并有其它代码extends Ticker。 所以在运行到新版本的Guava代码时,就会抛出异常。

另外,升级库版本时,为了防止运行时抛出IncompatibleClassChangeError异常,最好针对新的库重新编译程序 (这样编译时就能发现错误); 而不是简单替换库的jar包。

← Previous