2013-01-11 61 views
14

मेरे पास Formula है, जो पैकेज javaapplication4 में स्थित है, जिसे मैं URLClassLoader के साथ लोड करता हूं। हालांकि, जब मैं इसे उसी पैकेज में स्थित किसी अन्य श्रेणी Test1 से कॉल करता हूं, तो मैं उन विधियों तक नहीं पहुंच सकता जिनके पास डिफ़ॉल्ट एक्सेस संशोधक है (मैं सार्वजनिक विधियों तक पहुंच सकता हूं)।URLClassLoader और पैकेज-निजी विधियों की पहुंच

java.lang.IllegalAccessException:

मैं निम्न अपवाद प्राप्त कक्षा javaapplication4.Test1 संशोधक के साथ वर्ग javaapplication4.Formula के एक सदस्य तक नहीं पहुँच सकता ""

मैं कैसे कर सकता है उसी पैकेज से रनटाइम पर लोड की गई कक्षा के पैकेज-निजी विधियों का उपयोग करें?

मुझे लगता है कि यह एक अलग वर्ग लोडर का उपयोग करने में एक समस्या है, लेकिन यह सुनिश्चित नहीं है कि (मैंने URLClassLoader के माता-पिता को क्यों सेट किया है)।

SSCCE मुद्दा (विंडोज पथ) के पुनरुत्पादन - मुझे लगता है कि इस मुद्दे को loadClass विधि में है:

public class Test1 { 

    private static final Path TEMP_PATH = Paths.get("C:/temp/"); 

    public static void main(String[] args) throws Exception { 
     String thisPackage = Test1.class.getPackage().getName(); 
     String className = thisPackage + ".Formula"; //javaapplication4.Formula 
     String body = "package " + thisPackage + "; " 
        + "public class Formula {   " 
        + " double calculateFails() { " 
        + "  return 123;   " 
        + " }       " 
        + " public double calculate() {" 
        + "  return 123;   " 
        + " }       " 
        + "}        "; 

     compile(className, body, TEMP_PATH); 
     Class<?> formulaClass = loadClass(className, TEMP_PATH); 

     Method calculate = formulaClass.getDeclaredMethod("calculate"); 
     double value = (double) calculate.invoke(formulaClass.newInstance()); 
     //next line prints 123 
     System.out.println("value = " + value); 

     Method calculateFails = formulaClass.getDeclaredMethod("calculateFails"); 
     //next line throws exception: 
     double valueFails = (double) calculateFails.invoke(formulaClass.newInstance()); 
     System.out.println("valueFails = " + valueFails); 
    } 

    private static Class<?> loadClass(String className, Path path) throws Exception { 
     URLClassLoader loader = new URLClassLoader(new URL[]{path.toUri().toURL()}, Test1.class.getClassLoader()); 
     return loader.loadClass(className); 
    } 

    private static void compile(String className, String body, Path path) throws Exception { 
     List<JavaSourceFromString> sourceCode = Arrays.asList(new JavaSourceFromString(className, body)); 

     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
     StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); 
     fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(path.toFile())); 
     boolean ok = compiler.getTask(null, fileManager, null, null, null, sourceCode).call(); 

     System.out.println("compilation ok = " + ok); 
    } 

    public static class JavaSourceFromString extends SimpleJavaFileObject { 
     final String code; 

     JavaSourceFromString(String name, String code) { 
      super(URI.create("string:///" + name.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension), 
        JavaFileObject.Kind.SOURCE); 
      this.code = code; 
     } 

     @Override 
     public CharSequence getCharContent(boolean ignoreEncodingErrors) { 
      return code; 
     } 
    } 
} 
+0

क्या आपने 'URLClassLoader.newInstance()' का उपयोग करने का प्रयास किया है? (संपादित करें: शायद कोई फर्क नहीं पड़ता) – fge

+0

@fge वही व्यवहार – assylias

+2

आपका कोड मेरे लिए संकलित नहीं हुआ था, मुझे हर जगह 'डबल' से 'डबल'' (ओरेकल जेडीके 1.7u10) – fge

उत्तर

6

रनटाइम पर एक वर्ग दोनों अपनी पूरी तरह से योग्य नाम और इसके classloader द्वारा पहचाना जाता है।

उदाहरण के लिए, जब आप दो Class<T> समानता के लिए ऑब्जेक्ट्स का परीक्षण करते हैं, यदि उनके पास समान कैनोलिक नाम है लेकिन विभिन्न क्लासलोडर्स से लोड किए गए हैं, तो वे बराबर नहीं होंगे।

एक ही पैकेज से संबंधित दो वर्गों के लिए (और बदले में पैकेज-निजी विधियों तक पहुंचने में सक्षम होने के लिए), उन्हें उसी क्लासलोडर से भी लोड किया जाना चाहिए, जो यहां नहीं है। वास्तव में Test1 सिस्टम क्लासलोडर द्वारा लोड किया गया है, जबकि फॉर्मूला को URLClassLoader द्वारा loadClass() के अंदर बनाया गया है।

यदि आप Test1 लोड करने के लिए अपने URLClassLoader के लिए पैरेंट लोडर निर्दिष्ट करते हैं, तो अभी भी दो अलग-अलग लोडर का उपयोग किया जाता है (आप इसे लोडर्स समानता पर जोर देकर देख सकते हैं)।

मुझे नहीं लगता कि (यदि आप एक अच्छी तरह से ज्ञात पथ का उपयोग और CLASSPATH पर डाल दिया होगा) आप Formula वर्ग एक ही Test1 classloader द्वारा लोड कर सकते हैं, लेकिन मैं विपरीत करने के लिए एक रास्ता मिल गया : सूत्र लोड करने के लिए उपयोग किए गए क्लासलोडर में Test1 का एक और उदाहरण लोड करना।

class Test1 { 

    public static void main(String... args) { 
    loadClass(formula); 
    } 

    static void loadClass(location) { 
    ClassLoader loader = new ClassLoader(); 
    Class formula = loader.load(location); 
    Class test1 = loader.load(Test1); 
    // ... 
    Method compute = test1.getMethod("compute"); 
    compute.invoke(test1, formula); 
    } 

    static void compute(formula) { 
    print formula; 
    } 
} 

यहाँ pastebin है: यह स्यूडोकोड में लेआउट है। कुछ नोट्स: मैंने ऊपर सूचीबद्ध समस्या से बचने के लिए URLClassLoader के लिए null अभिभावक निर्दिष्ट किया है, और मैंने उद्देश्य प्राप्त करने के लिए तारों का उपयोग किया - लेकिन यह नहीं पता कि यह दृष्टिकोण अन्य तैनाती परिदृश्यों में कितना मजबूत हो सकता है। इसके अलावा, URLCLassLoader मैं केवल खोजों दो निर्देशिका में वर्ग परिभाषाओं को खोजने के लिए इस्तेमाल किया, नहीं सभी प्रविष्टियों CLASSPATH में सूचीबद्ध

+0

मैं अपनी कक्षा को मुख्य श्रेणी लोडर में कैसे लोड कर सकता हूं (कक्षा है संकलन समय पर उपलब्ध नहीं है इसलिए मुझे कक्षा फ़ाइल "डाउनलोड" करने की आवश्यकता है)? – assylias

+0

@assylias यह नहीं पता कि आप उस कार्यक्रम में क्या कर सकते हैं, जब तक कि TMP_PATH अच्छी तरह से ज्ञात न हो और प्रोग्राम शुरू होने से पहले क्लासस्पैट में जोड़ा जाए। आप नए लोडर (पैकेज-प्राइवेट सदस्यों का उपयोग न करने के छोटे समाधान के बगल में) 'टेस्ट 1' लोड करने के विपरीत, कोशिश कर सकते हैं – Raffaele

+1

@ वासिलियास यदि आप एक निश्चित निर्देशिका (उदाहरण के लिए सी:/temp /) को शामिल कर सकते हैं तो यह पर्याप्त हो सकता है क्लासपाथ, जब आप इसे लोड करने का प्रयास करते हैं तो क्लासलोडर कक्षा को तब पढ़ेगा। कम से कम UrlClassLoader यह करता है। बस सुनिश्चित करें कि आप पैकेज संरचना (javaapplication4) के अनुसार उपनिर्देशिका में सहेजते हैं। – gaborsch

5

जवाब है:

sun.reflect.Reflection पैकेज में एक विधि isSameClassPackage (वास्तविक हस्ताक्षर कहा जाता है private static boolean isSameClassPackage(ClassLoader arg0, String arg1, ClassLoader arg2, String arg3); है)। यह विधि यह तय करने के लिए ज़िम्मेदार है कि दो वर्ग एक ही पैकेज के हैं या नहीं।

पहली बार यह जांच कर रही है कि यह तर्क है कि यह arg0 और arg2, (दो क्लासलोडर) की तुलना करता है यदि वे अलग हैं, तो यह झूठा हो जाता है।

तो, यदि आप दो वर्गों के लिए अलग-अलग क्लासलोडर्स का उपयोग करते हैं, तो यह मेल नहीं खाएगा।

संपादित करें: (अनुरोध पर) पूरा कॉल श्रृंखला है:

Method.invoke() 
Method.checkAccess() -- fallback to the real check 
Method.slowCheckMemberAccess() -- first thing to do to call 
Reflection.ensureMemberAccess() -- check some nulls, then 
Reflection.verifyMemberAccess() -- if public, it,'s OK, otherwise check further 
Reflection.isSameClassPackage(Class, Class) -- get the class loaders of 2 classes 
Reflection.isSameClassPackage(ClassLoader, String, ClassLoader, String) 
+0

दिलचस्प है, लेकिन क्या आप "कॉल चेन" प्रिंट करना चाहते हैं (यानी, हम इस विधि में '.invoke()' से कैसे प्राप्त करते हैं)? – fge

+0

सिवाय इसके कि यह एक कार्यान्वयन विस्तार नहीं है, बल्कि एक भाषा डिजाइन पसंद है।असिलियास ने प्रासंगिक जेएलएस को लिखा, इसलिए वास्तविक श्रृंखला अप्रासंगिक है क्योंकि यह वास्तव में अपेक्षित व्यवहार – Raffaele

+0

@Raffaele यह सच है, लेकिन कोड में खोदना हमेशा दिलचस्प होता है, यह जानने के लिए कि यह वास्तव में क्यों काम कर रहा है। वैसे भी, जिस कॉल श्रृंखला पर मैंने स्केच किया था वह मेरे जेडीके 7 के लिए सटीक था, और अन्य कार्यान्वयन से भिन्न हो सकता है। – gaborsch

2

मैं JVM specification 5.4.4 (जोर मेरा) में स्पष्टीकरण पाया:

एक क्षेत्र या विधि आर के लिए सुलभ है एक वर्ग या इंटरफ़ेस डी अगर और केवल निम्न स्थितियों में से कोई भी सत्य है:

  • [...]
  • आर या तो संरक्षित या डिफ़ॉल्ट एक्सेस (कि, न तो सार्वजनिक है और न ही संरक्षित है और न ही निजी है) है, और एक ही क्रम पैकेज में एक वर्ग द्वारा घोषित किया जाता है डी

और क्रम के रूप में पैकेज specs #5.3 में परिभाषित किया गया है:

कक्षा या अंतरफलक के क्रम पैकेज पैकेज का नाम और वर्ग लोडर वर्ग या अंतरफलक के को परिभाषित करने से निर्धारित होता है।

नीचे पंक्ति: यह अपेक्षित व्यवहार है।

+0

का उलटा स्टैक ट्रेस है जो मैंने अपने जवाब में कहा था। हालांकि मुझे यह काम करने के लिए [एक समाधान] (http://pastebin.com/eN5DyBrC) मिला। – Raffaele

+0

@Raffaele हाँ मुझे लगता है कि मैंने जो कहा है उसके बीच का लिंक नहीं बनाया है और तथ्य यह है कि पैकेज (क्लास नहीं) को क्लासलोडर द्वारा भी परिभाषित किया गया है। आपके सुझाव के लिए धन्यवाद - इसे अभी जांचें। – assylias

1

Test1.class

Class<?> formulaClass = Class.forName(className); 

यह आपकी समस्या का समाधान होगा के रूप में जावा classpath को c:\temp जोड़े और एक ही classloader साथ Formula.class लोड।

+0

धन्यवाद, मैंने अंत में क्या किया। – assylias