Spring #Configuration runtime injection - java

In my non-Boot Spring 5 project I need to manually register and initialize some beans. After that I want to add a #Configuration class to context, that imports a config from an external lib:
#Configuration
#Import(ExtLibConfig.class)
public class MyExtLibConfig {
#Bean
public ExtLibBean extLibBean() {
return ExtLibBean.builder().build();
}
}
ExtLibConfig has many of its own #ComponentScan and #Import, and I wish them all to be configured automatically, including my ExtLibBean.
Is it possible to do so in runtime? External lib scans ApplicationContext, and I need it to do so, when my manually registered beans are added.
UPD:
The problem is not actual about beans register order. The ext lib is scanning ApplicationContext after its refresh, so I need my beans to be there at this time

The solution was to implement BeanDefinitionRegistryPostProcessor
public class MyMockBeanDefinitioRegistrynPostProcessor implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// registry.registerBeanDefinition(mockBeanClass, mockBeanDefinition);...
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// nothing to do
}
Then declare it as a Bean:
#Configuration
public class MockBeanConfig {
#Bean
public MyMockBeanDefinitioRegistrynPostProcessor mockBeanDefinitionPp() {
return new MyMockBeanDefinitioRegistrynPostProcessor();
}
}
And add it to context:
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(MockBeanConfig.class);
context.register(MyExtLibConfig.class);
context.refresh();

Related

javax.inject.Named with dynamic name

I have a Spring 4 based application and using #Named annotation.
#Named("test")
public class DocumentMapper implements Mapper<Test> {
Here, the name test for the Named annotation is hardcoded. Is it possible to make it property driven as opposed to hardcoding the name?
I tried the below:
public class NameGenerator implements BeanNameGenerator {
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if(definition.getBeanClassName().contains("DocumentMapper")) {
return "test";
}
}
}
Here, I want to return the bean name based on property. How can I inject my property config class?
If your goal is to register bean (with a custom bean name) programmatically
You can use the BeanDefinitionRegistryPostProcessor interface which is :
Extension to the standard BeanFactoryPostProcessor SPI, allowing for
the registration of further bean definitions before regular
BeanFactoryPostProcessor detection kicks in. In particular,
BeanDefinitionRegistryPostProcessor may register further bean
definitions which in turn define BeanFactoryPostProcessor instances.
And example would be (here I want to register MyBean class with a name from bean.name property):
#Component
public class BeanDefinitionRegistryPP implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
private Environment env;
#Override
public void setEnvironment(Environment environment) {
this.env=environment;
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
GenericBeanDefinition beanDefinition= new GenericBeanDefinition();
beanDefinition.setBeanClass(MyBean.class);
registry.registerBeanDefinition(env.getProperty("bean.name"), beanDefinition);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// no code
}
}

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

How to register ServletContextListener in spring boot

Hello I'm trying to rewrite my old code to use Spring Boot.
I have one listener public class ExecutorListener implements ServletContextListener.
How can I register this listener for Spring Boot?
I've tried:
#SpringBootApplication
#ComponentScan
public class Application extends SpringBootServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new ExecutorListener());
}
}
But the contextInitialized method is not called.
You can try couple of things:
Register ExecutorListener as a #Bean explicitly:
#Bean
public ExecutorListener executorListener() {
return new ExecutorListener();
}
or
You can try it with explicitly creating ServletRegistrationBean:
#Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet=new DispatcherServlet();
servlet.getServletContext().addListener(new ExecutorListener());
return servlet;
}
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(dispatcherServlet(), "/rest/v1/*");
registrationBean
.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return registrationBean;
}
If using an embedded container, there will soon be a third option if using SpringBoot 1.3.0+
Annotate your ServletContextListener implementation with #WebListener from servlet spec 3, then annotate one of your Spring #Configuration classes with the new #ServletComponentScan (and optionally tell it which packages to scan for filters, servlets and listeners).
Only available in 1.3.0+ at the moment though: http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/servlet/ServletComponentScan.html
Docs:
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-embedded-container-servlets-filters-listeners
In case you prefer auto discovery using annotations only, make your ExecutorListener implement the ServletContextInitializer and e.g. annotate it with javax.annotation.ManagedBean. From there, just implement the onStartup method:
#ManagedBean
public final class ExecutorListener implements ServletContextInitializer {
...
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
...
}
}
Another way is to create a class implements ServletContextListener and add #WebListner to the top which will tell Springboot this is a ServletContextListener , then add #ServletComponentScan(basePackages = "xxx") in SpringBootApplication to actually register it into the container
#WebListener
public class MyListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
...
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
...
}
}

Spring Bean injection in web application

When request is processed by spring controller, the service is not wired:
#Controller
#RequestMapping(value = "/login")
public class LoginController {
#Inject
private AccountService accountService;
#RequestMapping(method = RequestMethod.POST)
public String handleLogin(HttpServletRequest request, HttpSession session){
try {
...
//Next line throws NullPointerException, this.accountService is null
Account account = this.accountService.login(username, password);
} catch (RuntimeException e) {
request.setAttribute("exception", e);
return "login";
}
}
}
AccountService and its only implementation are defined in module service as:
package com.savdev.springmvcexample.service;
...
public interface AccountService {
...
package com.savdev.springmvcexample.service;
#Service("accountService")
#Repository
#Transactional
public class AccountServiceImpl implements AccountService {
The web configuration is loaded by files, located in web module:
package com.savdev.springmvcexample.web.config;
public class SpringMvcExampleWebApplicationInitializer implements WebApplicationInitializer
{
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerDispatcherServlet(servletContext);
}
private void registerDispatcherServlet(final ServletContext servletContext) {
WebApplicationContext dispatcherContext = createContext(WebMvcContextConfiguration.class);
...
}
private WebApplicationContext createContext(final Class<?>... annotatedClasses) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(annotatedClasses);
return context;
}
The WebMvcContextConfiguration file that scans packages to discovery beans:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "com.savdev.springmvcexample.web", "com.savdev.springmvcexample.service" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setOrder(1);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
This class is loaded, cause view resolving is used according to InternalResourceViewResolver logic. As a result the "com.savdev.springmvcexample.web" is scanned cause controller that processes request is found.
The "com.savdev.springmvcexample.service" is scanned, but it is in another module, I don't know can it be an issue or not, but I don't get any errors.
UPDATED:
#JBNizet, module - means module in maven multimodule project. I've removed #Repository and now I'm getting an error in test:
NoSuchBeanDefinitionException: No qualifying bean of type
[javax.sql.DataSource] found for dependency.
That means, that means the spring profile is not activated. DataSource is loaded only for profiles.
In web infrastructure I manage profiles with:
public class SpringMvcExampleProfilesInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
#Override
public void initialize(ConfigurableWebApplicationContext ctx) {
ConfigurableEnvironment environment = ctx.getEnvironment();
List<String> profiles = new ArrayList<String>(getProfiles());
if( profiles == null || profiles.isEmpty() )
{
throw new IllegalArgumentException("Profiles have not been configured");
}
environment.setActiveProfiles(profiles.toArray( new String[0]));
}
//TODO add logic
private Collection<String> getProfiles() {
return Lists.newArrayList("file_based", "test_data");
}
}
If I'm not wrong SpringMvcExampleProfilesInitializer is used before Spring ApplicationContext is loaded. And it is made automatically. Nothing additional has to be configured for this. But it's not working. Please fix me, if I'm wrong.
Please note, the initializer has the following signature:
SpringMvcExampleProfilesInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext>
At the moment when DispatcherServlet is configured I can setup it using:
setContextInitializers(ApplicationContextInitializer<ConfigurableApplicationContext>... contextInitializers)
How can I setup setContextInitializers but pass something that implements ApplicationContextInitializer<ConfigurableWebApplicationContext>, but not ApplicationContextInitializer<ConfigurableApplicationContext>
#Inject requires the presence of the JSR 330 jar on the classpath. It could be that it's visible at compile time but not at runtime.
Try this:
#Autowired(required=true)
private AccountService accountService;
If this does not work, it means that the bean AccountServiceImpl is not being scanned correctly.
If it does work, it means that there is a problem with #Inject support (maybe a missing jar at runtime).
If the scanning is not working, try to do the scanning via xml:
<context:component-scan base-package="com.savdev.springmvcexample.service" />
Can you post back:
If #Autowired(required=true) works
if and how the JSR-330 is added in the poms (output of mvn dependency:tree)
did the scanning via xml worked

Categories

Resources