Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 void 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 ,

like image 338
smallufo Avatar asked Feb 03 '23 01:02

smallufo


1 Answers

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):

  1. Separate your concerns and use different beans for different interfaces
  2. Create a meta-interface that extends OtherInterface and PersonDao and let your bean implement that metainterface
  3. Cast the bean to to the interface you need at any given moment.
like image 149
Sean Patrick Floyd Avatar answered Feb 05 '23 16:02

Sean Patrick Floyd