Why #EnableWs removed aop proxy from spring bean - java

I am trying to add custom interceptor in my spring boot web service project. I follow this example and created this config:
package org.example;
import java.util.List;
import org.aspect.PersistentAspect;
import org.springframework.aop.support.AopUtils;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.server.EndpointInterceptor;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
#EnableWs
#Configuration
public class WsConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
final MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/v1/*");
}
#Bean
public XsdSchema schema() {
return new SimpleXsdSchema(new ClassPathResource("country.xsd"));
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
String[] jaxbContext = new String[] { "io.spring.guides.gs_producing_web_service" };
marshaller.setContextPaths(jaxbContext);
return marshaller;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
// aop not working
interceptors.add(new CustomValidatingInterceptor(schema(), config()));
// aop working
// interceptors.add(new CustomValidatingInterceptor(schema(), null));
}
#Bean
public AppConfig config() {
return new AppConfig();
}
#Bean
public PersistentAspect persistentAspect() {
PersistentAspect persistentAspect = new PersistentAspect();
return persistentAspect;
}
#Bean
public Object testAop() {
System.out.println("is config aop proxy: " + AopUtils.isAopProxy(config()));
return null;
}
}
however when I am adding new interceptor in addInterceptors method I have problem with removed aop proxy in my config class. Any idea why ? Whole project is on git.

The problem is the initialization sequence in Spring. Technically, because there is a BeanPostProcessor for the WS Endpoint (AnnotationActionEndpointMapping in spring-ws), it will force the early initialization of any dependencies this needs - especially any EndpointInterceptor beans.
One way to counter this is to rearrange the BeanPostProcessor's, or even create one of your own, but usually its simpler to stay with the default configuration in Spring - to avoid similar surprises elsewhere in the initialization sequence.
Perhaps a simpler way to avoid the problem is to use an ObjectFactory in the EndpointInterceptor bean. This will delay instantiating the AppConfig bean until it is referenced, by which time the Aop weaving will also have taken place.
#Component
public class CustomValidatingInterceptor extends PayloadValidatingInterceptor {
#Autowired
private ObjectFactory<AppConfig> konfigurace;
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws IOException, SAXException, TransformerException {
System.out.println("is config aop proxy in interceptor: " +
AopUtils.isAopProxy(konfigurace.getObject()));
return super.handleRequest(messageContext, endpoint);
}
Clearly, this then means the CustomValidatingInterceptor must be referenced from WsConfig as an injected (autowired) bean.
Thanks for the example - there's a fork here that uses the ObjectFactory technique. This showed the config bean as an Aop proxy in all of WsConfig.testAop(), the CountryEndpoint and the CustomValidatingInterceptor when I sent a request in from SoapUI.

Here's another possibility to solve this issue. This relates to following Stack overflow question: Spring WS interceptors with injected DAO's #Transactional not working. In short the issue comes from the fact that the method
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
gets called before Spring dependency injection has time to register Spring AOP beans. In my case it was #Transactional that was ignored by Spring-WS but it could be anything.
Fortunately for us Spring-WS uses mutable collections instead of immutable. When the addInterceptors() method
gets called, we can just save the collection and thus we have a reference to the same collection instance that is used by Spring-WS. Later on you can initialize your interceptor bean properly and add it to the collection.
You also have to get around the fact that if you use #Autowired the bean gets prepared before the annotations can take place. Thus you have to create it manually by calling ApplicationContext.getBean() method.
#EnableWs
#Configuration
// The magic is to implement both ApplicationContextAware
// that injects the applicationContext for us
// and BeanPostProcessor that gives us postProcessBeforeInitialization()
// where we initialize our interceptor correctly
// and add it to the collection
public class WebServiceConfig extends WsConfigurerAdapter implements ApplicationContextAware, BeanPostProcessor {
// This is the interceptor that uses dependencies with #Transactional annotation.
// It will not work with #Autowired
private MyInterceptorThatHasTransactionalDependencies myInterceptorThatHasTransactionalDependencies;
// Fortunately Spring WS uses mutable collections so we can fill
// this list later on as long as we just initialize it with
private List<EndpointInterceptor> interceptors;
// This is our application context where all the beans are defined
private ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// save application context for later use
this.context = applicationContext;
}
#Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// This method gets called multiple times so initialize interceptor just once
if(myInterceptorThatHasTransactionalDependencies == null){
myInterceptorThatHasTransactionalDependencies = context.getBean(MyInterceptorThatHasTransactionalDependencies.class);
interceptors.add(myInterceptorThatHasTransactionalDependencies);
}
return bean;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
// Save the list of interceptors so we can modify it later on
this.interceptors = interceptors;
if (myInterceptorThatHasTransactionalDependencies == null) {
System.out.println("myInterceptorThatHasTransactionalDependencies was null like we expected");
} else {
interceptors.add(myInterceptorThatHasTransactionalDependencies);
}
}
}
Just to let you know that I am not Spring bean lifecycle expert, so there might be a better place to situate the interceptor initialization than postProcessBeforeInitialization(). That said, this works for me.

Related

Java Spring-boot - How to use #Autowired with #ServerEndpoint?

I know there are lot of questions on this topic. I have read the spring boot doc and all of the solutions here. According spring boot doc, #ServerEndpoint is a Javax annotation and #Autowired components are spring-boot managed. These two cannot be used together. The solution to this would be to add SpringConfigurator as configurator of the ServerEndpoint. When I tried this I do get the following error:
Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?
There is no example in the spring-boot websocket page to use ContextLoaderListener. How can use ContextLoaderListener so that components can be injected into #ServerEndpoint annotated controllers?
The following is my code.
Websocket controller
#ServerEndpoint(value = "/call-stream", configurator = SpringConfigurator.class)
public class CallStreamWebSocketController
{
#Autowired
private IntelligentResponseService responseServiceFacade;
// Other methods
}
Websocket configurations
#Configuration
public class WebSocketConfiguration
{
#Bean
public CallStreamWebSocketController callStreamWebSocketController()
{
return new CallStreamWebSocketController();
}
#Bean
public ServerEndpointExporter serverEndpointExporter()
{
return new ServerEndpointExporter();
}
}
Edit:
This has been tagged as a duplicate of this question. I have tried the solution specified in the answers. The solution is to add SpringConfigurator as configurator of the #ServerEndpoint. After adding this I still do get the error mentioned in the details.
After some research I found a way to force spring-boot to inject a component into an externally managed/instantiated class.
1) Add a generic method to your class extending ApplicationContextAware to return a bean.
#Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringContext.context = context;
}
public ApplicationContext getApplicationContext() {
return context;
}
// Generic method to return a beanClass
public static <T> T getBean(Class<T> beanClass)
{
return context.getBean(beanClass);
}
}
2) Use this method to initialize the class object you want to be injected
private IntelligentResponseService responseServiceFacade = SpringContext.getBean(IntelligentResponseService.class);
So after the above changes my websocket controller would look like this
#ServerEndpoint(value = "/call-stream", configurator = SpringConfigurator.class)
public class CallStreamWebSocketController
{
private IntelligentResponseService responseServiceFacade = SpringContext.getBean(IntelligentResponseService.class);
// Other methods
}

Spring Jersey to inject ContainerRequestContext on request scoped class

I already looked at a lot of posts and nothing seems to work quite as i liked it to.
I want to inject an object into ContainerRequestContext properties from a filter and retrieve it later in other classes.
here is my filter:
#Priority(Priorities.AUTHENTICATION)
public class AuthorizationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.setProperty("myObject", new Object());
}
}
here is the class I want access to ContainerRequestContext:
#Provider
public class SessionContextProvider implements ISessionContextProvider {
#Context
private ContainerRequestContext request;
#Override
public Object getSessionContext() {
return request.getProperty("mySessionContext");
}
}
and my spring config:
#Bean(name="sessionContextProvider")
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public ISessionContextProvider sessionContextProvider() {
return new SessionContextProvider();
}
Everything works as expected if I inject ContainerRequestContext into my web resource. However if call my provider class ContainerRequestContext is always null.
I don't seem why this would not work.
Reagrds
Jonas
The problem is that with the Jersey/Spring integration, it allows us to successfully inject Spring beans into Jersey components, but this is not always true the other way around.
Jersey has it's own DI framework, HK21, and it is responsible for handle the injections with Jersey components. With the Jersey Spring integration, Jersey will lookup the Spring Bean, and take it as is, it won't inject it with any dependencies, I guess assuming Spring should take care of it's own injections.
That being said, if you don't require the ISessionContextProvider to be a Spring bean, then you can just make it an HK2 service. It's pretty simple. If you don't require any special initialization, you can just let HK2 create it. Here a simple configuration
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AbstractBinder() {
bind(SessionContextProvider.class)
.to(ISessionContextProvider.class)
.in(RequestScoped.class);
});
}
}
And that's it. You have an injectable ISessionContextProvider2.
If you require the ISessionContextProvider provider to be a Spring bean, then another option is to grab the bean from the Spring ApplicatinContext, and explicitly inject it yourself, using HK2's analogue of the ApplicationContext, its ServiceLocator. To do that we would need to use a Factory to do all the work transparently, so you can still inject the bean without doing any extra work on the outside
import javax.inject.Inject;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.springframework.context.ApplicationContext;
public class SessionContextProviderFactory
implements Factory<SessionContextProvider> {
private final ISessionContextProvider provider;
#Inject
public SessionContextProviderFactory(ApplicationContext ctx,
ServiceLocator locator) {
provider = ctx.getBean(ISessionContextProvider.class);
locator.inject(provider);
}
#Override
public ISessionContextProvider provide() {
return provider;
}
#Override
public void dispost(ISessionContextProvider provider) { /* noop */ }
}
Then just register the factory
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AbstractBinder() {
bindFactory(SessionContextProviderFactory.class)
.to(ISessionContextProvider.class)
.in(RequestScoped.class);
});
}
}
1 - hk2
2 - See also Dependency injection with Jersey 2.0
I found a workaround. I could inject the Spring HttpServletRequest into my AuthorizationFilter and set the SessionContext on this one.
#Autowired
private HttpServletRequest request;
....
request.setAttribute("mySessionContext", sessionContext);
And then because HttpServletRequest is known to spring and besically represents the same thing in my SessionContextProvider I do the same:
#Autowired
private HttpServletRequest request;
#Override
public SessionContext getSessionContext() {
return (SessionContext) request.getAttribute("mySessionContext");
}
I dont think this is the best solution possible. But it works. Ill wait for peeskillet for any other input if there is a better solution.
Regards
Jonas

Spring with MyBatis: expected single matching bean but found 2

I've been using Spring with MyBatis and it's been working really well for a single database. I ran into difficulties when trying to add another database (see reproducible example on Github).
I'm using Spring Java configuration (i.e. not XML). Most of the examples I've seen show how to achieve this using XML.
I have two data configuration classes (A & B) like this:
#Configuration
#MapperScan("io.woolford.database.mapper")
public class DataConfigDatabaseA {
#Bean(name="dataSourceA")
public DataSource dataSourceA() throws SQLException {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriver(new com.mysql.jdbc.Driver());
dataSource.setUrl("jdbc:mysql://" + dbHostA + "/" + dbDatabaseA);
dataSource.setUsername(dbUserA);
dataSource.setPassword(dbPasswordA);
return dataSource;
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSourceA());
return sessionFactory.getObject();
}
}
Two mappers, and a service that autowires the mappers:
#Service
public class DbService {
#Autowired
private DbMapperA dbMapperA;
#Autowired
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
The application won't start:
Error creating bean with name 'dataSourceInitializer':
Invocation of init method failed; nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [javax.sql.DataSource] is defined:
expected single matching bean but found 2: dataSourceB,dataSourceA
I've read that it's possible to use the #Qualifier annotation to disambiguate the autowiring, though I wasn't sure where to add it.
Can you see where I'm going wrong?
If you want to use two data sources at same time and they are not primary and secondary, you should disable DataSourceAutoConfiguration by #EnableAutoConfiguration(excludes = {DataSourceAutoConfiguration.class}) on your application annotated by #SpringBootApplication. Afterwards, you can create your own SqlSessionFactory and bundle your own DataSource. If you also want to use DataSourceTransactionManager, you should do that too.
In this case, you haven't disabled DataSourceAutoConfiguration, so spring framework will try to #Autowired only one DataSource but got two, error occurs.
As what I've said before, you should disable DataSourceAutoConfiguration and configure it manually.
You can disable data source auto configuration as following:
#SpringBootApplication
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class YourApplication implements CommandLineRunner {
public static void main (String... args) {
SpringApplication.run(YourApplication.class, args);
}
}
And if you are really want to use multiple databases at same time, I suggest you to registering proper bean manually, such as:
package xyz.cloorc.boot.mybatis;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
#Configuration
public class SimpleTest {
private DataSource dsA;
private DataSource dsB;
#Bean(name = "dataSourceA")
public DataSource getDataSourceA() {
return dsA != null ? dsA : (dsA = new BasicDataSource());
}
#Bean(name = "dataSourceB")
public DataSource getDataSourceB() {
return dsB != null ? dsB : (dsB = new BasicDataSource());
}
#Bean(name = "sqlSessionFactoryA")
public SqlSessionFactory getSqlSessionFactoryA() throws Exception {
// set DataSource to dsA
return new SqlSessionFactoryBean().getObject();
}
#Bean(name = "sqlSessionFactoryB")
public SqlSessionFactory getSqlSessionFactoryB() throws Exception {
// set DataSource to dsB
return new SqlSessionFactoryBean().getObject();
}
}
#Repository
public class SimpleDao extends SqlSessionDaoSupport {
#Resource(name = "sqlSessionFactoryA")
SqlSessionFactory factory;
#PostConstruct
public void init() {
setSqlSessionFactory(factory);
}
#Override
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public <T> T get (Object id) {
return super.getSqlSession().selectOne("sql statement", "sql parameters");
}
}
In the end, we put each mapper in its own folder:
src/main/java/io/woolford/database/mapper/a/DbMapperA.java
src/main/java/io/woolford/database/mapper/c/DbMapperB.java
We then created two DataConfig classes, one for each database. The #MapperScan annotation resolved the expected single matching bean but found 2 issue.
#Configuration
#MapperScan(value = {"io.woolford.database.mapper.a"}, sqlSessionFactoryRef="sqlSessionFactoryA")
public class DataConfigDatabaseA {
It was necessary to add the #Primary annotation to the beans in one of the DataConfig classes:
#Bean(name="dataSourceA")
#Primary
public DataSource dataSourceA() throws SQLException {
...
}
#Bean(name="sqlSessionFactoryA")
#Primary
public SqlSessionFactory sqlSessionFactoryA() throws Exception {
...
}
Thanks to everyone who helped. No doubt, there's more than one way to do this. I did try #Qualifier and #EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) as recommended by #eduardlofitskyi and #GeminiKeith, but that generated some further errors.
In case it's useful, the solution that worked for us is posted here: https://github.com/alexwoolford/mybatis-spring-multiple-mysql-reproducible-example
You can use #Qualifier annotation
The problem is that you have two the same type beans in Spring container. And when you try autowire beans, Spring cannot resolve which bean inject to field
The #Qualifier annotation is the main way to work with qualifiers. It can be applied alongside #Autowired or #Inject at the point of injection to specify which bean you want to be injected.
So, your DbService should look like this:
#Service
public class DbService {
#Autowired
#Qualifier("dataSourceA")
private DbMapperA dbMapperA;
#Autowired
#Qualifier("dataSourceB")
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
I had the same issue and could not start my Spring Boot application, and by renaming the offending class and all the layers that dealt with it, strangely the application started successfully.
I have the classes UOMService, UOMServiceImpl UOMRepository and UOMRepositoryImpl. I renamed them to be UomService, UomServiceImpl, UomRepository and UomRepositoryImpl and that solved the problem!

How to instantiate Spring managed beans at runtime?

I stuck with a simple refactoring from plain Java to Spring. Application has a "Container" object which instantiates its parts at runtime. Let me explain with the code:
public class Container {
private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
public void load() {
// repeated several times depending on external data/environment
RuntimeBean beanRuntime = createRuntimeBean();
runtimeBeans.add(beanRuntime);
}
public RuntimeBean createRuntimeBean() {
// should create bean which internally can have some
// spring annotations or in other words
// should be managed by spring
}
}
Basically, during load container asks some external system to provide him information about number and configuration of each RuntimeBean and then it create beans according to given spec.
The problem is: usually when we do in Spring
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Container container = (Container) context.getBean("container");
our object is fully configured and have all dependencies injected. But in my case I have to instantiate some objects which also needs dependency injection after I execute load() method.
How can I achieve that?
I am using a Java-based config. I already tried making a factory for RuntimeBeans:
public class BeanRuntimeFactory {
#Bean
public RuntimeBean createRuntimeBean() {
return new RuntimeBean();
}
}
Expecting #Bean to work in so called 'lite' mode. http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html Unfortunately, I found no difference with simply doing new RuntimeBean();
Here is a post with a similar issue: How to get beans created by FactoryBean spring managed?
There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html but it looks like a hammer in my case.
I also tried ApplicationContext.getBean("runtimeBean", args) where runtimeBean has a "Prototype" scope, but getBean is an awful solution.
Update 1
To be more concrete I am trying to refactor this class:
https://github.com/apache/lucene-solr/blob/trunk/solr/core/src/java/org/apache/solr/core/CoreContainer.java
#see #load() method and find "return create(cd, false);"
Update 2
I found quite interesting thing called "lookup method injection" in spring documentation:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-lookup-method-injection
And also an interesting jira ticket https://jira.spring.io/browse/SPR-5192 where Phil Webb says https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-86051 that javax.inject.Provider should be used here (it reminds me Guice).
Update 3
There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html
Update 4
The issue with all these 'lookup' methods is they don't support passing any arguments.. I also need to pass arguments as I would do with applicationContext.getBean("runtimeBean", arg1, arg2). Looks like it was fixed at some point with https://jira.spring.io/browse/SPR-7431
Update 5
Google Guice have a neat feature for it called AssistedInject. https://github.com/google/guice/wiki/AssistedInject
Looks like I found a solution. As I am using java based configuration it is even simpler than you can imagine. Alternative way in xml would be lookup-method, however only from spring version 4.1.X as it supports passing arguments to the method.
Here is a complete working example:
public class Container {
private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
private RuntimeBeanFactory runtimeBeanFactory;
public void load() {
// repeated several times depending on external data/environment
runtimeBeans.add(createRuntimeBean("Some external info1"));
runtimeBeans.add(createRuntimeBean("Some external info2"));
}
public RuntimeBean createRuntimeBean(String info) {
// should create bean which internally can have some
// spring annotations or in other words
// should be managed by spring
return runtimeBeanFactory.createRuntimeBean(info);
}
public void setRuntimeBeanFactory(RuntimeBeanFactory runtimeBeanFactory) {
this.runtimeBeanFactory = runtimeBeanFactory;
}
}
public interface RuntimeBeanFactory {
RuntimeBean createRuntimeBean(String info);
}
//and finally
#Configuration
public class ApplicationConfiguration {
#Bean
Container container() {
Container container = new Container(beanToInject());
container.setBeanRuntimeFactory(runtimeBeanFactory());
return container;
}
// LOOK HOW IT IS SIMPLE IN THE JAVA CONFIGURATION
#Bean
public BeanRuntimeFactory runtimeBeanFactory() {
return new BeanRuntimeFactory() {
public RuntimeBean createRuntimeBean(String beanName) {
return runtimeBean(beanName);
}
};
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
RuntimeBean runtimeBean(String beanName) {
return new RuntimeBean(beanName);
}
}
class RuntimeBean {
#Autowired
Container container;
}
That's it.
Thanks everyone.
i think that your concept is wrong by using
RuntimeBean beanRuntime = createRuntimeBean();
you are bypassing Spring container and resorting to using regular java constructor therefore any annotations on factory method are ignored and this bean is never managed by Spring
here is the solution to create multiple prototype beans in one method, not pretty looking but should work, I autowired container in RuntimeBean as proof of autowiring shown in log also you can see in log that every bean is new instance of prototype when you run this .
'
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
Container container = (Container) context.getBean("container");
container.load();
}
}
#Component
class Container {
private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
#Autowired
ApplicationContext context;
#Autowired
private ObjectFactory<RuntimeBean> myBeanFactory;
public void load() {
// repeated several times depending on external data/environment
for (int i = 0; i < 10; i++) {
// **************************************
// COMENTED OUT THE WRONG STUFFF
// RuntimeBean beanRuntime = context.getBean(RuntimeBean.class);
// createRuntimeBean();
//
// **************************************
RuntimeBean beanRuntime = myBeanFactory.getObject();
runtimeBeans.add(beanRuntime);
System.out.println(beanRuntime + " " + beanRuntime.container);
}
}
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public RuntimeBean createRuntimeBean() {
return new RuntimeBean();
}
}
// #Component
class RuntimeBean {
#Autowired
Container container;
} '
A simple approach:
#Component
public class RuntimeBeanBuilder {
#Autowired
private ApplicationContext applicationContext;
public MyObject load(String beanName, MyObject myObject) {
ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) applicationContext;
SingletonBeanRegistry beanRegistry = configContext.getBeanFactory();
if (beanRegistry.containsSingleton(beanName)) {
return beanRegistry.getSingleton(beanName);
} else {
beanRegistry.registerSingleton(beanName, myObject);
return beanRegistry.getSingleton(beanName);
}
}
}
#Service
public MyService{
//inject your builder and create or load beans
#Autowired
private RuntimeBeanBuilder builder;
//do something
}
Instead of using SingletonBeanRegistry you can use this:
BeanFactory beanFactory = configContext.getBeanFactory();
Anyway SingletonBeanBuilder extends HierarchicalBeanFactory and HierarchicalBeanFactory extends BeanFactory
You don't need the Container because all of the runtime objects should be created, held and managed by ApplicationContext. Think about a web application, they are much the same. Each request contains external data/environment info as you mentioned above. What you need is a prototype/request scoped bean like ExternalData or EnvironmentInfo which can read and hold runtime data through a static way, let's say a static factory method.
<bean id="externalData" class="ExternalData"
factory-method="read" scope="prototype"></bean>
<bean id="environmentInfo" class="EnvironmentInfo"
factory-method="read" scope="prototype/singleton"></bean>
<bean class="RuntimeBean" scope="prototype">
<property name="externalData" ref="externalData">
<property name="environmentInfo" ref="environmentInfo">
</bean>
If you do need a container to save the runtime objects, code should be
class Container {
List list;
ApplicationContext context;//injected by spring if Container is not a prototype bean
public void load() {// no loop inside, each time call load() will load a runtime object
RuntimeBean bean = context.getBean(RuntimeBean.class); // see official doc
list.add(bean);// do whatever
}
}
Official doc Singleton beans with prototype-bean dependencies.
It is possible to register beans dynamically by using BeanFactoryPostProcesor. Here you can do that while the application is booting (spring's application context has been initialized). You can not register beans latest, but on the other hand, you can make use of dependency injection for your beans, as they become "true" Spring beans.
public class DynamicBeansRegistar implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (! (beanFactory instanceof BeanDefinitionRegistry)) {
throw new RuntimeException("BeanFactory is not instance of BeanDefinitionRegistry");
}
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
// here you can fire your logic to get definition for your beans at runtime and
// then register all beans you need (possibly inside a loop)
BeanDefinition dynamicBean = BeanDefinitionBuilder.
.rootBeanDefinition(TheClassOfYourDynamicBean.class) // here you define the class
.setScope(BeanDefinition.SCOPE_SINGLETON)
.addDependsOn("someOtherBean") // make sure all other needed beans are initialized
// you can set factory method, constructor args using other methods of this builder
.getBeanDefinition();
registry.registerBeanDefinition("your.bean.name", dynamicBean);
}
#Component
class SomeOtherClass {
// NOTE: it is possible to autowire the bean
#Autowired
private TheClassOfYourDynamicBean myDynamicBean;
}
As presented above, you can still utilize Spring's Dependency Injection, because the post processor works on the actual Application Context.

Defining the request scope as prototype for integration tests

When writing integration tests with Spring 3.1, I usually define the request scope to be a SimpleThreadScope, with the following XML context configuration:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope" />
</entry>
</map>
</property>
</bean>
To define the request scope to be backed by a prototype scope implementation, I thought to change the class to be an implementation of the prototype scope. However I was not able to locate any.
Looking at the Scope Interface Javadoc, in the section All Known Implementing Classes, I see listed: AbstractRequestAttributesScope, PortletContextScope, RequestScope, ServletContextScope, SessionScope, SimpleThreadScope... nothing that looks like a prototype scope.
How can I define the request scope as prototype for integration tests?
UPDATE: I've managed to make my integration tests pass by creating my own prototype scope, which I've defined as follow, so my question now is, whether the following implementation is correct, or it has to be fixed.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class PrototypeScope implements Scope {
private static final Log logger = LogFactory.getLog(PrototypeScope.class);
public Object get(String name, ObjectFactory objectFactory) {
return objectFactory.getObject();
}
public Object remove(String name) {
return null;
}
public void registerDestructionCallback(String name, Runnable callback) {
logger.warn("PrototypeScope does not support destruction callbacks. "
+ "Consider using a RequestScope in a Web environment.");
}
public Object resolveContextualObject(String key) {
return null;
}
public String getConversationId() {
return Thread.currentThread().getName();
}
}
UPDATE 2: I am using TestNG and my Integration tests look like this:
#Test
#ContextConfiguration(locations = { "classpath:META-INF/spring/test-context.xml" })
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class MyIntegrationTest extends AbstractTransactionalTestNGSpringContextTests {
#Resource
private MyBeanThatShouldBePrototype bean;
#Transactional
public void testCase() {
...
In fact it's working in a different way - Spring checks if bean is a prototype, and then clones its definition and just populate new bean, so there is no backing class for holding such beans. If you want to look at the implementation please visit:
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean and you will find:
if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
If you want to use prototype scope in tests you can just manually create this bean by invoking new MyObjectThatShouldBePrototype() and then configure it as a Spring bean by using AutowireCapableBeanFactory (injected/autowired into your test):
#Autowired
AutowireCapableBeanFactory beanFactory;
public MyObjectThatShouldBePrototype getBean() {
MyObjectThatShouldBePrototype bean = new MyObjectThatShouldBePrototype();
beanFactory.autowireBean(bean);
return bean;
}
Of course there are several ways to create beans - you can find then here http://www.kubrynski.com/2013/09/injecting-spring-dependencies-into-non.html
Something like this is not working for you?
#Test
#ContextConfiguration(locations = { "classpath:META-INF/spring/test-context.xml" },
classes = MyIntegrationTest.TestConfig.class)
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class MyIntegrationTest extends AbstractTransactionalTestNGSpringContextTests {
#Resource
private MyBeanThatShouldBePrototype bean; // protype bean produced by spring
#Transactional
public void testCase() {
...
}
#Configuration
public static class TestConfig {
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public MyBeanThatShouldBePrototype myBeanThatShouldBePrototype() {
return new MyBeanThatShouldBePrototype();
}
}
}
Probably you could go another way?
What about writing a beanfactorypostprocessor that changes the request scoped bean candidates to prototype instead?
I havent tried it myself but you should be able to apply this to any bean declared as request scoped and set the prototype flag.
In the spring context for your unit tests you define this processor and in the context for integration tests this postprocessor would not be around.

Categories

Resources