वसंत: संदर्भ की तलाश में

कुछ महीने पहले, जेवीएम पर लोडिंग कक्षाओं पर एक विस्तृत पोस्ट मेरी प्रोफाइल में प्रकाशित हुई थी इस बात के बाद, मेरे सहयोगियों ने एक अच्छा सवाल पूछा: वसंत विन्यास को पार्स करने के लिए किस तंत्र का उपयोग करता है और यह संदर्भ से कक्षाओं को कैसे लोड करता है?




वसंत स्रोत डिबग के कई घंटों के बाद, मेरे सहकर्मी ने प्रयोगात्मक रूप से बहुत ही सरल और समझने योग्य सत्य पर विचार किया।

सिद्धांत की बिट


तुरंत निर्धारित करें कि ApplicationContext एक स्प्रिंग एप्लिकेशन में मुख्य इंटरफ़ेस है जो एप्लिकेशन कॉन्फ़िगरेशन जानकारी प्रदान करता है।

सीधे प्रदर्शन के लिए आगे बढ़ने से पहले, चलिए ApplicationContext बनाने में शामिल कदमों पर एक नज़र डालते हैं :



इस पोस्ट में हम पहले चरण का विश्लेषण करेंगे, क्योंकि हम कॉन्फ़िगरेशन पढ़ने और बीनडेफिनिशन बनाने में रुचि रखते हैं।

बीनडिफिनिशन एक इंटरफेस है जो एक बीन, उसके गुणों, निर्माता तर्क और अन्य मेटा-जानकारी का वर्णन करता है।

फलियों के विन्यास के संबंध में, वसंत में 4 विन्यास विधियां हैं:

  1. Xml कॉन्फ़िगरेशन - ClassPathXmlApplicationContext ("reference.xml");
  2. Groovy कॉन्फ़िगरेशन - GenericGroovyApplicationContext ("reference.groovy");
  3. स्कैनिंग के लिए पैकेज को इंगित करने वाले एनोटेशन के माध्यम से कॉन्फ़िगरेशन - एनोटेशनकॉन्फ़िगैप्लेशन कॉन्टेक्स्ट ("package.name");
  4. JavaConfig - @Configuration - AnnotationConfigApplicationContext (JavaConfig.class) के साथ चिह्नित वर्ग (या वर्ग सरणी) को इंगित करने वाले एनोटेशन के माध्यम से कॉन्फ़िगरेशन।

Xml कॉन्फ़िगरेशन


हम आधार के रूप में एक साधारण परियोजना लेते हैं:

public class SpringContextTest{
   private static String classFilter = "film.";
   
   public static void main(String[] args){
        
         printLoadedClasses(classFilter);
         /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
            All - 5 : 0 - Filtered      /*
        doSomething(MainCharacter.num); doSomething(FilmMaker.class);
        printLoadedClasses(classFilter);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
            All - 7 : 2 - Filtered     /*

यहां आपको थोड़ा समझाना चाहिए कि क्या तरीके और क्या उपयोग किए जाते हैं:

  • PrintLoadedClasses (स्ट्रिंग ... फ़िल्टर) - विधि लोड करने वाले और लोड किए गए JVM वर्गों के नाम को कंसोल में प्रिंट करता है जो एक पैरामीटर के रूप में पारित पैकेज से होता है। इसके अतिरिक्त, सभी भरी हुई कक्षाओं की संख्या के बारे में जानकारी है;
  • doSomething (ऑब्जेक्ट o) एक ऐसी विधि है जो आदिम कार्य करती है, लेकिन संकलन के दौरान अनुकूलन के दौरान उल्लिखित वर्गों को बाहर करने की अनुमति नहीं देती है।

हम स्प्रिंग प्रोजेक्ट से जुड़ते हैं (बाद में, स्प्रिंग 4 परीक्षण विषय के रूप में कार्य करता है):

11 public class SpringContextTest{
12    private static String calssFilter = "film.";
13    
14    public static void main(String[] args){
15        
16        printLoadedClasses(classFilter);
17       /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
18           All - 5 : 0 - Filtered      /*
19        doSomething(MainCharacter.num); doSomething(FilmMaker.class);
20        printLoadedClasses(classFilter);
21        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
22               class film.MainCharacter
23               class film.FilmMaker
24               All - 7 : 2 - Filtered   /*
25        ApplicationContext context = new ClassPathXmlApplicationContext(
26                  configLocation: "applicationContext.xml");
27        printLoadedClasses(classFilter);

लाइन 25 Xml कॉन्फ़िगरेशन के माध्यम से ApplicationContext की घोषणा और आरंभीकरण है

कॉन्फ़िगरेशन xml फ़ाइल निम्नानुसार है:

<beans xmlns = "http://www.spingframework.org/schema/beans" xmlns:xsi = "http..."
        <bean id = "villain" class = "film.Villain" lazy-init= "true">
                <property name = "name" value = "Vasily"/>
        </bean>
</beans> 

बीन को कॉन्फ़िगर करते समय, हम वास्तव में मौजूदा वर्ग को निर्दिष्ट करते हैं। निर्दिष्ट संपत्ति पर ध्यान दें lazy-init = "true" : इस मामले में, बिन को संदर्भ से अनुरोध करने के बाद ही बनाया जाएगा।

हम देखते हैं कि संदर्भ को बढ़ाते समय स्प्रिंग, विन्यास फाइल में घोषित वर्गों के साथ स्थिति को कैसे साफ करेगा:

public class SpringContextTest {
    private static String classFilter = "film.";
    
    public static void main(String[] args) {
        
           printLoadedClasses(classFilter);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
           All - 5 : 0 - Filtered      /*
        doSomething(MainCharacther.num); doSomething(FilmMaker.class);
        printLoadedClasses(classFilter);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
            All - 7 : 2 - Filtered     /*
        ApplicationContext context = new ClassPathXmlApplicationContext(
                  configLocation: "applicationContext.xml");
        printLoadedClasses(classFilter);
        /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
               class film.Villain

            All - 343 : 3- Filtered     /*

आइए हम एक्सएमएल कॉन्फ़िगरेशन के विवरण की जांच करें:

- कॉन्फ़िगरेशन फ़ाइल को पढ़ना कक्षा XmlBeanDefinitionReader है , जो इंटरफ़ेस BeanDefinitionReader को लागू करता है ;

- इनपुट पर XmlBeanDefinitionReader एक InputStream प्राप्त करता है और DefaultDocumentLoader के माध्यम से दस्तावेज़ लोड करता है :

Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);

- उसके बाद, इस दस्तावेज़ के प्रत्येक तत्व को संसाधित किया जाता है और, यदि यह एक बिन है, तो भरे हुए डेटा (आईडी, नाम, वर्ग, उपनाम, init- विधि, विनाश-विधि, आदि) के आधार पर BeanDefinition बनाया जाता है:

} else if (delegate.nodeNameEquals(ele, "bean")) {
    this.processBeanDefinition(ele, delegate);

- प्रत्येक बीनडिफाइनमेंट को एक मानचित्र में रखा जाता है, जिसे DefaultListableBeanFactory वर्ग में संग्रहीत किया जाता है:

this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);

कोड में, नक्शा इस तरह दिखता है:

/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);

अब एक ही विन्यास फाइल में, के साथ एक और सेम घोषणा जोड़ने film.BadVillain वर्ग :

<beans xmlns = "http://www.spingframework.org/schema/beans" xmlns:xsi = "http..."
        <bean id = "goodVillain" class = "film.Villain" lazy-init= "true">
                <property name = "name" value = "Good Vasily"/>
        </bean>
        <bean id = "badVillain" class = "film.BadVillain" lazy-init= "true">
                <property name = "name" value = "Bad Vasily"/>
        </bean>

यदि आप बीनडिफेनिशन नाम और लोड की गई कक्षाओं की सूची प्रिंट करते हैं तो हम देखेंगे कि क्या होता है :

ApplicationContext context = new ClassPathXmlApplicationContext(
        configLocation: "applicationContext.xml");
System.out.println(Arrays.asList(context.getBeanDefinitionNames()));
        
printLoadedClasses(calssFilter);

इस तथ्य के बावजूद कि फिल्म.बेडविलैन विन्यास फ़ाइल में निर्दिष्ट वर्ग मौजूद नहीं है, वसंत त्रुटियों के बिना काम करता है:

ApplicationContext context = new ClassPathXmlApplicationContext(
        configLocation: "applicationContext.xml");
System.out.println(Arrays.asList(context.getBeanDefinitionNames()));
//  [goodVillain, badVillain]
printLoadedClasses(calssFilter);
/* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
               class film.MainCharacter
               class film.FilmMaker
               class film.Villain
    All - 343 : 3- Filtered   /*

BeanDefenitionNames सूची 2 तत्व शामिल हैं;
अर्थात्, हमारी फ़ाइल में कॉन्फ़िगर किए गए 2 बीनडिफ़िनिशन बनाए गए थे।

दोनों डिब्बे के विन्यास अनिवार्य रूप से समान हैं। लेकिन, जब मौजूदा वर्ग लोड हुआ, तो कोई समस्या नहीं हुई। जिससे हम यह निष्कर्ष निकाल सकते हैं कि एक गैर-वर्ग वर्ग को लोड करने का एक प्रयास भी था, लेकिन एक असफल प्रयास कुछ भी नहीं तोड़ पाया।

आइए उनके नाम से फलियों को खुद पाने की कोशिश करें:

ApplicationContext context = new ClassPathXmlApplicationContext(
        configLocation: "applicationContext.xml");
System.out.println(Arrays.asList(context.getBeanDefinitionNames()));
//  [goodVillain, badVillain]
System.out.println(context.getBean( name: "goodVillain"));

System.out.println(context.getBean( name: "badVillain"));

हम निम्नलिखित प्राप्त करते हैं:



यदि पहले मामले में एक वैध बीन प्राप्त हुआ था, तो दूसरे मामले में अपवाद आ गया।

स्टैक ट्रेस पर ध्यान दें: वर्गों के आस्थगित लोड ने काम किया। सभी वर्ग लोडर को उस वर्ग को खोजने के प्रयास में क्रॉल किया जाता है जिसे वे पहले से लोड किए गए लोगों के बीच देख रहे हैं। और वांछित वर्ग नहीं पाए जाने के बाद, Utils.forName विधि को कॉल करके, नाम के द्वारा एक नॉनटेक्स्टेंट क्लास खोजने का प्रयास किया जाता है, जिससे एक प्राकृतिक त्रुटि हुई।

संदर्भ बढ़ाते समय, केवल एक वर्ग लोड किया गया था, जबकि एक भी नहीं फाइल लोड करने के प्रयास में त्रुटि नहीं हुई। यह क्यों हुआ?

ऐसा इसलिए है क्योंकि हमने आलसी-इन पंजीकृत किया है : सचऔर बीन का एक उदाहरण बनाने के लिए स्प्रिंग को मना किया, जहां पहले प्राप्त अपवाद उत्पन्न होता है। यदि आप इस संपत्ति को कॉन्फ़िगरेशन से हटा देते हैं या इसके मूल्य को आलसी-इनिट में बदल देते हैं : गलत है , तो ऊपर वर्णित त्रुटि भी दुर्घटनाग्रस्त हो जाएगी, लेकिन आवेदन को अनदेखा नहीं किया जाएगा। हमारे मामले में, संदर्भ को प्रारंभ किया गया था, लेकिन हम बीन का एक उदाहरण नहीं बना सके, क्योंकि निर्दिष्ट वर्ग नहीं मिला।

ग्रूवी विन्यास


ग्रूवी फ़ाइल का उपयोग करते हुए एक संदर्भ को कॉन्फ़िगर करते समय, आपको एक GenericGroovyApplicationContext उत्पन्न करना होगा , जो इनपुट के रूप में संदर्भ कॉन्फ़िगरेशन के साथ एक स्ट्रिंग प्राप्त करता है। इस मामले में, GroovyBeanDefinitionReader वर्ग संदर्भ को पढ़ने में लगा हुआ है यह कॉन्फ़िगरेशन अनिवार्य रूप से एक्सएमएल के समान काम करता है, केवल ग्रूवी फाइलों के साथ। इसके अलावा, GroovyApplicationContext Xml फ़ाइल के साथ अच्छी तरह से काम करता है।

एक साधारण ग्रूवी कॉन्फ़िगरेशन फ़ाइल का एक उदाहरण:

beans {
    goodOperator(film.Operator){bean - >
            bean.lazyInit = 'true' >
            name = 'Good Oleg' 
         }
    badOperator(film.BadOperator){bean - >
            bean.lazyInit = 'true' >
            name = 'Bad Oleg' / >
        }
  }

हम Xml के साथ भी ऐसा ही करने का प्रयास करते हैं:



त्रुटि तुरंत दुर्घटनाग्रस्त हो जाती है: Groovy, Xml की तरह, BeanDefenitions बनाता है, लेकिन इस मामले में पोस्टप्रोसेसर तुरंत एक त्रुटि देता है।

स्कैन या JavaConfig के लिए संकेत पैकेज के माध्यम से कॉन्फ़िगरेशन


यह कॉन्फ़िगरेशन पिछले दो से अलग है। एनोटेशन के माध्यम से कॉन्फ़िगरेशन 2 विकल्पों का उपयोग करता है: जावाकोनफिग और कक्षाओं पर एनोटेशन।

यहां एक ही संदर्भ का उपयोग किया गया है: एनोटेशनकॉन्फिग एपलिकेशनकोटेक्स्ट ("पैकेज" /JavaConfig.class)यह इस बात पर निर्भर करता है कि कंस्ट्रक्टर को क्या दिया गया था। AnnotationConfigApplicationContext

के संदर्भ में 2 निजी क्षेत्र हैं:

  • निजी अंतिम AnnotatedBeanDefinitionReader रीडर (JavaConfig के साथ काम करता है);
  • निजी अंतिम ClassPathBeanDefinitionScanner scanne r (पैकेट को स्कैन करता है)।

एनोटेटबिनडिफिनिशनरीडर की ख़ासियत यह है कि यह कई चरणों में काम करता है:

  1. आगे पार्सिंग के लिए सभी @Configuration फ़ाइलों का पंजीकरण ;
  2. एक विशेष बीनफैक्टरीपोस्टप्रोसेसो आर, जिसका नाम है बीनडेफिनेशनरेजिस्ट्रीपोस्टप्रोसेसर , जो कॉन्फ़िगरेशनक्लासपैसर क्लास का उपयोग करके रजिस्टर करता है , JavaConfig को पार्स करता है और एक बीनडेफिनेशन बनाता है

एक साधारण उदाहरण पर विचार करें:

@Configuration
public class JavaConfig {
    
    @Bean
    @Lazy
    public MainCharacter mainCharacter(){
        MainCharacter mainCharacter = new MainCharacter();
        mainCharacter.name = "Patric";
        return mainCharacter;        
   }
}

public static void main(String[] args) {

     ApplicationContext javaConfigContext = 
               new AnnotationConfigApplicationContext(JavaConfig.class);
     for (String str : javaConfigContext.getBeanDefinitionNames()){
          System.out.println(str);
     }
     printLoadedClasses(classFilter);

हम सबसे सरल बिन के साथ एक कॉन्फ़िगरेशन फ़ाइल बनाते हैं। हम देखते हैं कि क्या लोड होगा:



यदि एक्सएमएल और ग्रूवी के मामले में, जैसा कि घोषणा की गई थी, तो कई बीनडिफाइनमेंट लोड किए गए थे, तो इस मामले में, संदर्भ को बढ़ाने की प्रक्रिया में घोषित और अतिरिक्त बीनडिफाइनमेंट दोनों को लोड किया जाता है। JavaConfig के माध्यम से कार्यान्वयन के मामले में, सभी कक्षाएं तुरंत लोड होती हैं, जिसमें JavaConfig का वर्ग भी शामिल है, क्योंकि यह स्वयं एक सेम है।

एक अन्य बिंदु: Xml और ग्रूवी विन्यास के मामले में, 343 फाइलें अपलोड की गईं, यहां 631 अतिरिक्त फाइलों का "भारी" लोड हुआ। ClassPathBeanDefinitionScanner

काम के चरण :

  • निर्दिष्ट पैकेज स्कैनिंग के लिए फ़ाइलों की सूची निर्धारित करता है। सभी फाइलें निर्देशिकाओं में आती हैं;
  • , InputStream org.springframework.asm.ClassReader.class;
  • 3- , org.springframework.core.type.filter.AnnotationTypeFilter. Spring , Component , Component;
  • , BeanDefinition.

एनोटेशन के साथ काम करने के सभी "जादू", जैसा कि एक्सएमएल और ग्रूवी के साथ होता है, स्प्रिंगफ्रैमवर्क.स्मैश पैकेज से ClassReader.class वर्ग में ठीक-ठीक निहित है इस पाठक की ख़ासियत यह है कि यह बाइटकोड के साथ काम कर सकता है। यही है, पाठक बायोटेक से एक इनपुटस्ट्रीम लेता है, इसे स्कैन करता है और वहां एनोटेशन की तलाश करता है। एक साधारण उदाहरण का उपयोग करके स्कैनर पर विचार करें। संबंधित कक्षाओं के लिए खोज करने के लिए अपना एनोटेशन बनाएं:





import org.springframework.stereotype.Component
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyBeanLoader{
       String value() default "";

हम 2 कक्षाएं बनाते हैं: एक मानक घटक एनोटेशन के साथ , दूसरा कस्टम एनोटेशन के साथ:

@Component 
public class MainCharacter {
      public static int num = 1;
      @Value("Silence")
      public String name;
      public MainCharacter() { }

MyBeanLoader("makerFilm")
@Lazy 
public class FilmMaker {
      public static int staticInt = 1;
      @Value("Silence")
      public String filmName;
      public FilmMaker(){}

नतीजतन, हम इन वर्गों और सफलतापूर्वक भरी हुई कक्षाओं के लिए उत्पन्न बीनडिफाइनमेंट प्राप्त करते हैं।

ApplicationContext annotationConfigContext =
       new AnnotationConfigApplicationContext(...basePackages: "film");
for (String str : annotationConfigContext.getBeanDefinitionNames()){
     System.out.println(str);
}
printLoadedClasses(classFilter);



निष्कर्ष


पूर्वगामी से, प्रस्तुत प्रश्नों का उत्तर निम्नानुसार दिया जा सकता है:

  1. Spring ?

    , . BeanDefinition, : , BeanDefinition’ . BeanDefinition, ..
  2. Spring ?

    Java: , , , .

पीएस मुझे उम्मीद है कि इस पोस्ट में मैं "गोपनीयता के घूंघट को खोलने" में सक्षम था और विस्तार से दिखाता हूं कि पहले चरण में स्प्रिंगिंग संदर्भ का गठन कैसे होता है। यह पता चला है कि सब कुछ इतना "डरावना" नहीं है। लेकिन यह एक बड़े ढांचे का केवल एक छोटा सा हिस्सा है, जिसका मतलब है कि अभी भी बहुत कुछ नया और दिलचस्प है।

All Articles