Spring AOP doesn`t work with class comprising @Transactional method

939 Views Asked by At

I develop web-app, with a need to store heavy-weight files and use Apache FTP Server for this purpose. When a new user register his account, the folder named as his username must be created on remote server. To establish connection, before UserCreatingServiceImpl.createUser() method will be performed, I use 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;
            }
        }
    }
}

Here is the service class with createUser() method:

@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) {

    }
}

Everything had worked fine, until I added @Transactional method to service -class:

@Transactional
public void checkIfUsernameExist(String username) {

}

Now methods of Aspect-class don`t invoke. Could you explain the reason. Thanks in advance for help.

1

There are 1 best solutions below

0
On BEST ANSWER

The issue lies in your pointcut expression.

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

You are intercepting the execution of the createUser method on the UserCreatingServiceImpl. This works when you don't add something that creates a proxy for your implementation. As you will be directly calling this method.

However as you have added @Transactional a proxy is created and the method call is now done on the UserCreatingService as that is the interface that is left due do the created proxy. By default spring uses JDK Dynamic proxies, which are interface based.

To solve do one of these things

  1. Rewrite your pointcut to operate on the interface instead of implementing class
  2. Use class based instead of interface based proxies
  3. Use compile or load time weaving

Rewrite pointcut

Use execution(* com.storehouse.business.services.UserCreatingService+.createUser(..)) instead of what you have now. This will use the interface instead of the concrete class.

Use class based proxies

Assuming you use @EnableAspectJAutoProxy add proxyTargetClass=true to it, leading to @EnableAspectJAutoProxy(proxyTargetClass=true). This will create class based proxies and should make the original pointcut work.

Use compile or load time weaving

Instead of using proxies you also could change the way your code is build/loaded. For compile time weaving you would have to modify your build to use the AspectJ compiler to apply the aspects at compilation time, then you don't need the proxies any more.

Or instead of @EnableAspectJAutoProxy you could add @EnableLoadTimeWeaving which, if you use a recent servlet container, would weave the aspects as soon as a class is being loaded.

Both would eliminate the need for proxies (at least for this part) and would make the original pointcuts work.