【问题标题】:AOP around overridden methods of external library?围绕外部库的重写方法的AOP?
【发布时间】:2015-01-14 13:16:11
【问题描述】:

我正在寻找以下问题的实用解决方案:

  • 外部库提供组件作为基类。
  • 自定义组件是通过扩展这些基类来制作的。
  • 当实现抛出未处理的异常时,基类会中断。
  • 基类源代码不可用。只有一个二进制 jar。

我正在寻找的是一个通用的 AOP 错误处理建议。它将包装每个方法的代码,这些方法是外部库中方法的直接覆盖或实现。基本上是在尝试/捕获以进行错误恢复。

我当然可以手动搜索它们。然而,我们的代码库是广泛的。覆盖外部库类的方法的可能性也是如此。因此考虑使用 AOP 来帮助我。

排除了加载时间编织,因为它涉及 Web 应用程序。可能无法使用应用程序服务器添加 JVM 代理。所以一定是编译时编织。

有人知道如何用 AspectJ 做到这一点吗?

【问题讨论】:

    标签: java scala aop aspectj


    【解决方案1】:

    假设您的外部库类/接口位于某个包中,例如org.external.library 或其任何子包中,您可以在AspectJ 语法中将这些类/接口表示为org.external.library..*+.. 表示“包含子包”,* 表示“所有类”,+ 表示“包含子类”。现在追加.*(..),您将获得所有方法,无论这些类的名称和参数数量如何。

    现在以切入点为例

    execution(!static * org.external.library..*+.*(..))
    

    拦截任何库子类中的所有非静态方法,无论返回类型如何。

    坏消息是您不能将切入点限制为覆盖方法,因为@Override 注释具有保留类型“源”,即它不能用于方面匹配。但也许您只想将切入点缩小到公共方法,以便排除内部方法引发的错误。这取决于你的情况以及你想如何处理它。假设非覆盖方法也可以破坏基础组件,那么切入点很好。

    这是一个自洽的示例:

    库类/接口:

    package org.external.library;
    
    public abstract class AbstractBase {
        public abstract String getText();
        public abstract int getNumber();
        public abstract void doSomething();
    }
    
    package org.external.library;
    
    public interface Service {
        void start();
        void stop();
        boolean isRunning();
    }
    

    一个类扩展任何库类:

    如您所见,出于演示目的,此类在大约 50% 的情况下随机抛出运行时异常。我们希望它们由我们的方面处理。

    package de.scrum_master.app;
    
    import java.util.Random;
    
    public class MyOwnClass {
        private static final Random RANDOM = new Random();
    
        public String getGreeting(String recipient) {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot get greeting for '" + recipient + "'");
            return "Hello " + recipient + "!";
        }
    }
    

    类扩展/实现库类/接口:

    如您所见,出于演示目的,这些类在大约 50% 的情况下还会随机抛出运行时异常。我们希望它们由我们的方面处理。

    main 方法通过多次调用每个方法来演示整个设置,不处理覆盖的库类中的任何错误,而仅处理我们自己的类中的错误。

    package de.scrum_master.app;
    
    import java.util.Random;
    
    import org.external.library.Service;
    
    public class FooService implements Service {
        private static final Random RANDOM = new Random();
        private boolean isRunning;
    
        @Override
        public void start() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot start");
            isRunning = true;
        }
    
        @Override
        public void stop() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot stop");
            isRunning = false;
        }
    
        @Override
        public boolean isRunning() {
            return isRunning;
        }
    
        public void pause() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot pause");
            isRunning = false;
        }
    
        public void resume() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot resume");
            isRunning = true;
        }
    }
    
    package de.scrum_master.app;
    
    import java.util.Random;
    
    import org.external.library.AbstractBase;
    
    public class Application extends AbstractBase {
        private static final Random RANDOM = new Random();
    
        @Override
        public String getText() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot get text");
            return "Hello world!";
        }
    
        @Override
        public int getNumber() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot get number");
            return RANDOM.nextInt(10);
        }
    
        @Override
        public void doSomething() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot do something");
        }
    
        public void doSomethingElse() {
            if (RANDOM.nextBoolean())
                throw new RuntimeException("cannot do something else");
        }
    
        public static void main(String[] args) {
            Application application = new Application();
            FooService fooService = new FooService();
            MyOwnClass myOwnClass = new MyOwnClass();
            for (int i = 0; i < 5; i++) {
                application.getText();
                application.getNumber();
                application.doSomething();
                application.doSomethingElse();
                fooService.start();
                fooService.pause();
                fooService.isRunning();
                fooService.resume();
                fooService.isRunning();
                fooService.stop();
                try {
                    myOwnClass.getGreeting("world");
                    myOwnClass.getGreeting("guys");
                }
                catch (Exception e) {
                    System.out.println("Uncaught by aspect: " + e);
                }
            }
        }
    }
    

    错误处理方面:

    package de.scrum_master.aspect;
    
    public aspect ErrorHandler {
        Object around() : execution(!static * org.external.library..*+.*(..)) {
            try {
                return proceed();
            }
            catch (Exception e) {
                System.out.println(thisJoinPoint + " -> " + e);
                return null;
            }
        }
    }
    

    控制台输出:

    由于代码中的随机化,每次运行应用程序时输出看起来都会有所不同:

    execution(int de.scrum_master.app.Application.getNumber()) -> java.lang.RuntimeException: cannot get number
    execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
    execution(void de.scrum_master.app.Application.doSomethingElse()) -> java.lang.RuntimeException: cannot do something else
    execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
    execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
    execution(void de.scrum_master.app.FooService.resume()) -> java.lang.RuntimeException: cannot resume
    execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop
    Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'world'
    execution(String de.scrum_master.app.Application.getText()) -> java.lang.RuntimeException: cannot get text
    execution(int de.scrum_master.app.Application.getNumber()) -> java.lang.RuntimeException: cannot get number
    execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
    execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
    execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
    execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop
    Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'guys'
    execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
    execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
    execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
    Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'guys'
    execution(void de.scrum_master.app.Application.doSomethingElse()) -> java.lang.RuntimeException: cannot do something else
    execution(void de.scrum_master.app.FooService.resume()) -> java.lang.RuntimeException: cannot resume
    execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop
    Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'world'
    execution(String de.scrum_master.app.Application.getText()) -> java.lang.RuntimeException: cannot get text
    execution(int de.scrum_master.app.Application.getNumber()) -> java.lang.RuntimeException: cannot get number
    execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
    execution(void de.scrum_master.app.Application.doSomethingElse()) -> java.lang.RuntimeException: cannot do something else
    execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
    execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
    execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop
    

    【讨论】:

    • 我会尽快看看 - 我现在有点忙于工作。 :-) 但是知道我非常感谢为回答我所做的努力。我一完成就回来。
    • 好的!再次回来 ! (被转移到一个非常重要的主题中。)我想知道 AspectJ 中是否有可能拦截从 foo 的(子)包到(子)包 bar 的调用?因为通常这是添加扩展代码的地方。
    • 我也有几个星期没空。对于我相当详尽和广泛的答案,而不是没有反馈的后续问题,我将不胜感激。你有没有试过我的解决方案?它基本上可以满足您的要求。如果您有更多 AspectJ 问题,请随时提出新的 StackOverflow 问题,以免混淆不相关的主题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多