PDA

View Full Version : Class Cast Exception on context.getBean(... using intercept-methods attribute



simoncutting
Mar 1st, 2010, 04:58 PM
Hi,
I have a prototype application that is using Spring Security 2.0.5 to secure a JAX-RS annotated application. With JAX-RS there is a single entry point which maps to a bean. Depending on the URL of the requested resource this bean will instantiate another bean (and that bean might instantiate another and so on down the resource hierarchy). We've tried a number of different patterns for instantiating an instance of the bean and are currently using a factory which contains the context.getBean("bean name"). This approach works until I add the intercept-methods to the bean definition I am trying to instantiate which leads to a class cast exception. I have previously added AOP advice to the application which gave me the same error but using the proxy-target-class="true" attribute on the aop config fixed that.

I am pretty new to Spring at this level of detail but it appears that when I add the intercept-methods Spring is using the JDK dynamic proxy which causes all the heartburn when the getBean() method returns a proxy object. I have tried using both the implementation and the interface in the factory and aside from moving the point where the exception is thrown the behaviour is identical.

Any help would be greatly appreciated.
Simon

Snippet from beans.xml

<bean id="Patient" class="com.emc.cto.healthcare.impl.PatientImpl" scope="prototype" autowire="no">
<sec:intercept-methods access-decision-manager-ref="businessAccessDecisionManager" >
<sec:protect method="getPatient" access="ROLE_PHYSICIAN"/>
<sec:protect method="deletePatient" access="ROLE_ADMIN"/>
<sec:protect method="modifyPatient" access="ROLE_ADMIN,ROLE_PHYSICIAN"/>
</sec:intercept-methods>
</bean>

The call to the factory method in the "top level" bean:

@Path("/{pid}")
public Patient getPatientByID(@PathParam("pid")String pid) {
//Patient patient = new Patient(pid);
PatientImpl patient = (PatientImpl) ResourceFactory.getDefaultFactory().newPatient(pid ); << Exception here
patient.setBaseURI(getSelfURI());
return patient;
}


The factory method (Patient is an interface):

public Patient newPatient(String pid) {
Patient obj = null;
obj = (Patient) context.getBean("Patient");
obj.setPid(pid);
System.out.println("newPatient obj.getClass = " + obj.getClass());
return obj;
}

The println above prints "newPatient obj.getClass = class $Proxy43"

Now an excerpt from the stack trace:

Caused by: org.apache.cxf.interceptor.Fault: $Proxy43
at org.apache.cxf.service.invoker.AbstractInvoker.cre ateFault(AbstractInvoker.java:148)
at org.apache.cxf.service.invoker.AbstractInvoker.inv oke(AbstractInvoker.java:114)
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvo ker.java:130)
at com.emc.cto.cxf.jaxrs.XJAXRSInvoker.invoke(XJAXRSI nvoker.java:15)
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvo ker.java:82)
at org.apache.cxf.interceptor.ServiceInvokerIntercept or$1.run(ServiceInvokerInterceptor.java:58)
at java.util.concurrent.Executors$RunnableAdapter.cal l(Unknown Source)
at java.util.concurrent.FutureTask$Sync.innerRun(Unkn own Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at org.apache.cxf.workqueue.SynchronousExecutor.execu te(SynchronousExecutor.java:37)
at org.apache.cxf.interceptor.ServiceInvokerIntercept or.handleMessage(ServiceInvokerInterceptor.java:98 )
at org.apache.cxf.phase.PhaseInterceptorChain.doInter cept(PhaseInterceptorChain.java:236)
... 47 more
Caused by: java.lang.ClassCastException: $Proxy43
at com.emc.cto.healthcare.impl.PatientsImpl.getPatien tByID(PatientsImpl.java:70)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ e Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknow n Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Un known Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoi npointUsingReflection(AopUtils.java:310)
at org.springframework.aop.framework.ReflectiveMethod Invocation.invokeJoinpoint(ReflectiveMethodInvocat ion.java:182)
at org.springframework.aop.framework.ReflectiveMethod Invocation.proceed(ReflectiveMethodInvocation.java :149)
at org.springframework.security.intercept.method.aopa lliance.MethodSecurityInterceptor.invoke(MethodSec urityInterceptor.java:66)
at org.springframework.aop.framework.ReflectiveMethod Invocation.proceed(ReflectiveMethodInvocation.java :171)
at org.springframework.aop.framework.JdkDynamicAopPro xy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy38.getPatientByID(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ e Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknow n Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Un known Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.cxf.service.invoker.AbstractInvoker.per formInvocation(AbstractInvoker.java:166)
at org.apache.cxf.service.invoker.AbstractInvoker.inv oke(AbstractInvoker.java:82)
... 57 more

pmularien
Mar 1st, 2010, 06:24 PM
I think you're misreading the stack trace. You're getting an exception when newPatient *returns* and you attempt to cast to PatientImpl:

PatientImpl patient = (PatientImpl) ResourceFactory.getDefaultFactory().newPatient(pid ); << Exception here

This obviously will not work because PatientImpl is a class, not an interface.

simoncutting
Mar 1st, 2010, 06:50 PM
I realize that the exception occurs when the object is returned and I try to cast it to PatientImpl. I've tried structuring the code to use either classes or interfaces in the factory - in this scenario what is the preferred/recommended/right Spring approach?

The code runs without any exception until the security attributes are set on the Patients bean.

pmularien
Mar 1st, 2010, 07:12 PM
Generally, it is more correct (from an OO design sense) to code to interfaces and not implementation classes, irrespective of Spring proxies being in the mix.

Also generally, you will have better luck when using proxied objects in Spring (Spring Security, Spring Transactions etc) if you are coding and casting to interfaces and not class implementations. Attempting to proxy classes forces the use of CGLIB proxies, and not JDK dynamic proxies (which you are forcing anyway by including "proxy-target-class="true" "). I suspect you are running into this particular error because PatientImpl probably has some superclasses (just a hunch), and casting CGLIB proxies will typically be problematic in such scenarios.

Refer to Joerg's excellent post explaining some of the differences here: http://insufficientinformation.blogspot.com/2007/12/spring-dynamic-proxies-vs-cglib-proxies.html

Hope this helps!

simoncutting
Mar 2nd, 2010, 08:45 AM
Thanks, changing the code to use interfaces where possible resolved the proxy issue.

pmularien
Mar 2nd, 2010, 09:22 AM
Great! Thanks for reporting back.