我需要为我的一个项目实现动态构造函数调用。但由于使用的是Java 7,所以我使用java.lang.invoke而不是“传统”的反射API。
代码:
@ParametersAreNonnullByDefault
public class PathMatcherProvider
{
private static final MethodHandles.Lookup LOOKUP
= MethodHandles.publicLookup();
private static final MethodType CONSTRUCTOR_TYPE
= MethodType.methodType(void.class, String.class);
private final Map<String, Class<? extends PathMatcher>> classMap
= new HashMap<>();
private final Map<Class<? extends PathMatcher>, MethodHandle> handleMap
= new HashMap<>();
public PathMatcherProvider()
{
registerPathMatcher("glob", GlobPathMatcher.class);
registerPathMatcher("regex", RegexPathMatcher.class);
}
public final PathMatcher getPathMatcher(final String name, final String arg)
{
Objects.requireNonNull(name);
Objects.requireNonNull(arg);
final Class<? extends PathMatcher> c = classMap.get(name);
if (c == null)
throw new UnsupportedOperationException();
try {
return c.cast(handleMap.get(c).invoke(arg));
} catch (Throwable throwable) {
throw new RuntimeException("Unhandled exception", throwable);
}
}
protected final void registerPathMatcher(@Nonnull final String name,
@Nonnull final Class<? extends PathMatcher> matcherClass)
{
Objects.requireNonNull(name);
Objects.requireNonNull(matcherClass);
try {
classMap.put(name, matcherClass);
handleMap.put(matcherClass, findConstructor(matcherClass));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException("cannot find constructor", e);
}
}
private static <T extends PathMatcher> MethodHandle findConstructor(
final Class<T> matcherClass)
throws NoSuchMethodException, IllegalAccessException
{
Objects.requireNonNull(matcherClass);
return LOOKUP.findConstructor(matcherClass, CONSTRUCTOR_TYPE);
}
public static void main(final String... args)
{
new PathMatcherProvider().getPathMatcher("regex", "^a");
}
}
好的,这样可行。
我遇到的问题出现在这一行:
return c.cast(handleMap.get(c).invoke(arg));
如果我用
invokeExact
替换invoke
,我会得到以下堆栈跟踪:Exception in thread "main" java.lang.RuntimeException: Unhandled exception
at com.github.fge.filesystem.path.matchers.PathMatcherProvider.getPathMatcher(PathMatcherProvider.java:62)
at com.github.fge.filesystem.path.matchers.PathMatcherProvider.main(PathMatcherProvider.java:89)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.invoke.WrongMethodTypeException: expected (String)RegexPathMatcher but found (String)Object
at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:350)
at java.lang.invoke.Invokers.checkExactType(Invokers.java:361)
at com.github.fge.filesystem.path.matchers.PathMatcherProvider.getPathMatcher(PathMatcherProvider.java:60)
我还不太明白。无论是
GlobPathMatcher
还是 RegexPathMatcher
,它们都使用一个带有 String
参数的单个构造函数,因此两者的 MethodType
都是在 CONSTRUCTOR_TYPE
中定义的。否则,我无法“获取”MethodHandle
。但是我收到了一个 WrongMethodTypeException
异常。为什么呢?
编辑:在阅读答案后,以下是代码;现在我不需要中间 map 了:我只需要一个 map,将
String
映射到一个 MethodHandle
:@ParametersAreNonnullByDefault
public class PathMatcherProvider
{
private static final MethodHandles.Lookup LOOKUP
= MethodHandles.publicLookup();
private static final MethodType CONSTRUCTOR_TYPE
= MethodType.methodType(void.class, String.class);
private final Map<String, MethodHandle> handleMap
= new HashMap<>();
public PathMatcherProvider()
{
registerPathMatcher("glob", GlobPathMatcher.class);
registerPathMatcher("regex", RegexPathMatcher.class);
}
public final PathMatcher getPathMatcher(final String name, final String arg)
{
Objects.requireNonNull(name);
Objects.requireNonNull(arg);
final MethodHandle handle = handleMap.get(name);
if (handle == null)
throw new UnsupportedOperationException();
try {
return (PathMatcher) handle.invokeExact(arg);
} catch (Throwable throwable) {
throw new RuntimeException("Unhandled exception", throwable);
}
}
protected final void registerPathMatcher(@Nonnull final String name,
@Nonnull final Class<? extends PathMatcher> matcherClass)
{
Objects.requireNonNull(name);
Objects.requireNonNull(matcherClass);
final MethodHandle handle;
final MethodType type;
try {
handle = LOOKUP.findConstructor(matcherClass, CONSTRUCTOR_TYPE);
type = handle.type().changeReturnType(PathMatcher.class);
handleMap.put(name, handle.asType(type));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException("cannot find constructor", e);
}
}
}
c.cast()
的参数类型会被记录为预期的类型?嗯,我完全忽略了这部分,解释得很好;) - Vogel612