由于 Spring 注入的代理副作用,AOP 在 Spring 中无法捕获 DAO 异常

发布于 2024-10-31 22:43:38 字数 5016 浏览 2 评论 0原文

目的是处理所有持久性异常并包装为简单的通用异常,以便服务层可以轻松处理它们。

解决方案是使用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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

揽清风入怀 2024-11-07 22:43:38

您应该将 @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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文