निर्भरता इंजेक्शन में एक विरासत परियोजना का अनुवाद। सीथ का रास्ता

मैं डार्क प्रोग्रामिंग ट्रेंड में भी योगदान दूंगा।
आप में से कई लोग दुविधा से परिचित हैं: अपनी परियोजना में डि का उपयोग करना है या नहीं।
DI पर स्विच करने के कारण:DI का उपयोग न करने के कारण:
मान लीजिए कि हमारे पास काम करने का एक बड़ा मसौदा है, एक निर्णय लिया गया है: DI का अनुवाद। डेवलपर्स अपनी क्षमता महसूस करते हैं, रक्त के रोल में मिडीक्लोरियन का स्तर।

एक कांटेदार और लंबा रास्ता आपका इंतजार करता है, मेरे युवा पडावन का।

यदि परियोजना बड़ी है और कई डेवलपर्स हैं, तो यह संभावना नहीं है कि एक पुनरावृत्ति एकल प्रतिबद्ध के साथ संभव होगी। इसलिए, हम कई बुरी प्रथाओं का उपयोग करते हैं, संक्रमण को सरल करते हैं, और फिर उनसे छुटकारा पा लेते हैं।

कहां से शुरू करें
DI के पास कोड में वास्तुशिल्प जाम खोलने की एक अद्भुत विशेषता है, इसलिए यह तैयारी के काम को करने के लिए समझ में आता है। DI अवधारणा में, सभी वर्गों को सशर्त रूप से दो श्रेणियों में विभाजित किया जा सकता है - हम उन्हें सेवाओं और डिब्बे कहेंगे। पूर्व आमतौर पर संदर्भ के भीतर एक ही प्रति में मौजूद होता है और इसे बांधा जाता है। बाद वाला संसाधित डेटा को स्वयं स्टोर करता है और अन्य बीन्स को संदर्भित कर सकता है, लेकिन सेवाओं को नहीं। कभी-कभी मिश्रित रूपांतर होते हैं:
import org.jetbrains.annotations.Nullable; public class Jedi { private long id; private String name; @Nullable private Long masterId; // fields, constructors, getters/setters, equals, hashCode, toString, etc... public long getId() { return id; } public String getName() { return name; } @Nullable public Long getMasterId() { return masterId; } @Nullable public Jedi getMaster() { if (masterId == null) { return null; } return DBJedi.getJedi(masterId); } } 



एक सभ्य जेडी गेटमास्टर विधि पूरी तरह से हटा दी जाएगी या किसी अन्य वर्ग (सेवा) में स्थानांतरित कर दी जाएगी। नतीजतन, जेडी क्लास सिर्फ एक डेटा बिन बन जाएगा। यदि किसी कारण से किसी विधि को स्थानांतरित करना अभी संभव नहीं है (उदाहरण के लिए, कोड जो रीफैक्टरिंग के लिए उपलब्ध नहीं है, उस पर निर्भर करता है), तो आप इसे अस्वीकृत घोषित कर सकते हैं और इसे अभी के लिए छोड़ सकते हैं (विकल्प के रूप में, उस संस्करण को घोषित करें जिसमें यह विधि हटा दी जाएगी, जैसा कि अमरूद डेवलपर्स करते हैं) )।
अब हम DBJedi के साथ सौदा करते हैं:
 public class DBJedi { public static Jedi getJedi(long id) { DataSource dataSource = ConnectionPools.getDataSource("jedi"); Jedi jedi; // magic return jedi; } } 
इस तरह के एक क्लासिक सिंगलटन में रीमेक बनाना तर्कसंगत है, उदाहरण के लिए, इस तरह:
 import javax.sql.DataSource; public class DBJedi { private static final DBJedi instance = new DBJedi(); private final ConnectionPools connectionPools; private DBJedi() { this.connectionPools = ConnectionPools.getInstance(); } public static DBJedi getInstance() { return instance; } public Jedi getJedi(long id) { DataSource dataSource = connectionPools.getDataSource("jedi"); Jedi jedi; // magic return jedi; } } 
नतीजतन, हमें एक अधिक सुसंगत और पठनीय कोड संरचना (एक बहुत ही विवादास्पद तथ्य, निश्चित रूप से) मिलती है। यदि आप समाप्त कर लेते हैं जो आपने शुरू किया था, तो सामान्य तौर पर, डीआई को संक्रमण मानक गाइड का उपयोग करके किया जा सकता है।
लेकिन अगर आप एक सीथ हैं, तो संभवतः कक्षाएं हैं (हमारे उदाहरण में, गेटमास्टर विधि के साथ जेडी क्लास), जो अच्छे तरीके से मानक तरीके से अनुवादित नहीं हैं।

अब आपको डीआई को खराब करने की सलाह के बारे में फिर से सोचने की जरूरत है। यदि अभी भी इच्छा बनी हुई है - जारी रखें।
उदाहरण मुख्य रूप से स्प्रिंग पर आंशिक रूप से डुप्लिकेट किए गए गुएस पर होंगे। ढांचे की पसंद के लिए - वह चुनें जिसे आप सबसे अच्छी तरह जानते हैं।

बुरा अभ्यास 1 - इंजेक्टर के लिए एक स्थिर लिंक रखें

कुछ बिंदु पर, यह सवाल होगा - सिंगलेट्स को बाहर निकालने के लिए इंजेक्टर का उदाहरण कहां मिलेगा? आइए उपयोगिता वर्ग प्राप्त करें:
 import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; import com.google.inject.Injector; // import com.google.common.base.Preconditions; // guava public class InjectorUtil { private static volatile Injector injector; public static void setInjector(@NotNull Injector injector) { // Preconditions.checkNotNull(injector); // Preconditions.checkState(InjectorUtil.injector == null, "Injector already initialized"); InjectorUtil.injector = injector; } @TestOnly public static void rewriteInjector(@NotNull Injector injector) { // Preconditions.checkNotNull(injector); InjectorUtil.injector = injector; } @Deprecated // use fair injection, Sith! @NotNull public static Injector getInjector() { // Preconditions.checkState(InjectorUtil.injector != null, "Injector not initialized"); return InjectorUtil.injector; } } 
स्प्रिंग के लिए, कोड केवल इंजेक्टर - ApplicationContext के बजाय समान होगा। या दूसरा विकल्प:
परफेक्शनिस्ट का बुरा सपना
 import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import javax.inject.Named; @Named public class ApplicationContextUtil implements ApplicationContextAware { private static volatile ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) { ApplicationContextUtil.applicationContext = applicationContext; } @Deprecated public static ApplicationContext getApplicationContext() { // Preconditions.checkState(applicationContext != null); return applicationContext; } } 

अब हमारे एकल गीतों को इस तरह से फिर से लिखा जा सकता है:
 @javax.inject.Singleton @javax.inject.Named //   Spring component-scan,  Guice   public class DBJedi { private final ConnectionPools connectionPools; @javax.inject.Inject public DBJedi(ConnectionPools connectionPools) { this.connectionPools = connectionPools; } @Deprecated public static DBJedi getInstance() { return InjectorUtil.getInjector().getInstance(DBJedi.class); } public Jedi getJedi(long id) { DataSource dataSource = connectionPools.getDataSource("jedi"); Jedi jedi; // ... return jedi; } } 
कृपया ध्यान दें कि एनोटेशन JSR-330, पैकेज javax.inject हैं। उनका उपयोग करते हुए, आप बाद में एक डीआई से दूसरे में आसानी से स्विच कर सकते हैं, आदर्श मामले में, एक विशिष्ट ढांचे से पूरी तरह से विच्छेदित (जेएसआर -330 संगतता के अधीन)। नामांकित एनोटेशन वसंत-संदर्भ में एक सेम प्रविष्टि बनाने की अनुमति नहीं देता है। xml, अगर xml कॉन्फ़िगरेशन में यह प्रविष्टि अभी भी निहित है, तो एनोटेशन को हटा दिया जाना चाहिए।

बुरा अभ्यास 2 - बीन फैक्टरी

यदि क्लास एक डेटा बिन है, लेकिन एक ही समय में सिंगलटन ऑब्जेक्ट्स को एक्सेस करता है, तो आप एक फैक्टरी क्लास बना सकते हैं:
 public class Jedi { private long id; private String name; @Nullable private Long masterId; private final DBJedi dbJedi; private Jedi(long id, String name, @Nullable masterId, DBJedi dbJedi) { this.id = id; this.name = name; this.masterId = masterId; this.dbJedi = dbJedi; } //... public long getId() { return id; } public String getName() { return name; } @Nullable public Long getMasterId() { return masterId; } @Nullable public Jedi getMaster() { if (masterId == null) { return null; } return dbJedi.getJedi(masterId); } @Singleton @Named public static class Factory { private final DBJedi dbJedi; @Inject public Factory(DBJedi dbJedi) { this.dbJedi = dbJedi; } @Deprecated // refactor Jedi class to simple bean, Sith! public Jedi create(long id, String name, @Nullable masterId) { return new Jedi(id, name, masterId, dbJedi); } } } 

बुरा अभ्यास 3 - परिपत्र निर्भरता

हमारे उदाहरण में, DBJedi और Jedi.Factory वर्गों के बीच एक परिपत्र निर्भरता का गठन किया जाता है। जब हम रनटाइम में इन ऑब्जेक्ट्स को बनाने की कोशिश करते हैं, तो हमें DI कंटेनर त्रुटि मिलती है, उदाहरण के लिए, StackOverflowError। प्रदाता इंटरफ़ेस बचाव के लिए आता है:
 import javax.inject.Singleton; import javax.inject.Named; import javax.inject.Inject; import javax.inject.Provider; import javax.sql.DataSource; @Singleton @Named public class DBJedi { private final ConnectionPools connectionPools; private final Provider<Jedi.Factory> jediFactoryProvider; @Inject public DBJedi(ConnectionPools connectionPools, Provider<Jedi.Factory> jediFactoryProvider) { this.connectionPools = connectionPools; this.jediFactoryProvider = jediFactoryProvider; } @Deprecated public static DBJedi getInstance() { return InjectorUtil.getInjector().getInstance(DBJedi.class); } public Jedi getJedi(long id) { DataSource dataSource = connectionPools.getDataSource("jedi"); // ... final Jedi.Factory jediFactory = jediFactoryProvider.get(); return jediFactory.create(id, name, masterId); } } 
यह ध्यान रखना सही है कि सामान्य घोषणाएँ परावर्तन के माध्यम से उपलब्ध नहीं हैं। गाइस एंड स्प्रिंग के लिए, वे दोनों कक्षा के बाईटकोड को पढ़ते हैं और इस प्रकार सामान्य प्रकार प्राप्त करते हैं।

लेखन परीक्षण

Testng में एक अद्भुत Guice एनोटेशन है जो कोड परीक्षण को सरल करता है। वसंत के लिए, विरूपण साक्ष्य org.springframework है: वसंत-परीक्षण।
चलो हमारी कक्षाओं के लिए एक परीक्षण करें:
 import org.testng.annotations.*; import com.google.inject.Injector; import com.google.inject.AbstractModule; @Guice(modules = JediTest.JediTestModule.class) public class JediTest { private static final long JEDI_QUI_GON_ID = 12; private static final long JEDI_OBI_WAN_KENOBI_ID = 22; @Inject private Injector injector; @Inject private DBJedi dbJedi; @BeforeClass public void setInjector() { InjectorUtil.rewriteInjector(injector); } @Test public void testJedi() { final Jedi obiWan = dbJedi.getJedi(JEDI_OBI_WAN_KENOBI_ID); final Jedi master = obiWan.getMaster(); Assert.assertEquals(master.getId(), JEDI_QUI_GON_ID); } public static class JediTestModule extends AbstractModule { @Override public void configure() { //  ConnectionPools , ..      bind(ConnectionPools.class).toInstance(new ConnectionPools("pools.properties")); } } } 

परिणाम क्या है?
और अंत में, हमारे पास दो संभावित परिणाम हैं। पहला वहां रुकना है। यह मेरी एक परियोजना में हुआ था, इसे पूरी तरह से ईमानदार डीआई में अनुवाद करना संभव नहीं था, इसकी विरासत कोड बहुत थी। मुझे लगता है कि यह स्थिति बहुतों से परिचित है। आप इसे थोड़ा सुधार सकते हैं, उदाहरण के लिए, इनजेक्टोरटील में स्थैतिक क्षेत्र को थ्रेडलोक के साथ बदलना, इस प्रकार एक ही स्थिर स्थान में विभिन्न डीआई वातावरण के साथ समवर्ती परीक्षण की समस्या को हल करना।
अधिक जानकारी
 public class InjectorUtil { private static final ThreadLocal<Injector> threadLocalInjector = new InheritableThreadLocal<Injector>(); private InjectorUtil() { } /** * Get thread local injector for current thread * * @return * @throws IllegalStateException if not set */ @NotNull public static Injector getInjector() throws IllegalStateException { final Injector Injector = threadLocalInjector.get(); if (Injector == null) { throw new IllegalStateException("Injector not set for current thread"); } return Injector; } /** * Set Injector for current thread * * @param Injector * @throws java.lang.IllegalStateException if already set */ public static void setInjector(@NotNull Injector injector) throws IllegalStateException { if (injector == null) { throw new NullPointerException(); } if (threadLocalInjector.get() != null) { throw new IllegalStateException("Injector already set for current thread"); } threadLocalInjector.set(injector); } /** * Rewrite Injector for current thread, even if already set * * @param injector * @return previous value if was set */ public static Injector rewriteInjector(@NotNull Injector injector) { if (injector == null) { throw new NullPointerException(); } final Injector prevInjector = threadLocalInjector.get(); threadLocalInjector.set(injector); return prevInjector; } /** * Remove Injector from thread local * * @return Injector if was set, else null */ public static Injector removeInjector() { final Injector prevInjector = threadLocalInjector.get(); threadLocalInjector.remove(); return prevInjector; } } 
दूसरा काम खत्म करना है। हमारे उदाहरण में, पहले हमें जेडी.गेटमास्टर पद्धति से छुटकारा मिलता है, फिर जेडी एक साधारण बीन में बदल जाता है। उसके बाद, हम जेडी.फैक्टरी को हटा देते हैं। चक्रीय निर्भरता भी गायब हो जाएगी। नतीजतन, InjectorUtil वर्ग स्वयं नहीं होगा। ऐसे वर्ग के बिना परियोजनाएं एक वास्तविकता हैं। इन सभी चरणों से गुजरना आवश्यक नहीं है, लेकिन, मुझे आपको याद दिलाना है, हम एक विरासत परियोजना की स्थिति के बारे में बात कर रहे हैं, एक नई परियोजना में इस समस्या से शुरुआत से ही बचा जा सकता है।



वास्तव में, और यह सब नहीं है। यदि जिस परियोजना का आप DI में अनुवाद कर रहे हैं, वह एक साझा पुस्तकालय है, तो यह DI से ही अमूर्त करने के लिए समझ में आता है, लेकिन यह एक अलग पोस्ट का विषय है।

उन लोगों के लिए जो अंत तक पढ़े हैं

मई -फोर्स आपके साथ हो सकता है।

Source: https://habr.com/ru/post/In217523/


All Articles