首页 > 解决方案 > bean xml中的Spring配置方法引用

问题描述

我的配置类中有一个类似于下面代码的地图(尽管我的实际问题涉及一组不同的类):

private Map<Class, Function<String, ?>> someParser = ImmutableMap.of(
            Short.class, Short::parseShort, Integer.class, Integer::parseInt, 
            Double.class, Double::parseDouble);

有没有办法在 XML 文件中配置它?就像在 XML 文件中将方法引用视为 bean 一样?由于下面的代码显然不起作用:

<util:map id="someParser" key-type="java.lang.Class">
    <entry key="java.lang.Short" value-ref="Short::parseShort" />
    <entry key="java.lang.Integer" value-ref="Integer::parseInteger" />
    <entry key="java.lang.Double" value-ref="Double::parseDouble" />
</util:map>

标签: javaspringspring-bootmethod-reference

解决方案


简短的回答是肯定的,但我需要一个方法引用工厂类来帮助我生成这些 bean。这绝对是太冗长了,但它的工作原理。我最终得到了这样的结果:

<bean id="methodFactory" class="com.foo.bar.util.MethodRefFactory" />    
<util:map id="someParser" key-type="java.lang.Class">
    <entry key="java.lang.Integer" value-ref="parseInteger" />
    <entry key="java.lang.Double" value-ref="parseDouble" />
</util:map>
<bean id="parseInteger" factory-bean="methodFactory" factory-method="getMethodRef">
    <constructor-arg value="java.lang.Integer::parseInt" />
    <constructor-arg value="java.util.function.Function" type="java.lang.Class" />
    <constructor-arg><null /></constructor-arg>
</bean>
<bean id="parseDouble" factory-bean="methodFactory" factory-method="getMethodRef">
    <constructor-arg value="java.lang.Double::parseDouble" />
    <constructor-arg value="java.util.function.Function" type="java.lang.Class" />
    <constructor-arg><null /></constructor-arg>
</bean>

工厂类应该只接受方法引用语法<class|instance>::<method>作为字符串,拆分它并将方法引用作为函数返回。但是我想要一个可以处理静态和非静态方法引用(例如java.class.Integer::parseIntfileValidator::validate)的通用工厂类,所以这是我想出的工厂类。我希望你发现这个类也很有用:

public class MethodRefFactory {

    private ApplicationContext context;

    public MethodRefFactory(ApplicationContext context) {
        this.context = context;
    }

    /** Use this method to create non-static method references like fileValidator::validate */
    public <T> T getMethodSpring(String signature, Class<T> lambdaFuncType) throws Throwable {
        String[] sigTokens = signature.split("::");
        String sigClass = sigTokens[0];
        Object objToInvoke = null;
        try {
            objToInvoke = context.getBean(sigClass);
        } catch(BeansException ex) {
            ex.printStackTrace();
        }
        return getMethodRef(signature, lambdaFuncType, objToInvoke);
    }

    /** Use this method to create static method references like java.lang.Integer::parseInt */
    public <T> T getMethodRef(String signature, Class<T> lambdaFuncType, Object objToInvoke) throws Throwable {
        String[] sigTokens = signature.split("::");
        String sigClass = sigTokens[0];
        String sigMethod = sigTokens[1];
        boolean isStaticMethod = Objects.isNull(objToInvoke);
        Class realType = isStaticMethod ? Class.forName(sigClass) : objToInvoke.getClass();
        Method realMethod = getRealMethod(realType, sigMethod);

        MethodHandles.Lookup caller = MethodHandles.lookup();
        MethodHandle realMethodHandle = caller.unreflect(realMethod);
        MethodType realMethodHandleType = realMethodHandle.type();
        MethodType lambdaFuncMethodType = isStaticMethod ? realMethodHandleType.generic() :
                generateLambdaFuncMethodType(lambdaFuncType);
        MethodType targetMethodType = isStaticMethod ? realMethodHandleType :
                extractMatchingMethodTypeForRealMethod(realMethodHandleType);
        String lambdaFuncMethodName = lambdaFuncType.getMethods()[0].getName();
        MethodType lambdaFuncAndRealType = isStaticMethod ? MethodType.methodType(lambdaFuncType) :
                MethodType.methodType(lambdaFuncType, realType);
        CallSite site = LambdaMetafactory.metafactory(caller, lambdaFuncMethodName, lambdaFuncAndRealType,
                lambdaFuncMethodType, realMethodHandle, targetMethodType);
        MethodHandle factory = site.getTarget();
        if (!isStaticMethod) {
            factory = factory.bindTo(objToInvoke);
        }
        return (T) factory.invoke();
    }

    private Method getRealMethod(Class type, String methodName) {
        return Arrays.stream(type.getMethods()).filter(m -> m.getName().equals(methodName))
                .sorted(this::compareMethods).findFirst().get();
    }

    private MethodType extractMatchingMethodTypeForRealMethod(MethodType target) {
        return MethodType.methodType(target.returnType(), Arrays.copyOfRange(target.parameterArray(),1, target.parameterCount()));
    }

    private <T> MethodType generateLambdaFuncMethodType(Class<T> funcType) {
        Method method = funcType.getMethods()[0];
        if (method.getParameterCount() == 0) {
            return MethodType.methodType(Object.class);
        }
        Class[] params = Arrays.copyOfRange(method.getParameterTypes(), 1, method.getParameterCount());
        return MethodType.methodType(method.getReturnType(), method.getParameterTypes()[0], params);
    }

    private int compareMethods(Method m1, Method m2) {
        return m1.getName().equals(m2.getName()) ? Integer.compare(m1.getParameterCount(), m2.getParameterCount()) :
                m1.getName().compareTo(m2.getName());
    }
}

这是 SimpleBean 类和 MyInterface 接口:

interface MyInterface<R> {

    R process(Object in);
}

class SimpleBean {

    public String simpleFunction(String in) {
        return "java.util.function.Function test: " + in;
    }

    public String simpleBiFunction(Double in, Integer y) {
        return "java.util.function.BiFunction test: " + (in + y);
    }

    public String simpleIntFunction(Integer y) {
        return "java.util.function.IntFunction test: " + y;
    }

    public String simpleSupplier() {
        return "java.util.function.Supplier test ";
    }

    public void simpleConsumer(String param) {
        System.out.println("java.util.function.Consumer test: " + param);
    }
}

这是我的测试用例。请注意,它支持Function, BiFunction, IntFunction, Supplier, Consumer, 甚至是自定义功能接口:

public static void main(String[] args) throws Throwable {

    MethodRefFactory factory = new MethodRefFactory(null);
    Function f = factory.getMethodRef("java.lang.Short::parseShort", Function.class, null);
    System.out.println(f.apply("2343"));

    SimpleBean bean = new SimpleBean();

    Function f2 = factory.getMethodRef("com.foo.bar.util.SimpleBean::simpleFunction", Function.class, bean);
    System.out.println(f2.apply("foo"));

    BiFunction f3 = factory.getMethodRef("com.foo.bar.util.SimpleBean::simpleBiFunction", BiFunction.class, bean);
    System.out.println(f3.apply(25.0, 4));

    Supplier f4 = factory.getMethodRef("com.foo.bar.util.SimpleBean::simpleSupplier", Supplier.class, bean);
    System.out.println(f4.get());

    Consumer f5 = factory.getMethodRef("com.foo.bar.util.SimpleBean::simpleConsumer", Consumer.class, bean);
    f5.accept("bar");

    IntFunction f6 = factory.getMethodRef("com.foo.bar.util.SimpleBean::simpleIntFunction", IntFunction.class, bean);
    System.out.println(f6.apply(1234));

    MyInterface f7 = factory.getMethodRef("com.foo.bar.util.SimpleBean::simpleFunction", MyInterface.class, bean);
    System.out.println(f7.process("myInterface param"));
}

这是非静态方法引用的最后一个示例 XML:

<util:map id="executorMap" key-type="java.lang.Class">
    <entry key="com.foo.bar.action.Read" value-ref="readerReadMsg" />
    <entry key="com.foo.bar.action.Validate" value-ref="validatorValidateMsg" />
    <entry key="com.foo.bar.action.Transform" value-ref="transformerTransformMsg" />
    <entry key="com.foo.bar.action.Persist" value-ref="persisterPersistMsg" />
</util:map>

<bean id="readerReadMsg" factory-bean="methodFactory" factory-method="getMethodSpring">
    <constructor-arg value="reader::readMsg" />
    <constructor-arg value="java.util.function.BiFunction" type="java.lang.Class" />
</bean>
<bean id="validatorValidateMsg" factory-bean="methodFactory" factory-method="getMethodSpring">
    <constructor-arg value="validator::validateMsg" />
    <constructor-arg value="java.util.function.BiFunction" type="java.lang.Class" />
</bean>
<bean id="transformerTransformMsg" factory-bean="methodFactory" factory-method="getMethodSpring">
    <constructor-arg value="transformer::transformMsg" />
    <constructor-arg value="java.util.function.BiFunction" type="java.lang.Class" />
</bean>
<bean id="persisterPersistMsg" factory-bean="methodFactory" factory-method="getMethodSpring">
    <constructor-arg value="persister::persistMsg" />
    <constructor-arg value="java.util.function.BiFunction" type="java.lang.Class" />
</bean>

以下链接极大地帮助我了解了它的LambdaMetafactory.metafactory工作原理以及它如何使我能够实现我想要的:thisthisthis


推荐阅读