4

将一个类标记为@Transactional(见下文)后,当我的应用程序在用户仍登录的情况下重新加载时,我收到关于“尝试重复的类定义”的 LinkageErrors。

错误:

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)

其中 adminUserDao 是我们在自定义 UserDetailsS​​ervice 中用于在身份验证期间加载用户的 dao。

原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();
          }
    }
}

事务性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();
    }
}

事务管理器 Bean

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

重现步骤:

  1. 登录
  2. 重新启动应用程序(不注销)
  3. 重新加载页面(用户仍应登录)
  4. 登出
  5. 尝试使用同一用户再次登录。当 Spring 尝试对用户进行身份验证时会触发该错误。

我看到 Stack Overflow 上的其他人在错误地定义了自定义 ClassLoader (此处)时遇到了这个问题;但是,我没有使用自定义 ClassLoader。

潜在相关的依赖版本:

  • 爪哇:11
  • Spring Boot:2.1.1.RELEASE
  • 春天:5.1.3.RELEASE
  • Spring Security:5.1.4.RELEASE
4

1 回答 1

0

上面给出的链接:https ://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html https://stackoverflow.com/users/1567452/jwilner 给出 了正确的提示(谢谢 ;) )

@Transactional 注释意味着 Spring 正在为您生成一个代理类,该类使用以下内容包装您的方法:

GeneratedProxy {

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

     yourClass.yourMethod(); 

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

现在 spring 有两个选项(参见https://botproxy.net/docs/how-to/mismatched-proxy-types-jdk-vs-cglib-when-using-enablecaching-with-custom-aop-advice/):

如果您的代码实现了一个接口,他将使用 JDK 代理并公开该接口的所有方法:

(有关 JDK 代理示例,请参见https://www.byteslounge.com/tutorials/jdk-dynamic-proxies )

如果您的代码没有实现任何接口,他将创建一个 CGLIB 代理(并且基本上猜测要公开哪些方法)

最后,在他生成 CGLIB 代理的情况下,他将创建一个运行时类,如 AuthcUserService$$EnhancerBySpringCGLIB$$5c9dcb68

一个 JDK 代理,他将创建一个类似 com.sun.proxy.$Proxy1631 的类

这两个将在 setter(生成的 setter)方法中被强制转换为正确的类:

在第一种情况下,您可以在父类中使用类本身,在第二种情况下,您应该使用接口,否则您会得到 org.springframework.beans.factory.BeanNotOfRequiredTypeException

在您的情况下,您可能能够摆脱@Transactional,在我的情况下,我无法摆脱@Transactional,因为我正在跨多个数据库进行真正的交易。

总而言之,这发生在 java 13 中,可能是由于 jee 和反射的变化,实际上在 jdk1.8 中,下面的代码完全不知道这些机制。

于 2020-02-29T20:53:02.433 回答