How to Autowire a class at runtime in a method - java

Is it possible to Autowire fields in a dynamic class?
I am getting a class name from the database and I want to autowire this class

Short Answer
That's not possible. Spring needs to know what Beans there are for injecting them.
Long Answer
You could #Autowire every possible bean into a class and then cache them in a Map, where the Class represents the key, and the Object the value. See below simplified example:
public class MyClass{
private final Map<Class<?>, Object> cache = new HashMap<>();
#Autowired
public MyClass(Service1 s1, Service2 s2){
// registering the beans
cache.put(Service1.class, s1);
cache.put(Service2.class, s2);
}
public <T> T getService(String className) throws ClassNotFoundException{
// getting the bean
Class<?> clazz = Class.forName(className);
return (T) cache.get(clazz);
}
}

Not sure it's a good idea, but you can inject a class like mentionned here :
Injecting beans into a class outside the Spring managed context

You can try this:
import javax.annotation.PostConstruct;
#Component
public class ApplicationContextAccessor {
private static ApplicationContextAccessor instance;
#Autowired
private ApplicationContext applicationContext;
public static T getBean(Class clazz) {
return instance.applicationContext.getBean(clazz);
}
#PostConstruct
private void registerInstance() {
instance = this;
}
}
Read this post : https://www.helicaltech.com/uses-of-springs-applicationcontext-while-using-reflection/

Related

Spring Dependency Injection not working in singleton class

i did a singleton class named AcessoCliente
public class AcessoCliente {
private HashMap<String, Cliente> clientes;
private new HashMap<String, Date> clientesNaoEncontrados;
private static AcessoCliente instance;
static {
instance = new AcessoCliente();
}
public static AcessoCliente get() {
return instance;
}
private AcessoCliente() {
clientes = new HashMap<String, Cliente>();
clientesNaoEncontrados = new HashMap<String, Date>();
}
/*business*/
}
But i need to do a dependency injection of a class named ValidadorNivelDeAcessoBusiness on my singleton class
#Component
public class ValidadorNivelDeAcessoBusiness {
#Autowired
QuerysNiveisDeAcesso querysNiveisDeAcesso;
/*business*/
}
I'm trying do this dependency injection but isn't working, This is what I did:
public class AcessoCliente {
#Autowired
ValidadorNivelDeAcessoBusiness validadorNivelDeAcessoBusiness;
private HashMap<String, Cliente> clientes;
private new HashMap<String, Date> clientesNaoEncontrados;
private static AcessoCliente instance;
static {
ApplicationContext context = new AnnotationConfigApplicationContext(AcessoCliente.class);
instance = context.getBean(AcessoCliente.class);
}
public static AcessoCliente get() {
return instance;
}
private AcessoCliente() {
clientes = new HashMap<String, Cliente>();
clientesNaoEncontrados = new HashMap<String, Date>();
}
/*business*/
}
but the dependency injection isn't working and I get this error:
Error creating bean with name 'acessoCliente': Unsatisfied dependency expressed through field 'validadorNivelDeAcessoBusiness'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'my.project.business.interceptorBusiness.ValidadorNivelDeAcessoBusiness' available: expected at least 1 bean which qualifies as autowire candidate.
Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
Edit1. That's the QuerysNiveisDeAcesso class
#Component
public class QuerysNiveisDeAcesso extends QuerysClientes {
public QueryIntegratorBuilder queryBuscaNiveisDeAcesso(String[] condicoesQuery) throws Exception {
return super.executaQuery("BUSCA_NIVEIS_DE_ACESSO", condicoesQuery);
}
public QueryIntegratorBuilder queryBuscaNiveisDeAcesso() throws Exception {
return super.executaQuery("BUSCA_NIVEIS_DE_ACESSO");
}
public QueryIntegratorBuilder queryBuscaNiveisDeAcesso(String sqlWhere, String[] condicoesQuery) throws Exception {
return super.executaQuery("BUSCA_NIVEIS_DE_ACESSO", condicoesQuery, sqlWhere);
}
}
You are trying to mix Java Singleton and Spring singleton.
To make it compatible with plain java and spring both, you should make static factory method with your injected service in parameter and make a singleton's bean in #Configuration file.
public class AcessoCliente {
ValidadorNivelDeAcessoBusiness validadorNivelDeAcessoBusiness;
private HashMap<String, Cliente> clients;
private HashMap<String, Date> clientesNaoEncontrados;
private static AcessoCliente instance;
public static AcessoCliente getInstance(ValidadorNivelDeAcessoBusiness validadorNivelDeAcessoBusiness) {
if(instance == null) {
instance = new AcessoCliente(validadorNivelDeAcessoBusiness);
}
return instance;
}
private AcessoCliente(ValidadorNivelDeAcessoBusiness validadorNivelDeAcessoBusiness) {
clientes = new HashMap<String, Cliente>();
clientesNaoEncontrados = new HashMap<String, Date>();
this.validadorNivelDeAcessoBusiness = validadorNivelDeAcessoBusiness;
}
}
#Configuration
public class AcessoClienteConfiguration
{
#Bean
#Scope("singleton")
public AcessoCliente acessoCliente(ValidadorNivelDeAcessoBusiness validadorNivelDeAcessoBusiness)
{
return AcessoCliente.getInstance(validadorNivelDeAcessoBusiness);
}
}
I've tried to recreate the same issue you are having, it would seem that your dependencies in ValidadorNivelDeAcessoBusiness class is trying to load, however one of the fields in this class might be missing an #Component annotation, for the Spring application context to load.
#Component public class AcessoCliente {
#Autowired
ValidadorNivelDeAcessoBusiness validadorNivelDeAcessoBusiness;
}
#Component
public class ValidadorNivelDeAcessoBusiness {
#Autowired
QuerysNiveisDeAcesso querysNiveisDeAcesso;
}
public class QuerysNiveisDeAcesso {
// some code
}
Would produce the above error: 'acessoCliente': Unsatisfied dependency expressed through field 'validadorNivelDeAcessoBusiness'
Ensuring that all the fields in ValidadorNivelDeAcessoBusiness have an #Component got the spring application context to work i.e.:
#Component
public class QuerysNiveisDeAcesso {
// some code
}
You've completely misused the purpose of spring.
First off, there is no point in creating an application context inside the AcessoCliente class.
Application context is a "global" spring's registry objects that usually exists once in a whole application.
If you're using Plain Spring - you can create the application context right in the public static void main method. And then get the beans from there.
Next, when you create the application context, you should pass to it the configuration objects and not a single class. You can also work with component scanning, there are many techniques. But all-in-all it should accept the "rules" to configure - read find and load the beans.
Now lets return to the AccessCliente class. You've defined it as a singleton with static methods and private constructor, that's ok. But It doesn't work with spring - in fact if you're using spring, you can make this class a Singleton in a sense that there will be a single bean in the whole application context.
This is much more manageable and clear (+ no boilerplate code).
In fact all beans by default are singletons in spring universe.
The next thing to mentions is when you make is a singleton bean (a class whose instance is managed by spring) then the whole autowiring magic will start working automatically.
What you've done is a strange hybrid that won't work anyway (how spring can create a bean if its not instructed to do so, and even if it was, the constructor is private).
So, to recap, you need something like this:
public class Main {
public static void main(String [] args) {
ApplicationContext ctx = ...;
ctx.getBean(SomeClassThatStartsTheFlow.class).doSomething();
}
}
#Service
public class AcessoCliente {
#Autowired
ValidadorNivelDeAcessoBusiness validadorNivelDeAcessoBusiness;
private HashMap<String, Cliente> clientes;
private new HashMap<String, Date> clientesNaoEncontrados;
public AcessoCliente() {
clientes = new HashMap<String, Cliente>();
clientesNaoEncontrados = new HashMap<String, Date>();
}
/*business*/
}

Inject spring beans into a non-managed class

I have this non-managed class that I want to inject spring beans (that I don't known a-priory what they are). How can I do that?
For example, let's say I have the following class:
public class NonManagedClass extends APIClass {
#Resource
private Service1 service;
#Resource
private Service2 service2;
// here i can declare many different dependencies
#Resource
private ServiceN serviceN;
#Override
public void executeBusinessStuffs() {
// business logics
}
}
I need in someway to let spring inject these dependencies in my class. I have access to these objects after created, so it's easy to me call any method that can accomplish this functionality. For example:
#Service
public void SomeAPIService {
#Resource
private BeanInjector beanInjector; // I'm looking for some funcionality of spring like this
public void someProcessingFunction(Class<? extends APIClass> clazz) throws Exception {
APIClass instance = clazz.getConstructor().newInstance();
beanInjector.injectBeans(instance);
instance.executeBusinessStuffs();
}
}
Does Spring have such functionality to inject beans based on fields annotation for a non-managed class?
Replace BeanInjector with ApplicationContext and you are almost there. From there you can get the AutowireCapableBeanFactory which provides some handy methods like createBean and autowireBean.
#Service
public void SomeAPIService {
#Resource
private ApplicationContext ctx;
public void someProcessingFunction(Class<? extends APIClass> clazz) throws Exception {
APIClass instance = ctx.createBean(clazz);
instance.executeBusinessStuffs();
}
}
or if you really like to construct stuff yourself instead of using the container:
#Service
public void SomeAPIService {
#Resource
private ApplicationContext ctx;
public void someProcessingFunction(Class<? extends APIClass> clazz) throws Exception {
APIClass instance = clazz.getConstructor().newInstance();
ctx.getAutowireCapableBeanFactory().autowireBean(instance);
instance.executeBusinessStuffs();
}
}

Inject value from properties in Spring Boot

I have a Rest Controller in which I initialise a service like this :
class Config {
#Value(${"number.of.books"})
private final static String numberOfBooks;
}
class MyController {
private final Service myService = new ServiceImplementation(Config.numberOfBooks)
public ResponseEntity methodA() { ... }
}
The numberOfBooks field has a initialisation value but when it's passed in the ServiceImplementation constructor it comes null.
I'm thinking I'm missing something obvious over here.
What is the mistake and which would be the best practice to inject a value from a property file into a constructor?
I recommend you to directly inject numberOfBooks in your ServiceImplementation, as follows:
public class ServiceImplementation implements Service {
#Value("${number.of.books}")
private String numberOfBooks;
}
Otherwise use setter injection for static variables, as follows:
#Component
class Config {
public static String numberOfBooks;
#Value("${number.of.books}")
public void setNumberOfBooks(String numberOfBooks) {
numberOfBooks = numberOfBooks;
}
}
After studying a little I've found out that the dependency injection happens after the constructor has been called. This being said the approach used was to use Autowired on my services constructor.
class ServiceImplementation implements Service {
private final String numberOfBooks;
#Autowired
private ServiceImplementation(Config config) {
this.numberOfBooks = config.getNumberOfBooks();
}
}
In this way Spring creates the dependency tree and makes sure that Config is not null when injected.

When to init a Map in a Service called from a Controller in Spring with Dependency Injection?

I have a Controller class which is invoked first in my application. There I was planning to retrieve a value from a Map from a Service class.
Here's the controller:
#Controller
public class AppController {
public Service doSomethingWithTheMap(String key) {
return ServiceImpl.getMapValueFor(key).exec();
}
}
I get issues because during the initialization, well during the put of values to the Service's Map to be more precise, I require the BeanFactory because the values in the Map are Service implementations.
Doing it in a static block will cause the BeanFactory to be null because it is not injected yet I would guess.
So ending up with this initMap() call makes me feel a bit like ... there should be a better solution.
Any hints somebody?
I have to admit that I am new to Spring and maybe I mess things up here. FYI the Map came into my mind after having endless if else checks deciding which Service to call based on a String input. Therefore I replaced it with the Map and a simple one liner in the Controller.
ServiceImpl.getMapValueFor(key).exec();
Here' the Service class:
#Service
public class ServiceImpl {
private static Map<String, Service> map;
private static ApplicationContext context;
#Autowired
public void setApplicationContext(ApplicationContext factory) {
this.context = factory;
}
public static Service getMapValueFor(String key) {
if (map == null) {
initMap();
}
return map.get(key);
}
private static void initMap() {
/*
* FIXME: We can not init the map in a static block or directly
* initialize it since the factory is not injected until execution of a
* static block and will be null.
*/
BeanFactory factory = context;
map = new HashMap<String, Service>();
map.put("key", factory.getBean(SomeService.class));
}
}
The first thing I want to say is that you have a bug, because you are using a HashMap with no synchronization! - Don't be alarmed many (if not most) java developers would make the same mistake.
Unless you have oversimplified the code, your service looks more like a command than a service; A service is a singleton. It is not impossible for services to have methods without arguments, but I would say it is uncommon. Are you sure you should not be using prototype beans instead of singletons ?
Typically the number of services are finite, and if you have multiple services of the same type you would use the #Qualifier when autowiring them. In any case this code looks dodgy to me, so perhaps you should try to explain the problem at a higher level, because there may be a better way than you current code-path.
Your service class ServiceImpl must implement the interface org.springframework.context.ApplicationContextAware to get the instance of Spring's application context.
Here is a very basic solution, it uses the fact that the name of a #Bean is the name of the method which creates it, you will probably need a better strategy. The idea is to hide getBean inside a Provider class which can then be Autowired (and tested)
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
CallableProvider provider = ctx.getBean(CallableProvider.class);
System.out.println(provider.getCommand("aCommand").call());
System.out.println(provider.getCommand("bCommand").call());
}
public static class Config {
#Bean
public ACommand aCommand() {
return new ACommand();
}
#Bean
public BCommand bCommand() {
return new BCommand();
}
#Bean
public CallableProvider callableProvider() {
return new CallableProvider();
}
}
public static class CallableProvider implements ApplicationContextAware {
private ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
public Command getCommand(String name) {
return context.getBean(name, Command.class);
}
}
public static class ACommand implements Command {
// autowire stuff
#Override
public String call() {
return "A";
}
}
public static class BCommand implements Command {
// autowire stuff
#Override
public String call() {
return "B";
}
}
public interface Command {
String call();
}
}

How to use Singleton object in Spring?

I am newbie to Spring Framework.I have tried following example in spring.
#Path("/XZY")
#Service
#Transactional
public class XZY {
#Autowired
SampleDAO sampleDao;
#Autowired
TestDAO testDao;
#Autowired
XZYinterface xzyinterface;
#POST
#Produces("text/plain")
#Path("/checkservice")
public Response XZYservice(#FormParam("Code") String Code,
#FormParam("source") String source,
#FormParam("value") String value) {
//return xzyinterface.checkXYZService(Code,sourceName,source);
XZYinterface xyz = ServiceFactory.getXZY(999);
return xyz.checkXYZService(Code,sourceName,source);
}
}
The following code will use to create singleton object
public class Singleton {
private static sampleA sampleClassA=null;
private static SampleB sampleClassB=null;
public static XZYAbstract getXZY(long id){
if(id == 999){
if(sampleClass == null){
sampleClassA = new sampleA();
}
return sampleClass;
}
if(id == 9999){
sampleClassB = new sampleA();
}
return sampleClassB;
}
}
Interface
public interface XZYinterface {
Response XZYservice(String Code, String source,String value)
}
Abstract class and implements Interface
public class XZYAbstract implements XZYinterface {
public XZYAbstract(){
super();
}
#Autowired
SampleDAO sampleDao;
#Autowired
TestDAO testDao;
public Response checkXYZService(String Code,String source,String value){
String sample = sampleDao.getValue(code);
//..source code
}
}
The following class extends abstract class.
public class sampleA extends XZYAbstract {
//some methods.
}
If i run the application it throws following errors
SEVERE [com.sun.jersey.spi.container.ContainerResponse] The RuntimeException could not be mapped to a response, re-throwing to the HTTP container: java.lang.NullPointerException
at com.test.xyz.XZYAbstract.checkXYZService(XZYAbstract.java:112) [:]
at com.test.XYZ.XZYservice(XZY.java:140) [:]
If i call directly without singleton object, values are initialized properly using Auto wired (//return xzyinterface.checkXYZService(Code,sourceName,source);) and it's working fine.
Throw from singleton object, values(sampleDAo,testDao) are not initialized properly.
How to resolve this error?
The reason is quite trivial: it's because Spring is just a library, and not a change to the Java language. Spring doesn't instrument nor enhance constructors, so the only way to get initialized Spring bean is to get it from the Spring context.
If you call new Bean(), you becomes Bean instance untouched by Spring.
For the question how to use singleton bean: do nothing. Spring beans are Singletons by default. You can specify other scope via #org.springframework.beans.factory.config.Scope annotation. See for example #Scope("prototype") bean scope not creating new bean, how it works.

Categories

Resources