
Pendahuluan
Setiap pengembang pemula harus terbiasa dengan konsep Inversion of Control.
Hampir setiap proyek baru sekarang dimulai dengan pilihan kerangka kerja yang dengannya prinsip injeksi ketergantungan akan dilaksanakan.
Inversion of Control (IoC) adalah prinsip penting pemrograman berorientasi objek, yang digunakan untuk mengurangi koherensi dalam program komputer dan salah satu dari lima prinsip terpenting SOLID.
Saat ini, ada beberapa kerangka kerja utama tentang topik ini:
1.
Belati2.
Google Guice3.
Kerangka Kerja PegasSaya masih menggunakan Spring dan sebagian puas dengan fungsinya, tetapi sekarang saatnya untuk mencoba sesuatu dan saya sendiri, bukan?
Tentang diri saya
Nama saya Nikita, saya berusia 24 tahun, dan saya telah melakukan java (backend) selama 3 tahun. Dia belajar hanya dengan contoh-contoh praktis, sementara secara bersamaan mencoba memahami bintik-bintik kelas. Saat ini saya sedang bekerja (freelance) - menulis CMS untuk proyek komersial, tempat saya menggunakan Spring Boot. Baru-baru ini saya mengunjungi pemikiran - βMengapa tidak menulis Wadah IoC (DI) Anda sesuai dengan visi dan keinginan Anda?β. Secara kasar - "Saya ingin milik saya sendiri dengan blackjack ...". Ini akan dibahas hari ini. Nah, tolong, di bawah kucing.
Tautan ke sumber proyek .
Fitur
- Fitur utama dari proyek ini adalah Injeksi Ketergantungan.
3 metode injeksi ketergantungan utama didukung:
- Bidang kelas
- Konstruktor kelas
- Fungsi kelas (setter standar untuk satu parameter)
* Catatan:
- saat memindai kelas, jika ketiga metode injeksi digunakan sekaligus, metode injeksi melalui konstruktor kelas yang ditandai dengan penjelasan @IoCDependency akan menjadi prioritas. Yaitu hanya satu metode injeksi yang selalu berhasil.
- inisialisasi malas komponen (sesuai permintaan);
- File konfigurasi loader bawaan (format: ini, xml, properti);
- pengendali argumen baris perintah;
- Mengolah modul dengan membuat pabrik;
- Acara dan pendengar bawaan;
- informan bawaan (Sensibles) untuk "memberi tahu" komponen, pabrik, pendengar, prosesor (ComponentProcessor) bahwa informasi tertentu harus dimuat ke dalam objek, tergantung pada informan;
- modul untuk mengelola / membuat kumpulan utas, mendeklarasikan fungsi sebagai tugas yang dapat dieksekusi untuk beberapa waktu dan menginisialisasi mereka di kumpulan pabrik, serta memulai dengan parameter SimpleTask.
Bagaimana pemindaian paket terjadi:Ini menggunakan API Refleksi pihak ketiga dengan pemindai standar.
Kami mendapatkan koleksi kelas menggunakan filter anotasi, tipe.
Dalam hal ini, ini adalah @IoCComponent, @Property dan progenitor Analyzer <R, T>
Urutan inisialisasi konteks:1) Pertama-tama, tipe konfigurasi diinisialisasi.
* Penjelasan:
Annotation @Property memiliki parameter string yang diperlukan - path (path ke file konfigurasi). Di sinilah file dicari untuk parsing konfigurasi.
Kelas PropertiesLoader adalah kelas utilitas untuk menginisialisasi bidang kelas yang terkait dengan bidang file konfigurasi.
Function DependencyFactory # addInstalledConfiguration (Object) - memuat objek konfigurasi ke pabrik sebagai SINGLETON (jika tidak masuk akal untuk memuat ulang konfigurasi yang tidak sesuai permintaan).
2) Inisialisasi alat analisis
3) Inisialisasi komponen yang ditemukan (Kelas ditandai dengan anotasi @IoCComponent)
* Penjelasan:
Kelas ClassAnalyzer - mendefinisikan metode injeksi ketergantungan, juga jika ada kesalahan penempatan anotasi yang salah, deklarasi konstruktor, parameter dalam metode - mengembalikan kesalahan. Function Analyzer <R, T> #analyze (T) - mengembalikan hasil analisis. Function Analyzer <R, T> #supportFor (T) - mengembalikan parameter Boolean tergantung pada kondisi yang ditentukan.
Function DependencyFactory # instantiate (Class, R) - menginstal tipe ke pabrik menggunakan metode yang didefinisikan oleh ClassAnalyzer atau melempar pengecualian jika ada kesalahan baik dalam analisis atau dalam proses inisialisasi objek.
3) Metode pemindaian
- metode untuk menyuntikkan parameter ke konstruktor kelas
private <O> O instantiateConstructorType(Class<O> type) { final Constructor<O> oConstructor = findConstructor(type); if (oConstructor != null) { final Parameter[] constructorParameters = oConstructor.getParameters(); final List<Object> argumentList = Arrays.stream(constructorParameters) .map(param -> mapConstType(param, type)) .collect(Collectors.toList()); try { final O instance = oConstructor.newInstance(argumentList.toArray()); addInstantiable(type); final String typeName = getComponentName(type); if (isSingleton(type)) { singletons.put(typeName, instance); } else if (isPrototype(type)) { prototypes.put(typeName, instance); } return instance; } catch (Exception e) { throw new IoCInstantiateException("IoCError - Unavailable create instance of type [" + type + "].", e); } } return null; }
- metode untuk menyuntikkan parameter ke bidang kelas
private <O> O instantiateFieldsType(Class<O> type) { final List<Field> fieldList = findFieldsFromType(type); final List<Object> argumentList = fieldList.stream() .map(field -> mapFieldType(field, type)) .collect(Collectors.toList()); try { final O instance = ReflectionUtils.instantiate(type); addInstantiable(type); for (Field field : fieldList) { final Object toInstantiate = argumentList .stream() .filter(f -> f.getClass().getSimpleName().equals(field.getType().getSimpleName())) .findFirst() .get(); final boolean access = field.isAccessible(); field.setAccessible(true); field.set(instance, toInstantiate); field.setAccessible(access); } final String typeName = getComponentName(type); if (isSingleton(type)) { singletons.put(typeName, instance); } else if (isPrototype(type)) { prototypes.put(typeName, instance); } return instance; } catch (Exception e) { throw new IoCInstantiateException("IoCError - Unavailable create instance of type [" + type + "].", e); } }
- Metode menyuntikkan parameter melalui fungsi kelas
private <O> O instantiateMethodsType(Class<O> type) { final List<Method> methodList = findMethodsFromType(type); final List<Object> argumentList = methodList.stream() .map(method -> mapMethodType(method, type)) .collect(Collectors.toList()); try { final O instance = ReflectionUtils.instantiate(type); addInstantiable(type); for (Method method : methodList) { final Object toInstantiate = argumentList .stream() .filter(m -> m.getClass().getSimpleName().equals(method.getParameterTypes()[0].getSimpleName())) .findFirst() .get(); method.invoke(instance, toInstantiate); } final String typeName = getComponentName(type); if (isSingleton(type)) { singletons.put(typeName, instance); } else if (isPrototype(type)) { prototypes.put(typeName, instance); } return instance; } catch (Exception e) { throw new IoCInstantiateException("IoCError - Unavailable create instance of type [" + type + "].", e); } }
API pengguna1. ComponentProcessor - utilitas yang memungkinkan Anda mengubah komponen sesuai keinginan, baik sebelum inisialisasi dalam konteks dan setelahnya.
public interface ComponentProcessor { Object afterComponentInitialization(String componentName, Object component); Object beforeComponentInitialization(String componentName, Object component); }
* Penjelasan:Fungsi #afterComponentInitialization (String, Object) - memungkinkan Anda untuk memanipulasi komponen setelah menginisialisasi dalam konteks, parameter yang masuk - (nama tetap komponen, objek objek komponen yang dipakai).
Fungsi #beforeComponentInitialization (String, Object) - memungkinkan Anda untuk memanipulasi komponen sebelum menginisialisasi dalam konteks, parameter yang masuk - (nama tetap komponen, objek objek komponen yang dipakai)
2. CommandLineArgumentResolver
public interface CommandLineArgumentResolver { void resolve(String... args); }
* Penjelasan:Fungsi #resolve (String ...) adalah antarmuka yang
menangani berbagai perintah yang dikirim melalui cmd ketika aplikasi dimulai, parameter input adalah array tak terbatas dari string baris perintah (parameter).
3. Informan (Sensibles) - menunjukkan bahwa kelas anak dari informan perlu menanamkan opr. fungsionalitas tergantung pada jenis informan (ContextSensible, EnvironmentSensible, ThreadFactorySensible, dll.)
4. Pendengar
Fungsi pendengar diimplementasikan, eksekusi multi-threading dijamin dengan jumlah deskriptor yang direkomendasikan yang dikonfigurasi untuk acara yang dioptimalkan.
@org.di.context.annotations.listeners.Listener
** Penjelasan:Fungsi dispatch (Event) adalah fungsi utama dari pengendali event sistem.
- Ada implementasi standar pendengar dengan memeriksa jenis acara serta dengan filter pengguna bawaan {@link Filter}. Filter standar termasuk dalam paket: AndFilter, ExcludeFilter, NotFilter, OrFilter, InstanceFilter (custom). Implementasi pendengar standar:
FilteredListener dan TypedListener. Yang pertama menggunakan filter untuk memeriksa objek acara yang masuk. Yang kedua memeriksa objek acara atau yang lainnya untuk menjadi milik contoh tertentu.
Modul1) Modul untuk bekerja dengan tugas streaming di aplikasi Anda
- menghubungkan dependensi
<repositories> <repository> <id>di_container-mvn-repo</id> <url>https://raw.github.com/GenCloud/di_container/threading/</url> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </repository> </repositories> <dependencies> <dependency> <groupId>org.genfork</groupId> <artifactId>threads-factory</artifactId> <version>1.0.0.RELEASE</version> </dependency> </dependencies>
- penanda anotasi untuk penyertaan modul dalam konteks (@ThreadingModule)
@ThreadingModule @ScanPackage(packages = {"org.di.test"}) public class MainTest { public static void main(String... args){ IoCStarter.start(MainTest.class, args); } }
- implementasi pabrik modul ke dalam komponen aplikasi yang diinstal
@IoCComponent public class ComponentThreads implements ThreadFactorySensible<DefaultThreadingFactory> { private final Logger log = LoggerFactory.getLogger(AbstractTask.class); private DefaultThreadingFactory defaultThreadingFactory; private final AtomicInteger atomicInteger = new AtomicInteger(0); @PostConstruct public void init() { defaultThreadingFactory.async(new AbstractTask<Void>() { @Override public Void call() { log.info("Start test thread!"); return null; } }); } @Override public void threadFactoryInform(DefaultThreadingFactory defaultThreadingFactory) throws IoCException { this.defaultThreadingFactory = defaultThreadingFactory; } @SimpleTask(startingDelay = 1, fixedInterval = 5) public void schedule() { log.info("I'm Big Daddy, scheduling and incrementing param - [{}]", atomicInteger.incrementAndGet()); } }
* Penjelasan:ThreadFactorySensible adalah salah satu kelas informan anak untuk implementasi ke dalam komponen ODA yang dipakai. informasi (konfigurasi, konteks, modul, dll.).
DefaultThreadingFactory - pabrik modul threading-pabrik.
Annotation @SimpleTask adalah anotasi marker yang dapat diparameterisasi untuk mengidentifikasi implementasi tugas komponen dalam fungsi. (memulai streaming dengan parameter yang ditentukan dengan anotasi dan menambahkannya ke pabrik, dari mana ia dapat diperoleh dan, misalnya, menonaktifkan eksekusi).
- fungsi penjadwalan tugas standar
*** Harap dicatat bahwa sumber daya dalam kumpulan utas terjadwal terbatas dan tugas harus diselesaikan dengan cepat.
- konfigurasi kolam standar
# Threading threads.poolName=shared threads.availableProcessors=4 threads.threadTimeout=0 threads.threadAllowCoreTimeOut=true threads.threadPoolPriority=NORMAL
Titik awal atau cara kerjanya
Kami menghubungkan ketergantungan proyek:
<repositories> <repository> <id>di_container-mvn-repo</id> <url>https://raw.github.com/GenCloud/di_container/context/</url> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </repository> </repositories> ... <dependencies> <dependency> <groupId>org.genfork</groupId> <artifactId>context</artifactId> <version>1.0.0.RELEASE</version> </dependency> </dependencies>
Aplikasi kelas tes.
@ScanPackage(packages = {"org.di.test"}) public class MainTest { public static void main(String... args) { IoCStarter.start(MainTest.class, args); } }
** Penjelasan:
Penjelasan @ScanPackage - memberi tahu konteks paket mana yang harus dipindai untuk mengidentifikasi komponen (kelas) untuk injeksi mereka. Jika paket tidak ditentukan, paket kelas yang ditandai dengan anotasi ini akan dipindai.
IoCStarter # start (Object, String ...) - titik masuk dan inisialisasi konteks aplikasi.
Selain itu, kami akan membuat beberapa kelas komponen untuk memeriksa fungsi secara langsung.
Componententa @IoCComponent @LoadOpt(PROTOTYPE) public class ComponentA { @Override public String toString() { return "ComponentA{" + Integer.toHexString(hashCode()) + "}"; } }
Komponen b @IoCComponent public class ComponentB { @IoCDependency private ComponentA componentA; @IoCDependency private ExampleEnvironment exampleEnvironment; @Override public String toString() { return "ComponentB{hash: " + Integer.toHexString(hashCode()) + ", componentA=" + componentA + ", exampleEnvironment=" + exampleEnvironment + '}'; } }
Komponenc @IoCComponent public class ComponentC { private final ComponentB componentB; private final ComponentA componentA; @IoCDependency public ComponentC(ComponentB componentB, ComponentA componentA) { this.componentB = componentB; this.componentA = componentA; } @Override public String toString() { return "ComponentC{hash: " + Integer.toHexString(hashCode()) + ", componentB=" + componentB + ", componentA=" + componentA + '}'; } }
Komponend @IoCComponent public class ComponentD { @IoCDependency private ComponentB componentB; @IoCDependency private ComponentA componentA; @IoCDependency private ComponentC componentC; @Override public String toString() { return "ComponentD{hash: " + Integer.toHexString(hashCode()) + ", ComponentB=" + componentB + ", ComponentA=" + componentA + ", ComponentC=" + componentC + '}'; } }
* Catatan:
- dependensi siklik tidak disediakan, ada tulisan rintisan dalam bentuk analisa, yang, pada gilirannya, memeriksa kelas yang diterima dari paket yang dipindai dan melempar pengecualian jika ada loop.
** Penjelasan:
@IoCComponent annotation - menunjukkan konteks bahwa ini adalah komponen dan perlu dianalisis untuk mengidentifikasi dependensi (anotasi yang diperlukan).
Annotation @IoCDependency - menunjukkan kepada penganalisa bahwa ini adalah ketergantungan komponen dan perlu dipakai di dalam komponen.
Anotasi @LoadOpt - menampilkan konteks jenis pemuatan komponen yang harus digunakan. Saat ini, 2 jenis didukung - SINGLETON dan PROTOTYPE (tunggal dan multipel).
Mari kita memperluas implementasi kelas utama:
Maintest @ScanPackage(packages = {"org.di.test", "org.di"}) public class MainTest extends Assert { private static final Logger log = LoggerFactory.getLogger(MainTest.class); private AppContext appContext; @Before public void initializeContext() { BasicConfigurator.configure(); appContext = IoCStarter.start(MainTest.class, (String) null); } @Test public void printStatistic() { DependencyFactory dependencyFactory = appContext.getDependencyFactory(); log.info("Initializing singleton types - {}", dependencyFactory.getSingletons().size()); log.info("Initializing proto types - {}", dependencyFactory.getPrototypes().size()); log.info("For Each singleton types"); for (Object o : dependencyFactory.getSingletons().values()) { log.info("------- {}", o.getClass().getSimpleName()); } log.info("For Each proto types"); for (Object o : dependencyFactory.getPrototypes().values()) { log.info("------- {}", o.getClass().getSimpleName()); } } @Test public void testInstantiatedComponents() { log.info("Getting ExampleEnvironment from context"); final ExampleEnvironment exampleEnvironment = appContext.getType(ExampleEnvironment.class); assertNotNull(exampleEnvironment); log.info(exampleEnvironment.toString()); log.info("Getting ComponentB from context"); final ComponentB componentB = appContext.getType(ComponentB.class); assertNotNull(componentB); log.info(componentB.toString()); log.info("Getting ComponentC from context"); final ComponentC componentC = appContext.getType(ComponentC.class); assertNotNull(componentC); log.info(componentC.toString()); log.info("Getting ComponentD from context"); final ComponentD componentD = appContext.getType(ComponentD.class); assertNotNull(componentD); log.info(componentD.toString()); } @Test public void testProto() { log.info("Getting ComponentA from context (first call)"); final ComponentA componentAFirst = appContext.getType(ComponentA.class); log.info("Getting ComponentA from context (second call)"); final ComponentA componentASecond = appContext.getType(ComponentA.class); assertNotSame(componentAFirst, componentASecond); log.info(componentAFirst.toString()); log.info(componentASecond.toString()); } @Test public void testInterfacesAndAbstracts() { log.info("Getting MyInterface from context"); final InterfaceComponent myInterface = appContext.getType(MyInterface.class); log.info(myInterface.toString()); log.info("Getting TestAbstractComponent from context"); final AbstractComponent testAbstractComponent = appContext.getType(TestAbstractComponent.class); log.info(testAbstractComponent.toString()); } }
Kami memulai proyek menggunakan IDE atau baris perintah Anda.
Hasil eksekusi Connected to the target VM, address: '127.0.0.1:55511', transport: 'socket' 0 [main] INFO org.di.context.runner.IoCStarter - Start initialization of context app 87 [main] DEBUG org.reflections.Reflections - going to scan these urls: file:/C:/Users/GenCloud/Workspace/di_container/context/target/classes/ file:/C:/Users/GenCloud/Workspace/di_container/context/target/test-classes/ [main] DEBUG org.reflections.Reflections - could not scan file log4j2.xml in url file:/C:/Users/GenCloud/Workspace/di_container/context/target/test-classes/ with scanner SubTypesScanner [main] DEBUG org.reflections.Reflections - could not scan file log4j2.xml in url file:/C:/Users/GenCloud/Workspace/di_container/context/target/test-classes/ with scanner TypeAnnotationsScanner [main] INFO org.reflections.Reflections - Reflections took 334 ms to scan 2 urls, producing 21 keys and 62 values [main] INFO org.di.context.runner.IoCStarter - App context started in [0] seconds [main] INFO org.di.test.MainTest - Initializing singleton types - 6 [main] INFO org.di.test.MainTest - Initializing proto types - 1 [main] INFO org.di.test.MainTest - For Each singleton types [main] INFO org.di.test.MainTest - ------- ComponentC [main] INFO org.di.test.MainTest - ------- TestAbstractComponent [main] INFO org.di.test.MainTest - ------- ComponentD [main] INFO org.di.test.MainTest - ------- ComponentB [main] INFO org.di.test.MainTest - ------- ExampleEnvironment [main] INFO org.di.test.MainTest - ------- MyInterface [main] INFO org.di.test.MainTest - For Each proto types [main] INFO org.di.test.MainTest - ------- ComponentA [main] INFO org.di.test.MainTest - Getting ExampleEnvironment from context [main] INFO org.di.test.MainTest - ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]} [main] INFO org.di.test.MainTest - Getting ComponentB from context [main] INFO org.di.test.MainTest - ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}} [main] INFO org.di.test.MainTest - Getting ComponentC from context [main] INFO org.di.test.MainTest - ComponentC{hash: 49d904ec, componentB=ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}}, componentA=ComponentA{48e4374}} [main] INFO org.di.test.MainTest - Getting ComponentD from context [main] INFO org.di.test.MainTest - ComponentD{hash: 3d680b5a, ComponentB=ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}}, ComponentA=ComponentA{4b5d6a01}, ComponentC=ComponentC{hash: 49d904ec, componentB=ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}}, componentA=ComponentA{48e4374}}} [main] INFO org.di.test.MainTest - Getting MyInterface from context [main] INFO org.di.test.MainTest - MyInterface{componentA=ComponentA{cd3fee8}} [main] INFO org.di.test.MainTest - Getting TestAbstractComponent from context [main] INFO org.di.test.MainTest - TestAbstractComponent{componentA=ComponentA{3e2e18f2}, AbstractComponent{}} [main] INFO org.di.test.MainTest - Getting ComponentA from context (first call) [main] INFO org.di.test.MainTest - ComponentA{10e41621} [main] INFO org.di.test.MainTest - Getting ComponentA from context (second call) [main] INFO org.di.test.MainTest - ComponentA{353d0772} Disconnected from the target VM, address: '127.0.0.1:55511', transport: 'socket' Process finished with exit code 0
+ Ada built-in penguraian file konfigurasi (ini, xml, properti).
Tes run-in ada di repositori.
Masa depan
Berencana untuk memperluas dan mendukung proyek sebanyak mungkin.
Apa yang ingin saya lihat:
- Menulis modul tambahan - jaringan / bekerja dengan database / solusi penulisan untuk masalah umum.
- Mengganti Java Reflection API dengan CGLIB
- dll. (Saya mendengarkan pengguna, jika ada)
Ini akan diikuti oleh akhir logis dari artikel.
Terima kasih semuanya. Saya harap seseorang akan menemukan pekerjaan saya bermanfaat.
UPD Pembaruan artikel - 15/09/2018.
Rilis 1.0.0