Spring AOP не работает с классом, содержащим метод @Transactional

Я разрабатываю веб-приложение, в котором нужно хранить тяжелые файлы и для этой цели использую FTP-сервер Apache. Когда новый пользователь регистрирует свою учетную запись, папка, названная как его имя пользователя, должна быть создана на удаленном сервере. Чтобы установить соединение, перед выполнением метода UserCreatingServiceImpl.createUser() я использую Spring AOP:

@Component
@Aspect
public class RemoteServerConnectionEstablisher {
    private static boolean connectionEstablished = false;

    @Autowired
    private RemoteServerConnector serverConnector;

    @Pointcut("execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..)) ||"
            + " execution (* com.storehouse.business.services.impl.ItemCreatingServiceImpl.createItem(..)) ||"
            + "execution (* com.storehouse.business.services.impl.FileDownloadingServiceImpl.downloadFile(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void establishConnection(JoinPoint jp) {
        if (!connectionEstablished) {
            if (serverConnector.connectToRemoteServer()) {
                connectionEstablished = true;
            }
        }

    }

    @After("pointcut()")
    public void disconnect(JoinPoint jp) {
        if (connectionEstablished) {
            if (serverConnector.disconnect()) {
                connectionEstablished = false;
            }
        }
    }
}

Вот класс обслуживания с методом createUser ():

@Service
public class UserCreatingServiceImpl implements UserCreatingService {

    @Autowired
    private UserService userService;

    @Autowired
    private FTPClient ftpClient;

    @Override
    public boolean createUser(UserDto userDto) {
        try {
            ftpClient.makeDirectory(userDto.getUsername());
            UserMapper userMapper = new UserMapper();
            userService.persistUser(userMapper.dtoToEntity(userDto));
            return true;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Transactional
    public void checkIfUsernameExist(String username) {

    }
}

Все работало нормально, пока я не добавил метод @Transactional в service -class:

@Transactional
public void checkIfUsernameExist(String username) {

}

Теперь методы Aspect-класса не вызывают. Не могли бы вы объяснить причину. Заранее спасибо за помощь.

1 ответ

Решение

Проблема заключается в вашем выражении pointcut.

execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..))

Вы перехватываете исполнение createUser метод на UserCreatingServiceImpl, Это работает, когда вы не добавляете что-то, что создает прокси для вашей реализации. Как вы будете напрямую вызывать этот метод.

Однако, как вы добавили @Transactional прокси создан, и теперь вызов метода выполняется на UserCreatingService поскольку это интерфейс, который оставлен из-за созданного прокси. По умолчанию Spring использует JDK Dynamic прокси, которые основаны на интерфейсе.

Чтобы решить, сделать одну из этих вещей

  1. Перепишите свой pointcut для работы с интерфейсом вместо реализации класса
  2. Используйте основанные на классе вместо основанных на интерфейсе прокси
  3. Использовать компиляцию или ткачество времени загрузки

Переписать pointcut

использование execution(* com.storehouse.business.services.UserCreatingService+.createUser(..)) вместо того, что у вас есть сейчас. Это будет использовать интерфейс вместо конкретного класса.

Используйте прокси на основе классов

Предполагая, что вы используете @EnableAspectJAutoProxy добавлять proxyTargetClass=true к этому, что приводит к @EnableAspectJAutoProxy(proxyTargetClass=true), Это создаст прокси на основе классов и должно заставить работать оригинальный pointcut.

Использовать компиляцию или ткачество времени загрузки

Вместо использования прокси вы также можете изменить способ сборки / загрузки вашего кода. Для ткачества во время компиляции вам придется изменить свою сборку, чтобы использовать компилятор AspectJ для применения аспектов во время компиляции, тогда вам больше не нужны прокси.

Или вместо @EnableAspectJAutoProxy Вы могли бы добавить @EnableLoadTimeWeaving который, если вы используете последний контейнер сервлета, будет переплетать аспекты, как только будет загружен класс.

И то, и другое устранит необходимость в прокси-серверах (по крайней мере, для этой части) и позволит работать оригинальным pointcut.

Другие вопросы по тегам