运行 Java 中的函数 如有必要,请使用 Swing EDT

Running a function in Java Swing EDT if necessary

如果 class 的 public 函数更新了一些 java swing GUI 元素,我必须检查调用此函数的上下文并确保更新已完成在美国东部时间。这就是我解决它的方法,但由于可能有很多功能我必须做同样的事情(但调用 class 的不同私有方法)或其他 classes 我必须做的一样,我厌倦了总是写同样的代码

public void updateGUI() 
{
    if(!SwingUtilities.isEventDispatchThread())
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() 
            {
                update();
            }
        });
    }
    else
    {
        update();
    }
}

private void update()
{
    //update the swing elements here
}

一个可能的解决方案可能是创建一个具有此更新方法的接口(名为 UpdateInterface)。 class 现在将实现此接口,我使用静态方法创建一个助手 class,该方法引用此接口并在 EDT 上下文中调用更新方法:

public static void callUpdateInEDT(UpdateInterface f) 
{
    if(!SwingUtilities.isEventDispatchThread())
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() 
            {
                f.update();
            }
        });
    }
    else
    {
        f.update();
    }
}

但这不是很灵活,因为我可能有一些我应该在 EDT 上下文中调用的其他私有方法。是否有任何我迄今为止没有找到的 SwingUtilities 解决方案或任何其他比我提出的更灵活的解决方案?

所以在写了很多包装代码之后,我开始思考,在 inner/anonymous classes 之前,我写了一个基于反射的 API 基本上简化使用任意数量的参数调用任意方法的过程

所以,您的代码会变得更像...

SwingSafeMethodInvoker.invokeLater(this, "update").execute();

这只是一种可能的解决方案(而且我很懒,所以我想减少必须(重新)编写的代码量)

该代码足够智能,可以知道您是否在 EDT 上并采取适当的措施。最棒的是它允许我调用 protected 方法(记住,在 inner/anonymous class 之前,你必须创建一个独立的外部 Runnable class,所以它不能调用 protected 方法)

API 是建立在附加的反射实用程序之上的,因此有点复杂。

警告 此 API 使用了反射,众所周知反射效率较低,因此您需要知道何时不使用它。它还 "breaks" protectedprivate 适用于 class 的访问限制,所以你可以做一些非常有趣的事情,其中​​大部分我不鼓励你 trying/doing.

这也不会提取您可能对代码所做的任何重构,因此如果您重命名一个方法,这将看不到它,请小心。

记住:这是我写的 bigger reflection API 的一部分,它允许我在 classes 上执行方法,所以它被故意分解成几个 classes

SwingSafeMethodInvoker

public class SwingSafeMethodInvoker {

    public static InvokeLater invokeLater(Object obj, String methodName) {

        return new InvokeLater(obj, methodName);

    }

    public static InvokeLater invokeLater(Class clazz, String methodName) {

        return new InvokeLater(clazz, methodName);

    }

    public static InvokeAndWait invokeAndWait(Object obj, String methodName) {

        return new InvokeAndWait(obj, methodName);

    }

    public static InvokeAndWait invokeAndWait(Class clazz, String methodName) {

        return new InvokeAndWait(clazz, methodName);

    }

    public static InvokeAfter invokeAfter(Object obj, String methodName) {

        return new InvokeAfter(obj, methodName);

    }

    public static InvokeAfter invokeAfter(Class clazz, String methodName) {

        return new InvokeAfter(clazz, methodName);

    }

    public static InvokeAfterAndWait invokeAfterAndWait(Object obj, String methodName) {

        return new InvokeAfterAndWait(obj, methodName);

    }

    public static InvokeAfterAndWait invokeAfterAndWait(Class clazz, String methodName) {

        return new InvokeAfterAndWait(clazz, methodName);

    }

    public static MethodInvoker invoke(Object obj, String methodName) {

        return new MethodInvoker(obj, methodName);

    }

    public static MethodInvoker invoke(Class clazz, String methodName) {

        return new MethodInvoker(clazz, methodName);

    }

}

InvokeLater

import java.awt.EventQueue;
import javax.swing.SwingUtilities;

/**
 * This will make a synchronised call into the EventQueue.
 * 
 * There is no way to determine when the actually call will be made.  If you
 * need to wait for the result of the call, you are better  of using 
 * InvokeAndWait instead
 * 
 * If the invoke method is called within the ETD, it will be executed
 * immediately.
 * 
 * If you want the call to occur later (ie have it placed at the end
 * of the EventQueue, use InvokeAfter)
 * 
 * The invoke method will always return null.
 * @author shane
 */
public class InvokeLater extends AbstractSwingMethodInvoker {

    public InvokeLater(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);

    }

    public InvokeLater(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public InvokeLater(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public Object execute() throws SynchronisedDispatcherException {

        if (EventQueue.isDispatchThread()) {

            run();

        } else {

            SwingUtilities.invokeLater(this);

        }

        return null;

    }

}

InvokeAndWait

import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;

public class InvokeAndWait extends AbstractSwingMethodInvoker {

    public InvokeAndWait(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);

    }

    public InvokeAndWait(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public InvokeAndWait(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public Object execute() {

        if (EventQueue.isDispatchThread()) {

            run();

        } else {

            try {

                SwingUtilities.invokeAndWait(this);

            } catch (InterruptedException ex) {

                throw new InvocationException("Failed to invokeAndWait", ex);

            } catch (InvocationTargetException ex) {

                throw new InvocationException("Failed to invokeAndWait", ex);

            }

        }

        return getResult();

    }

}

InvokeAfter

import javax.swing.SwingUtilities;

/**
 * This will place the method call onto the end of the event dispatching
 * queue and return immediately.
 * 
 * There is no means to known when the call actually takes and place and if
 * you are interested in the return result, you are better of using InvokeAndWait
 * @author shane
 */
public class InvokeAfter extends InvokeLater {

    public InvokeAfter(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);

    }

    public InvokeAfter(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public InvokeAfter(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public Object execute() throws SynchronisedDispatcherException {

        SwingUtilities.invokeLater(this);

        return null;

    }
}

AbstractSwingMethodInvoker

import core.util.MethodInvoker;

public abstract class AbstractSwingMethodInvoker extends MethodInvoker implements Runnable {

    private Object result;

    public AbstractSwingMethodInvoker(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);


    }

    public AbstractSwingMethodInvoker(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public AbstractSwingMethodInvoker(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public void run() {

        result = super.execute();

    }

    public Object getResult() {

        return result;

    }

    public class SynchronisedDispatcherException extends Error {

        public SynchronisedDispatcherException(String message) {
            super(message);
        }

        public SynchronisedDispatcherException(String message, Throwable cause) {
            super(message, cause);
        }

    }

}

MethodInvoker

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Provides a means to invoke any method on any class/object using reflection
 *
 * @author Shane Whitegead
 */
public class MethodInvoker {

    private Object parent;
    private Class parentClass;
    private String methodName;
    private List<Parameter> lstParameters;

    public MethodInvoker() {

    }

    public MethodInvoker(Object parent, Class parentClass, String methodName) {

        this.parent = parent;
        this.parentClass = parentClass;
        this.methodName = methodName;

        lstParameters = new ArrayList<Parameter>(5);

    }

    public MethodInvoker(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public MethodInvoker(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    public static MethodInvoker invoke(Object parent, String methodName) {

        return new MethodInvoker(parent, methodName);

    }

    public static MethodInvoker invoke(Class parent, String methodName) {

        return new MethodInvoker(parent, methodName);

    }

    public static MethodInvoker invoke(Object parent, Class clazz, String methodName) {

        return new MethodInvoker(parent, clazz, methodName);

    }

    public MethodInvoker setParent(Object parent) {

        this.parent = parent;
        if (parent != null) {

            setParentClass(parentClass.getClass());

        }

        return this;

    }

    public MethodInvoker setParentClass(Class parent) {

        this.parentClass = parent;

        return this;

    }

    public MethodInvoker setMethodName(String method) {

        this.methodName = method;

        return this;

    }

    public <T> MethodInvoker with(Class<T> type, T value) {

        with(new Parameter(value, type));

        return this;

    }

    public MethodInvoker with(Class[] types, Object[] parameters) {

        if (types == null || parameters == null) {
        } else if (types.length != parameters.length) {
        } else {

            for (int index = 0; index < types.length; index++) {

                with(types[index], parameters[index]);

            }

        }

        return this;

    }

    public MethodInvoker with(Parameter parameter) {

        lstParameters.add(parameter);

        return this;

    }

    public Object getParent() {
        return parent;
    }

    public Class getParentClass() {
        return parentClass;
    }

    public String getMethodName() {
        return methodName;
    }

    public Class[] getParameterTypes() {

        List<Class> lstTypes = new ArrayList<Class>(lstParameters.size());
        for (Parameter parameter : lstParameters) {

            lstTypes.add(parameter.getType());

        }

        return lstTypes.toArray(new Class[lstTypes.size()]);

    }

    public Object[] getParameterValues() {

        List<Object> lstTypes = new ArrayList<Object>(lstParameters.size());
        for (Parameter parameter : lstParameters) {

            lstTypes.add(parameter.getValue());

        }

        return lstTypes.toArray(new Object[lstTypes.size()]);

    }

    public Object execute() {

        Object result = null;

        Class type = getParentClass();
        String methodName = getMethodName();
        Class[] lstTypes = getParameterTypes();
        Object[] lstValues = getParameterValues();
        Object parent = getParent();

        try {


            Method method = findMethod(type, methodName, lstTypes);

            if (method == null) {
                throw new NoSuchMethodException(getMethodDescription(type, methodName, lstTypes));
            }

            method.setAccessible(true);

//          logger.info("Method = " + method);

            result = method.invoke(parent, lstValues);

        } catch (Exception ex) {

            StringBuilder sb = new StringBuilder(64);

            sb.append("parent = ").append(parent).append("\n");
            sb.append("type = ").append(type).append("\n");
            sb.append("methodName = ").append(methodName).append("\n");
            for (int index = 0; index < lstTypes.length; index++) {

                sb.append("[").append(index).append("] ").append(lstTypes[index].getName()).append(lstValues[index]).append("\n");

            }

            System.err.println("Called by\n" + sb.toString());

            throw new InvocationException("Failed to invoke " + methodName, ex);

        }

        return result;

    }

    public static Field findField(Class parent, String name) {

        Field field = null;

        try {

            field = parent.getDeclaredField(name);

        } catch (NoSuchFieldException noSuchFieldException) {

            try {

                field = parent.getField(name);

            } catch (NoSuchFieldException nsf) {

                if (parent.getSuperclass() != null) {

                    field = findField(parent.getSuperclass(), name);

                }

            }

        }

        if (field != null) {

            field.setAccessible(true);

        }

        return field;


    }

    /**
     * This method basically walks the class hierarchy looking for a matching
     * method.
     *
     * The issue is getXMethod only returns a list of the methods for the current
     * class and not the inherited methods. This method basically over comes that
     * limitation.
     *
     * If no method can be found matching the criteria, then this method returns a
     * null value.
     *
     * This makes this method not only very powerful, but also very dangerous, as
     * it has the power of finding ANY declared method within the hierarchy,
     * public, protected or private.
     *
     * @param parent
     * @param name
     * @param lstTypes
     * @return
     */
    public static Method findMethod(Class parent, String name, Class[] lstTypes) {

        Method method = null;

        try {

            method = parent.getDeclaredMethod(name, lstTypes);

        } catch (NoSuchMethodException noSuchMethodException) {

            try {

                method = parent.getMethod(name, lstTypes);

            } catch (NoSuchMethodException nsm) {

                if (parent.getSuperclass() != null) {

                    method = findMethod(parent.getSuperclass(), name, lstTypes);

                }

            }

        }

        return method;

    }

    /**
     * Builds up a description of the method call in the format of
     * [package.class.method([{package.class}{, package.class}{...}])
     *
     * This is typically used to throw a NoMethodFound exception.
     *
     * @param clazz
     * @param name
     * @param lstTypes
     * @return
     */
    public static String getMethodDescription(Class clazz, String name, Class[] lstTypes) {

        StringBuilder sb = new StringBuilder();
        sb.append(clazz.getName()).append(".").append(name).append("(");

        for (Class type : lstTypes) {

            sb.append(type.getName()).append(", ");

        }

        if (lstTypes.length > 0) {
            sb.delete(sb.length() - 2, sb.length());
        }

        sb.append(")");

        return sb.toString();

    }

    public class Parameter<T> {

        private T value;
        private Class<T> clazz;

        public Parameter(T value, Class<T> clazz) {
            this.value = value;
            this.clazz = clazz;
        }

        public T getValue() {
            return value;
        }

        public Class<T> getType() {
            return clazz;
        }
    }

    public class InvocationException extends Error {

        public InvocationException(String message) {
            super(message);
        }

        public InvocationException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public static boolean hasMethod(Object instance, String methodName) {

        return hasMethod(instance.getClass(), methodName);

    }

    public static boolean hasMethod(Object instance, String methodName, Class[] types) {

        return hasMethod(instance.getClass(), methodName, types);

    }

    public static boolean hasMethod(Class clazz, String methodName) {

        return hasMethod(clazz, methodName, new Class[0]);

    }

    public static boolean hasMethod(Class clazz, String methodName, Class[] types) {

        return findMethod(clazz, methodName, types) != null;

    }
}

我已经通过代理解决了这样的样板代码。

您的 UI 管理抽象:

public interface UIManager {
    void normal();
    void internalCall();
    void throwException();
}

跟踪实现:

public class UIManagerImpl implements UIManager {
    private void traceCall(String method) {
        new Exception(Thread.currentThread().getName() + " > " + method).printStackTrace(System.out);
    }
    @Override
    public void normal() {
        traceCall("normal");
    }
    @Override
    public void internalCall() {
        traceCall("internalCall");
        normal();
    }
    @Override
    public void throwException() {
        traceCall("throwException");
        throw new RuntimeException("doB");
    }
}

一个简单的 EDT 感知代理:

public class EdtHandler implements InvocationHandler {

    private Object target;
    public  EdtHandler(Object target) {
        this.target = target;
    }
    public static <T> T newProxy(Class<T> contract, T impl) {
        return (T) Proxy.newProxyInstance(impl.getClass().getClassLoader(), new Class<?>[] { contract }, new EdtHandler(impl));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (SwingUtilities.isEventDispatchThread()) {
            dispatch(method, args);
        } else {
            SwingUtilities.invokeLater(() -> dispatch(method, args));
        }
        return null;
    }

    protected void dispatch(Method method, Object[] args) {
        try {
            method.invoke(target, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            onError(e);
        }
    }
    protected void onError(Throwable e) {
        throw new IllegalStateException(e);
    }
}

现在主要检查:

public static void main(String[] args) {
    UIManagerImpl impl = new UIManagerImpl();
    UIManager     edt  = EdtHandler.newProxy(UIManager.class, impl);
    Runnable      block = () -> { System.out.println("---- Start ---- "); edt.normal(); edt.internalCall(); edt.throwException(); System.out.println("---- Stop ---- "); };
    block.run();
    SwingUtilities.invokeLater(block);
}

实施说明:

  • 无论您是否使用美国东部夏令时间,始终拨打 invokeLater 可能会更好。
  • 处理程序总是 returns null 并且更适合 void 方法。
  • 您可以轻松实现 return 感知代理:
    1. 使用 SynchronousQueue
    2. 等阻塞机制
    3. (使用当前实现)引入回调参数(即 Consumer

在阅读了一些有关 Lambda 的资料后,我得出了以下解决方案。我使用与 SwingUtilites 同名的静态方法创建了一个简单的 class,例如 invokeLater:

  public static void invokeLater(Runnable doRun)
  {
    if(SwingUtilities.isEventDispatchThread())
    {
      doRun.run();      
    }
    else
    {
      SwingUtilities.invokeLater(() -> {        
        doRun.run();        
      });
    }
  }

现在,假设新的 class 是 SwingUtilities2(我仍在为新的 class 寻找一个好名字 :-)),将此函数与 Lambda 一起使用非常容易:

SwingUtilities2.invokeLater(() -> {

  //here is the code which should run in the EDT

});