Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Anyway to @Inject/@Autowire an inner class into an outer class?

In Spring/JSR-330, is there a way to properly declare an inner class which requires dependency injection, such that I can inject it into the outer class?

For example:

@Component
public class TestClass{

    // How to declare this class?
    private class TestClassInner{
       @Autowired private SomeBean somebean;

       public boolean doSomeWork(){
          return somebean.doSomething();
       }               
    }

    // Inject the inner class here in the outer class such that the outer class can use an instance of it
    @Autowired TestClassInner innerClass;

    @PostConstruct
    public void init(){
        ...
    }

    public void someMethod(){
       innerClass.doSomeWork();
       ...
    }
}

I've tried annotating the inner class with @Component, making it a public class, making it public static, etc, but it seems that every combination I've tried always ends up throwing one error or another.

As a private inner class, Spring complains that it is missing a constructor, even if I define one.

As an annotated @Component public static class, Spring complains that it find two beans - TestClass@TestClassInner and testClass.TestClassInner. And if I use a @Qualifier, it then complains that the bean cannot be found.

I presume I am misunderstanding how these inner beans work/interact with Spring to properly understand if/how to declare them.

Is this even possible?

Edit

So here are a few combinations I've tried (including tried implementing a new constructor based on @SotiriosDelimanolis response):

    // How to declare this class?
@Component
public class TestClassInner{
    @Autowired private ProviderService providerService;

    public TestClassInner(){
        super();
    }
    public TestClassInner( TestClass t){
        super();
    }
}

Throws error (both public and private inner class throw the same error):

Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.ia.exception.TestClass$TestClassInner]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.ia.exception.TestClass$TestClassInner.<init>()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1030)
    ... 54 more

I just tried using a static public nested class with my test class (above) and it seems to inject properly. On the other hand, in my actual controller, it discovers 2 matching classes:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.ia.web.ContractController$InnerClass] is defined: expected single matching bean but found 2: com.ia.web.ContractController$InnerClass,contractController.InnerClass
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:865)

Edit 2

@Controller
public class ContractController {

    @Component
    static public class InnerClass extends AttachmentControllerSupport{

        /**
         * 
         */
        public InnerClass() {
            super();
            // TODO Auto-generated constructor stub
        }

        public InnerClass( ContractController c){
            super();
        }
    }

    @Autowired private InnerClass innerclass;

    @Autowired private AttachmentControllerSupport attachmentControllerSupport;
    @Autowired private ContractService contractService;

}

applicationContext.xml:

<context:component-scan base-package="com.ia">
    <context:exclude-filter expression=".*_Roo_.*" type="regex"/>
    <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<context:spring-configured/>

restmvc-config.xml:

<mvc:annotation-driven conversion-service="applicationConversionService" >
  <mvc:argument-resolvers>
    <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" />
  </mvc:argument-resolvers>
</mvc:annotation-driven>
like image 859
Eric B. Avatar asked Jun 13 '14 21:06

Eric B.


People also ask

Can we Autowire @component class?

Component scanningThe @Autowired annotation only works if annotation-driven injection is enabled. Make sure that all of your beans/classes are being scanned to enable annotation-driven injection. If Spring isn't scanning the right classes it won't autowire those dependencies.

Can inner class calling outer method?

Method Local inner classes can't use a local variable of the outer method until that local variable is not declared as final. For example, the following code generates a compiler error.

What is the correct way to instantiate the inner class in the main of outer?

To instantiate an inner class, you must first instantiate the outer class. Then, create the inner object within the outer object with this syntax: OuterClass outerObject = new OuterClass(); OuterClass. InnerClass innerObject = outerObject.

Can outer class access inner class methods?

The outer class can access any member of the inner class indirectly through an object of the inner class.


1 Answers

It is possible to declare and instantiate inner class beans through @Component annotations, but solution is ugly, but I will get to it later. First, here's how you can do it with <bean> declarations in XML. Given

package com.example;

public class Example {
    @Autowired
    private Inner inner;
    public class Inner {        
    }
}

you'd have

<bean name="ex" class="com.example.Example" />
<bean name="inner" class="com.example.Example$Inner">
    <constructor-arg ref="ex"></constructor-arg>
</bean>

For inner classes, any constructor implicitly declares its first parameter as an instance of the enclosing type.

So

public Inner() {}

above would actually be compiled to

public Inner (Example enclosingInstance) {}

With Java code, the argument for that parameter is implicitly provided with the syntax

enclosingInstance.new Inner();

Spring uses reflection to instantiate your bean classes and initialize your beans. And the concept described here also applies to reflection. The Constructor used to initialize the Inner class has to have its first parameter be of the type of the enclosing class. That's what we are doing explicitly here by declaring a constructor-arg.

The solution for using @Component depends on a few things. First, you have to know all the things discussed above. Basically, with a Constructor object, when you call newInstance(), you need to pass an instance of the enclosing class as the first argument. Second, you have to know how Spring processes the annotation. When the annotated class has a single constructor annotated with @Autowired, that's the constructor it will choose to initialize the bean. It also uses the ApplicationContext to resolve beans to inject as arguments to the constructor.

Playing on those two facts, you can write a class like this

@Component
public class Example {
    @Component
    public class Inner {
        @Autowired
        public Inner() {}

    }
}

Here, our inner class has an @Autowired constructor, so Spring knows exactly which Constructor object to use. Because of the @Autowired it will also try to find a bean from the ApplicationContext to match and inject for every parameter the constructor has. In this case, the only parameter is of type Example, the enclosing class. Since Example is also annotated with @Component, it's also a bean in the context, so Spring can inject it into the constructor of the inner class.

like image 170
Sotirios Delimanolis Avatar answered Oct 16 '22 05:10

Sotirios Delimanolis