How to programatically add an #Bean definition to Spring context? - java

Normally I'm adding my objects to spring context using #Bean definition:
#Autowired
private SpringBus bus;
//register a singleton
#Bean
public WebservicePort getPort() {
//new port()
//initialize
//configure
//return port;
}
But now I need deeper control of the process, especially I want to create the bean name dynamically under which the bean is registered.
I tried:
#Service
public class MyPortRegistrar implements BeanDefinitionRegistryPostProcessor {
#Autowired
private SpringBus bus;
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println(bus); //prints null
//create and configure port with the SpringBus
Port port = new WebservicePort(bus); // -> throws NullPointerException
beanFactory.autowireBean(port);
beanFactory.initializeBean(port, "myDynamicPortName");
}
}
But this throws an NPE as the autowired dependecies are not yet initialized here!
So, how can I add those beans programatically?

You should autowire the bean factory, and use a #PostConstruct to register your bean. That way, you guarantee that all dependencies has been injected (the bean factory is injected by the container, no setup is needed).
#Service
public class MyPortRegistrar {
#Autowired
private ConfigurableBeanFactory beanFactory;
#Autowired
private SpringBus bus;
#PostConstruct
public void createPort() {
Port port = new WebservicePort(bus);
beanFactory.registerSingleton("myDynamicPortName", port);
}
}

You should put this before:
beanFactory.autowireBean(port);
But, if you want to initialize a bean, I think that you want to create a single instance (I'm saying this because in the example, you used the #Bean annotation):
beanFactory.initializeBean(port, "myDynamicPortName");
instead of a singleton one:
beanFactory.registerSingleton("myDynamicPortName", port);

An alternative to Khalid's answer (which I think requires some additional dependencies and configuration) is to implement the InitializingBean interface:
#Service
public class MyPortRegistrar implements InitializingBean {
#Autowired
private SpringBus bus;
#Autowired
private ConfigurableBeanFactory beanFactory;
#Override
public void afterPropertiesSet() throws Exception
System.out.println(bus); //prints null
//create and configure port with the SpringBus
Port port = new WebservicePort(bus); // -> throws NullPointerException
beanFactory.autowireBean(port);
beanFactory.initializeBean(port, "myDynamicPortName");
}
}

Related

#autowired annotation for service class is not working in #configure class spring boot

when i am using #autowire to inject my dependencies in Configuration
class its giving me as null please refer the code below .
#Configuration
public class DataSourceConfig {
#Autowired
AppService appService;
#Bean
public BeanDefinitionRegistryPostProcessor beanPostProcessor() {
return new BeanDefinitionRegistryPostProcessor() {
public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {
}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanRegistry) throws BeansException {
createBeans(beanRegistry);
}
};
}
private void createBeans(BeanDefinitionRegistry beanRegistry,DataSourceConfigService ds) {
appService.getDbDetails();
appService is null here if i will call it using this way
BeanDefinitionRegistryPostProcessor beanPostProcessor(AppService
appService) then in AppServiceImpl class AppDao dependency will be null
}
}
//// Service
#Service
public class AppServiceImpl implements AppService{
#Autowired
AppDao ds;
#Override
public List<A> getDatabaseConfiguration() {
return ds.getDbDetails(); // here ds is null
}
}
//dao
#Repository
public class AppDaoImpl implements AppDao {
#Qualifier("nameParamJdbcTemplate")
#Autowired
public NamedParameterJdbcTemplate nameParamJdbcTemplate;
#Override
public List<A> getDbDetails() {
return nameParamJdbcTemplate.query(SELECT_QUERY, new DataSourceMapper()); // nameParamJdbcTemplate is null
}
// datasource config
#Configuration
public class DataSourceBuilderConfig {
#Bean(name = "dbSource")
#ConfigurationProperties(prefix = "datasource")
#Primary
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
#Bean(name = "nameParamJdbcTemplate")
#DependsOn("dbSource")
#Autowired
public NamedParameterJdbcTemplate jdbcTemplate1(#Qualifier("dbSource") DataSource dbSource) {
return new NamedParameterJdbcTemplate(dbSource);
}
}
What i want is when ever my beanPostProcessor()
is executed i want all my dependent beans should be instantiated ie
#Autowired
AppService appService;
#Autowired
AppDao ds;
#Qualifier("nameParamJdbcTemplate")
#Autowired
public NamedParameterJdbcTemplate nameParamJdbcTemplate;
I am new to spring so any help or working examples would be great. Thanks
It is null because this #Configuration class also defines a BeanDefinitionRegistryPostProcessor that forces the context to create that bean very early on.
Because you are using field injection, the context has to resolve AppService bean but it can't yet because the post-processor have to be applied first.
Your configuration looks very complex so you may want to simplify it a bit:
Separate low-level infrastructure configuration from main configuration
Always define such post processor as public static method so that the context can invoke the #Bean method without having to construct the class first.

Spring Dependency Injection into JPA entity listener

I need to have a Spring dependency injected into a JPA entity listener. I know I can solve this using #Configurable and Spring's AspectJ weaver as javaagent, but this seems like a hacky solution. Is there any other way to accomplish what I'm trying to do?
Since Hibernate 5.3 org.hibernate.resource.beans.container.spi.BeanContainer and Spring 5.1 org.springframework.orm.hibernate5.SpringBeanContainer you do not need to extra autowiring effort any more. See details of this feature in https://github.com/spring-projects/spring-framework/issues/20852
Simply annotate your EntityListener class with #Component, and do any autowiring like so:
#Component
public class MyEntityListener{
private MySpringBean bean;
#Autowired
public MyEntityListener(MySpringBean bean){
this.bean = bean;
}
#PrePersist
public void prePersist(final Object entity) {
...
}
}
In Spring Boot the configuration of LocalContainerEntityManagerFactoryBean is done automatically in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration.
Outside of Spring Boot, you have to register SpringBeanContainer to Hibernate:
LocalContainerEntityManagerFactoryBean emfb = ...
emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
Another trick is to implement an utility class with static method that helps you to use Spring beans everywhere, not only in managed classes:
#Component
public final class BeanUtil {
private static ApplicationContext context;
private BeanUtil(ApplicationContext context) {
BeanUtil.context = context;
}
public static <T> T getBean(Class<T> clazz) throws BeansException {
Assert.state(context != null, "Spring context in the BeanUtil is not been initialized yet!");
return context.getBean(clazz);
}
}
Here's a solution in Kotlin (Spring Boot 2.3.9, Hibernate 5.4.29.Final). First part is similar to Matthias' answer. However, the second part was needed even though it's a Spring Boot application.
Bean declaration
#Component
class EntityXyzListener(val mySpringBean: MySpringBean) {
#PostLoad
fun afterLoad(entityXyz: EntityXyz) {
// Injected bean is available here. (In my case the bean is a
// domain service that I make available to the entity.)
entityXyz.mySpringBean= mySpringBean
}
}
Datasource configuration
I already had this datasource #Configuration in my spring boot app. I only had to add the line of code that puts the BEAN_CONTAINER property in the jpaPropertyMap.
#Resource
lateinit var context: AbstractApplicationContext
#Primary
#Bean
#Qualifier("appDatasource")
#ConfigurationProperties(prefix = "spring.datasource")
fun myAppDatasource(): DataSource {
return DataSourceBuilder.create().build()
}
#Primary
#Bean(name = ["myAppEntityManagerFactory"])
fun entityManagerFactoryBean(builder: EntityManagerFactoryBuilder): LocalContainerEntityManagerFactoryBean {
val localContainerEntityManagerFactoryBean =
builder
.dataSource(myAppDatasource())
.packages("com.mydomain.myapp")
.persistenceUnit("myAppPersistenceUnit")
.build()
// the line below was the long-sought solution :^)
localContainerEntityManagerFactoryBean.jpaPropertyMap.put(
AvailableSettings.BEAN_CONTAINER, SpringBeanContainer(context.beanFactory))
return localContainerEntityManagerFactoryBean
}
You can try this solution
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public final class AutowireHelper implements ApplicationContextAware {
private static final AutowireHelper INSTANCE = new AutowireHelper();
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
/**
* Tries to autowire the specified instance of the class if one of the specified beans which need to be autowired
* are null.
*
* #param classToAutowire the instance of the class which holds #Autowire annotations
* #param beansToAutowireInClass the beans which have the #Autowire annotation in the specified {#classToAutowire}
*/
public static void autowire(Object classToAutowire, Object... beansToAutowireInClass) {
for (Object bean : beansToAutowireInClass) {
if (bean == null) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
return;
}
}
}
/**
* #return the singleton instance.
*/
public static AutowireHelper getInstance() {
return INSTANCE;
}
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
}
and then
#Autowired
SomeService thatToAutowire;
AutowireHelper.autowire(this, this.thatToAutowire);//this in the method
Extending a bit the above responses:
Since Hibernate 5.3 org.hibernate.resource.beans.container.spi.BeanContainer and Spring 5.1. You can use this to post process loaded domain entities for instance. Instead of using the aspect.
See:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/hibernate5/SpringBeanContainer.html
In your config:
#Bean
LocalContainerEntityManagerFactoryBean customCartEntityManagerFactory(DataSource customCartDataSource, EntityManagerFactoryBuilder builder, ConfigurableListableBeanFactory beanFactory) {
var mf = builder
.dataSource(customCartDataSource)
.packages("com.my.domain")
.build();
mf.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
return mf;
}
In your entity bean:
#EntityListeners(MyEntityListener.class)
The listener, notice no #Component decoration.
#Slf4j
public class MyEntityListener implements BeanFactoryAware, InitializingBean {
private final BeanConfigurerSupport beanConfigurerSupport = new BeanConfigurerSupport();
public CustomCartEntityListener() {
log.info("MyEntityListener created");
}
#PostLoad
public void postLoad(MyEntity entity) {
beanConfigurerSupport.configureBean(entity);
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanConfigurerSupport.setBeanWiringInfoResolver(new AnnotationBeanWiringInfoResolver());
this.beanConfigurerSupport.setBeanFactory(beanFactory);
}
#Override
public void afterPropertiesSet() {
this.beanConfigurerSupport.afterPropertiesSet();
log.info("MyEntityListener initialized");
}
}

Not able to load Application Context in Spring Boot app

I am working on a Spring Boot application wherein I am using that application to expose a SOAP webservice. I am using Apache CFX framework for SOAP impl in Spring boot app. I am using Annotation based approach.
I am facing issue in setting the Application Context from the Spring Boot Configuration file in one of the Beans. Below is my code.
#SpringBootApplication
#ComponentScan("com.test")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The configuration file is as below.
#Configuration
public class WebServiceConfiguration {
//All individual bean definitions should go here
#Autowired
ApplicationContext appContext;
#Bean
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/soap-api/*");
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean(name="IValidator")
public IValidator getValidator(){
return new Validator();
}
#Bean(name="SOAPprocessImpl")
public IPSoap getService() {
return new SOAPprocessImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), getService());
endpoint.publish("/WS_1.0");
endpoint.setWsdlLocation("process.wsdl");
return endpoint;
}
Now I have the bean SOAPprocessImpl implementation in which I need to get the Application Context so that I can get handle to the Validator bean. I have declared SOAPprocessImpl as a bean in the configuraton file. The code is as below
#javax.jws.WebService (endpointInterface="com.test.IPSoap")
public class SOAPprocessImpl implements IPSoap, ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ac)
throws BeansException {
context = ac;
}
private static final Logger logger = Logger.getLogger(SOAPprocessImpl.class.getName());
private IValidator validator = (IValidator) context.getBean("IValidator"); // context is NULL here
public IRResponse GetBalance(TSSearchParams SearchParams) {
// Some processing logic
}
}
So the issue is that when I run the boot application by deploying to the embedded Tomcat then the Application Context is not getting set in the SOAPprocessImpl class even after implementing the ApplicationContextAware. I also tried Autowiring but that also is not working.
Strangely I tried to see if I can get the ApplicationContext in the Configuration file where all the bean are defined. Here it is getting setting properly.
Can anyone help me how to solve this issue. I am new to Spring Boot and may have missed some configutaion. Thanks in advance.
Option(1): To fix the issue, you need to use #Configuration to register your SOAPprocessImpl bean to the Spring container as shown below so that ApplicationContext object can be injected :
#Configuration
#javax.jws.WebService (endpointInterface="com.test.IPSoap")
public class SOAPprocessImpl implements IPSoap, ApplicationContextAware {
private static ApplicationContext context;
private IValidator validator;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ac)
throws BeansException {
SOAPprocessImpl.context = ac;
}
#PostConstruct//use PostConstruct
public void init() {
validator = (IValidator) context.getBean("IValidator");
}
//add your current code
}
The important point is that you can't use the context object until the bean is prepared by the container, so you need to use #PostConstruct method as shown above to initialise your variables.
Option2 (recommended):
The best approach is that you can use #Autowired to inject IValidator object into SOAPprocessImpl as shown below so that you don't need your SOAPprocessImpl bean to be aware of ApplicationContextAware. Spring container will inject the instance for the implementation provided for the IValidator class (provided it is under the packages of #Componentscan).
#Component
#javax.jws.WebService (endpointInterface="com.test.IPSoap")
public class SOAPprocessImpl implements IPSoap {
private static final Logger logger = Logger.getLogger(SOAPprocessImpl.class.getName());
#Autowired //spring directly injects this object
private IValidator validator;
public IRResponse GetBalance(TSSearchParams SearchParams) {
// Some processing logic
}
}

Constructor Injection in SimpleMessageListenerContainer

I'm trying to get a better understanding of how to implement constructor injection in my application. I have some background processes that are executed by SimpleMessageListenerContainer workers that pull messages off of an AMQP server.
My application contains a service layer and a repo layer, a worker uses the services for data reads/writes. My controller, services, and repos are all setup using constructor injection, however since new versions of workers need to be instantiated I am stuck on how to initialize the worker.
Worker
public class RandomWorker extends Worker {
private UserService userService;
#Autowired
public RandomWorker(UserService userService) {
this.userService = userService;
}
#Override
public byte[] handleMessage(byte[] message) {
... do work ...
}
}
Service Layer
#Service
public class UserService {
private SecurityAreaRepo securityAreaRepo;
private SecurityRoleRepo securityRoleRepo;
private UserRepo userRepo;
#Autowired
public UserService(SecurityAreaRepo securityAreaRepo,
SecurityRoleRepo securityRoleRepo,
UserRepo userComponent) {
this.securityAreaRepo = securityAreaRepo;
this.securityRoleRepo = securityRoleRepo;
this.userRepo = userRepo;
}
}
WorkerConfig
#Configuration
public class WorkerConfig {
#Bean
public RandomWorker randomWorker() {
return new RandomWorker();
}
#Bean(name="randomWorkerContainer")
public SimpleMessageListenerContainer randomWorkerContainer() {
SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
smlc.setConnectionFactory(connectionFactory());
smlc.setMessageListener(new MessageListenerAdapter(randomWorker(), "handleMessage"));
smlc.setQueueNames("random.worker.queue");
smlc.setConcurrentConsumers(5);
smlc.start();
return smlc;
}
}
Since my worker requires the UserService, I must provide an instance of this in the WorkerConfig when I initialize a new RandomWorker. So am I going to have to create a #Bean for EVERY service that all of workers use? My WorkerConfig would look something like this:
#Configuration
public class WorkerConfig {
#Bean
public UserService userService() {
return new UserService(new SecurityAreaRepo(), new SecurityRoleRepo(), new UserRepo());
}
#Bean
public RandomWorker randomWorker() {
return new RandomWorker(userService());
}
#Bean(name="randomWorkerContainer")
public SimpleMessageListenerContainer randomWorkerContainer() {
SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
smlc.setConnectionFactory(connectionFactory());
smlc.setMessageListener(new MessageListenerAdapter(randomWorker(), "handleMessage"));
smlc.setQueueNames("random.worker.queue");
smlc.setConcurrentConsumers(5);
smlc.start();
return smlc;
}
}
If this is the case, I just do not see the point of constructor injection, when field injection makes everything so much simpler. Can somebody shed some light on this?
Spring automatically injects dependencies if you specify them as bean method's arguments. In your case, you just need to modify your worker bean method to:
#Bean
public RandomWorker randomWorker(UserService userService) {
return new RandomWorker(userService);
}
If UserService service is available in context, Spring will automatically inject it as userService parameter. You don't need to use #Bean methods for every service - any method of registering beans in context will work (e.g. #ComponentScan, #SpringBootApplication or even manually adding the bean to context). It doesn't matter if you use constructor or setter injection.
As a side note - constructor injection is better because you can be sure your object is always instantiated in valid state. It's generally a good design to keep all of your objects in valid state all the time.

#Autowire not preperly injected with Spring #Bean configuration

I am practising on spring-social and it seems that the userConnectionRepository is not properly autowired in the following code when I do a "Run as Junit Test" in Eclipse. I get a Null pointer exception on the usersConnectionRepository when creating a new FacebookOffLine although breakpoints put in the #Bean java creation code shows that they seem to be properly created. Thanks in advance,
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Here is the test code :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
org.springframework.social.quickstart.config.MainConfig.class,
org.springframework.social.quickstart.config.SocialConfig.class })
public class FacebookOfflineTest {
#Test
public void test1() {
FacebookOffline essai = new FacebookOffline(new User("yves"));
And the Spring configuration classes adapted from Keith Donald Quick Start Sample :
#Configuration
#ComponentScan(basePackages = "org.springframework.social.quickstart", excludeFilters = { #Filter(Configuration.class) })
#PropertySource("classpath:org/springframework/social/quickstart/config/application.properties")
public class MainConfig {
#Bean
public DataSource datasource() {
DriverManagerDataSource toReturn = new DriverManagerDataSource("jdbc:mysql://localhost:3306/spring_social");
toReturn.setDriverClassName("com.mysql.jdbc.Driver");
toReturn.setUsername("spring");
toReturn.setPassword("spring");
return toReturn;
}
}
#Configuration
public class SocialConfig {
#Inject
private Environment environment;
#Inject
private DataSource dataSource;
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(environment
.getProperty("facebook.clientId"), environment
.getProperty("facebook.clientSecret")));
return registry;
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(
dataSource, connectionFactoryLocator(), Encryptors.noOpText());
return repository;
}
}
Actually there are 2 problems here.
Spring cannot autowire beans it doesn't control (i.e. created with new)
Dependencies aren't available in the constructor (an object instance is needed before it can be injected)
The first one can be mitigated by letting spring manage an instance of FacebookOffline (or if you need multiple instances make the bean request or session scoped).
The second is a bit harder but can probaly solved by using a method annotated with #PostConstruct (or by implementing InitializingBean from spring).
You did
FacebookOffline essai = new FacebookOffline(new User("yves"));
That means, Spring isn't managing this essai instance and thus spring can't autowire any variables in the essai.
You'll have to create bean of FacebookOffline in SocialConfig.
Then you can have
/* ... */
public class FacebookOfflineTest {
#Autowired
ApplicationContext context;
#Test
public void test1() {
FacebookOffline essai = context.getBean(FacebookOffline.class);
OR
/* ... */
public class FacebookOfflineTest {
#Autowired
FacebookOffline essai;
#Test
public void test1() {
// You can use essai now
Also, you'll need to update FacebookOffline as Dependencies ain't available in constructor.
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
}
#PostConstruct
void loadFacebook() {
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Spring can't autowire fields on an instance you create via new since it doesn't know about it. Declare a bean of type FacebookOffline instead.

Categories

Resources