由于 Spring 注入的代理副作用,AOP 在 Spring 中无法捕获 DAO 异常
目的是处理所有持久性异常并包装为简单的通用异常,以便服务层可以轻松处理它们。
解决方案是使用AOP来拦截DAO实现中的异常。这是spring的配置:
<bean id="DBExceptions" class="com.dao.impl.DAOExceptionTranslator" />
<aop:config>
<aop:aspect id="dbExceptionsAspect" ref="DBExceptions">
<aop:after-throwing throwing="ex"
pointcut="execution(* com.dao.impl.*.*(*))" method="doDAOActions" />
</aop:aspect>
</aop:config>
这是DAO的实现:
@Transactional
public class UserDAOImpl extends GenericDAOImpl implements UserDAO {
@PersistenceContext
protected EntityManager entityManager;
@Override
public User findUserByUsername(String username) throws DAOException {
Query query = entityManager
.createQuery("select u from User u where u.username=:username");
query.setParameter("username", username);
Object userObject = query.getSingleResult();
return (User) userObject;
}
下面是使用DAO的代码:
private UserDAO userDAO;
public User getUserById(int id) throws UserServiceException {
try {
Object user = userDAO.findById(User.class, id);
...
userDAO的实现是由spring注入的,但是对于正常的db异常,可以拦截,对于连接异常,则失败。
我认为是因为在spring注入DAO实现之前,它会先构造连接,并且失败了,所以它还没有调用目标操作。
我想拦截DAO的所有异常,如何通过spring注入解决代理副作用。
这里是两个不同的堆栈:
db逻辑错误:
UserDAOImpl.findUserByUsername(String) line: 23
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 309
ReflectiveMethodInvocation.invokeJoinpoint() line: 183
ReflectiveMethodInvocation.proceed() line: 150
AspectJAfterThrowingAdvice.invoke(MethodInvocation) line: 55
ReflectiveMethodInvocation.proceed() line: 172
TransactionInterceptor.invoke(MethodInvocation) line: 110
ReflectiveMethodInvocation.proceed() line: 172
ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 202
$Proxy17.findUserByUsername(String) line: not available
UserService.getUser(String) line: 74
但如果是DB连接错误:
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:382)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy17.findUserByUsername(Unknown Source)
at com.service.UserService.getUser(UserService.java:74)
如何解决问题?
解决办法是标明各个代理的顺序,改变的是
<aop:config>
<aop:aspect id="dbExceptionsAspect" ref="DBExceptions" order="1">
<aop:after-throwing throwing="ex"
pointcut="execution(* com.dao.impl.*.*(*))" method="doDAOActions" />
</aop:aspect>
</aop:config>
添加关键字顺序后,会先调用DBExceptions代理,看改变后的堆栈。
DAOExceptionTranslator.doDAOActions(Exception) line: 12
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AspectJAfterThrowingAdvice(AbstractAspectJAdvice).invokeAdviceMethodWithGivenArgs(Object[]) line: 621
AspectJAfterThrowingAdvice(AbstractAspectJAdvice).invokeAdviceMethod(JoinPointMatch, Object, Throwable) line: 603
AspectJAfterThrowingAdvice.invoke(MethodInvocation) line: 59
ReflectiveMethodInvocation.proceed() line: 172
ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 202
$Proxy17.findUserByUsername(String) line: not available
UserService.getUser(String) line: 74
The purpose is to handle all persistence exception and wrapped to simple general exception, so service layer can easily handle them.
The solution is to use AOP to intercept the exception from DAO implementation. Here is the spring configuration:
<bean id="DBExceptions" class="com.dao.impl.DAOExceptionTranslator" />
<aop:config>
<aop:aspect id="dbExceptionsAspect" ref="DBExceptions">
<aop:after-throwing throwing="ex"
pointcut="execution(* com.dao.impl.*.*(*))" method="doDAOActions" />
</aop:aspect>
</aop:config>
Here is DAO implementation:
@Transactional
public class UserDAOImpl extends GenericDAOImpl implements UserDAO {
@PersistenceContext
protected EntityManager entityManager;
@Override
public User findUserByUsername(String username) throws DAOException {
Query query = entityManager
.createQuery("select u from User u where u.username=:username");
query.setParameter("username", username);
Object userObject = query.getSingleResult();
return (User) userObject;
}
And here is the code to use the DAO:
private UserDAO userDAO;
public User getUserById(int id) throws UserServiceException {
try {
Object user = userDAO.findById(User.class, id);
...
The implementation of userDAO is injected by spring, but for normal db exception, it can be intercepted, and for connection exception, it failed.
I think because before spring injected the DAO implementation, it will first construct the connection, and it failed, so it hasn't call the target operation.
I want to intercept all exception from DAO, how to solve the proxy side effect by spring injection.
here is two different stack:
The db logical error:
UserDAOImpl.findUserByUsername(String) line: 23
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 309
ReflectiveMethodInvocation.invokeJoinpoint() line: 183
ReflectiveMethodInvocation.proceed() line: 150
AspectJAfterThrowingAdvice.invoke(MethodInvocation) line: 55
ReflectiveMethodInvocation.proceed() line: 172
TransactionInterceptor.invoke(MethodInvocation) line: 110
ReflectiveMethodInvocation.proceed() line: 172
ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 202
$Proxy17.findUserByUsername(String) line: not available
UserService.getUser(String) line: 74
But if it is DB connection error:
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:382)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy17.findUserByUsername(Unknown Source)
at com.service.UserService.getUser(UserService.java:74)
How to solve the problem?
The solution is to indicate the sequence of each proxy, the change is
<aop:config>
<aop:aspect id="dbExceptionsAspect" ref="DBExceptions" order="1">
<aop:after-throwing throwing="ex"
pointcut="execution(* com.dao.impl.*.*(*))" method="doDAOActions" />
</aop:aspect>
</aop:config>
After add the key word order, DBExceptions proxy will be invoked first, see the stack after change.
DAOExceptionTranslator.doDAOActions(Exception) line: 12
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AspectJAfterThrowingAdvice(AbstractAspectJAdvice).invokeAdviceMethodWithGivenArgs(Object[]) line: 621
AspectJAfterThrowingAdvice(AbstractAspectJAdvice).invokeAdviceMethod(JoinPointMatch, Object, Throwable) line: 603
AspectJAfterThrowingAdvice.invoke(MethodInvocation) line: 59
ReflectiveMethodInvocation.proceed() line: 172
ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 202
$Proxy17.findUserByUsername(String) line: not available
UserService.getUser(String) line: 74
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您应该将 @Transactional 注释移到您的 Service 方法上。当使用 @Transaction 注解一个类时,Spring 会从中创建一个代理。
问题是 @Transactional 拦截器(代理)尝试启动事务,但由于没有与数据库的连接而失败。该错误不会被 DAOExceptionTranslator 拦截,因为它是在拦截的代码之前执行的。
You should move the @Transactional annotation on your Service method. When annotating a class with @Transaction Spring will create a proxy out of it.
The problem is that the @Transactional interceptor (the proxy) tries to initiate a transaction but it fails because there is no connection to the db. The error in not intercepted by your DAOExceptionTranslator because it is executed Before your intercepted code.