0

In Spring framework and Java world, there is an interesting object collector pattern that I use. For example consider below -

public interface Calculator {
    SomeOutput calculate(SomeInput input);
}

@Component
public class CalImpl1 implements Calculator {
 public SomeOutput calculate(SomeInput input){
  //some implementation
 }
}

@Component
public class CalImpl2 implements Calculator {
 public SomeOutput calculate(SomeInput input){
  //some implementation
 }
}

Now this can easily injected in another class using Spring DI

@Component
public class Main {

 //This line collects all to implementors of this and set it here.
 @Autowired
 public List<Calculator> calculators;

 //other methods
}

Now problem is I am not sure how same thing can be achieved in scala. I have done some search and found cake pattern (http://loicdescotte.github.io/posts/scala-di/) used in scala but that didn't seem to achieve same thing as object collectors like above. I also want to follow open close principle which I think gets violated in cake pattern but using object collectors I can easily achieve it.

is there a way achieve same object collectors like implementation in scala?

Mohammad Adnan
  • 6,527
  • 6
  • 29
  • 47
  • This is more a feature of the DI container than a general pattern. i.e. Autowiring, when encountered a list, will consider injecting multiple matching beans. Unless you give us more information on what kind of DI container you are using in Scala, it is almost impossible to give relevant and meaningful answer. Given you can use Spring with Scala, I think what you want to achieve will still work if u adopt Spring in your Scala project – Adrian Shum Oct 07 '16 at 08:19
  • I can use absolutely any kind of framework that let me allow achieving same in scala. – Mohammad Adnan Oct 07 '16 at 09:33
  • So, given Spring works with Scala, I believe such injection behavior should still be available, so what's the reason not using it or how it is not working? – Adrian Shum Oct 07 '16 at 09:34
  • The same example like above doesn't work for scala(after tweaking small syntaxes of scala and using trait instead of interface etc). – Mohammad Adnan Oct 07 '16 at 09:39
  • Not familiar with Scala, just want to know if it works if there is only 1 `Calculator` bean and you are wiring a `Calculator` instead of `List`? Because I believe this is behavior of Spring that shouldn't affect by the language (as long as the generated code is equvilent to Java with 1. beans are implementing `Calculator` interface 2. `@Autowired` field is of type `List` with proper type param. – Adrian Shum Oct 07 '16 at 10:01
  • Actually I am not aware of such behavior of Spring (capable of autowiring list of beans automatically). I just feel unrealistic in this usage: given type erasure, during runtime, Spring should not be able to know `calculators` is a List of **Calculator**, I believe there should be some other pre-requisite to make it work even in Java, for which may be a hint why it is not working in Scala – Adrian Shum Oct 07 '16 at 10:03
  • By reading http://docs.spring.io/spring/docs/4.3.4.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#beans-autowired-annotation, I guess the autowiring beans behavior works for annotate on field if it is an array, or annotation on **setter** if it is a collection (which is reasonable, as method signature is the only place that Spring can determine the type parameter). I believe your problem is the generated bytecode is not fulfilling these requirement (e.g. the annotation is put on getter/field instead of setter) – Adrian Shum Oct 07 '16 at 10:09
  • if my guess is correct, you are not doing this in your `Main` class in Scala: it should be `@(Autowired @beanGetter) @BeanProperty var calculators : List[Calculator]` (I believe Scala `List` is not converted into Java `List`, you may want to look at Spring-Scala to use the appropriate PropertyEditor) – Adrian Shum Oct 07 '16 at 10:17
  • Fine I'll try spring-scala for this. The bigger question is is there a better way. – Mohammad Adnan Oct 07 '16 at 10:37
  • @AdrianShum see comment section https://spring.io/blog/2012/12/10/introducing-spring-scala/ this project is not supported anymore. – Mohammad Adnan Oct 07 '16 at 10:40
  • There is another fork maintained by a original contributor. The main idea is the property editor which should not be something hard to write even if there is no out of box lib. And, as I said before, it is simply a DI container feature which is meaningless to say "better" without knowing your choice of container. In brief, as long as the container allow you to determine the type of bean, and allow you to, in some level, control the auto wiring behaviour, there will be workarounds to give you similar feature – Adrian Shum Oct 07 '16 at 10:42

2 Answers2

0

There are templates in lighbend activator that illustration using spring DI on Play, Akka and Scala applications. Please see this: https://www.lightbend.com/activator/templates#filter:spring

I haven't used Spring as DI, I usually use Guice (explicitly used because it's default on play framework 2) and Implicits parameters both as a compilation DI.

Sample:

class B

class X(x: Int)(implicit c: B)

//DI - mostly define in main method/application

implicit val c: B = new B
val x = new X(2)
0

Explicitly using java.util.List worked for me. This is not the prettiest solution but it shows that it basically works. Haven't tried that but implementing a corresponding PropertyEditor you could stick with the Scala types.

trait Calculator {
   def calculate(input: SomeInput) : SomeOutput
}

@Component
class CalImpl1 extends Calculator {
    override def calculate(input: SomeInput): SomeOutput = ...
}

@Component
class CalImpl2 extends Calculator {
  override def calculate(input: SomeInput): SomeOutput = ...
}

@Component
class Main @Autowired()(calculators: java.util.List[Calculator]) {
    // or inject field if constructor injection is not desired
    // @Autowired
    // var calculators: java.util.List[Calculator] = _
}

object Main {
    def main(args: Array[String]) = {
        val ctx = new AnnotationConfigApplicationContext("your package name here")
        val main = ctx.getBean(classOf[Main])
        // calculators should now be wired in the returned instance
  }
}
Josef
  • 321
  • 1
  • 6
  • did you try this example working? I am not sure if it works. – Mohammad Adnan Oct 08 '16 at 05:56
  • Yes, works for me. Just updated the code adding a little main method. Running it as a stand-alone application I end up with the `Main` instance having their `calculators` properly wired. Also replaced `@Inject` with `@Autowired`. Doesn't make any difference but maybe was some source of confusion? – Josef Oct 09 '16 at 12:36