In this third and last post of our Spring MVC and Scala experiment we will continue to work on the web application that we used in the previous posts so you might want to get the example code on GitHub so you can try out the examples on your own as we go along.
JSR-303 Validation
Another very common use case when building web services is the use of JSR-303 Bean Validation for validating input data. Lets take a look on how the Spring MVC support for JSR-303 will work together with Scala by adding some more functionality to our web service.If we wanted our Java web service to be able to receive data about a persons name and age, and we want to add some basic validation to the submitted data using JSR-303 validation, we could create a POJO with some JSR-303 annotations that would look like this:
public class JavaIndata { @NotNull private final String name; @Min(1) private final Integer age; public JavaIndata(@JsonProperty("name") String name, @JsonProperty("age") Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public Integer getAge() { return age; } }This would make sure that the name is set to a non-null value and that the person is at least one year old. To validate this POJO in our Spring MVC controller we simply annotate the corresponding method parameter with
@Valid
. The @Valid
annotation ensures that the method parameter is validated before it is passed into the method. The method to receive the POSTed, and validated, data will then look like this:@RequestMapping(value = "/indata", method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) public void receiveData(@RequestBody @Valid JavaIndata indata) { logger.debug("Got valid POSTed data: {}", indata); }So what if we translate this directly to Scala? Will it work? Unfortunately, the answer is no. Scala does not follow the Java Bean naming conventions so the validator framework will not work out of the box. (For the record, we are using the Hibernate validator in this example)
The first thing that comes to mind is that maybe we can implement this in Scala by using the
@BeanProperty
annotation the same way as we did with the Jackson processor in the previous post. That approach would result in the following Scala class for the indata:case class ScalaIndata(@BeanProperty @NotNull val name: String, @BeanProperty @Min(1) val age: Integer)(Note that we are also using the Scala module for Jackson that we discussed earlier in the previous post so we do not need the JSON specific annotations either)
However, the way the JSR-303 specification is written this will not work either. The reason for this is because with Scala, the
@NotNull
and @Min
annotations on our ScalaIndata
class will only be applied to the actual constructor parameters and not to any accessor methods or fields. (This holds true for any annotation for that matter and not just the ones we are using in this example.) And since the JSR-303 spec. wants the annotations to be on either the accessor methods or the fields, nothing will be picked up by the validator.But do not despair, Scala still has some cards up its sleeve that will help us solve this. One such card is called meta-annotations. What a meta annotation allows you to do is to actually annotate the annotation. An example of a meta-annotation is
@beanGetter
. What @beanGetter
does is it says that the meta-annotated annotation should be applied only to the getter method generated by the @Beanproperty
annotation. Notice that we actually lose the default behavior here and if we still want the constructor parameter to be annotated we also would have to add a @param
as well.To illustrate this, here is what our
ScalaIndata
class will look like with meta-annotations:case class ScalaIndata(@BeanProperty @(NotNull @beanGetter) val name: String, @BeanProperty @(Min @beanGetter)(1) val age: Integer)So with the meta-annotations, the
@NotNull
annotation will be applied to the getName()
method, and the @Min
annotation to the getAge()
method, and we will have a Scala class that can be JSR-303 validated.Something to consider here is that in some cases the meta-annotations might make the code a bit messy and on the borderline of what can be considered to be readable code. Imagine if we needed even more annotations for other purposes then there would be more annotations than code in this example. An alternative to all these annotations would be to simply make the class follow the Java Bean convention and define the getters explicitly and then annotate the methods themselves. Like this:
case class ScalaIndata(val name: String, val age: Integer) { @NotNull def getName() = name @Min(1) def getAge() = age }This, to me, is a very straight forward and readable piece of code compared to a solution where we try to do everything in the constructor. The take away here is that just because we can do something in a one-liner does not mean that we always should. Use your own judgment for which approach is most appropriate in your case.
Conclusion
In this series of posts we have looked at how we can use Java, Spring MVC and Scala together. We have seen that there is no difference in creating Spring MVC controllers in Scala compared to Java and we have also seen that when bringing in additional Java frameworks we sometimes need to slightly adjust the Scala code to ensure compatibility.Depending on your needs, the more advanced you get and the more frameworks and features you use the more likely you are to run into compatibility issues. As so often, the KISS principle applies. Keeping the integration points between Scala and Java simple will help you avoid extra work. If you need a lot of functionality in your MVC controllers it might be easier to keep them as pure Java implementations to ensure compatibility with other frameworks, and integrate your Scala code at a lower level.
Hopefully these posts have given you some ideas about what is possible when integrating Java and Scala and I hope you will have fun experimenting on your own.
I think that Scala + Spring (+ REST) is a real sweet spot - thanks for the tutorial.
ReplyDeleteI am having some problems getting the scala-jackson-module configured properly - I get the following stack trace.
Am I doing anything obviously wrong? I am using Spring 3.2, Scala 2.10.1
java.lang.IllegalStateException: Failed to load ApplicationContext
at
Caused by: java.lang.NoSuchMethodError: scala.Predef$.augmentString(Ljava/lang/String;)Lscala/collection/immutable/StringOps;
at com.fasterxml.jackson.module.scala.JacksonModule$.(JacksonModule.scala:11)
at com.fasterxml.jackson.module.scala.JacksonModule$.(JacksonModule.scala)
at com.fasterxml.jackson.module.scala.JacksonModule$class.version(JacksonModule.scala:38)
at com.fasterxml.jackson.module.scala.DefaultScalaModule.version(DefaultScalaModule.scala:15)
at org.codehaus.jackson.map.ObjectMapper.registerModule(ObjectMapper.java:446)
at au.com.intellibill.WebAppConfig.createJsonMessageConverterWithScalaSupport(WebAppConfig.java:48)
at au.com.intellibill.WebAppConfig.configureMessageConverters(WebAppConfig.java:30)
at org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite.configureMessageConverters(WebMvcConfigurerComposite.java:66)
at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration.configureMessageConverters(DelegatingWebMvcConfiguration.java:95)
at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.getMessageConverters(WebMvcConfigurationSupport.java:499)
at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.requestMappingHandlerAdapter(WebMvcConfigurationSupport.java:362)
at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerByCGLIB$$5d5c02bc.CGLIB$requestMappingHandlerAdapter$23()
at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerByCGLIB$$5d5c02bc$$FastClassByCGLIB$$5f5f5b95.invoke()
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:286)
at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerByCGLIB$$5d5c02bc.requestMappingHandlerAdapter()
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:160)
... 54 more