2011-09-15 12 views
7

चलो कहते हैं कि मैं एक अमूर्त वर्ग करते हैं:रनटाइम पर सार तरीकों को लागू करना?

abstract class Foo extends Bar { 

    public abstract int foo(); 

} 

कि मैं कार्यावधि में विस्तार करना चाहते एक कक्षा वस्तु बनाने के लिए। आशा होगा मैं एक गतिशील रूप से उत्पन्न वर्ग हो सकता है:

class FooImpl extends Foo { 

    @Override 
    public int foo() { 
     return 5; 
    } 

} 

है कि एक वर्ग वस्तु का प्रतिनिधित्व करती जाएगा और मैं तो के नए उदाहरण बना करने के लिए प्रतिबिंब इस्तेमाल कर सकते हैं। कुंजी यह है कि मैं रनटाइम पर विधि foo() के वापसी मूल्य का निर्णय लेना चाहता हूं। मेरा विचार कक्षा के लिए बाइटकोड बनाने के लिए एएसएम का उपयोग करना है और फिर क्लास को परिभाषित करने के लिए क्लासलोडर ऑब्जेक्ट पर प्रतिबिंब का उपयोग करना है।

एएसएम का उपयोग कर रहा है और फिर जेनरेट बाइट्स पर क्लासलोडर # defineClass विधि का प्रतिबिंब गैर-हार्डकोडेड मानों के साथ रनटाइम पर सार विधियों को लागू करने का सबसे अच्छा तरीका है?

यदि हां, तो मैं ऐसा करने के लिए कैसे जाउंगा। मेरा आंत ASMifierClassVisitor का उपयोग करना है, लेकिन मुझे ऐसा करने की सटीक विधि पर बिल्कुल यकीन नहीं है। मुझे पता है कि अगर सब कुछ विफल हो जाता है तो मैं एक विशिष्ट वर्ग को परिभाषित करने के लिए आवश्यक JVM निर्देशों को मैन्युअल रूप से देख सकता हूं लेकिन मुझे लगता है कि एक आसान तरीका होना चाहिए।

यदि नहीं, तो सबसे अच्छा तरीका क्या है और मैं सबसे अच्छा तरीका उपयोग करने के बारे में कैसे जाउंगा?

संपादित करें: मैंने सभी उत्तरों की जांच की और मैंने फैसला किया कि उनमें से कोई भी बिल्कुल ठीक नहीं था जिसे मैं ढूंढ रहा था। मैं एएसएम के बारे में जो बात कर रहा था उसके बारे में एक छोटा सा कार्यान्वयन करना समाप्त कर दिया। मुझे लगा कि मुझे इसे यहां पोस्ट करना चाहिए:

import org.objectweb.asm.*; 

import java.io.File; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.PrintWriter; 
import java.lang.reflect.Field; 
import java.lang.reflect.Method; 
import java.util.HashMap; 

/** 
* Created by IntelliJ IDEA. 
* User: Matt 
* Date: 9/17/11 
* Time: 12:42 PM 
*/ 
public class OverrideClassAdapter extends ClassAdapter { 

    private final HashMap<String, Object> code; 
    private final String className; 

    private final ClassWriter writer; 

    private String superName; 

    public OverrideClassAdapter(ClassWriter writer, String className, Queue<int[]> constructorCode, HashMap<String, Object> code) { 
     super(writer); 
     this.writer = writer; 
     this.className = className; 
     this.code = code; 
    } 

    @Override 
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 
     this.superName = name; 
     if((access & Opcodes.ACC_ABSTRACT) != 0) 
      access &= ~Opcodes.ACC_ABSTRACT; 
     if((access & Opcodes.ACC_INTERFACE) != 0) 
      access &= ~Opcodes.ACC_INTERFACE; 
     cv.visit(version, access, className, signature, name, null); 
    } 

    @Override 
    public void visitSource(String source, String debug) { 
    } 

    @Override 
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 
     boolean isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0; 
     if(isAbstract) 
      access &= ~Opcodes.ACC_ABSTRACT; 
     MethodWriter mw = (MethodWriter) cv.visitMethod(access, name, desc, signature, exceptions); 
     Object value = code.get(name); 
     if(isAbstract || value != null) { 
      if(value instanceof BytecodeValue) { 
       BytecodeValue returnableValue = (BytecodeValue) value; 
       int[] byteCode = new int[returnableValue.getValueCode().length + 1]; 
       System.arraycopy(returnableValue.getValueCode(), 0, byteCode, 0, returnableValue.getValueCode().length); 
       if(returnableValue.getValueCode().length > 1 && returnableValue.getValueCode()[1] == 0) { 
        byteCode[1] = writer.newConst(returnableValue.getValue()); 
       } 
       byteCode[byteCode.length - 1] = returnableValue.getReturnCode(); 
       value = byteCode; 
      } 
      return new OverrideMethodAdapter(mw, (int[]) value); 
     } 
     return mw; 
    } 

    private class OverrideMethodAdapter extends MethodAdapter { 

     private final int[] code; 

     private final MethodWriter writer; 

     public OverrideMethodAdapter(MethodWriter writer, int[] code) { 
      super(writer); 
      this.writer = writer; 
      this.code = code; 
     } 

     @Override 
     public void visitEnd() { 
      try { 
       Field code = MethodWriter.class.getDeclaredField("code"); 
       code.setAccessible(true); 
       ByteVector bytes = new ByteVector(); 
       for(int b : this.code) 
        bytes.putByte(b); 
       code.set(writer, bytes); 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    public static byte[] extendClassBytes(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException { 
     ClassReader cr = new ClassReader(clazz.getName()); 
     ClassWriter cw = new ClassWriter(0); 
     cr.accept(new OverrideClassAdapter(cw, className, methodImpls), ClassReader.SKIP_DEBUG); 
     cr = new ClassReader(cw.toByteArray()); 
     cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 
     cr.accept(cw, ClassReader.SKIP_DEBUG); 
     //CheckClassAdapter.verify(new org.objectweb.asm.ClassReader(cw.toByteArray()), true, new PrintWriter(System.out)); 
     /*File file = new File(className + ".class"); 
     new FileOutputStream(file).write(cw.toByteArray());*/ 
     return cw.toByteArray(); 
    } 


    public static Class extendClass(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException { 
     return defineClass(extendClassBytes(clazz, className, methodImpls), className); 
    } 

    public static Class defineClass(byte[] code, String name) { 
     try { 
      Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); 
      method.setAccessible(true); 
      return (Class) method.invoke(Thread.currentThread().getContextClassLoader(), name, code, 0, code.length); 
     } catch (Exception e) { 
      e.printStackTrace(); 
      return null; 
     } 
    } 
} 
+0

मुझे लगता है कि आपको सिंथेटिक तरीकों को देखना पड़ सकता है। – Shahzeb

+0

रनटाइम पर 'foo' के वापसी मूल्य का निर्णय लें? एक वर्ग कॉल कैसे करेगा कि अगर यह मनमाना प्रकार देता है? –

+0

रनटाइम पर इस अर्थ में कि वर्ग उत्पन्न होने पर foo का मान निर्धारित होता है, और इसे बाद में बदलने की आवश्यकता नहीं होती है। – mburke13

उत्तर

5

आप CGLib का उपयोग करना चाहते हैं। यह जावा की गतिशील प्रॉक्सी कर सकता है लेकिन अमूर्त कक्षाओं के साथ-साथ इंटरफेस के लिए भी कर सकता है, और इसमें जावा.लंग.फ्रैलेक्ट.प्रॉक्सी के लिए भी ऐसा ही एक एपीआई है। CGLib दृश्यों के पीछे एएसएम का उपयोग करता है, लेकिन CGLib का उपयोग करके आपको सीधे बाइटकोड तैयार करना होगा।

package cglibtest; 

import java.lang.reflect.Method; 

import net.sf.cglib.proxy.Enhancer; 
import net.sf.cglib.proxy.MethodInterceptor; 
import net.sf.cglib.proxy.MethodProxy; 

public class CGLibTest 
{ 
    public static void main(String... args) 
    { 
     MyAbstract instance = (MyAbstract)Enhancer.create(MyAbstract.class, new MyInterceptor(42)); 
     System.out.println("Value from instance: " + instance.valueMethod()); 
    } 

    public static class MyInterceptor implements MethodInterceptor 
    { 
     private final Object constantValue; 

     public MyInterceptor(Object constantValue) 
     { 
      this.constantValue = constantValue; 
     } 

     @Override 
     public Object intercept(Object obj, Method method, Object[] args, 
       MethodProxy proxy) throws Throwable 
     { 
      if ("valueMethod".equals(method.getName())) 
       return(constantValue); 
      else 
       return(null); 
     } 
    } 

    public static abstract class MyAbstract 
    { 
     public abstract int valueMethod(); 
    } 
} 
+0

मैंने सीजीएलआईबी की जांच की और मैं थोड़ा खो गया हूं कि मैं जेनरेट क्लास में अमूर्त तरीकों को लागू करने के बारे में कैसे जाऊंगा। क्या आपके पास कोई दस्तावेज या पढ़ना है जिसे मैं देख सकता हूं कि क्या किया जाना चाहिए? – mburke13

+0

@ मैट ने एक उदाहरण जोड़ा, अधिक जानकारी के लिए वेबसाइट देखें (जैसे [उदाहरण] (http://cglib.sourceforge.net/howto.html) या [javadocs] (http://cglib.sourceforge.net /apidocs/index.html) – prunge

+0

उदाहरण और लिंक के लिए धन्यवाद। मैंने आपके प्रश्न को मेरे प्रश्न के लिए सबसे अच्छा चुना है। जिज्ञासा से, विधि इंटरसेप्टर का उपयोग अमूर्त तरीकों को लागू करने का सबसे अच्छा/एकमात्र तरीका है? हालांकि यह काम पूरा हो जाता है मैं सोच रहा हूं कि कक्षाओं के साथ क्या होता है जो 4 या 5 अमूर्त तरीकों से कहता है जब विधि के कोड को निष्पादित करने के लिए प्रत्येक विधि का नाम चेक किया जाना चाहिए। ऐसा लगता है कि यह एक प्रकार का गुंजाइश है (यानी अगर (abstractMethod1.equals (method.getName()) doThis(); अन्यथा अगर (abstractMethod2.equals (method.getName()) doThis1(); आदि – mburke13

0

हां, उस दृष्टिकोण को काम करना चाहिए। लेकिन यदि आप बहुत सारी कक्षा पीढ़ी करते हैं तो यह महंगा होगा। (हम शायद बाइटकोड फ़ाइल उत्पन्न करने के लिए सैकड़ों हजारों निर्देशों के बारे में बात कर रहे हैं और फिर इसे लोड कर सकते हैं। और फिर लोड होने पर कक्षा का प्रतिनिधित्व करने की स्मृति की आवश्यकता होती है।)

अन्य दृष्टिकोण (भी महंगा) उत्पन्न करना है स्रोत कोड और संकलन और इसे रनटाइम पर लोड करें।

अंत में, आपको किसी प्रकार के दुभाषिया का उपयोग करके तालिका-संचालित वस्तुओं को लागू करने या इसे कार्यान्वित करने के दृष्टिकोण पर विचार करना चाहिए। यदि आप वास्तव में को विभिन्न कक्षाएं रखने के लिए की आवश्यकता है, तो आप इसे जावा की गतिशील प्रॉक्सी क्लास तंत्र का उपयोग करके लपेट सकते हैं; जैसे देखें java.lang.reflect.Proxy

+0

मैं प्रॉक्सी विकल्प देख रहा था लेकिन मैं इस तथ्य से सीमित हूं कि सार तत्वों को एक अमूर्त वर्ग में घोषित किया जाना चाहिए, न कि एक इंटरफ़ेस (मुझे कक्षा का विस्तार करने के लिए अमूर्त वर्ग की आवश्यकता है)। कक्षाओं की पीढ़ी शुरूआत में की जाएगी और 10-100 से अधिक कक्षाएं नहीं होंगी। – mburke13

+0

@ मैट - आप अपनी अमूर्त कक्षा को दोबारा कर सकते हैं ताकि यह एक इंटरफेस और एक अमूर्त वर्ग है जो इसे लागू करता है, फिर प्रॉक्सी के अंदर अमूर्त वर्ग का उदाहरण लपेटें। कक्षाओं की उस संख्या के लिए, कोड जनरेशन दृष्टिकोण बहुत महंगा नहीं होना चाहिए, लेकिन यह समस्या का समाधान करने के लिए एक जटिल और (संभावित रूप से) नाजुक तरीका है ... यदि उत्पन्न विधियों को जटिल होने की आवश्यकता है। (और यदि वे नहीं करते हैं, तो एक टेबल-संचालित दृष्टिकोण आसान होगा।) –

1

क्या से मूल्य 5 पढ़ने से आप रोक कहना गुण है और इसे वापस लौट:

यहाँ कैसे CGLib यह करने के लिए उपयोग करने के लिए का एक उदाहरण है? यह बहुत आसान है, इसलिए मुझे लगता है कि आपके पास एक ऐसा इंट लौटने से कहीं अधिक जटिल होना चाहिए जिसे आप यहां पूरा करना चाहते हैं। मैं उपर्युक्त पदों से सहमत हूं कि रनटाइम पर कक्षाएं उत्पन्न करना बहुत महंगा होगा। यदि आप अपने व्यापार तर्क को पहले से जानते हैं, तो आप रनटाइम पर परिभाषित इंटरफेस के वांछित कार्यान्वयन को लोड करने के लिए फैक्टरी पैटर्न लागू कर सकते हैं। इस तरह जेडीबीसी पुस्तकालय काम करते हैं।

यदि आप पहले से ही व्यवसाय तर्क को नहीं जानते हैं और इसके बहुत सारे हैं, तो आपको तर्क को संसाधित करने और परिणामों को वापस अपने जावा प्रोग्राम में वापस करने के लिए शेल्फ नियम इंजन का उपयोग करने से लाभ हो सकता है। इस तर्क को नियम इंजन में बनाए रखना बहुत आसान है, विशेष रूप से यदि यह अक्सर बदल रहा है।

संबंधित मुद्दे