Is it possible to dynamically instantiate a bean in Spring using a factory?
I'd like to be able to instantiate some beans when a particular annotation is detected.
E.g.
#Retention ( RetentionPolicy.RUNTIME)
#Target (ElementType.TYPE)
#Import(CreateFooRegistrar.class)
public #interface CreateFoo {
String name();
}
public interface Foo {
String getName();
void setName(String name);
}
public class FooImpl implements Foo {
...
}
I can easily create beans directly using the concrete class:
public class CreateFooRegistrar implements ImportBeanDefinitionRegistrar {
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(CreateFoo.class.getName()));
String fooName = attributes.getString("name");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FooImpl.class);
builder.addPropertyValue("name", fooName);
registry.registerBeanDefinition(fooName + "Foo", builder.getBeanDefinition());
}
}
But is there any way I can create a bean using the returned object from a factory?
E.g.
public class FooFactory {
public static FooImpl create(String name) {
FooImpl foo = new FooImpl();
foo.setName(name);
return foo;
}
}
The reason I ask is that all the beans I want to automatically create are typically instantiate via a factory. I don't own the factory code, so I'd prefer not to attempt to mimic its inner-workings myself.
Related
I've a class where I've autowired a list of type
Class A{
#Autowired
List<Super> supers;
}
#Qualifier("B")
Class BSuperImpl implements Super{....}
#Qualifier("C")
Class CSuperImpl implements Super{....}
#Qualifier("D")
Class DSuperImpl implements Super{....}
Now I want to write a code in Class A, where I can look upon the bean, based upon the qualifier like following, Without making my bean aware of the Context. Using Spring MVC 4.3
Class A {
#Autowired
List<Super> supers;
void process(String str){
// Code to do supers.applyMethod(getTheBeanWithQualifierAs(str));
}
}
For what it's worth
#Service
public class A {
#Autowired
List<Super> supers;
void process(String str) {
for(Super sup : supers) {
if(str.equals((sup.getClass().getAnnotation(Qualifier.class).value()))){
// executes when the bean qualifier name and str matches.
}
}
}
}
---- Another attempt ----
Interface
public interface Super {
String getQualifier();
}
Sample Implemenation
#Service
#Qualifier(BSuperImpl.QUALIFIER)
public class BSuperImpl implements Super {
static final String QUALIFIER = "B";
#Override
public String getQualifier() {
return QUALIFIER;
}
}
A
#Service
public class A {
#Autowired
Map<String,Super> supers;
void process(String str) {
System.out.println(supers);
for(String beanName : supers.keySet()) {
if(str.equals(supers.get(beanName).getQualifier())){
// execute the logic
}
}
}
}
When a bean is required from the container not through dependency injection , one way or another you will need to refer the application context.
In this approach , the usage of #Qualifier is not required infact . Usage of map is to demonstrate a possiblity that the beanName can also be passed as parameter to the method .
I need to add a dynamically created bean when my 'normal' bean gets created. I tried this so far:
//generate a health bean dynamically, and register it
#PostConstruct
public void init(){
solrhealth = new SolrHealthIndicator(solr);
//context.??
}
I build a SolrHealthIndicatior bean programatically, as I am not using Spring Solr Data. Now I want it registered so it shows up in /health.
I have my context wired, but cannot find how to register the newly created bean in there...
You should be able to programatically define your bean by using the #Bean annotation within a #Configuration class.
#Bean
public SolrHealthIndicator solrHealthIndicatior() {
//you can construct the object however you want
return new SolrHealthIndicator();
}
Then you can just inject it like any other bean(#Autowired constructor, field, setter injection, etc.), if there are multiple beans with the same type you can use #Qualifier to distinguish between them.
You need to use #Lookup annotation.
#Component
public class SolrHealthIndicator {
public SolrHealthIndicator(Solr solr) {
}
}
public class BeanInQuestion {
#PostConstruct
public void init() {
solrHealthIndicator = getHealthIndicatorBean();
}
#Lookup
public SolrHealthIndicator getHealthIndicatorBean() {
//Spring creates a runtime implementation for this method
return null;
}
}
You could make the class containing your #PostConstruct implement BeanDefinitionRegistryPostProcessor. Then you'd then be able to register your beans programmatically:
#Bean
public class MyBean implements BeanDefinitionRegistryPostProcessor {
private BeanDefinitionRegistry registry;
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.registry = registry;
}
#PostConstruct
public void init(){
registry.registerBeanDefinition("solrHealthIndicator", new SolrHealthIndicator(solr));
}
}
Assume I am creating a PrinterService class that has a AbstractPrinter object. AbstractPrinter is subclassed by classes such as HPPrinter, FilePrinter etc.
The exact kind of printer object to be used is mentioned in the RequestParam object passed to my Controller (it is a request attribute).
Is there any way I can inject the right kind of concrete printer class using Spring?
All the other dependencies are injected using #Autowired annotation. How to inject this one?
You can create and load a factory of AbstractPrinter objects during container startup as shown below and dynamically call the respective the AbstractPrinter's print() (or your own method) based on the input parameter (comes from controller) to the service.
In the below code for PrinterServiceImpl class, the main point is that all of the List<AbstractPrinter> will be injected by Spring container (depends upon how many implementation classes you provide like HPPrinter, etc..). Then you will load those beans into a Map during container startup with printerType as key.
#Controller
public class YourController {
#Autowired
private PrinterService printerService;
public X myMethod(#RequestParam("input") String input) {
printerService.myServiceMethod(input);
//return X
}
}
PrinterServiceImpl class:
public class PrinterServiceImpl implements PrinterService {
#Autowired
private List<AbstractPrinter> abstractPrinters;
private static final Map<String,AbstractPrinter> myPrinters = new HashMap<>();
#PostConstruct
public void loadPrinters() {
for(AbstractPrinter printer : abstractPrinters) {
myPrinters.put(printer.getPrinterType(), printer);
}
}
//Add your other Autowired dependencies here
#Override
public void myServiceMethod(String input){//get input from controller
AbstractPrinter abstractPrinter= myPrinters.get(input);
abstractPrinter.print();//dynamically calls print() depending on input
}
}
HPPrinter class:
#Component
public class HPPrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "HP";
}
#Override
public void print() {
// Your print code
}
}
FilePrinter class:
#Component
public class FilePrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "FILE";
}
#Override
public void print() {
// Your print code
}
}
You could create a dedicated PrinterService instance per AbstractPrinter concrete class. For example you could achieve this using Spring configuration which follow the factory pattern:
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private HPPrinter hpPrinter;
#Autowired
private FilePrinter filePrinter;
#Bean
public PrinterService hpPrinterService() {
return new PrinterService(hpPrinter);
}
#Bean
public PrinterService filePrinterService() {
return new PrinterService(filePrinter);
}
public PrinterService findPrinterService(PrinterType type){
if (type == HP)
return hpPrinterService();
....
}
}
Then in your controller, inject PrinterServiceConfiguration then call findPrinterService with the right printer type.
Don't forget to add PrinterServiceConfiguration at your configuration #Import.
If the list of printer is dynamic you could switch to prototype bean :
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private List<AbstractPrinter> printers;
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrinterService createPrinterService(PrinterType type){
return new PrinterService(findPrinterByType(type));
}
private Printer findPrinterByType(PrinterType type) {
// iterate over printers then return the printer that match type
// throw exception if no printer found
}
}
I'm having a Spring boot application in which based on a variable, I need to call corresponding implementation classes of an interface. This is what I have right now:
public interface Parent{
public void call();
}
public class ABC implements Parent{
public void call(){
System.out.println("Called ABC");
}
}
public class XYZ implements Parent{
public void call(){
System.out.println("Called XYZ");
}
}
#Service("caller")
public class Caller{
#Autowired
protected OrderInfoRepository orderInfoRepository;
AnnotationConfigApplicationContext context;
public Caller(){
context = new AnnotationConfigApplicationContext(Config.class);
}
public void callMethod(String param){
Parent p = (Parent) context.getBean(param+"_Caller");
p.call();
}
}
#Configuration
public class Config{
#Bean(name="ABC_Caller")
public Parent getABC(){
return new ABC();
}
#Bean(name="XYZ_Caller")
public Parent getXYZ(){
return new XYZ();
}
}
#Repository
public interface MyRepo extends Repository<MyDAO, Long> {
// ....
}
Basically what I want to do is, based on the param passed to Caller.callMethod(), I want to add "_Caller" to the param, and call the corresponding implementation class. So, I'm defining a #Configuration class, where I define which implementation class to return. And then using the AnnotationConfigApplicationContext, I get the corresponding bean. This works fine.
The problem I'm having is, when I try to Autowire anything in the implementation classes, I get a NoSuchBeanDefinitionException. For instance, when I autowire in the implementation class ABC
public class ABC implements Parent{
#Autowired
MyRepo myRepo;
public void call(){
System.out.println("Called ABC");
}
}
I get Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.persistence.repositories.MyRepo] when I try to start the application. However, when I do the Autowiring in the Caller class instead, it works fine. I had asked a similar question a while back, but was not able to resolve it. Any ideas?
I have a Spring class.
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager {
GenericXmlApplicationContext context;
#PersistenceContext
private EntityManager em;
public DatabaseManager(GenericXmlApplicationContext context) {
this.context = context;
}
....
} //end of class DatabaseManager
I have SpringUtil class
public class SpringUtil {
public static GenericXmlApplicationContext loadSpringContext(String springXmlFile) {
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
context.load(springXmlFile);
context.refresh();
return context;
} //end of loadSpringContext()
} //end of class SpringUtil
Now in main i am using some thing like
public class Regulator {
public static void main( String[] args ) {
Test test = new Test;
test.start();
} //end of main()
} //end of class Regulator
Here is test class
public class Test {
public void start() {
String springXmlFile = "classpath:spring/plcb-app-context-xml.xml";
GenericXmlApplicationContext context = SpringUtil.loadSpringContext(springXmlFile);
} //end of reportStudent()
} //end of class Test
But i am getting error that
Could not instantiate bean class [...DatabaseManager]: No default constructor
found; nested exception is java.lang.NoSuchMethodException:
...DatabaseManager.<init>()
I want that when DatabaseManager class created then spring context taht i am creating using SpringUtil.loadSpringContext(springXmlFile) must pass to it. How can i do it ?
Thanks
Edit
-------------------
public void switchDataSource(DatabaseType databaseType) {
DriverManagerDataSource dataSource = null;
if (databaseType == DatabaseType.LEGACY) {
dataSource = (DriverManagerDataSource)context.getBean("myLegacyDataSource");
} else if (databaseType == DatabaseType.LS360) {
dataSource = (DriverManagerDataSource)context.getBean("myLs360DataSource");
}
LocalContainerEntityManagerFactoryBean emf = context.getBean("myEmf", LocalContainerEntityManagerFactoryBean.class);
emf.setDataSource(dataSource);
}
#SuppressWarnings("unchecked")
#Transactional(readOnly=true)
public List<Object> getResultList(String query, Class mappingClass) throws Exception {
Query emQuery = em.createNativeQuery(query, mappingClass);
return emQuery.getResultList();
} //end of findTraineeFromLegacy()
Actually i have these two methods in my DatabaseManager class. I am setting context so i can get bean from the context in switchDataSource() method.
One thing that i can do is remove instance filed and change the method to
public void switchDataSource(DatabaseType databaseType, GenericXmlApplicationContext context) {
....
}
This is why i am doing this ?
Thanks
Have a no-arg constructor for DatabaseManager.
Implements ApplicationContextAware in DatabaseManager. Spring will know this bean needs to be notified of the application context:
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager implements ApplicationContextAware {
private ApplicationContext context;
public DatabaseManager() {...}
#Override
public void setApplicationContext(ApplicationContext appContext) {
this.context = appContext;
}
} //end of class DatabaseManager
however, double think if you really need that injected. In most case you are doing something wrong.
Update:
For your requirement in your update, which you want your DB Manager to switch datasource base on input type, although it doesn't seems very normal doing such thing, you can simply have your DB Manager injected with a Map and do whatever you want, instead of injecting the app context.
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager implements ApplicationContextAware {
#Resource("&emfBean")
private LocalContainerEntityManagerFactoryBean emfBean;
#Resource("dbManagerDsMap")
private Map<DatabaseType, Datasource> dsMapping;
public DatabaseManager() {...}
public void switchDataSource(DatabaseType databaseType) {
emfBean.setDatasource(dsMapping.get(databaseType));
}
} //end of class DatabaseManager
However I strongly suggest you not doing such thing. Consider having individual entityManagerFactory for each DB you are connecting to, and use the correct emf to connect to DB, instead doing this weird switching logic. I believe it is not supposed to be changed after your application start.