スプリングのDIはどのように操作しますか?

77772 ワード


これまでJavaやSpringで開発されていましたが、依存注入がどのように実現されているのか一度も気にならず、自然に書かれていました.
この機会にスプリング内部の動き方を学びたいと思います.
DI内部操作方式?
スプリングを用いた開発では,サービスとレポートと呼ばれるコンポーネントを基本的に作成した.
@Autowiredを使用してサービスにレポートを注入します.
このプロセスを依存注入(DI),すなわち依存注入と呼ぶ.
実際にテストクラスを作成すると
package com.example.demo;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class BookServiceTest {

    @Autowired
    BookService bookService;

    @Test
    public void di() {
    	// 스프링이 bookRepository를 생성하여, bookService에 DI 해줌
        Assert.assertNotNull(bookService);
        Assert.assertNotNull(bookService.bookRepository);
    }
}
テストは正常に合格し、bookserviceとbookrepositoryはnullではありません.
では、スプリングはどのようにDIを行うのでしょうか.
はんしゃ
1)特定のクラスタイプを知らずにそのクラスのメソッド,タイプ,変数にアクセスできるJava API.
2)コンパイル時間ではなく実行時間において特定のクラス情報を動的に抽出できるプログラムテクニック.
Javaはコンパイル時にタイプを決定する静的言語であり、クラスを動的に使用する必要がある場合にreplicationを使用します.
つまり、作成時にどのクラスを使用するべきか分かりませんが、実行時にそのクラスを実行する必要がある場合に使用します.
典型的な例は、springがreplicationを使用して実行時に開発者が登録した空のファイルをアプリケーションから取得することです.
  • スプリングフレーム:DI
  • MVC:将来ビューからのデータをオブジェクトにバインドするときに
  • を使用
  • Hibernate:@Entityクラスにsetterがない場合、このフィールド
  • に値を注入します.
  • JUnit:反射Utilsクラスを内部で直接定義し、
  • を使用
    では、Replicationの使い方について説明しましょう.
    Replication API 1:クラス情報の表示
    Javaでは、返信レベルはクラスインスタンスからアクセスできます.(Replicationが提供するAPI)
    https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
    次の例では、返信の使用方法を示します.
    
    public class Book {
    
        private static String a = "aBOOK";
    
        private static final String b = "bBOOK";
    
        private String c = "cBOOK";
    
        public String d = "dBOOK";
    
        protected String e = "eBOOK";
    
        public Book() {
        }
    
        public Book(String c) {
            this.c = c;
        }
    
        public Book(String c, String d, String e) {
            this.c = c;
            this.d = d;
            this.e = e;
        }
    
        private void privateMethod() {
            System.out.println("privateVoidMethod");
        }
    
        public void publicMethod() {
            System.out.println("publicVoidMethod");
        }
    
        public int sum(int l, int r) {
            return l + r;
        }
    }
  • Replicationを使用すると、「フィールド、メソッド、ジェネレータ、およびアクセスインジケータ」など、クラスが持つさまざまな情報にアクセスできます.
  • import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.util.Arrays;
    
    public class Main {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            // 생성한 객체에 접근하려면, class가 필요하다. 클래스 인스턴스에 접근하는 방법은 3가지다.
    
            // 1) 타입을 통해 클래스 객체를 가져오는 방법
            // 클래스 로딩이 끝나면, 클래스 타입의 인스턴스를 만들어서 heap에 저장하므로 인스턴스를 가져올 수 있음
            Class<Book> bookClass = Book.class;
    
            // 2) 인스턴스를 통해 클래스 객체를 가져오는 방법
            // 인스턴스가 이미 만들어져있는 경우, getClass() 를 사용하여 가져올 수 있음
            Book book = new Book();
            Class<? extends Book> aClass = book.getClass();
    
            // 3) 경로를 통해 클래스 객체를 가져오는 방법
            // 클래스가 없으면 ClassNotFoundException 이 발생함
            Class<?> aClass1 = Class.forName("com.example.demo.Book");
    
    		////////////////////////////////////////////////////////////////////////////////////
            // 필드 목록 가져오기
            System.out.println("-----모든 필드 목록 가져오기-----");
            Arrays.stream(bookClass.getDeclaredFields()).forEach(System.out::println);
            System.out.println();
    
            System.out.println("-----public 필드 목록만 가져오기-----");
            Arrays.stream(bookClass.getFields()).forEach(System.out::println);
            System.out.println();
    
            System.out.println("-----특정 이름을 가진 필드 목록만 가져오기-----");
            // 필드가 없으면 NoSuchFieldException 이 발생함
            Arrays.stream(new Field[]{bookClass.getDeclaredField("b")}).forEach(System.out::println);
            System.out.println();
    
            System.out.println("-----필드가 가지고 있는 값 목록 가져오기 (값을 가져올 때는 객체가 있어야함)-----");
            Arrays.stream(bookClass.getDeclaredFields()).forEach(f -> {
                try {
                    f.setAccessible(true); // reflection으로는 이렇게 접근 지시자를 무시할 수 있음
                    System.out.printf("%s %s \n", f, f.get(book));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            });
            System.out.println();
    
            // 메서드 목록 가져오기
            System.out.println("-----모든 메서드 목록 가져오기-----");
            // 직접 정의한 public 메서드 외에도, Object로부터 상속받은 메서드도 출력됨
            // private 메서드도 출력하려면 getDeclaredMethods() 
            Arrays.stream(bookClass.getMethods()).forEach(System.out::println); 
            System.out.println();
    
            // 생성자 목록 가져오기
            System.out.println("-----모든 생성자 목록 가져오기-----");
            Arrays.stream(bookClass.getDeclaredConstructors()).forEach(System.out::println);
            System.out.println();
    
            // 접근 지시자 확인하기
            System.out.println("-----접근 지시자 확인하기-----");
            Arrays.stream(bookClass.getDeclaredFields()).forEach(f -> {
                int modifiers = f.getModifiers();
                System.out.print(f + "-> ");
                System.out.print("private? " + Modifier.isPrivate(modifiers));
                System.out.println(", static? " + Modifier.isStatic(modifiers));
            });
        }
    }
    
    -----모든 필드 목록 가져오기-----
    private static java.lang.String com.example.demo.Book.a
    private static final java.lang.String com.example.demo.Book.b
    private java.lang.String com.example.demo.Book.c
    public java.lang.String com.example.demo.Book.d
    protected java.lang.String com.example.demo.Book.e
    
    -----public 필드 목록만 가져오기-----
    public java.lang.String com.example.demo.Book.d
    
    -----특정 이름을 가진 필드 목록만 가져오기-----
    private static final java.lang.String com.example.demo.Book.b
    
    -----필드가 가지고 있는 값 목록 가져오기 (값을 가져올 때는 객체가 있어야함)-----
    private static java.lang.String com.example.demo.Book.a aBOOK 
    private static final java.lang.String com.example.demo.Book.b bBOOK 
    private java.lang.String com.example.demo.Book.c cBOOK 
    public java.lang.String com.example.demo.Book.d dBOOK 
    protected java.lang.String com.example.demo.Book.e eBOOK 
    
    -----모든 메서드 목록 가져오기-----
    public int com.example.demo.Book.sum(int,int)
    public void com.example.demo.Book.publicMethod()
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public java.lang.String java.lang.Object.toString()
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    
    -----모든 생성자 목록 가져오기-----
    public com.example.demo.Book(java.lang.String,java.lang.String,java.lang.String)
    public com.example.demo.Book(java.lang.String)
    public com.example.demo.Book()
    
    -----접근 지시자 확인하기-----
    private static java.lang.String com.example.demo.Book.a-> private? true, static? true
    private static final java.lang.String com.example.demo.Book.b-> private? true, static? true
    private java.lang.String com.example.demo.Book.c-> private? true, static? false
    public java.lang.String com.example.demo.Book.d-> private? false, static? false
    protected java.lang.String com.example.demo.Book.e-> private? false, static? false
  • はまた、replicationを介してクラスが有する「親、インタフェース」などの様々な情報にアクセスすることもできる.
  • public interface MyInterface {}
    public interface BookInterface {}
    
    public class MyBook extends Book implements MyInterface, BookInterface {}
    import java.util.Arrays;
    
    public class Main {
        public static void main(String[] args) {
        
            Class<MyBook> myBookClass = MyBook.class;
            
            // 상위 클래스 목록 가져오기
            System.out.println("-----부모클래스 목록 가져오기-----");
            System.out.println(myBookClass.getSuperclass());
            System.out.println();
    
            // 인터페이스 목록 가져오기
            System.out.println("-----모든 인터페이스 목록 가져오기-----");
            Arrays.stream(myBookClass.getInterfaces()).forEach(System.out::println);
            System.out.println();
        }
    }
    -----부모클래스 목록 가져오기-----
    class com.example.demo.Book
    
    -----모든 인터페이스 목록 가져오기-----
    interface com.example.demo.MyInterface
    interface com.example.demo.BookInterface
    反射とAnnotation
  • Replicationでは、クラスが所有する「確認」情報も表示できます.
  • ただし,Bookクラスが持つ操作情報をチェックすると,実際の結果は何も出力されない.(?_?)
    public @interface MyAnnotation {}
    
    @MyAnnotation
    public class Book {}
    import java.util.Arrays;
    
    public class Main {
        public static void main(String[] args) {
            // 어노테이션 목록 가져오기
            System.out.println("-----모든 어노테이션 목록 가져오기-----");
            Arrays.stream(Book.class.getAnnotations()).forEach(System.out::println);
            System.out.println();
    
        }
    }
    -----모든 어노테이션 목록 가져오기-----
    
    
    宣言は根本的に注釈とみなされるため、宣言情報はクラスとソースに保持されますが、バイトコードをロードするときにメモリに残ることはありません.(つまり、音声情報以外の情報は読み込まれます.)
    @Retention
    したがって、運転時までに通話情報を保持したい場合は、@Retentionの通話情報を使用することができる.
  • バイトコードairnation情報があるかどうかを確認するには、java -c -v 클래스_절대경로.classを使用します.
  • import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    // Retention 기본값은 RetentionPolicy.CLASS
    @Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
    public @interface MyAnnotation {
    }
    -----모든 어노테이션 목록 가져오기-----
    @com.example.demo.MyAnnotation()
    @Target
    適用範囲を設定することもできます.
    次の例では、MyAnnotationをコンストラクション関数またはメソッドに適用しようとすると、エラーが発生します.
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.FIELD}) // 어노테이션을 사용할 수 있는 곳은 클래스와 필드
    @Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
    public @interface MyAnnotation {
    }
    @Inherited
    クエリーをサブクラスに渡すか、継承するかを決定することもできます.
    次の例では、BookクラスはMyAnnotationである.
    MyBookクラスはBookAnnotationに適しており、Bookクラスも継承されている.
    import java.lang.annotation.*;
    
    @Inherited // 상속이 가능하도록 한다.
    @Target({ElementType.TYPE, ElementType.FIELD}) // 어노테이션을 사용할 수 있는 곳은 클래스와 필드
    @Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
    public @interface MyAnnotation {
    }
    
    @Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
    public @interface BookAnnotation {
    }
    @MyAnnotation
    public class Book {}
    
    @BookAnnotation
    public class MyBook extends Book implements MyInterface, BookInterface {}
    この状態でMyBookクラスの説明リストが出力されると、Bookクラスに適用されるMyAnnotationもサブクラスMyBookクラスに適用される.
    親ではなく、この(子)クラスで指定した名前のリストのみを出力することもできます.
    import java.util.Arrays;
    
    public class Main {
            System.out.println("----모든 어노테이션 목록 가져오기-----");
            Arrays.stream(MyBook.class.getAnnotations()).forEach(System.out::println);
            System.out.println();
    
            System.out.println("----명시된 어노테이션 목록 가져오기-----");
            Arrays.stream(MyBook.class.getDeclaredAnnotations()).forEach(System.out::println);
            System.out.println();
        }
    }
    ----모든 어노테이션 목록 가져오기-----
    @com.example.demo.MyAnnotation()
    @com.example.demo.BookAnnotation()
    
    ----명시된 어노테이션 목록 가져오기-----
    @com.example.demo.BookAnnotation()
    
    Replication API 2:クラス情報の変更と実行
    クラスの情報をreplicationにインポートするだけでなく、
    1)コンストラクション関数を使用してインスタンスを作成できます.2)フィールド値を入力または変更できます.3)メソッドを実行できます.
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class Main2 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
                InstantiationException, IllegalAccessException, NoSuchFieldException {
    
            Class<?> bookClass = Class.forName("com.example.demo.Book");
    
            // 1) 생성자를 사용하여 인스턴스 생성하기 Constructor.newInstance(params)
            Constructor<?> constructor = bookClass.getConstructor(null); // 기본 생성자를 사용하여 인스턴스를 만들겠다.
            Book book = (Book) constructor.newInstance();
            System.out.println(book);
    
            // 2) static 필드 값 가져오기 Field.get(object)
            Field a = Book.class.getDeclaredField("a");
            a.setAccessible(true); // private scope 이기 때문에 접근 지시자를 무시하도록 설정해줘야 함
            System.out.println(a.get(null)); // static 변수이므로, 특정 인스턴스를 넘겨줄 것이 없다. 그러므로 null을 넘긴다.
    
            // 2) static 필드 값 수정하기 Field.set(object, value)
            a.set(null, "abook");
            System.out.println(a.get(null));
    
            // 2) 필드 값 가져오기 Field.get(object)
            // 특정 인스턴스가 가지고 있는 값을 가져오는 것이기 때문에, 인스턴스가 미리 만들어져 있어야한다.
            Field d = Book.class.getDeclaredField("d");
            System.out.println(d.get(book)); // 미리 만들어져 있던 Book 클래스 객체(book)를 사용하여 해당 객체의 필드 값을 가져옴
    
            // 2) 필드 값 수정하기 Field.set(object, value)
            d.set(book, "dbook");
            System.out.println(d.get(book));
    
            // 3) 메소드 실행하기 Method.invoke(object, params)
            Method privateMethod = Book.class.getDeclaredMethod("privateMethod");
            privateMethod.setAccessible(true);
            privateMethod.invoke(book);
    
            // 3) 파라미터와 리턴 값이 있는 메서드 실행하기 Method.invoke(object, params)
            Method sum = Book.class.getDeclaredMethod("sum", int.class, int.class);
            int result = (int) sum.invoke(book, 10, 20);
            System.out.println(result);
        }
    }
    com.example.demo.Book@3f0ee7cb
    aBOOK
    abook
    dBOOK
    dbook
    privateVoidMethod
    30
    レプリケーションを使用したDIフレームワークの作成
  • test/java
  • package com.example.di;
    
    public class BookRepository {}
    
    public class BookService {
        @Inject // 필드(BookRepository) 주입
        BookRepository bookRepository;
        
        BookRepository bookRepositoryWithOutInject;
    }
    package com.example.di;
    
    import org.junit.Test;
    
    import static org.junit.Assert.assertNotNull;
    
    public class ContainerServiceTest {
    
        @Test
        public void getObject_BookRepository() {
        	// @Inject 를 적용하지 않은 객체
            BookRepository bookRepository = ContainerService.getObject(BookRepository.class);
            assertNotNull(bookRepository);
        }
    
        @Test
        public void getObject_BookService() {
        	// @Inject 를 적용하여 객체를 주입한 객체
            BookService bookService = ContainerService.getObject(BookService.class);
            assertNotNull(bookService);
            assertNotNull(bookService.bookRepository);
            assertNotNull(bookService.bookRepositoryWithOutInject); // FAIL 
        }
    }
  • src/main/java
    1) @Inject
    フィールド注入用のオブジェクトであるかどうかを確認するために、@Injectという構文を作成します.
    運転時に参考する必要があるため、@Retention明細書も適用される.
  • package com.example.di;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Inject {
    }
    2) ContainerServicegetObjectメソッドでクラスタイプのオブジェクトを作成します.
    ただし、オブジェクトのフィールドに@Inject項目がある場合は、このフィールドも作成されます.
    package com.example.di;
    
    import java.util.Arrays;
    
    public class ContainerService {
    
    	// 클래스 타입이 들어오면, 해당 클래스의 인스턴스를 리턴하도록 Generic 타입으로 정의함
        public static <T> T getObject(Class<T> classType) {
            T instance = createInstance(classType); // 리플렉션을 이용하여 인스턴스를 생성한다.
            Arrays.stream(classType.getDeclaredFields()).forEach(f -> { // 클래스 타입의 필드들을 확인하면서,
                if (f.getAnnotation(Inject.class) != null) { // 필드에 적용된 어노테이션이 @Inject 이면
                    Object fieldInstance = createInstance(f.getType()); // 필드 타입에 맞는 클래스 인스턴스를 생성하고
                    f.setAccessible(true); // 접근 지시자를 무시하도록 설정하고
                    try {
                        f.set(instance, fieldInstance); // 해당 필드에 객체를 주입한다.
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
    
            return instance;
        }
    
        private static <T> T createInstance(Class<T> classType) { // 리플렉션 이용-> 생성자를 만들어 리턴해준다.
            try {
                return classType.getConstructor(null).newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    上記の例では、異常処理やキャッシュなどの最適化手法は使用されていないため、有効なIOCコンテナではない.実際,スプリングはDIを行う際にはずっと複雑である.
    ただし、スプリングがDIを実行する方法は、上記のコピーを使用してオブジェクトを作成および注入することであることを覚えておいてください.
    整理する
    Replicationは強力な機能であるため、過度に使用したり、不適切に使用したりすると、パフォーマンスの問題が発生する可能性があります.
  • インスタンスが作成され、引き続き使用できる場合は、コールバックメソッドを使用する必要はありません.(返信を使用し続ける場合は、新しいオブジェクトの作成を続行するため、効率が低下します)
  • コンパイル
  • では確認せず、実行時にのみ問題が発生します.
  • アクセスインジケータは無視できます.(パッケージングを無視)
  • しかし、不適切な使用はパフォーマンスの問題を引き起こし、適切に使用すればreplicationは強力な機能です.
    乱発しないで,材料によって教えなければならない.(思考の開発者になる@ㅠ;)
    白奇仙のより多くのjava、「コードを処理する様々な方法」を聞いてまとめた内容です.