"LinkageError-> загрузчик 'app' попытался определить дубликат класса" после маркировки классов как @Transactional
После маркировки класса как @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 - это дао, которое мы используем в нашем пользовательском UserDetailsService для загрузки пользователей во время аутентификации.
Оригинальный 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
public HibernateTransactionManager transactionManager(@Qualifier("sessionFactory") SessionFactory sessionFactory) {
return new HibernateTransactionManager(sessionFactory);
}
Действия по воспроизведению:
- Авторизоваться
- Перезапустите приложение (без выхода)
- Перезагрузите страницу (пользователь должен войти в систему)
- Выйти
- Попытайтесь войти снова с тем же пользователем. Ошибка срабатывает, когда Spring пытается аутентифицировать пользователя.
Я видел, как другие в переполнении стека сталкивались с этой проблемой, когда они неправильно определяли пользовательский ClassLoader ( здесь); Однако я не использую пользовательский ClassLoader.
Потенциально релевантные версии зависимостей:
- Ява: 11
- Spring Boot: 2.1.1. ВЫПУСК
- Весна: 5.1.3. ВЫПУСК
- Spring Security: 5.1.4. ВЫПУСК
0 ответов
Приведенная выше ссылка: https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html по 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 и предоставит все методы интерфейса:
(см., например, https://www.byteslounge.com/tutorials/jdk-dynamic-proxies для примера прокси JDK)
если ваш код не реализует какой-либо интерфейс, он создаст прокси CGLIB (и по существу угадывает, какие методы раскрыть)
Наконец, если он сгенерирует прокси CGLIB, он создаст класс среды выполнения, например AuthcUserService$$EnhancerBySpringCGLIB$$5c9dcb68
прокси JDK он создаст вместо класса com.sun.proxy.$Proxy1631
Эти два будут отнесены к правильному классу в методе установщика (сгенерированного установщика):
в первом случае вы можете использовать сам класс в родительском классе, во втором случае вы должны использовать интерфейс, иначе вы получите org.springframework.beans.factory.BeanNotOfRequiredTypeException
В вашем случае вы можете избавиться от @Transactional, в моем случае я не могу избавиться от @Transactional, потому что я выполняю реальную транзакцию в wildfly через несколько БД.
В заключение, это происходит в java 13, вероятно, из-за изменений в jee и отражении, и на самом деле в jdk1.8 приведенный ниже код совершенно не знал об этой механике.