簡體   English   中英

如有必要,在 Java Swing EDT 中運行函數

[英]Running a function in Java Swing EDT if necessary

如果一個類的公共函數更新了一些 java swing GUI 元素,我必須檢查這個函數在哪個上下文中被調用,並確保更新是在 EDT 中完成的。 這就是我解決它的方法,但由於可能有很多函數我必須做同樣的事情(但調用類的不同私有方法)或其他類我必須做同樣的事情,我厭倦了總是寫相同的代碼

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)。 該類現在將實現這個接口,我創建了一個帶有靜態方法的輔助類,它引用這個接口並在 EDT 上下文中調用 update 方法:

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

但這不是很靈活,因為我可能有一些其他的私有方法,我應該在 EDT 上下文中調用它們。 SwingUtilities 是否有任何我目前沒有找到的解決方案,或者任何其他比我提出的更靈活的解決方案?

因此,在多年編寫更多包裝器代碼之后,我開始考慮在內部/匿名類出現之前,我編寫了一個基於反射的 API,它基本上可以簡化調用具有任意數量參數的任何方法的過程

所以,你的代碼會變得更像......

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

這只是一種可能的解決方案(而且我很懶,所以我想減少我必須(重新)編寫的代碼量)

該代碼足夠智能,可以知道您是否在 EDT 上並采取適當的措施。 關於這個的絕妙之處在於它允許我調用protected方法(請記住,在內部/匿名類之前,您必須創建一個獨立的外部Runnable類,因此它無法調用protected方法)

API 建立在額外的反射實用程序之上,所以它有點復雜。

警告此 API 使用反射,眾所周知,反射效率較低,因此您需要知道何時不使用它。 它還“打破”了protectedprivate應用於類的訪問限制,因此您可以做一些非常有趣的事情,其中​​大部分我會勸阻您不要嘗試/做。

這也不會進行您可能對代碼進行的任何重構,因此如果您重命名一個方法,這將看不到它,請小心。

記住:這是我編寫的更大的反射 API 的一部分,它允許我在類上執行方法,所以它故意分解為幾個類

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);

    }

}

稍后調用

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;

    }

}

調用和等待

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();

    }

}

調用之后

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);
        }

    }

}

方法調用者

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);
}

實施注意事項:

  • 無論您是否在 EDT 上,始終調用invokeLater可能會更好。
  • Handler 總是返回null ,更適合void方法。
  • 您可以輕松實現return感知代理:
    1. 使用阻塞機制,例如SynchronousQueue
    2. (使用當前實現)引入回調參數(即Consumer

在閱讀了一些關於 Lambda 的內容后,我得到了以下解決方案。 我使用與 SwingUtilites 同名的靜態方法創建了一個簡單的類,例如 invokeLater:

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

現在,假設新類是 SwingUtilities2(我仍在為新類尋找一個好名字 :-)),那么將此函數與 Lambda 一起使用非常容易:

SwingUtilities2.invokeLater(() -> {

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

});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM