How to cast spring beans loaded in applicationContext to interface? - java

I'm creating a Strategy Factory that loads through applicationContext all beans that have specific annotations. In my service, i would like to pass a string parameter to this factory and it should return me the correct implementation. But i'm faced with Cast Exception:
#Autowired
private ApplicationContext applicationContext;
private Map<String,Object> strategyCache = new HashMap<>();
#PostConstruct
public void init(){
Map<String, Object> annotatedBeanClasses = applicationContext.getBeansWithAnnotation(SimulationType.class);
for(Object bean : annotatedBeanClasses.values()){
SimulationType strategyAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SimulationType.class);
strategyCache.put(strategyAnnotation.platform(),bean.getClass());
}
}
public SimulationStrategy getSimulationStrategy(String platform){
SimulationStrategy strategy = (SimulationStrategy) strategyCache.get(platform);
return strategy;
}
Im my service i would like to call this way:
SimulationStrategy strategy = simulationFactory.getSimulationStrategy(platform);
And this is my strategy class:
#Component
#SimulationType(platform="costumer")
public class ProductSimulation extends SimulationTemplate {
Do Stuff....
}

You are putting the bean's Class in your Map, instead of the bean itself.
strategyCache.put(strategyAnnotation.platform(), bean.getClass());
should be
strategyCache.put(strategyAnnotation.platform(), bean);

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*/
}

How to ask for Prototype bean in Spring service class without applicationContext

I have a component defined with prototype scope. I want to use that component in my service class. I want spring to provide me a new instance of that Bean everytime I call for it.
Component Class:
#Getter
#Setter
#Component
#Scope("prototype")
public class ProtoTypeBean {
//.. Field variables
}
Service Class:
#AllArgsConstructor
#Service
public class ServiceClass {
ProtoTypeBean prototypeBean;
ArrayList<ProtoTypeBean> prototypeBeans;
public void demoMethod(ArrayList<String> someArrayList) {
for(var singleString: someArrayList) {
prototypeBean.setFieldValue(singleString);
prototypeBeans.add(prototypeBean);
}
System.out.println(prototypeBeans.toString());
}
}
By using this configuration, I am getting the same instance of ProtoTypeBean in my prototypeBeans ArrayList. The question is, how would I make Spring understand to give me a new instance of prototypeBean every time I am calling it into the foreach loop?
I found I can use ApplicationContext.getBean() to get a new instance of the Bean in foreach loop but I also heard that it's a bad practice. So kindly help me with the best practice.
Use an ObjectProvider to lazily get the result you want. However the first prototype scoped bean will not be represented in the list of beans as, well they are prototype scoped.
#AllArgsConstructor
#Service
public class ServiceClass {
private final ObjectProvider<ProtoTypeBean> provider;
public void demoMethod(ArrayList<String> someArrayList) {
PrototypeBean pb = provider.getIfUnique();
for(var singleString: someArrayList) {
pb.setFieldValue(singleString);
pb.add(prototypeBean);
}
System.out.println(prototypeBean.toString());
}
}
Also if you don't need all the dependency injection, proxy creation etc. for your object then why bother. There is nothing wrong with just the new keyword in a Spring application. Not everything has to be managed by Spring.
Set up your prototype bean similar to this:
#Getter
#Setter
#Component
#Scope("prototype")
public class ProtoTypeBean {
final private String param;
public ProtoTypeBean(final String p) {
this.param = p;
}
}
Now, in your service class use a BeanFactory to create the beans for you:
#Service
#AllArgsConstructor
public class ServiceClass {
private final BeanFactory factory;
private List<ProtoTypeBean> prototypeBeans;
#Autowired
public ServiceClass(final BeanFactory f) {
this.factory = f;
}
public void demoMethod(List<String> someArrayList) {
this.prototypeBeans = someArrayList
.stream()
.map(param -> factory.getBean(ProtoTypeBean.class, param))
.collect(Collectors.toList());
}
}
I came across this issue recently. I am sure there must be a better way than mine, but this is how I did it:
public class ServiceClass {
ArrayList<ProtoTypeBean> prototypeBeans = new ArrayList<>();
#Autowired
ApplicationContext ctx;
public void demoMethod(ArrayList<String> someArrayList) {
for(var singleString: someArrayList) {
//magic is in below line.. getting a bean from ApplicatioContext.
ProtoTypeBean prototypeBean= ctx.getBean("protoTypeBean"); //Or ctx.getBean(ProtoTypeBean.class);
prototypeBean.setFieldValue(qBean.getFieldValue());
prototypeBeans.add(prototypeBean);
}
System.out.println(prototypeBeans.toString());
}
This way, Spring container always give you a new instance. And it is totally managed by Spring container.
The way you tried it, I tried that as well, but it would always inject one instance at the time of autowiring, hence defeating the purpose of prototyping.
You could have gone the route of using new Keyword. But then that is just regular Java instantiation and I think that new instance is not managed by Spring because it is annotated with #Component instead of #Configuration. I could be wrong here though.

How to Autowire a class at runtime in a method

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/

Make List of Spring Bean

I have interface EventService and class #Component Event implementing it. Class #Component BerlinEvent extends #Component Event and implements EventService.
On configuration class I have this:
#Configuration
public class Configuration {
//Country name
#Bean
#ConditionalOnProperty(name = "country", havingValue = "UK")
public Event defaultService(){return new Event();}
#Bean
#ConditionalOnProperty(name = "country", havingValue = "germany", matchIfMissing = true)
public Event germanyEventService(){return new BerlinEvent();}
}
And on main I make the bean:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(EventscraperApplication.class, args);
EventsManagerService eventsManager = context.getBean(EventsManager.class);
eventsManager.run(context.getBean(Event.class));
}
Now on class EventsManagerService I need to make a List with either BerlinEvent or Event objects depending on which bean was created and each object with different values but I cant figure out how to do it
Spring could autowire all beans that implement the same interface into the list like this
#Autowired
private List<Event> events;
By default, the autowiring fails whenever zero candidate beans are available; the default behavior is to treat annotated methods, constructors, and fields as indicating required dependencies. This behavior can be changed as demonstrated below. To avoid that you need to pass addtitonal parameter to annotation like this:
#Autowired(required = false)
private List<Event> events;
Here is the link to Spring documentation: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation
In you bean class, you can just do
#Service
public class EventsManagerService {
#Autowired
private ApplicationContext applicationContext;
private Map<String, Event> beans;
#PostConstruct
public void setMocks() {
beans = applicationContext.getBeansOfType(Event.class);
}
}
This will get you all the beans implementing Events class.

#Configuration not working for Bean creation

I have the following code, where in i am trying to create a bean out of the return type of the method.
It give error while starting the application as below:
Error creating bean with name 'myMap' defined in class path resource
[com/test/MyServiceImpl.class]: No matching factory method found:
factory bean 'MyServiceImpl'; factory method 'myMap()'. Check that a
method with the specified name exists and that it is non-static.
Code:
#Configuration
public class MyServiceImpl implements MyService
{
#Autowired
private MyDao myDao;
#Override
#Bean
#Scope("singleton")
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Map<String, String> myMap()
{
return myDao.getMapFromDB();
}
}
public interface MyService
{
Map<String, String> myMap()
}
My application is based spring mvc and I have added the relevant configuration in the xml.
<mvc:annotation-driven/>
<context:component-scan>
I'm not 100% sure what you try to do, but here's an approach which would work:
#Service
public class MyServiceImpl implements MyService {
#Autowired
private MyDao myDao;
#Override
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Map<String, String> myMap() {
return myDao.getMapFromDB();
}
}
interface MyService {
Map<String, String> myMap()
}
This would define your service implementation as a Spring-managed #Service. Your MyDao would automatically be injected and you can use it in your method.
If you want that your MyDao has a certain scope (singleton is already the default), than you would annotate the MyDao class.
If you want to write a configuration, you would need to do it like this:
#Configuration
class MyConfig {
public MyService myService() {
return new MyServiceImpl();
}
}

Categories

Resources