Reputation: 302
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?
Upvotes: 0
Views: 1511
Reputation: 13093
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
.
Circular Dependencies in Spring
.getBean
to initiate manually<!-- 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
instancespublic interface Injectable {
/**
* Initiate all beans dependencies.
* @param manager Beans manager allows to get singleton instances
*/
void init(BeansManager manager);
}
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;
}
}
}
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);
}
@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
Upvotes: 1
Reputation: 2701
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;
}
}
Upvotes: 2