反射抛出自定义异常问题

4 篇文章 0 订阅

反射抛出自定义异常问题

作者: MysticalYcc

转载请注明出处:反射抛出自定义异常问题

问题描述

  1. 反射调用方法时,方法内部抛出了自定义异常,但是无法在反射调用点捕获到抛出的自定义异常。
  2. 反射调用方法时,方法再次调用反射抛出自定义异常,导致最底层异常消失。

调用逻辑代码

@Slf4j
public abstract class AbstractService implements BaseService<BaseDto> {

    protected String strategy = "ABS_BUS";

    /**
     * 注册表
     */
    protected static Map<String, BaseService<BaseDto>> strategyMap = new HashMap<>();

    /**
     * 强制要求实现类构造方法调用 super(strategy),注册实现类的自定义类型;
     * 如果实现类不覆盖属性 strategy {@link AbstractService#strategy},则注册为ABS_BUS方案。
     *
     * @param strategy
     */
    public AbstractService(String strategy) {
        this.addBean(strategy);
    }

    @Override
    public String dispose(BaseDto data) {
        String operation = data.getOperation();
        Object invoke = getObject(data, operation);
        return (String) invoke;
    }

    public String create(BaseDto baseDto) {
        String event = baseDto.getEvent();
        String data = baseDto.getData();
        //这里获取需要执行的创建事件。
        Object object = getObject(data, event);


        return (String) object;
    }

    protected <T> Object getObject(T data, String operation) {
        Object invoke;
        if (StringUtils.isEmpty(operation)) {
            throw new EventBaseException(CommonEnum.METHOD_LOST.getResultCode(), "无法找到对应的执行事件,请检查请求路劲和请求体参数");
        }
        try {
            Method method = this.getClass().getMethod(operation, data.getClass());
            invoke = method.invoke(this, data);
            System.out.println(invoke);
        } catch (ReflectiveOperationException e) {
            String format = String.format("未设置此方法:%s,请检查是否输错,或者联系管理员设置%s方法", operation, operation);
            log.error(format);
            e.printStackTrace();
            throw new EventBaseException(CommonEnum.METHOD_LOST.getResultCode(), format);
        }
        return invoke;
    }

    protected void addBean(String strategy) {
        strategyMap.putIfAbsent(strategy, this);
    }

    public static Optional<BaseService<BaseDto>> getOperation(String strategy) {
        return Optional.ofNullable(strategyMap.get(strategy));
    }

}

执行方式如图:

image-20210416103406348

执行的gif:

giteegalerry

过程描述:

首先第一调用getObject方法,执行了invoke方法,invoke方法执行了create方法,create方法调用了getObject方法,这个时候判断为null,抛出自定义异常;

这个时候回到第一次调用invoke的地方异常了,这个时候按说不会被catch住,因为抛出的是自定义异常。结果catch住的是ReflectiveOperationException

猜想

invoke方法将抛出固定异常,其他的异常被处理;

参见JDK的API文档

可以发现,JDK的API文档中,关于method.invoke的注释说明

公共 对象 invoke(Object obj,
Object … args)
抛出IllegalAccessException
IllegalArgumentException
InvocationTargetException
在Method 具有指定参数的指定对象上调用此对象表示的基础方法。各个参数将自动解包以匹配原始形式参数,并且原始参数和引用参数都必须根据需要进行方法调用转换。
如果基础方法是静态的,则obj 忽略指定的参数。它可以为空。

如果基础方法所需的形式参数的数量为0,则提供的args数组的长度可以为0或为null。

如果基础方法是实例方法,则使用《 Java语言规范,第二版》第15.12.4.4节中所述的动态方法查找来调用该方法。特别是,将发生基于目标对象的运行时类型的覆盖。

如果基础方法是静态的,则声明该方法的类将被初始化(如果尚未初始化)。

如果该方法正常完成,则将其返回的值返回给invoke的调用者;否则,返回false。如果该值具有原始类型,则首先将其适当包装在一个对象中。但是,如果该值具有原始类型的数组的类型,则该数组的元素不会包装在对象中;相反,换句话说,将返回原始类型的数组。如果基础方法的返回类型为void,则调用返回null。

参数:
obj -从其调用基础方法的对象
args -用于方法调用的参数
返回值:
obj用参数 分派此对象表示的方法的结果args
抛出:
IllegalAccessException-如果此Method对象正在强制执行Java语言访问控制,并且无法访问基础方法。
````IllegalArgumentException-如果该方法是实例方法,并且指定的对象参数不是声明基础方法(或其子类或实现者)的类或接口的实例;如果实际参数和形式参数的数量不同;如果原始参数的展开转换失败;或者在可能的解包之后,无法通过方法调用转换将参数值转换为相应的形式参数类型。 <font color =red> InvocationTargetException -如果基础方法引发异常。</font>(<font color=#BBFFFF>这里会处理基础方法引发的异常,猜想是这里处理了我们抛出的异常</font>)NullPointerException-如果指定的对象为null,并且该方法是实例方法。ExceptionInInitializerError```-如果此方法引发的初始化失败。

InvocationTargetException

java.lang.reflect

类InvocationTargetException

  • java.lang.Object


  • 公共类InvocationTargetException
    扩展了ReflectiveOperationException
    

    ``InvocationTargetException```是一个已检查的异常,该异常包装了被调用的方法或构造函数引发的异常。

    从版本1.4开始,已对该异常进行了改进以符合通用异常链机制。现在将在构建时提供并通过该getTargetException()方法访问的“目标异常” 称为原因,并且可以通过该Throwable.getCause()方法以及上述“旧式方法”进行访问。

  • 方法细节

    • getTargetException
      公共 Throwable  getTargetException()
      

      获取引发的目标异常。

      此方法早于通用异常链接工具。Throwable.getCause()现在,该方法是获取此信息的首选方法。

      • 返回值:

        引发的目标异常(此异常的原因)。

    • getCause
      公共 Throwable  getCause()
      

      返回此异常的原因(引发的目标异常,可能是null)。

      • 覆写:

        getCause 在班上 Throwable

      • 返回值:

        此异常的原因。

      • 自从:

        1.4

目标发生的异常被封印在InvocationTargetException中, 我们从中获取即可。

修改代码

将反射的异常抛出,在最外层catch InvocationTargetException,通过getCause获取内部抛出的自定义异常。

@Slf4j
public abstract class AbstractService implements BaseService<BaseDto> {

  protected String strategy = "ABS_BUS";

    /**
     * 注册表
     */
    protected static Map<String, BaseService<BaseDto>> strategyMap = new HashMap<>();

    /**
     * 强制要求实现类构造方法调用 super(strategy),注册实现类的自定义类型;
     * 如果实现类不覆盖属性 strategy {@link AbstractService#strategy},则注册为ABS_BUS方案。
     *
     * @param strategy
     */
    public AbstractService(String strategy) {
        this.addBean(strategy);
    }

    @Override
    public String dispose(BaseDto data) {
        String operation = data.getOperation();
        Object invoke = null;
        try {
            invoke = getObject(data, operation);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e) {
            e.printStackTrace();
            String format = String.format("未设置此方法:%s,请检查是否输错,或者联系管理员设置%s方法", operation, operation);
            log.error(format);
            e.printStackTrace();
            throw new EventBaseException(CommonEnum.METHOD_LOST.getResultCode(), format);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            Throwable cause = e.getCause();
            //获取反射调用方法抛出的自定义异常
            if (cause instanceof EventBaseException) {
                EventBaseException ex = (EventBaseException) cause;
                throw new EventBaseException(ex.getErrorCode(), ex.getMessage());
            }
        }
        return (String) invoke;
    }

    /**
     * 公共创建事件方法
     *
     * @param baseDto {@link BaseDto}
     * @return result
     * @throws NoSuchMethodException     NoSuchMethodException {@link NoSuchMethodException}
     * @throws IllegalAccessException    IllegalAccessException{@link IllegalAccessException}
     * @throws InvocationTargetException InvocationTargetException {@link InvocationTargetException}
     */
    public String create(BaseDto baseDto) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        String event = baseDto.getEvent();
        String data = baseDto.getData();
        Object object = null;
        //获取需要执行的创建事件。
        object = getObject(data, event);
        return (String) object;
    }

    /**
     * 获取具体的执行方案
     *
     * @param data
     * @param operation 方案
     * @param <T>
     * @return
     * @throws NoSuchMethodException     NoSuchMethodException {@link NoSuchMethodException}
     * @throws IllegalAccessException    IllegalAccessException{@link IllegalAccessException}
     * @throws InvocationTargetException InvocationTargetException {@link InvocationTargetException}
     */
    protected <T> Object getObject(T data, String operation) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Object invoke;
        if (StringUtils.isEmpty(operation)) {
            throw new EventBaseException(CommonEnum.METHOD_LOST.getResultCode(), "无法找到对应的执行事件,请检查请求路劲和请求体参数");
        }
        Method method = this.getClass().getMethod(operation, data.getClass());
        method.setAccessible(true);
        invoke = method.invoke(this, data);
        System.out.println(invoke);

        return invoke;
    }

    /**
     * 将实现类注册到注册表中,如果存在则忽略,即无法覆盖已有注册类。
     *
     * @param strategy {@link AbstractService#strategy}
     */
    protected void addBean(String strategy) {
        strategyMap.putIfAbsent(strategy, this);
    }

    /**
     * 获取方案
     *
     * @param strategy {@link AbstractService#strategy}
     * @return 方案具体实现类 extends AbstractService {@link AbstractService}
     */
    public static Optional<BaseService<BaseDto>> getOperation(String strategy) {
        return Optional.ofNullable(strategyMap.get(strategy));
    }
    
}

这样修改后即可。


  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 是的,在 Spring Boot 中使用 AOP 时,如果在切面类中定义异常,实际上会被包装成一个 `UndeclaredThrowableException` 异常。这是因为,在使用 JDK 动态代理的 AOP 实现中,代理类方法的调用是通过反射来实现的,当在切面类中异常时,如果该异常并不是被代理类方法声明异常,那么就会被包装成一个 `UndeclaredThrowableException` 异常。 为了解决这个问题, 你可以在切面类中捕获你的自定义异常,然后用另外的方式来处理(例如记录日志,发送邮件等) 也可以改用CGLIB的代理方式。只要在配置类上加上 ``` @EnableAspectJAutoProxy(proxyTargetClass = true) ``` 选择CGLIB的代理方式即可,但是CGLIB比JDK多一些内存占用。 ### 回答2: 在Spring Boot中,使用切面类(Class)时,如果切面类方法定义异常,有时候会导致UndeclaredThrowableException异常。 UndeclaredThrowableException是一个运行时异常,它表示未声明的Throwable对象,即无法在方法签名中声明的异常。当切面类方法定义异常时,但是该异常不在方法签名中声明或者不是方法中throws语句的任何已知异常时,Spring会将该异常包装在UndeclaredThrowableException中。 通常使用Spring的AOP(面向切面编程)功能时,我们会定义切面类和切点来实现一些横向的关注点。在切面类中可以定义一些通知(advice)方法,当目标方法执行前、后、或者异常时执行。其中,异常的时候,可以自定义异常类来标识特定的错误或业务逻辑。 然而,由于Java的异常处理机制,只能在方法声明中包括方法可能的所有已检查异常。而对于未检查的异常,我们无法在方法签名中显式声明。因此,如果切面类中的方法定义异常,但是该异常不是方法签名中声明的已检查异常,就会导致UndeclaredThrowableException异常现。 为了解决这个问题,可以考虑两种方式: 1. 将自定义异常类声明为继承RuntimeException等未检查异常。这样就不需要在方法签名中声明该自定义异常,也不会导致UndeclaredThrowableException异常。 2. 在切面类的通知方法中,捕获自定义异常并处理,而不是将其。这样即使异常不在方法签名中声明,也不会导致UndeclaredThrowableException异常。可以通过日志记录、返回特定的错误码等方式来处理异常,以保证程序的正常执行。 总之,当切面类中的方法定义异常时,如果该异常不在方法签名中声明,就会UndeclaredThrowableException异常。为了避免这种情况,我们可以将自定义异常类声明为未检查异常,或者在通知方法中捕获并处理该异常。 ### 回答3: 在使用Spring Boot的切面类中,如果切面方法中了自定义异常,可能会导致UndeclaredThrowableException异常。 UndeclaredThrowableException异常是Java反射机制的异常,它是由于通过反射调用方法时,被调用方法了一个检查异常,但调用方没有声明该异常,导致未进行异常处理异常。 切面类是用于实现面向切面编程的一种方式,它可以在方法执行前、执行后、异常时等关键点插入额外的逻辑。当切面方法中定义异常时,如果被调用的方法没有声明该异常反射机制会将该异常包装在UndeclaredThrowableException中。 为了解决这个问题,我们可以在切面方法中声明定义异常,并且被调用方也要声明该异常或它的父类异常,并且在调用方进行异常处理。如果被调用方无法修改,我们可以通过try-catch捕获UndeclaredThrowableException异常,并处理其中的原始异常。 在切面类中定义异常时,需要确保异常类型正确与声明相符,否则反射机制可能无法正确处理,仍然会UndeclaredThrowableException异常。 总之,Spring Boot中的切面类可能会UndeclaredThrowableException异常,这是由于反射机制中方法了一个检查异常,但调用方没有声明该异常所导致的。我们可以通过在切面方法中声明自定义异常、被调用方声明相应异常、进行异常处理等方式解决该问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值