Abstract DAO pattern and Spring's "Proxy cannot be cast to ..." problem!
I know this is very often asked , but I cannot find a working solution :
This is my AbstractDAO :
public interface AbstractDao<T>
{
public T get(Serializable id);
//other CRUD operations
}
And this is my JPA's implementation:
public abstract class AbstractDaoJpaImpl<T> implements AbstractDao<T> , Serializable
{
protected EntityManager em;
protected Class<T> clazz;
@SuppressWarnings("unchecked")
public AbstractDaoJpaImpl()
{
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
this.clazz = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
}
public abstract void setEntityManager(EntityManager em);
//implementations skipped
}
And this is one entity's dao :
public interface PersonDao extends AbstractDao<Person>
{
//empty
}
Here is its implementation:
@Repository
public class PersonDaoImpl extends AbstractDaoJpaImpl<Person> implements PersonDao , OtherInterface
{
@PersistenceContext(unitName="company")
@Override
public vo开发者_如何学编程id setEntityManager(EntityManager em)
{
this.em = em;
}
@Override // implements OtherInterface.additionalMethods()
public additionalMethods()
{
// implements...
}
}
The whole architecture is simple :
Interface AbstractDao defines simple CRUD methods.
Interface PersonDao extends AbstractDAO without any addon methods.
class AbstractDaoJpaImpl defines JPA's implementation of AbstractDao
class PersonDaoImpl extends AbstractDaoJpaImpl and implements PersonDao AND OtherInterface , which adds aditionalMethods()...
IF , PersonDaoImpl only implements PersonDao , without implementing OtherInterface.additionalMethods() , everything works fine.
I can use
<tx:annotation-driven transaction-manager="transactionManager" />
in my spring's XML file.
BUT , PersonDaoImpl implements OtherInterface(s) , when testing/running , I have to cast the DAO from PersonDao to PersonDaoImpl or OtherInterfaces , such as :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:app.xml"})
@TransactionConfiguration(transactionManager="transactionManager" , defaultRollback=false)
public class PersonDaoTest
{
@Inject
PersonDao dao;
@Test
public void testAdditionalMethod()
{
PersonDaoImpl impl = (PersonDaoImpl) dao;
System.out.println(impl.additionalMethod(...));
}
}
The problem occurs when (PersonDaoImpl) dao
, which throws "Proxy cannot be cast to PersonDaoImpl" exception:
java.lang.ClassCastException: $Proxy36 cannot be cast to foobar.PersonDaoImpl
at foobar.PersonDaoTest.testAdditionalMethod(PersonDaoTest.java:36)
this is often asked when googleing , everyone suggest adding proxy-target-class="true"
to <tx:annotation-driven>
:
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
This will make use of CGLIB instead of JDK's dynamic proxy.
BUT it throws another exception when initializing Spring :
Caused by: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
in AbstractDaoJpaImpl's constructor :
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
Every question stops here , I cannot find any working solutions now.
Can anyone give me a working solution ? Thanks a lot !
Environment : Spring-3.0.4 , javaee-api-6.0 , javax.inject , cglib-2.2 , hibernate-jpa-2.0-api-1.0.0 ,
You're solving the wrong problem. Proxied beans are not meant to be casted to the original classes, one way or the other. That would break the whole point of dependency injection. After all: when you specify a dependency as an interface you are requesting a bean that fulfills a contract, but not the implementation details. Casting it to the original bean class breaks this loose coupling.
You are saying the additional methods are backed up by an interface you call OtherInterface
, so why not use that instead? After all, the proxy will implement all the target class' interfaces, not only the injected one.
@Test
public void testAdditionalMethod()
{
OtherInterface oi = (OtherInterface) dao;
System.out.println(oi.additionalMethod(...));
}
Basically you have these options (sorted from clean to dirty):
- Separate your concerns and use different beans for different interfaces
- Create a meta-interface that extends
OtherInterface
andPersonDao
and let your bean implement that metainterface - Cast the bean to to the interface you need at any given moment.
yes spring always creates proxy classes and thats how it actually discovered non intrusive weaving and aop by xml config... try googling for that error in spring documentation there shud be rules to follow and work arounds.
精彩评论