crockpotveggies crockpotveggies - 6 months ago 56
Java Question

Scala class final def this() declares no public no-argument constructor

Trying to build a generic Scala 2.11 Jackson serializer that uses Jackson Smile 2.6.6 and the jackson-module-scala. For whatever reason, Java can't find a public no-argument constructor. Although the class below compiles, I get the following error from Kafka clients at runtime:

Caused by: org.apache.kafka.common.KafkaException: Could not instantiate class ai.bernie.api.util.serialization.kafka.JacksonGenericSerializer Does it have a public no-argument constructor?
at org.apache.kafka.common.utils.Utils.newInstance(Utils.java:318) ~[kafka-clients-0.10.0.0.jar:na]
at org.apache.kafka.common.config.AbstractConfig.getConfiguredInstance(AbstractConfig.java:201) ~[kafka-clients-0.10.0.0.jar:na]
at org.apache.kafka.clients.consumer.KafkaConsumer.<init>(KafkaConsumer.java:671) ~[kafka-clients-0.10.0.0.jar:na]
... 27 common frames omitted
Caused by: java.lang.InstantiationException: ai.bernie.api.util.serialization.kafka.JacksonGenericSerializer
at java.lang.Class.newInstance(Class.java:427) ~[na:1.8.0_51]
at org.apache.kafka.common.utils.Utils.newInstance(Utils.java:314) ~[kafka-clients-0.10.0.0.jar:na]
... 29 common frames omitted
Caused by: java.lang.NoSuchMethodException: ai.bernie.api.util.serialization.kafka.JacksonGenericSerializer.<init>()
at java.lang.Class.getConstructor0(Class.java:3082) ~[na:1.8.0_51]
at java.lang.Class.newInstance(Class.java:412) ~[na:1.8.0_51]
... 30 common frames omitted


Here's the class, how can I fix it? I've tried various combinations of using
final def
and moving the private var into the class, and although I may be doing it wrong, also tried creating a companion object with a no-argument
apply
method.

class JacksonGenericSerializer[T: Manifest] private (
private var mapper: ObjectMapper with ScalaObjectMapper = null
) extends Closeable with AutoCloseable with Serializer[T] with Deserializer[T] {

final def this() = this(new ObjectMapper(new SmileFactory) with ScalaObjectMapper)

override def configure(map: java.util.Map[String, _], b: Boolean) {
if (mapper == null) {
mapper = new ObjectMapper(new SmileFactory) with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
}
}

override def serialize(s: String, record: T): Array[Byte] = {
try {
mapper.writeValueAsBytes(record)
}
catch {
case e: JsonProcessingException => {
throw new IllegalArgumentException(e)
}
}
}

override def deserialize(s: String, bytes: Array[Byte]): T = {
try {
mapper.readValue[T](bytes)
}
catch {
case e: IOException => {
throw new IllegalArgumentException(e)
}
}
}

override def close {
mapper = null
}
}


Edit



I should also mention that I'm using Google Guice, though I'm skeptical it would impact this since the serialization class itself is not being injected, only the Kafka Consumer/Producer parent class where it is used.

Answer

Because JacksonGenericSerializer type parameter has a context bound (i.e. [T: Manifest]) the auxiliary "no-argument" constructor actually takes an argument: Manifest[T]

You can confirm this with javap:

$ javap JacksonGenericSerializer.class

Compiled from "JacksonGenericSerializer.scala"
public class JacksonGenericSerializer<T> implements java.io.Closeable, java.lang.AutoCloseable, org.apache.kafka.common.serialization.Serializer<T>, org.apache.kafka.common.serialization.Deserializer<T> {
  public void configure(java.util.Map<java.lang.String, ?>, boolean);
  public byte[] serialize(java.lang.String, T);
  public T deserialize(java.lang.String, byte[]);
  public void close();
  public JacksonGenericSerializer(scala.reflect.Manifest<T>);
}

This question touches on a similar issue: How to create new instance of Scala class with context bound via Java reflection using only zero argument constructor?

Comments