4

After marking a class as @Transactional (see below), I'm getting LinkageErrors about "attempted duplicate class definition" when my app is reloaded while a user is still logged in.

Error:

org.springframework.security.authentication.InternalAuthenticationServiceException: java.lang.LinkageError-->loader 'app' (instance of jdk.internal.loader.ClassLoaders$AppClassLoader) attempted duplicate class definition for [redacted].AdminUserDao$$FastClassBySpringCGLIB$$2ffa020e.
        at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:126)

Where adminUserDao is the dao we use in our custom UserDetailsService to load users during authentication.

Original AdminUserDao.java

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class AdminUserDao {
    private final SessionFactory sessionFactory;

    @Autowired
    public AdminUserDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public AdminUser getUserByEmailAddress(String emailAddress) {
        String hql = "from " + AdminUser.class.getName()
                + " admin_user where admin_user.emailAddress = :emailAddress";
        Session session = null;
        try {
          session = sessionFactory.openSession();
          return session
                .createQuery(hql, AdminUser.class)
                .setParameter("emailAddress", emailAddress)
                .uniqueResult();
       } finally {
          if (session != null && session.isOpen()) {
            session.close();
          }
    }
}

Transactional AdminUserDao.java

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Repository
public class AdminUserDao {
    private final SessionFactory sessionFactory;

    @Autowired
    public AdminUserDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public AdminUser getUserByEmailAddress(String emailAddress) {
        String hql = "from " + AdminUser.class.getName()
                + " admin_user where admin_user.emailAddress = :emailAddress";
        return sessionFactory.getCurrentSession()
                .createQuery(hql, AdminUser.class)
                .setParameter("emailAddress", emailAddress)
                .uniqueResult();
    }
}

Transaction Manager Bean

@Bean
public HibernateTransactionManager transactionManager(@Qualifier("sessionFactory") SessionFactory sessionFactory) {
    return new HibernateTransactionManager(sessionFactory);
}

Steps to reproduce:

  1. Log in
  2. Restart the app (without logging out)
  3. Reload the page (user should still be logged in)
  4. Log out
  5. Attempt to log in again with the same user. The error is triggered when Spring attempts to authenticate the user.

I've seen others on Stack Overflow run into this issue when they incorrectly define a custom ClassLoader (here); however, I'm not using a custom ClassLoader.

Potentially relevant dependency versions:

  • Java: 11
  • Spring Boot: 2.1.1.RELEASE
  • Spring: 5.1.3.RELEASE
  • Spring Security: 5.1.4.RELEASE
llaborde
  • 98
  • 2
  • 7
  • What's whacky is that the class it's duplicating is a dynamic CGLIB proxy. It's probably that `@Transactional` anno driving the proxy. Is this reproducible if you remove that annotation? – jwilner Feb 26 '19 at 21:20
  • “The constructor of your proxied object will be called twice.” -- https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html Seems relevant since you're using CGLIB. – jwilner Feb 26 '19 at 21:56
  • Yep, removing all instances of `@Transactional` fixed it. I'm still confused why marking daos as transactional caused this, though. I'll update the question now to better reflect the issue. – llaborde Feb 28 '19 at 03:18

1 Answers1

0

the link given above: https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html by https://stackoverflow.com/users/1567452/jwilner gives the right hint (thx ;) )

The @Transactional annotation means that Spring is generating for you a proxy class which wraps your method with something like :

GeneratedProxy {

void proxiedMethod(){
   try{ 
     tx.begin();

     yourClass.yourMethod(); 

     tx.commit();
   }
   .. plus usual rollback and error stuff .. 
}
}

Now spring has two options (see https://botproxy.net/docs/how-to/mismatched-proxy-types-jdk-vs-cglib-when-using-enablecaching-with-custom-aop-advice/):

in case your code implements an interface he will use a JDK proxy and expose all methods of the interface :

(see e.g. https://www.byteslounge.com/tutorials/jdk-dynamic-proxies for a JDK proxy example)

if your code does not implement any interface he will create a CGLIB proxy (and essentially guess which methods to expose)

Finally in the case he generates a CGLIB proxy he will create a runtime class like AuthcUserService$$EnhancerBySpringCGLIB$$5c9dcb68

a JDK proxy he will create instead a class like com.sun.proxy.$Proxy1631

These two will be casted to the correct class in the setter (generated setter) method:

in the first case you can use the class itself in the parent class in the second case you shall use the interface otherwise you get org.springframework.beans.factory.BeanNotOfRequiredTypeException

In your case you may be able to get rid of @Transactional, In my case I cannot get rid of @Transactional cause I am doing a real transaction in wildfly across multiple DBs.

To conclude this happens in java 13, probably due to the changes about jee and reflection and actually in jdk1.8 the code below was totally unaware of these mechanics.