围绕外部库的重写方法的 AOP? [英] AOP around overridden methods of external library?

查看:24
本文介绍了围绕外部库的重写方法的 AOP?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am searching for a practical solution for the following problem:

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

我正在寻找的是一个通用的 AOP 错误处理建议.它将包装每个方法的代码,这些方法是外部库中方法的直接覆盖或实现.基本上是通过 try/catch 来恢复错误.

What I am looking for is to have a generic AOP error handling advice. It would wrap the code of every method that is a direct override or implementation of a method from the external library. Basically doing a try/catch for error recovery.

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

I can search for them manually of course. However our code base is extensive. And so are the possibilities to override methods of the external library classes. Hence thinking about AOP to help me out.

加载时间编织被排除,因为它涉及网络应用程序.可能无法向应用程序服务器添加 JVM 代理.所以一定是编译时织入.

Load time weaving is ruled out because it concerns a web application. It might not be possible to add a JVM agent with the application server. So it must be compile time weaving.

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

Does somebody know how to do this with AspectJ ?

推荐答案

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

Assuming that your external library classes/interfaces are in a certain package such as org.external.library or in any of its subpackages, you can express those classes/interfaces in AspectJ syntax as org.external.library..*+. The .. means "include subpackages", the * means "all classes", the + means "include subclasses". Now append .*(..) and you get all methods regardless of name and number of arguments for those classes.

现在例如切入点

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

拦截任何库子类中的所有非静态方法,而不管返回类型.

intercepts all non-static methods in any library subclasses regardless of return type.

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

The bad news is that you cannot limit the pointcut to overriding methods because the @Override annotation has retention type "source", i.e. it is unavailable for aspect matching. But maybe you want to narrow down the pointcut to public methods only in order to exclude errors thrown by internal methods. It depends on your situation and how you want to deal with it. Assuming that also non-overriding methods can break base components, the pointcut is good, though.

这是一个自洽的示例:

库类/接口:

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();
}

一个扩展任何库类的类:

A class not extending any library class:

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

As you can see, this class throws runtime exceptions randomly in ~50% of all cases for demo purposes. We expect them not to be handled by our aspect.

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% 的所有情况下随机抛出运行时异常.我们希望它们由我们的方面来处理.

As you can see, these classes also throw runtime exceptions randomly in ~50% of all cases for demo purposes. We expect them to be handled by our aspect.

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

The main method demonstrates the whole setup by calling each method a few times, not handling any errors in overridden library classes, but only in our own class.

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;
        }
    }
}

控制台输出:

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

The output looks a bit different each time you run the application due to the randomisation in the code:

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

这篇关于围绕外部库的重写方法的 AOP?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆