スプリングの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を使用して実行時に開発者が登録した空のファイルをアプリケーションから取得することです.
では、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;
}
}
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
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
反射とAnnotationpublic @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
の通話情報を使用することができる.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フレームワークの作成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
}
}
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は強力な機能であるため、過度に使用したり、不適切に使用したりすると、パフォーマンスの問題が発生する可能性があります.
乱発しないで,材料によって教えなければならない.(思考の開発者になる@ㅠ;)
白奇仙のより多くのjava、「コードを処理する様々な方法」を聞いてまとめた内容です.
Reference
この問題について(スプリングのDIはどのように操作しますか?), 我々は、より多くの情報をここで見つけました https://velog.io/@suyeon-jin/리플렉션-스프링의-DI는-어떻게-동작하는걸까テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol