استثناء الوصول عند استدعاء طريقة فئة مجهولة باستخدام انعكاس Java

StackOverflow https://stackoverflow.com/questions/2659517

سؤال

أحاول استخدام مرسل الأحداث للسماح لنموذج بإخطار المستمعين المشتركين عند تغييره. يتلقى مرسل الحدث فئة معالج واسم طريقة للاتصال بها أثناء الإرسال. يشترك مقدم العرض في تغيير النموذج ويوفر تنفيذ المعالج ليتم استدعاؤه على التغييرات.

إليك الرمز (أنا آسف إنه طويل بعض الشيء).

EventDispacther:

package utils;

public class EventDispatcher<T> {
    List<T> listeners;
    private String methodName;

    public EventDispatcher(String methodName) {
        listeners = new ArrayList<T>();
        this.methodName = methodName;
    }

    public void add(T listener) {
        listeners.add(listener);
    }

    public void dispatch() {
        for (T listener : listeners) {
            try {
                Method method = listener.getClass().getMethod(methodName);
                method.invoke(listener);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

نموذج:

package model;

public class Model {
    private EventDispatcher<ModelChangedHandler> dispatcher;

    public Model() {
        dispatcher = new EventDispatcher<ModelChangedHandler>("modelChanged");
    }

    public void whenModelChange(ModelChangedHandler handler) {
        dispatcher.add(handler);
    }

    public void change() {
        dispatcher.dispatch();
    }
}

ModelChangedHandler:

package model;

public interface ModelChangedHandler {
    void modelChanged();
}

مقدم:

package presenter;

public class Presenter {

    private final Model model;

    public Presenter(Model model) {
        this.model = model;
        this.model.whenModelChange(new ModelChangedHandler() {

            @Override
            public void modelChanged() {
                System.out.println("model changed");
            }
        });
    }
}

رئيسي:

package main;

public class Main {
    public static void main(String[] args) {
        Model model = new Model();
        Presenter presenter = new Presenter(model);
        model.change();
    }
}

الآن ، أتوقع الحصول على رسالة "تغيير النموذج". ومع ذلك ، سأحصل على java.lang.IlegalAccessException: لا يمكن للطبقة utils.EventDispatcher الوصول إلى عضو في مقدم الفصل.

أنا أفهم أن الفصل الدراسي الذي يلومه هو الفئة المجهولة التي قمت بإنشائها داخل مقدم العرض ، ومع ذلك لا أعرف كيفية جعلها أكثر "عامة" أكثر مما هي عليه حاليًا. إذا استبدلته بفصل متداخل اسمه يبدو أنه يعمل. إنه يعمل أيضًا إذا كان مقدم العرض و EventDispatcher في نفس الحزمة ، لكن لا يمكنني السماح بذلك (يجب على العديد من مقدمي العروض في حزم مختلفة استخدام EventDispatcher)

أيه أفكار؟

هل كانت مفيدة؟

المحلول

هذا خطأ في JVM (علة 4819108)

الحل البديل هو الاتصال method.setAccessible(true) قبل الدعوة إلى method.invoke(listener)

نصائح أخرى

أظن أن فئة مجهولة هي دائما private, ، لكنني لم أجد بيانًا واضحًا حول هذا الموضوع في مواصفات لغة Java (نظرت في الفقرة 15.9.5)

في Java ، إذا كان النوع غير متاح ، فليس أعضاؤه.

إذا كنت تحب Black Magic ، فيمكنك تعطيل فحص الوصول باستخدام method.setAccessible(true). بديل ، قد تطلب من معالجات الأحداث الخاصة بك تسمية فصول ، أو الطريقة المعنية التي يتم الإعلان عنها في أنواع يمكن الوصول إليها.

المشكلة هنا هي أنه في الكود الذي يستخدم الانعكاس ، فإنك تعكس الفصل بدلاً من الواجهة.

في ظل الظروف غير الانعكاسية ، listener لن يعتبر من النوع presenter.Presenter$1. سوف تستخدمه من خلال ModelChangedHandler المرجعي. ModelChangedHandler هو نوع عام ولديه طريقة عامة ، وسيتم السماح بالوصول متعدد الأشكال.

ولكن لأنك تستخدم getClass(), ، أنت تحصل على فئة التنفيذ الفعلية. عادة ، لا يمكن الوصول إلى هذه الفئة على الإطلاق. الفصول المحلية والمجهولة ليست من المستوى الأعلى وليس الفصول الأعضاء. لذلك لم يتم تعريف "الوصول" بالنسبة لهم.

في الواقع ، فإن الخطأ الحقيقي هنا هو حقيقة أن آلية الانعكاس تنظر إلى "عدم وجود معدلات الوصول" على أنها "وصول افتراضي" وهو "حزمة خاصة". لذلك يتيح هذه العملية عندما تكون الأنواع في نفس الحزمة. IMO ، كان ينبغي أن يكون قد ذكرت IllegalAccessException حتى عندما يكونون في نفس الحزمة ، حيث لا يوجد إمكانية الوصول إلى الفئة المحددة من حيث تسميها ، ويجب رفع تقييد الوصول صراحة method.setAccessible(true).

إذن ماذا سيكون الطريقة الصحيحة للقيام بذلك؟ يجب عليك الوصول إليه باستخدام واجهه المستخدم Class هدف.

package util;

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

public class EventDispatcher<T> {
    List<T> listeners;
    Method method;

    public EventDispatcher(Class<? extends T> cls, String methodName) throws NoSuchMethodException, SecurityException {
        listeners = new ArrayList<T>();
        this.method = cls.getMethod(methodName);
    }

    public void add(T listener) {
        listeners.add(listener);
    }

    public void dispatch() {
        for (T listener : listeners) {
            try {
                method.invoke(listener);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

في هذا الإصدار ، نقوم بتمرير كائن فئة مُنشئ للواجهة المطلوبة ، وكذلك اسم الطريقة. نخلق Method كائن في المنشئ. إنه انعكاس للطريقة في واجهه المستخدم بحد ذاتها. ليس الفصل.

في dispatch, ، عندما نستدعي الطريقة ، نطبق واجهات طريقة للمستمع المعطى. هذا هو الانعكاس جنبا إلى جنب مع تعدد الأشكال.

package model;

import util.EventDispatcher;

public class Model {
    private EventDispatcher<ModelChangedHandler> dispatcher;

    public Model() throws NoSuchMethodException, SecurityException {
        dispatcher = new EventDispatcher<ModelChangedHandler>(ModelChangedHandler.class, "modelChanged");
    }

    public void whenModelChange(ModelChangedHandler handler) {
        dispatcher.add(handler);
    }

    public void change() {
        dispatcher.dispatch();
    }
}

حتى هنا في Model, ، نستخدم فئة الواجهة الحرفية - التي نعرفها لأنه هنا نقرر واجهة استخدامها.

package main;

import model.Model;
import presenter.Presenter;

public class Main {
    public static void main(String[] args) {
        Model model;
        try {
            model = new Model();
            Presenter presenter = new Presenter(model);
            model.change();

        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
}

التغيير الوحيد هنا هو المحاولة.

هذه المرة - لا توجد مشاكل في الوصول. يتم استدعاء هذه الطريقة متعددة الأشكال ، ويمكن الوصول إليها تمامًا!

هذه فكرة سيئة حقًا لاستخدام التفكير في هذه الحالة. فقط دع المرسل الخاص بك يتصل بالطريقة المطلوبة. إذا كنت بحاجة إلى العديد من المرسلين لاستدعاء طرق مختلفة ، فما عليك سوى الفئة الفرعية.

Java مفقودة الإغلاق ولكن المساعدة في الطريق!

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top