【问题标题】:Spring AOP continues iteration after throwingSpring AOP 抛出后继续迭代
【发布时间】:2016-12-27 17:08:14
【问题描述】:

我有MyClassAopLogger。如果doSomething 出现异常,则迭代停止。

如何防止退出logAround并继续下一个主机?而logAround返回的Object有什么用处,我们可以用这个Object做什么?

class MyClass{
    void check() throws Exception {    
        Iterator<Host> iter_host = configReader.getHostMap().values().iterator();       
        while (iter_host.hasNext()) {               
            Host host = (Host) iter_host.next();
            host.doSomething();
        }
    }
    void doSomething(){} //Exception 
}

class AopLogger {    
    @Around("execution(* com.mypackage..*.*(..))")
    Object logAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ 
        return proceedingJoinPoint.proceed();   
    } 
}

【问题讨论】:

  • 你没有提供足够的信息;如果连接点在 doSomething 方法中(假设它是公共的),那么将 try catch 块放在 returnproceedJoinPoint.proceed() 周围就足够了。 “What it is good for..”当你进入doSomething,当方法返回时,你可能会记录;实际上,您的代码中缺少这很奇怪。
  • 当我将 try catch 块放在 returnproceedJoinPoint.proceed() 周围时,迭代结束并且应用程序退出。当我将 try catch 块放在 host.doSomething() 周围时,迭代会继续,但我无法在 logAround 中记录任何内容
  • 首先找出什么是连接点:在返回proceedingJoinPoint.proceed() 的IDE 中放置一个断点并检查ProceedingJoinPoint 的各个字段——你应该看到哪个方法被拦截了。尝试从那里推理。

标签: java spring aop aspectj spring-aop


【解决方案1】:

首先,你的切面类应该有一个@Aspect 注释。其次,如果你想使用 Spring AOP 而不是完整的 AspectJ,你的方面和所有目标类也应该是 Spring @Components。

话虽如此,这里有一个小样本。我是用普通的 AspectJ 创建的,但是 Spring AOP 中的切面代码应该是一样的。

帮助代码编译和运行的类:

package de.scrum_master.app;

import java.util.Random;

public class Host {
    private static final Random RANDOM = new Random();

    private String name;

    public Host(String name) {
        this.name = name;
    }

    public void doSomething() {
        if (RANDOM.nextBoolean())
            throw new RuntimeException("oops!");
    }

    @Override
    public String toString() {
        return "Host(name=" + name + ")";
    }
}
package de.scrum_master.app;

import java.util.HashMap;
import java.util.Map;

public class ConfigReader {
    private Map<Integer, Host> hostMap = new HashMap<>();

    public ConfigReader() {
        hostMap.put(1, new Host("mercury"));
        hostMap.put(2, new Host("venus"));
        hostMap.put(3, new Host("earth"));
        hostMap.put(4, new Host("mars"));
    }

    public Map<Integer, Host> getHostMap() {
        return hostMap;
    }
}

驱动程序应用:

我不喜欢旧 JDK 版本遗留下来的 Iterator,因此我将其替换为更现代的 Java 风格 for 循环。

package de.scrum_master.app;

class MyClass {
    private ConfigReader configReader = new ConfigReader();

    void check() throws Exception {
        for (Host host : configReader.getHostMap().values()) {
            System.out.println(host);
            host.doSomething();
        }
    }

    public static void main(String[] args) throws Exception {
        new MyClass().check();
    }
}

切入点/建议方面同时进行日志记录和异常处理:

还请注意我在代码末尾的评论。

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AopLogger {
    private static final InheritableThreadLocal<String> indent = new InheritableThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "";
        }
    };

    @Around("execution(* de.scrum_master.app..*(..)) && !execution(* toString())")
    public Object logAround(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        Object result = null;
        System.out.println(indent.get() + ">> " + thisJoinPoint);
        try {
            indent.set(indent.get() + "  ");
            result = thisJoinPoint.proceed();
            indent.set(indent.get().substring(2));
        } catch (Exception e) {
            System.out.println(indent.get() + "Caught exception: " + e);
            indent.set(indent.get().substring(2));
        }
        System.out.println(indent.get() + "<< " + thisJoinPoint);

        // Attention: If a method with a caught exception does not have 'void'
        // return type, we return a (probably unexpected) result of 'null' here.
        // So maybe we should not catch all execptions but rather pick more
        // specific joinpoints where we are sure we can cleanly handle the
        // corresponding exceptions.
        return result;
    }
}

控制台日志:

>> execution(void de.scrum_master.app.MyClass.main(String[]))
  >> execution(void de.scrum_master.app.MyClass.check())
    >> execution(Map de.scrum_master.app.ConfigReader.getHostMap())
    << execution(Map de.scrum_master.app.ConfigReader.getHostMap())
Host(name=mercury)
    >> execution(void de.scrum_master.app.Host.doSomething())
      Caught exception: java.lang.RuntimeException: oops!
    << execution(void de.scrum_master.app.Host.doSomething())
Host(name=venus)
    >> execution(void de.scrum_master.app.Host.doSomething())
    << execution(void de.scrum_master.app.Host.doSomething())
Host(name=earth)
    >> execution(void de.scrum_master.app.Host.doSomething())
      Caught exception: java.lang.RuntimeException: oops!
    << execution(void de.scrum_master.app.Host.doSomething())
Host(name=mars)
    >> execution(void de.scrum_master.app.Host.doSomething())
      Caught exception: java.lang.RuntimeException: oops!
    << execution(void de.scrum_master.app.Host.doSomething())
  << execution(void de.scrum_master.app.MyClass.check())
<< execution(void de.scrum_master.app.MyClass.main(String[]))

将日志记录与异常处理分开的第二个方面变体:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AopLogger {
    private static final InheritableThreadLocal<String> indent = new InheritableThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "";
        }
    };

    @Around("execution(* de.scrum_master.app..*(..)) && !execution(* toString())")
    public Object logAround(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        System.out.println(indent.get() + ">> " + thisJoinPoint);
        try {
            indent.set(indent.get() + "  ");
            Object result = thisJoinPoint.proceed();
            indent.set(indent.get().substring(2));
            System.out.println(indent.get() + "<< " + thisJoinPoint);
            return result;
        } catch (Exception e) {
            indent.set(indent.get().substring(2));
            System.out.println(indent.get() + "<< " + thisJoinPoint);
            throw e;
        }
    }

    @Around("execution(void de.scrum_master.app.Host.doSomething())")
    public void handleException(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        try {
            thisJoinPoint.proceed();
        } catch (Exception e) {
            System.out.println(indent.get() + "Caught exception: " + e);
        }
    }
}

日志输出保持不变,但这次异常处理在一个单独的建议中,具有更精确的切入点。日志记录建议只处理日志记录(如果不是正确的缩进,它甚至不需要 try-catch)。异常处理建议只做自己的工作。

请随时提出后续问题。

【讨论】:

  • 谢谢,我认为问题出在 Apache BasicDataSource 上。方法 dosomething 创建一个 jdbc 池。如果无法访问主机,则方法 check() 结束。当我将 check() 放入 try catch 块时,它可以工作。我正在尝试另一种方法,现在在一个可运行的类中做同样的事情,但不知道如何实现它可以抛出异常的 void run() 方法。我的意思是 void run() 抛出异常
猜你喜欢
  • 1970-01-01
  • 2021-05-13
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 2011-03-22
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
相关资源
最近更新 更多