Spring AOP 不适用于包含@Transactional 方法的class
Spring AOP doesn`t work with class comprising @Transactional method
我开发网络应用程序,需要存储重量级文件并为此使用 Apache FTP 服务器。当新用户注册他的帐户时,必须在远程服务器上创建以他的用户名命名的文件夹。要建立连接,在执行 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() 方法的服务 class:
@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 方法添加到服务中 -class:
@Transactional
public void checkIfUsernameExist(String username) {
}
现在 Aspect-class 的方法不会调用。能解释一下原因吗。在此先感谢您的帮助。
问题出在你的切入点表达式上。
execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..))
您正在拦截 createUser
方法在 UserCreatingServiceImpl
上的执行。当您不添加为您的实施创建代理的内容时,这会起作用。因为您将直接调用此方法。
但是,当您添加 @Transactional
时,会创建一个代理,并且现在会在 UserCreatingService
上完成方法调用,因为这是创建的代理留下的接口。默认情况下 spring 使用 JDK 基于接口的动态代理。
要解决做这些事情之一
- 重写切入点以在界面上操作而不是实现 class
- 使用基于 class 而不是基于接口的代理
- 使用编译或加载时编织
重写切入点
使用 execution(* com.storehouse.business.services.UserCreatingService+.createUser(..))
而不是您现在拥有的。这将使用接口而不是具体的 class.
使用基于 class 的代理
假设您使用 @EnableAspectJAutoProxy
添加 proxyTargetClass=true
到它,导致 @EnableAspectJAutoProxy(proxyTargetClass=true)
。这将创建基于 class 的代理,并且应该使原始切入点起作用。
使用编译或加载时编织
除了使用代理之外,您还可以更改代码的方式 build/loaded。对于编译时编织,您必须修改您的构建以使用 AspectJ 编译器在编译时应用方面,然后您不再需要代理。
或者您可以添加 @EnableLoadTimeWeaving
而不是 @EnableAspectJAutoProxy
,如果您使用最近的 servlet 容器,它会在加载 class 时立即编织方面。
两者都将消除对代理的需求(至少对于这部分)并且将使原始切入点起作用。
我开发网络应用程序,需要存储重量级文件并为此使用 Apache FTP 服务器。当新用户注册他的帐户时,必须在远程服务器上创建以他的用户名命名的文件夹。要建立连接,在执行 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() 方法的服务 class:
@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 方法添加到服务中 -class:
@Transactional
public void checkIfUsernameExist(String username) {
}
现在 Aspect-class 的方法不会调用。能解释一下原因吗。在此先感谢您的帮助。
问题出在你的切入点表达式上。
execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..))
您正在拦截 createUser
方法在 UserCreatingServiceImpl
上的执行。当您不添加为您的实施创建代理的内容时,这会起作用。因为您将直接调用此方法。
但是,当您添加 @Transactional
时,会创建一个代理,并且现在会在 UserCreatingService
上完成方法调用,因为这是创建的代理留下的接口。默认情况下 spring 使用 JDK 基于接口的动态代理。
要解决做这些事情之一
- 重写切入点以在界面上操作而不是实现 class
- 使用基于 class 而不是基于接口的代理
- 使用编译或加载时编织
重写切入点
使用 execution(* com.storehouse.business.services.UserCreatingService+.createUser(..))
而不是您现在拥有的。这将使用接口而不是具体的 class.
使用基于 class 的代理
假设您使用 @EnableAspectJAutoProxy
添加 proxyTargetClass=true
到它,导致 @EnableAspectJAutoProxy(proxyTargetClass=true)
。这将创建基于 class 的代理,并且应该使原始切入点起作用。
使用编译或加载时编织
除了使用代理之外,您还可以更改代码的方式 build/loaded。对于编译时编织,您必须修改您的构建以使用 AspectJ 编译器在编译时应用方面,然后您不再需要代理。
或者您可以添加 @EnableLoadTimeWeaving
而不是 @EnableAspectJAutoProxy
,如果您使用最近的 servlet 容器,它会在加载 class 时立即编织方面。
两者都将消除对代理的需求(至少对于这部分)并且将使原始切入点起作用。