Spring boot adding and removing singleton at runtime - java

With reference with below links, i want my spring boot app to replace bean at runtime in applicationcontext.
Add Bean
Remove Bean
Below is my try,
MainClass.java
#SpringBootApplication
public class MainClass {
public static void main(String[] args) {
SpringApplication.run(
MainClass.class, args);
new Thread(new MyThread()).run();
}
}
ApplicationContextProvider.java
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext(){
return context;
}
#Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
context = arg0;
}
public Object getBean(String name){
return context.getBean(name, Object.class);
}
public void addBean(String beanName, Object beanObject){
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
beanFactory.registerSingleton(beanName, beanObject);
}
public void removeBean(String beanName){
BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
reg.removeBeanDefinition(beanName);
}
}
Config.java
#Configuration
#ComponentScan(value="com.en.*")
public class Config {
#Bean
#Qualifier("myMap")
public MapBean myMap(){
MapBean bean = new MapBean();
Map<String, String> mp = new HashMap<>();
mp.put("a", "a");
bean.setMp(mp);
return bean;
}
#Bean
ApplicationContextProvider applicationContextProvider(){
return new ApplicationContextProvider();
}
}
MapBean.java
import java.util.Map;
public class MapBean {
private Map<String, String> mp;
public Map<String, String> getMp() {
return mp;
}
public void setMp(Map<String, String> mp) {
this.mp = mp;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MapBean [mp=");
builder.append(mp);
builder.append("]");
return builder.toString();
}
}
MyThread.java
import java.util.HashMap;
import java.util.Map;
import com.en.model.MapBean;
public class MyThread implements Runnable{
static ApplicationContextProvider appCtxPrvdr = new ApplicationContextProvider();
public void run(){
try {
Thread.sleep(5000);
if(ApplicationContextProvider.getApplicationContext().containsBean("myMap")){
System.out.println("AppCtx has myMap");
MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
System.out.println(newM);
appCtxPrvdr.removeBean("myMap");
System.out.println("Removed myMap from AppCtx");
}
MapBean bean1 = new MapBean();
Map<String, String> mp = new HashMap<>();
mp.put("b", "b");
bean1.setMp(mp);
appCtxPrvdr.addBean("myMap", bean1);
System.out.println("myMap added to AppCtx");
if(ApplicationContextProvider.getApplicationContext().containsBean("myMap")){
System.out.println("AppCtx has myMap");
MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
System.out.println(newM);
appCtxPrvdr.removeBean("myMap");
System.out.println("Removed myMap from AppCtx");
}
MapBean bean2 = new MapBean();
Map<String, String> map2 = new HashMap<>();
map2.put("c", "c");
bean2.setMp(map2);
appCtxPrvdr.addBean("myMap", bean2);
System.out.println("myMap added to AppCtx");
MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
System.out.println(newM);
} catch (Exception e) {
e.printStackTrace();
}
}
}
The output i am getting is as below,
AppCtx has myMap
MapBean [mp={a=a}]
Removed myMap from AppCtx
myMap added to AppCtx
AppCtx has myMap
MapBean [mp={b=b}]
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myMap' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.removeBeanDefinition(DefaultListableBeanFactory.java:881)
at com.en.config.ApplicationContextProvider.removeBean(ApplicationContextProvider.java:47)
at com.en.config.MyThread.run(MyThread.java:36)
at java.lang.Thread.run(Unknown Source)
at com.en.MainClass.main(MainClass.java:77)
So as per my understanding below things are happening.
In Config class, it is adding myMap to appctx.
In Mythread class, it is able to find myMap in appctx.
It is able to print and then remove from appctx.
It is able to add new myMap to appctx.
When above step is done. It is not able to remove it again.
Please advice on how to add and remove it multiple time.

BeanDefinitions and beans are totally different things in spring. When BeanDefinition is removed, the bean still exists in ApplicationContext.
Hence I can't really understand the implementation of ApplicationContextProvider in your example.
Now the thing you're asking for is very unusual, it could be great if you could provide more information on why do you need such a logic in runtime.
I personally don't think you should remove the beans when the application starts.
It's possible or at least kind of "conventional" to do the following:
Conditionally load the bean when the application context Starts with the help of #Conditional annotation (there are many of those) / #Profile annotation
Alter the bean during the runtime to give it additional functionality, for this use BeanPostProcessor
Alter Bean definition by means of defining BeanFactoryPostProcessor (used in extremely rare cases)
Now, if you're aware of all these mechanisms and none of them suits your needs, try the following:
Define an internal state in the singleton bean and check the state every time the bean's method is called.
This can be implemented right inside the bean, with wrapper / decorator or in any other way, but the logic is the same.
Example:
public class MySingleton {
private boolean shouldWork = true;
public void stop() {
shouldWork = false;
}
public void start() {
shouldWork = true;
}
public void doSomething() {
if(shouldWork) {
// do real logic
}
else {
// do nothing, or some minimal thing to not break anything
}
}
}

Well your logic is pretty wired and if you are really trying to do something like refresh the bean with different configurations at runtime or sort of a thing,
Please consider looking at externalized configurations and refresh configs on the fly
But if still you are not happy with this and you need to stick with what you have done above, I guess the problem is with your method :
public void addBean(String beanName, Object beanObject){
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
beanFactory.registerSingleton(beanName, beanObject);
}
Because since it does not register a bean definition, spring context will not know that its really there.
Would suggest to try adding :
BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
reg.registerBeanDefinition(beanName,beanDefinition);
so basically your addBean method should change as follows,
public void addBean(String beanName, Object beanObject){
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
beanFactory.registerSingleton(beanName, beanObject);
BeanDefinition beanDefinition = beanFactory.getBeanDefinition( beanName );
BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
reg.registerBeanDefinition(beanName,beanDefinition);
}

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

modifying property of a bean with singleton scope in Spring

I have a bean with singleton scope as below:
public class MyImpl implements MyInterface {
private HashMap<String, String> config = new HashMap<>();
private void load(String check) {
if ("abc".equalsIgnoreCase(check)) {
config.put("key", "val");
}
else {
config.put("key", "val_else");
}
}
#Override
public HashMap<String, String> getConfig(String check) {
load(check);
return config;
}
}
Then in other class, I inject MyImpl and try to use config as below:
#Service
public class Service {
#Inject
MyInterface impl;
public doJob(String check){
HashMap<String, String> config = impl.getConfig(check);
String myValue= config.get("key");
//some other code
}
}
If I have 100s of request/sec, and if value of check is abc for some request and and something else for other requests, would I still having different value in myValue? I tried to generalized the code as I can not share the exact code here. My question here is can we modify the property of singleton bean per request?
Create a ThreadLocal storage (see the example) to avoid the problem.
Alternatively you can change the bean scope to be REQUEST

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();
}
}

Spring proxy to choose implementation based on annotation and runtime value

I would like to inject a proxy implementation of an interface to a component and then let spring choose the right implementation based on a runtime property (and the value of an annotation at the implementation class). So my component does not have to care about choosing the right one.
It is kind of like a scope. But i think scopes are only for handling different instances of the same implementation class. Am i wrong with this?
I would like this to run for arbitrary interfaces without creating a service locator or some other construct for every new service.
Here is an example.
Suppose I have an interface defining a service
package test;
public interface IService {
void doSomething();
}
and two implementations:
package test;
import javax.inject.Named;
#Named
#MyAnnotation("service1")
public class Service1 implements IService {
#Override
public void doSomething() {
System.out.println("this");
}
}
...
package test;
import javax.inject.Named;
#Named
#MyAnnotation("service2")
public class Service2 implements IService {
#Override
public void doSomething() {
System.out.println("that");
}
}
Now I would like to inject an IService to another component and let spring choose the correct implementation based on some queryable run time property and the value of MyAnnotation.
Is there a way to do this in a general way in spring?
EDIT:
I have a Context that holds some value. It is a thread local in this case.
package test;
public class MyValueHolder {
private static final ThreadLocal<String> value = new ThreadLocal<>();
public static void set(String newValue) {
value.set(newValue);
}
public static String get() {
return value.get();
}
public static void reset() {
value.remove();
}
}
And I have an component which uses IService
package test;
import javax.inject.Inject;
import javax.inject.Named;
#Named
public class MyComponent {
#Inject
private IService service;
public void myImportantWorkflow(){
MyValueHolder.set("service1");
service.doSomething();
MyValueHolder.set("service2");
service.doSomething();
}
}
The injected service should only be a proxy. Depending on the value set in MyValueHolder the call to doSomething should delegate to service1 or service2. So in this example it should delegate to doSomething on service1 in the first call and to service2 in the second call.
I could write such a delegator implementing the IService interface and use it for this one service. But then i have to repeat this for every other service . I hoped spring could do something like this with proxies almost by itself. Of course i have to provide some method to look beans up based on the value hold in the thread local and register it to spring. But i have no idea if that is even possible without modifying the spring framework. And if it is possible how to accomplish this.
You could use a ProxyFactoryBean to create the proxies and a TargetSource to do the lookup.
For example (not tested)
public class AnnotatedBeanTargetSource implements TargetSource, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
private Class<? extends Annotation> annotationType;
private Class<?> implementedIterface;
private Map<String, Object> beans;
#Override
public Class<?> getTargetClass() {
return this.implementedIterface;
}
#Override
public boolean isStatic() {
return false;
}
#Override
public Object getTarget() throws Exception {
if (this.beans == null) {
this.beans = lookupTargets();
}
return this.beans.get(MyValueHolder.get());
}
protected Map<String, Object> lookupTargets() {
Map<String, Object> resolvedBeans = new HashMap<String, Object>();
String[] candidates = beanFactory.getBeanNamesForAnnotation(annotationType);
for (String beanName : candidates) {
Class<?> type = beanFactory.getType(beanName);
if (this.implementedIterface.isAssignableFrom(type)) {
Annotation ann = AnnotationUtils.getAnnotation(type, annotationType);
resolvedBeans.put((String) AnnotationUtils.getValue(ann), beanFactory.getBean(beanName));
}
}
return resolvedBeans;
}
#Override
public void releaseTarget(Object target) throws Exception {
// nothing to do
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
public Class<? extends Annotation> getAnnotationType() {
return annotationType;
}
public void setAnnotationType(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
public Class<?> getImplementedIterface() {
return implementedIterface;
}
public void setImplementedIterface(Class<?> implementedIterface) {
this.implementedIterface = implementedIterface;
}
}
This is what I would do:
#Named
public class MyComponent {
// introduce a marker interface for Injecting proxies
#InjectDynamic
IService service
...
public void useIService() {
service.doSomething();
...
service.doSomethingElse();
...
service.doFinally();
}
}
Define a BeanPostProcessor that scans for bean with fields annotated with #InjectDynamic, then creates and inject a Proxy implementing the type required by the field.
The Proxy implementation will look in the applicationContext for beans implementing Supplier<T> (Java 8 or guava versions) where <T> is the type of the field annotated with #InjectDynamic.
Then you can define
#Name
public IServiceSupplier implements Supplier<IService> {
#Override
public IService get() {
// here you implement the look-up logic for IService
}
}
In this way the look-up of active the current implementation is decoupled from the Proxy and can be change by target type.

Spring 3.1 Java configuration and inner beans

#Bean
public TimedRepository timedRepository(RealRepository repo) {
return new TimedRepository(repo, timer); // Adds some metrics
}
#Bean
public RealRepository realRepository(DataSource ds) {
return new RealRepository(ds); // The real jdbc implementation
}
In the old XML days I would configure the real repository as an anonymous inner bean. Is it possible to do something similar with the new Java configuration approach? Instantiating the real repository inside the timedRepository factory method is not an option because I want Spring to pick up on annotations on RealRepository.
The motivation is to avoid any other beans to get hold of the real repository implementation. I should also mention that both beans implement a Repository interface that'll be used by any beans depending on the repository (they should not have to know about TimedRepository or RealRepository.
I dont think theres an equivalent to inner or local beans when using java based configuration. I'd probably try to create the RealRepository in the TimedRepositories bean method as well by asking for all dependencies in the method signature. But if you really need to have spring to take care of the RealRepository dependencies you need to use the bean factory.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class ConfigTest {
#Autowired TimedRepository timedRepo;
#Test
public void testRepository() {
Assert.assertNotNull(timedRepo);
}
#Configuration
static class TimedRepositoryConfiguration {
#Autowired
private AutowireCapableBeanFactory beanFactory;
#Bean
public TimedRepository timedRepository() {
RealRepository realRepository = (RealRepository) beanFactory.createBean(RealRepository.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true);
return new TimedRepository(realRepository);
}
public RealRepository realRepository() {
return new RealRepository();
}
}
static class RealRepository {
}
static class TimedRepository {
private RealRepository realRepo;
public TimedRepository(RealRepository r) {
this.realRepo = r;
}
}
}
You can just instantiate the beans manually:
public class BeanThatDependsOnRealRepository() {
private final Repository repository;
#Inject
public BeanThatDependsOnRealRepository(DataSource dataSource) {
this.repository = new RealRepository(dataSource);
}
}
This is essentially what an anonymous inner bean does in XML. You've just explicitly constructed it and obtained its dependencies from Spring in the constructor of the enclosing class.
Late answer, but this is possible in Spring Core 4+ (and possibly Spring Core 3) with some trickery.
While standard Spring semantics do not support inner bean creation using JavaConfig, the internal functionality around inner beans can be taken advantage of to produce the same results.
Inner beans are produced during property value resolution by the BeanDefinitionValueResolver (see BeanDefinitionValueResolver#resolveValueIfNecessary). The concept of "inner beans" within Spring is primarily enclosed within this value resolver (which is the only producer of inner beans) and within bean factories under the term "contained beans" (from parent class DefaultSingletonBeanRegistry).
We can trick Spring into producing additional inner beans by defining a property as a BeanDefinition, according to the resolution strategy presented in BeanDefinitionValueResolver:
#Configuration
public class MyConfiguration {
private static Logger logger = LoggerFactory.getLogger(MyConfiguration.class);
private RealRepository realRepository;
private Timer timer;
public MyConfiguration(#SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") RealRepository realRepository, Timer timer) {
this.realRepository = realRepository;
this.timer = timer;
logger.info("Constructed MyConfiguration {}", this);
}
#Bean
public TimedRepository timedRepository() {
TimedRepository timedRepository = new TimedRepository(this.realRepository, this.timer);
logger.info("Created timed repo: {}", timedRepository);
return timedRepository;
}
public RealRepository realRepository(DataSource dataSource) {
RealRepository realRepository = new RealRepository(dataSource);
logger.info("Created real repo: {}", realRepository);
return realRepository;
}
#Override
public String toString() {
return "MyConfiguration{" +
"realRepository=" + realRepository +
", timer=" + timer +
'}';
}
}
#Component
public class InnerBeanInjectionBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
#Override
public int getOrder() {
// Preempt execution of org.springframework.context.annotation.ConfigurationClassPostProcessor
return 0;
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
String[] beanDefinitionNameList = ((ConfigurableListableBeanFactory) registry).getBeanNamesForType(MyConfiguration.class, true, false);
assert beanDefinitionNameList.length == 1;
BeanDefinition configurationBeanDefinition = registry.getBeanDefinition(beanDefinitionNameList[0]);
BeanDefinition realRepositoryBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MyConfiguration.class)
.setScope(BeanDefinition.SCOPE_SINGLETON)
.setFactoryMethod("realRepository")
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR)
.getBeanDefinition();
configurationBeanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(realRepositoryBeanDefinition);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Do nothing
}
}
The obvious issue with this solution is that it requires manual processing through a BeanDefinitionRegistryPostProcessor, which is a lot of work for a small gain here. What I would suggest instead is the following:
Create a custom annotation (e.g., #InnerBean)
Attach this annotation to methods in #Configuration classes and candidate component classes where desired
Adapt the BeanDefinitionRegistryPostProcessor to scan classes for #InnerBean-annotated static methods (component-classes should be scanned in #postProcessBeanFactory and configuration classes in #postProcessBeanDefinitionRegistry)
Attach the bean definition to the containing bean definition's autowired constructor fields (or setter fields if that is your convention)
The following is an example:
#Target(ElementType.METHOD)
public #interface InnerBean {
}
#Configuration
public class MyConfiguration {
private static Logger logger = LoggerFactory.getLogger(MyConfiguration.class);
private RealRepository realRepository;
private Timer timer;
public MyConfiguration(#SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") RealRepository realRepository, Timer timer) {
this.realRepository = realRepository;
this.timer = timer;
logger.info("Constructed MyConfiguration {}", this);
}
#Bean
public TimedRepository timedRepository() {
TimedRepository timedRepository = new TimedRepository(this.realRepository, this.timer);
logger.info("Created timed repo: {}", timedRepository);
return timedRepository;
}
#InnerBean
public static RealRepository realRepository(DataSource dataSource) {
RealRepository realRepository = new RealRepository(dataSource);
logger.info("Created real repo: {}", realRepository);
return realRepository;
}
#Override
public String toString() {
return "MyConfiguration{" +
"realRepository=" + realRepository +
", timer=" + timer +
'}';
}
}
#Component
public class InnerBeanInjectionBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private static Logger logger = LoggerFactory.getLogger(InnerBeanInjectionBeanFactoryPostProcessor.class);
private Set<BeanDefinition> processedBeanDefinitionSet = new HashSet<>();
#Override
public int getOrder() {
// Preempt execution of org.springframework.context.annotation.ConfigurationClassPostProcessor
return 0;
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
String[] configBeanDefinitionNames = beanFactory.getBeanNamesForAnnotation(Configuration.class);
Arrays.stream(configBeanDefinitionNames)
.map(beanFactory::getBeanDefinition)
.filter(this::isCandidateBean)
.peek(this.processedBeanDefinitionSet::add)
.forEach(this::autowireInnerBeans);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
Arrays.stream(beanFactory.getBeanDefinitionNames())
.map(beanFactory::getBeanDefinition)
.filter(this::isCandidateBean)
.filter(beanDefinition -> !this.processedBeanDefinitionSet.contains(beanDefinition))
.forEach(this::autowireInnerBeans);
}
private boolean isCandidateBean(BeanDefinition beanDefinition) {
return beanDefinition.getBeanClassName() != null && beanDefinition.getBeanClassName().startsWith("com.example.demo.");
}
private void autowireInnerBeans(BeanDefinition beanDefinition) {
// Get #InnerBean methods
assert beanDefinition instanceof AnnotatedBeanDefinition;
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
Set<MethodMetadata> innerBeanMethods = annotatedBeanDefinition.getMetadata().getAnnotatedMethods(InnerBean.class.getName());
// Attach inner beans as constructor parameters
for (MethodMetadata method : innerBeanMethods) {
String innerBeanName = method.getMethodName();
if (!method.isStatic()) {
logger.error("#InnerBean definition [{}] is non-static. Inner beans must be defined using static factory methods.", innerBeanName);
continue;
}
BeanDefinition innerBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(beanDefinition.getBeanClassName())
.setScope(BeanDefinition.SCOPE_SINGLETON)
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR)
.setFactoryMethod(innerBeanName)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(new ConstructorArgumentValues.ValueHolder(innerBeanDefinition, method.getReturnTypeName(), method.getMethodName()));
}
}
}
There will be a few benefits and caveats of doing this. One large benefit is that the bean lifecycle will be managed by the Spring IoC container, meaning that lifecycle callbacks (such as #PostConstruct and #PreDestroy) will be called. The bean can be automatically managed according to the lifecycle of the parent. Caveats include that the beans cannot be injected as factory-method parameters (although with a bit of work you might able to fix this) and that AOP proxying will not be applied to these methods within #Configuration classes (i.e., realRepository() should never be called as it will not reference the singleton inner bean -- instead, the instance field should always be referenced). Further proxying (similar to ConfigurationClassEnhancer.BeanMethodInterceptor) would need to be added in order to apply this.

Categories

Resources