2

I am trying to extract the bean from application context.

so I defined class:

public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    public void setApplicationContext(ApplicationContext _applicationContext) throws BeansException {
        applicationContext = _applicationContext;

    }

}

and in my applicationContext.xml

<bean id="workflowService" class="com.mycompany.util.WorkflowService">
    <bean id="applicationContextProvider" class="com.mycompany.util.ApplicationContextProvider"></bean>
   <context:annotation-config />

However in my code when I try:

WorkflowService service  = (WorkflowService) ApplicationContextProvider.getApplicationContext().getBean("workflowService");

I get:

java.lang.ClassCastException: $Proxy40 cannot be cast to com.mycompany.util.WorkflowService

EDITED:

WorkflowService code:

public class WorkflowService implements Serializable {
   ...
   @PostConstruct
       public void init() {
   }
   ...
   @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
       public Collection<lData> findData(Integer contractId) {
   }    
}
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
Dejell
  • 13,947
  • 40
  • 146
  • 229
  • A relative of this one - http://stackoverflow.com/questions/5133291/applicationcontextprovider-is-not-being-called - I would guess you're mixing up interface v. concrete class in your bean definition/references. Please post code for WorkflowService. – David Victor Mar 07 '11 at 21:00

3 Answers3

4

I guess WorkflowService is a class implementing at least one interface (you haven't provided enough code). You are trying to lookup the exact class from Spring, while you should ask for one of the interfaces.

This is because Spring most of the time wraps beans in several proxies (e.g. transactional ones). If the class implements at least one interface, resulting proxy implements all of them, but cannot be cast into original class. If the class does not implement any interfaces (commonly considered a bad practice for heavyweight services, questionable though), Spring will use CGLIB subclassing from original class. In this case you code would be valid.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • I suspect you are in the right area but there is probably a simpler answer to resolve the error - the suggests annotations on WorkflowService - which will be proxied as you say. @Odelya - please post the code for WorkflowService & which annotations you're using. One might guess @Service ? – David Victor Mar 07 '11 at 21:31
  • Also: within the app context xml - isn't applicationContextProvider an inner bean - can it "see" the outer bean ? – David Victor Mar 07 '11 at 21:35
  • @David, Tomasz - I edited my question with workflowservice code. – Dejell Mar 07 '11 at 21:54
  • @David - what do you mean by inner bean? How do I define it? – Dejell Mar 07 '11 at 21:54
  • @Odelya - an inner bean is just a bean defined inside another one. I.e. ... - it looked like this was how you had the beans defined in your app context .xml. However I do not think it is relevant to your problem. See: http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#beans-inner-beans – David Victor Mar 08 '11 at 07:12
4

Your problem is this bit:

WorkflowService implements Serializable

Any proxies that Spring generates will implement all of the interfaces that your class does - in this case, Serializable, which is almost certainly not what you want.

What you should do is extract a new interface from WorkflowService, which includes the findData method (let's call it WorkflowOperations). By implementing that interface, you'll then be able to cast to that interface, e.g.

public interface WorkflowOperations {
    Collection<lData> findData(Integer contractId);
}


public class WorkflowService implements WorkflowOperations {

    ...
    @PostConstruct
        public void init() {
    }
    ...
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
        public Collection<lData> findData(Integer contractId) {
    }

}

and then:

 WorkflowOperations service  = (WorkflowOperations) ApplicationContextProvider.getApplicationContext().getBean("workflowService");

You should probably also remove Serializable from WorkflowService. You almost certainly don't need this, it makes no sense to serialize Spring beans like this. If you just added Serializable out of habit, then remove it (and get out of that particular habit).

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • +1. Yes - avoid Serializable unless you absoultely need it. Read Bloch 'Effective Java' - v. thorough discussions of cons of Serializable. – David Victor Mar 08 '11 at 07:09
2

You are annotating your service with @Transactional, so Spring is wrapping your service bean with a transactional JDK dynamic proxy that implements the same interfaces as your bean, but is not a WorkflowService. That is why you get a ClassCastException when you try to assign it to a WorkflowService variable. I see two possible solutions:

  • Specify an interface WorkflowService with your business methods and implement it in a WorkflowServiceImpl class. Then in the Spring context change the bean definition from WorkflowService to WorkflowServiceImpl. This is what I recommend, both as a general design principle and specially to work in a Spring environment: Spring likes interfaces.

  • In your Spring context, add proxy-target-class="true" to your <tx:annotation-driven/> element in order to force Spring to implement proxies by subclassing, so that proxy instanceof WorkFlowService is true. I find this solution dirtier. Also note that you add a dependency on CGLIB this way.

gpeche
  • 21,974
  • 5
  • 38
  • 51