3

Currently I have a Spring configuration set up this way:

@Bean
@Autowired
public Manager manager(DataRetriever dataRetriever) { 
    Manager m = new Manager();
    m.setRetriever(dataRetriever); //DataRetriever is a @Component bean
    return m;
}

@Bean
public VendorFactoryBean factory() { 
    final VendorFactoryBean fb = new VendorFactoryBean(); 
    fb.setManager(manager());
    return fb;
}

VendorFactoryBean requires a Manager instance. Manager bean > DataRetriever bean.

Within DataRetriever I have a @Transactional annotation like so:

@Component("dataRetriever")
public class DataRetriever { 
    @Transactional public void retrieveStuff() {...} 
}

Now, VendorFactoryBean implements BeanPostProcessor. Here's where I run into problems.

According to this SO question, all BeanPostProcessors and their directly referenced beans will be instantiated on startup, and in addition: Since AOP auto-proxying is implemented as a BeanPostProcessor itself, no BeanPostProcessors or directly referenced beans are eligible for auto-proxying (and thus will not have aspects 'woven' into them.

In fact, the @Transactional annotation is ignored and I get an error: "No Hibernate session bound to this thread."

I have tried to move the fb.setManager(manager()); call from the VendorFactoryBean and set it using another BeanPostProcessor, however in this case I cannot do so, because VendorFactoryBean is from a library and it contains an assertion that on instantiation a Manager instance must have already been set.

Would like to ask if there are possible solutions to this situation.

Edit: specific example in this SO question.

Community
  • 1
  • 1
Jensen Ching
  • 3,144
  • 4
  • 26
  • 42

1 Answers1

0

I have thought of a solution for this specific situation.

Basically, we decouple the DataRetriever bean from the Manager bean.

(1) We revise the @Configuration class as follows:

@Bean
public Manager manager() { 
    Manager m = new Manager();
    //m.setRetriever(dataRetriever); !IMPT! remove this!
    return m;
}

@Bean
public VendorFactoryBean factory() { 
    final VendorFactoryBean fb = new VendorFactoryBean(); //implements BeanPostProcessor 
    fb.setManager(manager());
    return fb;
}

(2) We allow Spring to initialize the beans. First, the VendorFactoryBean bean is initialized together with the Manager bean. Then, the DataRetriever bean is initialized and its @Transactional annotations post-processed correctly.

(3) Since we're doing programmatic configuration, we have a WebApplicationInitializer class where we can plug in ServletContextListeners, like so:

public class AppInitializer implements WebApplicationInitializer {
    public void onStartup(ServletContext container) throws ServletException {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(SpringAppConfig.class);

        // Manage the lifecycle of the root application context
        container.addListener(new ContextLoaderListener(rootContext));
        // New listener to finalize initialization of Manager.
        container.addListener(new ManagerFinalizerListener(rootContext));
    }
}

(4) And finally we create a ServletContextListener class that ties the DataRetriever into the Manager bean.

public class ManagerFinalizerListener implements ServletContextListener {

    private AnnotationConfigWebApplicationContext ctx;

    public ManagerFinalizerListener(AnnotationConfigWebApplicationContext ctx) {
        this.ctx = ctx;
    }

    public void contextInitialized(ServletContextEvent sce) {
        final Manager mgr = (Manager)ctx.getBean("manager");
        mgr.setRetriever((DataRetriever)ctx.getBean("dataRetriever"));
    }
}

And this will be done after Spring context has completed initialization, therefore, no premature bean creation due to dependencies.

Jensen Ching
  • 3,144
  • 4
  • 26
  • 42