问题描述
- 反射调用方法时,方法内部抛出了自定义异常,但是无法在反射调用点捕获到抛出的自定义异常。
- 反射调用方法时,方法再次调用反射抛出自定义异常,导致最底层异常消失。
调用逻辑代码
@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));
}
}
执行方式如图:
执行的gif:
过程描述:
首先第一调用
方法,这个时候判断为null,抛出自定义异常;getObject
`方法,执行了invoke方法,invoke方法执行了create方法,create方法调用了
`getObject
这个时候回到第一次调用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
抛出:
-如果此Method对象正在强制执行Java语言访问控制,并且无法访问基础方法。
IllegalAccessException
-如果该方法是实例方法,并且指定的对象参数不是声明基础方法(或其子类或实现者)的类或接口的实例;如果实际参数和形式参数的数量不同;如果原始参数的展开转换失败;或者在可能的解包之后,无法通过方法调用转换将参数值转换为相应的形式参数类型。
IllegalArgumentException
InvocationTargetException -如果基础方法引发异常。(这里会处理基础方法引发的异常,猜想是这里处理了我们抛出的异常)
-如果指定的对象为null,并且该方法是实例方法。
NullPointerException
-如果此方法引发的初始化失败。
ExceptionInInitializerError
InvocationTargetException
java.lang.reflect
类InvocationTargetException
- java.lang.Throwable
- java.lang.ReflectiveOperationException
- java.lang.reflect.InvocationTargetException
- 所有已实现的接口:
公共类InvocationTargetException 扩展了ReflectiveOperationException
`
InvocationTargetException
“是一个已检查的异常,该异常包装了被调用的方法或构造函数引发的异常。从版本1.4开始,已对该异常进行了改进以符合通用异常链机制。现在将在构建时提供并通过该[
getTargetException()
](https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/InvocationTargetException.html#getTargetException())方法访问的“目标异常” 称为原因,并且可以通过该[Throwable.getCause()
](https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#getCause())方法以及上述“旧式方法”进行访问。方法细节
getTargetException
公共 Throwable getTargetException()
获取引发的目标异常。
此方法早于通用异常链接工具。[
Throwable.getCause()
](https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#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));
}
}
这样修改后即可。
深思熟虑后(用屁股没想明白),决定研究一下
源码。。……..java Method.invoke
源码:(翻译来自谷歌翻译……..如果又不对,还请谅解。)java Method.invoke
/**
* 在具有指定参数的指定对象上调用此{@code Method} 对象表示的基础方法。
* 各个参数将自动解包以匹配*原始形式参数,并且
* 原始和引用参数都必须根据需要进行方法调用转换。
* 如果基础方法是静态的,则忽略指定的{@code obj} 参数。它可以为空。
* 如果基础方法所需的形式参数数量为0,则提供的{@code args}数组的长度可以为0或为null。
* 如果基础方法是实例方法,则使用动态方法查找来调用它,如Java语言规范第二版15.12.4.4中所述;特别是,*将基于目标对象的运行时类型进行覆盖。
* 如果基础方法是静态的,则声明该方法的类将被初始化(如果尚未初始化)。
* 如果该方法正常完成,则它返回的值将返回给invoke的调用者;
* 如果该值具有原始类型,则首先将其适当包装在一个对象中。但是,如果该值具有原始类型的数组的类型,则该数组的元素不包装在对象中;
* 换句话说,将返回原始类型的数组。
* 如果基础方法的返回类型为void,则调用返回null。
*
* @param obj 目标对象
* @param args 目标对象方法形参
* @return the 目标对象方法执行结果
* {@code args}
*
* @exception IllegalAccessException 方法的修饰符当前执行线程目标点执行invoke没有权限时抛出,
* @exception IllegalArgumentException,如果该方法是实例方法,
* 并且指定的对象参数不是类或接口的实例声明基础方法(或子类或其实现);
* 如果实际和形式参数的数量不同;如果对原始参数的拆包转换失败;或者,如果在可能的拆包之后,
* 则无法通过方法调用转换将参数值转换为相应的形式参数类型。
* @exception InvocationTargetException 如果基础方法引发异常。
* @exception NullPointerException 在调用invoke方法是以实例对象的形式时,obj为null则抛出
*
* @Exception ExceptionInInitializerError。 方法初始化失败
*/
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
//1 首先判断目标方法是否为重写方法。
if (!override) {
//2 判断权限
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
//3
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
-
首先判断目标方法是否为重写方法。
-
如果是重写方法,判断调用点是否有权限,或者是否设置了调用权限
java method.setAccessible(true);。
;此方法最终调用了native方法。java Reflection.quickCheckMemberAccess(clazz, modifiers)
`,快速检测标识,避免调用
`java getCallerClass()
/** Retrieves the access flags written to the class file. For inner classes these flags may differ from those returned by Class.getModifiers(), which searches the InnerClasses attribute to find the source-level access flags. This is used instead of Class.getModifiers() for run-time access checks due to compatibility reasons; see 4471811. Only the values of the low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be valid. */ public static native int getClassAccessFlags(Class> c); /** A quick "fast-path" check to try to avoid getCallerClass() calls. */ boolean quickCheckMemberAccess(Class> memberClass, int modifiers) { return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers); }
查看native方法:(本人不会C,如有不对,还请指正)。
-- jvm.h /* Differs from JVM_GetClassModifiers in treatment of inner classes. This returns the access flags for the class as specified in the class file rather than searching the InnerClasses attribute (if present) to find the source-level access flags. Only the values of the low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be valid. */ JNIEXPORT jint JNICALL JVM_GetClassAccessFlags(JNIEnv *env, jclass cls); -- jvm.cpp #define JVM_ACC_ABSTRACT 0x0400 /* no definition provided */ #define JVM_ACC_FINAL 0x0010 /* no further subclassing, overriding */ #define JVM_ACC_PUBLIC 0x0001 /* visible to everyone */ JVM_ENTRY(jint, JVM_GetClassAccessFlags(JNIEnv *env, jclass cls)) { JVMWrapper("JVM_GetClassAccessFlags"); if (java_lang_Class::is_primitive(JNIHandles::resolve_non_null(cls))) { // Primitive type return JVM_ACC_ABSTRACT | JVM_ACC_FINAL | JVM_ACC_PUBLIC; } Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(cls)); return k->access_flags().as_int() & JVM_ACC_WRITTEN_FLAGS; } JVM_END
根据修饰符标识与/或 来得到最终的标识(是否有权限)值并返回,返回后与Modifier访问修饰符集合类的isPublic做比较,得出最终结果并返回。JVM_GetClassAccessFlags
Modifier类如下,注释明确说明了此类中整数集合的规范 参见Java虚拟机规范第4.1、4.4、4.5和4.7节中的表。查找表中数据对照Native方法返回值做与操作得到结果。
/** * 访问修饰符整数集合,参见Java虚拟机规范的第4.1、4.4、4.5和4.7节中的表 * @see Class#getModifiers() * @see Member#getModifiers() * * @author Nakul Saraiya * @author Kenneth Russell */ public class Modifier{ /** * Return {@code true} if the integer argument includes the * {@code public} modifier, {@code false} otherwise. * * @param mod a set of modifiers * @return {@code true} if {@code mod} includes the * {@code public} modifier; {@code false} otherwise. */ public static boolean isPublic(int mod) { return (mod & PUBLIC) != 0; } }
Table 4.1-A. Class access and property modifiers
Flag Name Value Interpretation ACC_PUBLIC
0x0001 Declared public
;可以从其程序包外部进行访问ACC_FINAL
0x0010 Declared final
; 子类无法访问ACC_SUPER
0x0020 Treat superclass methods specially when invoked by the invokespecial instruction. ACC_INTERFACE
0x0200 Is an interface, not a class. 接口 ACC_ABSTRACT
0x0400 Declared abstract
; 不能实例化ACC_SYNTHETIC
0x1000 Declared synthetic; 在源代码中不存在 ACC_ANNOTATION
0x2000 Declared as an annotation type. 注解 ACC_ENUM
0x4000 Declared as an enum
type. 枚举java虚拟机规范就取4.1节的table,大家看了了解就行了………
-
快速检查未通过则进行
检查。java Reflection.getCallerClass();
待续!!!