How to use Spring Autowired (or manually wired) in

2019-03-11 07:03发布

问题:

I am trying to use Spring with Scala. I know Autowired works with Scala class, but I am using a web-framework that requires an object and I want to inject a dao into it. I wonder how to do this? Sorry, I am quite new to Scala, thanks in advance.

    @Service
    object UserRest extends RestHelper {
        @Autowired
        @BeanProperty
        val userRepository: UserRepository = null;

        .....
    }

    <beans>
         .....
         <bean id="userRest" class="com.abc.rest.UserRest" >
              <!--- this is my attempt to manually wire it --->
              <property name="userRepository" ref="userRepository"/>
         </bean>
    </beans>

回答1:

Basically, you have two problems:

  • Property should be mutable, i.e. var rather than val

  • All methods of Scala object are static, whereas Spring expects instance methods. Actually Scala creates a class with instance methods named UserRest$ behind the scene, and you need to make its singleton instance UserRest$.MODULE$ available to Spring.
    Spring can apply configuration to preexisting singleton instances, but they should be returned by a method, whereas UserRest$.MODULE$ is a field. Thus, you need to create a method to return it.

So, something like this should work:

object UserRest extends RestHelper {
   @BeanProperty
   var userRepository: UserRepository = null;

   def getInstance() = this
   ...
}

.

<bean id="userRest" 
    class="com.abc.rest.UserRest" 
    factory-method = "getInstance">
    <property name="userRepository" ref="userRepository"/>
</bean>

You can replace <property> with @Autowired, but cannot replace manual bean declaration with @Service due to problems with singleton instance described above.

See also:

  • What is the Java equivalent of a Scala object?
  • 3.3.2.2 Instantiation with a static factory method


回答2:

All that's actually necessary is that you define your object as a class, rather than an object. That way Spring will instantiate it.

 @Service
    object UserRest extends RestHelper {
        @Autowired
        @BeanProperty
        val userRepository: UserRepository = null;

        .....
    }
<beans>
         .....
         <bean id="userRest" class="com.abc.rest.UserRest" >
              <!--- this is my attempt to manually wire it --->
              <property name="userRepository" ref="userRepository"/>
         </bean>
    </beans>

Changing the "val" to "var" is unnecessary (Spring uses reflection, which ignores immutability). I'm pretty sure that that @BeanProperty is also unnecessary (Spring will assign to the underlying field, reflectively).



回答3:

axtavt's solution did not work for me, but combining different suggestions from the other answers I think this is the most elegant solution:

object User {
    @Autowired val repo: UserRepository = null

    def instance() = this
}

@Configuration
class CompanionsConfig {
    @Bean def UserCompanion = User.instance
}

<context:component-scan base-package="your-package" />

A few notes:

  • Using @Configuration ensures that your companion objects are eagerly autowired
  • Using @Bean def avoids having to deal with noisy names Scala gives to the class that implements the companion object
  • val works just fine, as mentioned by Dave Griffith
  • there is no need for Scala's @BeanProperty, Spring understands Scala properties out of the box (I'm using 3.2.2)


回答4:

What I do is use AutowiredAnnotationBeanPostProcessor to inject the object at construction time.

For example:

object UserRest extends RestHelper {
    @Autowired
    var userRepository: UserRepository = _

    AppConfig.inject(this)
}

@Configuration
class AppConfig extends ApplicationListener[ContextRefreshedEvent] {

  // Set the autowiredAnnotationBeanPostProcessor when the Spring context is initialized
  def onApplicationEvent(event: ContextRefreshedEvent) {
    autowiredAnnotationBeanPostProcessor =
      event.applicationContext.
        getBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME).
          asInstanceOf[AutowiredAnnotationBeanPostProcessor]
  }
}

object AppConfig {
  var autowiredAnnotationBeanPostProcessor: AutowiredAnnotationBeanPostProcessor = null

  def inject(obj: AnyRef) {
    autowiredAnnotationBeanPostProcessor.processInjection(obj);
  }
}

Now you can use AppConfig.inject() to inject any object whose lifecycle is not controlled by Spring. For example, JPA Entities, etc.



回答5:

Normal Autowire from a class that is marked with @Component or @Bean would work with above mentioned ways.

But if you want to auto wire an interface extending Jpa repository then make sure the Dao is not an object but class.

ex:

DAO:

object dao{
 @Autowired val repo: jpaRepo = null
}

This won't work (tested). My guess is that since it's defined as an object, gets instantiated at run time with repo as null value, hence won't be able to autowire jpa repo.

Instead declare it as class and mark with @Component:

@Component
class dao{
@Autowired val repo: jpaRepo = null
}

It works since we are letting spring to manage the object creation(@component) that autowires jpa repo properly.



回答6:

In addition to https://stackoverflow.com/a/8344485/5479289, it's also possible to add scala package object to Spring context, as well as scala object, using factory method. Compiled package object is usual java class named package, so you can add it to Spring context. After that you will have all Spring's possibilities inside this object, i.e @Autowired, @Value, manual wiring etc.

Test package:

package my.module

package object A {
  def getInstance = this

  @Autowired
  private val b: B = null
}

And spring context xml is:

<beans ...>
  ...
  <bean id="a" class="my.module.A.package" factory-method="getInstance"/>
  ...
</beans>