0

I have a Service I would like to use on different places in my application:

@Service
public class ActionListMain  { /* .... */ }

First I want to use in an entity context:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "myTable")
public class myTable {
    @Autowired
    @Transient
    private static ActionListMain allActions;
    /* .... */
}

And I want to also use it in other non-annotated classes as for example:

public class Action {
    @Autowired
    private ActionListMain actionListMain;
}

On the other side I have a StartupComponent where it is wired as expected:

@Component
public class StartupComponent {
    @Autowired
    private ActionListMain actionListMain;
}

Why does it is NULL in all the other classes?

2 Answers 2

2

Spring can only autowire beans into Spring-managed classes. Since the Action and MyTable classes aren't managed by Spring the ActionListMain can't be autowired there.

There is a (hackish) workaround that includes creating a Spring-managed bean and autowiring applicationContext in there and then getting the beans from the static applicationContext.

@Component
public class SpringContext implements ApplicationContextAware {

    //Has to be static to have access from non-Spring-managed beans
    private static ApplicationContext context;

    public static <T extends Object> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }

    @Override
    // Not static
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        SpringContext.context = context;
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

As mentioned by @Janar, Spring can only autowire beans into Spring-managed classes.

You can add this tricky implementation which allows you get ActionListMain outside of @Component.

We are going to centralize all bean injection (Spring + Custom) in BeansManager.

PRO / CONT

  • All new services automatically will be instanced
  • Centralized initiation solves Circular Dependencies in Spring
  • You need to call .getBean to initiate manually

Dependencies

<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.12</version>
</dependency>

Classes which implements Injectable need to have constructor with no args

Injectable interfaces to get your @Service / @Component instances

public interface Injectable {

    /**
    * Initiate all beans dependencies.
    * @param manager Beans manager allows to get singleton instances
    */
    void init(BeansManager manager);
}

BeansManager class

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.reflections.Reflections;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class BeansManager {

    @Autowired
    private List<Injectable> injectables;

    /**
     * This method will make sure all the injectable classes will get the
     * BeansManager in its steady state, where it's class members are ready to be
     * set.
     */
    @PostConstruct
    protected void inject() throws Exception {

        // Init all Spring @Component classes
        for (Injectable injectableItem : injectables) {
            injectableItem.init(this);
        }

        //Setup your package to scan
        Reflections reflections = new Reflections("com.example.demo");
        Set<Class<? extends Injectable>> classes = reflections.getSubTypesOf(Injectable.class);

        for (Class<? extends Injectable> clazz : classes) {

            // Avoid calling twicely if clazz already initialized by Spring
            if (getBean(clazz, Boolean.FALSE) == null) {
                Method init = clazz.getDeclaredMethod("init", BeansManager.class);
                init.invoke(clazz.newInstance(), this);
            }

        }
    }

    /**
     * Get singleton from BeansManager.
     * 
     * @param <T>   Spring service / component class
     * @param clazz singleton class
     * @return Singleton / throws exception
     * @throws NoSuchBeanDefinitionException If bean not found (required=true)
     */
    public <T> T getBean(Class<T> clazz) {
        return getBean(clazz, Boolean.TRUE);
    }

    /**
     * Get singleton from BeansManager.
     * 
     * @param <T>      Component service / component class
     * @param clazz    singleton class
     * @param required If bean not found, it throw exception (true) or returns null
     *                 (false)
     * @return Singleton / null / throws exception
     * @throws NoSuchBeanDefinitionException If bean not found (required=true) 
     */
    public <T> T getBean(Class<T> clazz, boolean required) {
        Object bean = null;
        for (Injectable injectableItem : injectables) {
            if (clazz.isInstance(injectableItem)) {
                bean = clazz.cast(injectableItem);
                return clazz.cast(bean);
            }
        }

        if (required) {
            throw new NoSuchBeanDefinitionException(clazz);
            
        } else {
            return null;
        }
    }
}

Implement Injectable for your classes

@Service
public class ActionListMain implements Injectable
...

public class MyTable implements Injectable
...

    @Override
    public void init(BeansManager manager) {
        allActions = manager.getBean(ActionListMain.class);
    }
...

public class Action implements Injectable

    private static ActionListMain actionListMain;

    @Override
    public void init(BeansManager manager) {
        actionListMain = manager.getBean(ActionListMain.class);
    }

@Component
public class StartupComponent implements Injectable
...
    @Override
    public void init(BeansManager manager) {
        actionListMain = manager.getBean(ActionListMain.class);     
    }  

Initialize your application

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    @Autowired
    ActionListMain actionListMain;
    
    @Autowired
    StartupComponent startupComponent;
    
    Action action   = new Action();
    MyTable myTable = new MyTable();
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        actionListMain.sayHello();
        startupComponent.sayHello();
        action.sayHello();
        myTable.sayHello();
    }
}

com.example.demo.ActionListMain: ready
class com.example.demo.StartupComponent com.example.demo.ActionListMain@d28c214
class com.example.demo.Action com.example.demo.ActionListMain@d28c214
class com.example.demo.MyTable com.example.demo.ActionListMain@d28c214

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.