이전 기사의 분석을 통해 프록시 클래스는 프록시 클래스의 proxyclassfactory 공장을 통해 생성된다는 것을 알고 있습니다. 이 공장 클래스는 프록시 클래스의 바이트 코드를 생성하기 위해 proxygenerator 클래스의 GenerateProxyClass () 메소드를 호출합니다. proxygenerator 클래스는 Sun.Misc 패키지에 저장됩니다. OpenJDK 소스 코드를 통해이 클래스를 찾을 수 있습니다. 이 클래스의 GeneratedProxyClass () 정적 메소드의 핵심 내용은 GeneratedClassFile () 인스턴스 메소드를 호출하여 클래스 파일을 생성하는 것입니다. GenerateClassFile () 메소드 내에서 수행 된 작업을 살펴 보겠습니다.
private byte [] generateclassfile () {// 첫 번째 단계는 모든 메소드를 proxymethod 객체로 조립하는 것입니다. // 먼저 tostring, hashcode, equals 등과 같은 프록시 메소드를 생성하는 것입니다. AddProxymethod (EqualSmethod, object.class); AddProxymethod (TostringMethod, Object.Class); // 각 인터페이스의 각 방법을 전송하고 (int i = 0; i <interfaces.length; i ++) {method [] method = interfaces [i] .getMethods (); for (int j = 0; }} // 동일한 서명이있는 프록시 메소드의 경우 메소드의 리턴 값이 호환되는지 확인하십시오 (list <proxymethod> 서명 : proxymethods.values ()) {checkreturnTypes (sigmethods); } // 2 단계, 생성 할 클래스 파일의 모든 필드 정보 및 메소드 정보를 조립하십시오. {// 생성자 메소드 추가 메소드를 추가하십시오. // 캐시에서 프록시 메소드를 전송합니다 (list <proxymethod> signmethods : proxymethods.values ()) {for (proxymethod pm : signmethods) {// 프리 렉시 클래스의 정적 필드 추가 : private static method m1; fields.add (New FieldInfo (PM.MethodfieldName, "ljava/lang/recint/method;", acc_private | acc_static)); // 프록시 클래스 메소드의 프록시 방법을 추가합니다 .add (pm.generatemethod ()); }} // 정적 필드 초기화 메소드 추가 메소드를 추가합니다. } catch (ioexception e) {새 NECTERNERROR ( "예기치 않은 I/O 예외"); } // 확인 방법 및 필드 컬렉션은 65535보다 클 수 없습니다. if (methods.size ()> 65535) {Throw New New OregalArgumentException ( "메소드 제한 초과"); } if (fields.size ()> 65535) {New New OregalArgumentException ( "필드 한계 초과"); } if (fields.size ()> 65535) {New New OregalArgumentException ( "필드 한계 초과"); } // 3 단계, 최종 클래스 파일에 쓰기 // 상수 풀 cp.getClass (dottoslash (className))에 프록시 클래스의 자격을 갖춘 이름이 있는지 확인하십시오. // 상수 풀에 프록시 클래스 상위 클래스의 자격을 갖춘 이름이 완전히 있는지 확인하고 부모 클래스 이름은 다음과 같습니다. // (int i = 0; i <interfaces.length; i ++) {cp.getClass (dottoSlash (인터페이스 [i] .getName ()))에 대한 프록시 클래스 인터페이스의 전체 자격있는 이름을 확인하십시오. } // 파일 쓰기 시작 옆에 CP.SetReadOnly () 만 읽도록 상수 풀을 설정하십시오. BYTEARRAYOUTPUTSTREAM BOUT = 새로운 BYTEARRAYOUTPUTSTREAM (); DataOutputStream dout = 새로운 DataOutputStream (Bout); {// 1. 마법 번호 dout.writeint (0xcafebabe)에 쓰십시오. // 2. 보조 버전 번호 dout.writeshort (classfile_minor_version)에 쓰십시오. // 3. 기본 버전 번호 dout.writeshort (classfile_major_version)에 쓰십시오. // 4. Constant Pool Cp.Write (dout)에 쓰십시오. // 5. ac // 6. Class Index dout.writeshort (cp.getClass (dottoslash (className))); // 7. 작성자 클래스 인덱스 쓰기, 생성 된 프록시 클래스는 Proxy Dout.writeshort (cp.getClass (superClassName))에서 상속됩니다. // 8. 인터페이스 카운트 값 Dout.writeshort (인터페이스 .length) 쓰기; // 9. (int i = 0; i <interfaces.length; i ++) {dout.writeshort (cp.getclass (dottoslash (interfaces [i] .getname ())) } // 10. 쓰기 필드 수 값 Dout.writeshort (fields.size ()); // 11. (FieldInfo f : fields) {F.Write (dout)에 대한 필드 컬렉션을 쓰십시오. } // 12. 쓰기 메소드 카운트 값 dout.writeshort (methods.size ()); // 13. (methodinfo m : methods) {m.write (dout); } // 14. 쓰기 속성 수 값, 프록시 클래스 파일에는 속성이 없으므로 0 dout.writeshort (0)입니다. } catch (ioexception e) {새 NECTERNERROR ( "예기치 않은 I/O 예외"); } // 바이너리 배열로 전환하여 출력 반환 bout.tobytearRay ();}GenerateClassFile () 메소드가 클래스 파일 구조에 따라 동적으로 스 플라이 싱된다는 것을 알 수 있습니다. 클래스 파일이란 무엇입니까? 여기서 우리는 먼저 우리가 쓰는 Java 파일이 .java로 끝나는 것을 설명합니다. 작성한 후 컴파일러를 통해 컴파일하고 .class 파일을 생성하십시오. 이 .class 파일은 클래스 파일입니다. Java 프로그램의 실행은 클래스 파일에만 의존하며 Java 파일과 관련이 없습니다. 이 클래스 파일은 클래스의 정보를 설명합니다. 클래스를 사용해야 할 때 Java Virtual Machine 은이 클래스의 클래스 파일을 미리로드하고 초기화 및 관련 확인을 수행합니다. Java 가상 머신은이 클래스를 사용하기 전에 이러한 작업이 완료되도록 할 수 있습니다. 우리는 Java Virtual Machine이 어떻게로드되는지에 대해 신경 쓰지 않고 마음의 평화로 사용하면됩니다. 물론, Java 파일을 컴파일하여 클래스 파일을 컴파일 할 필요는 없습니다. 텍스트 편집기를 통해 직접 클래스 파일을 작성할 수도 있습니다. 여기서 JDK 동적 프록시는 프로그램을 통해 클래스 파일을 동적으로 생성합니다. 위의 코드로 돌아가서 클래스 파일을 생성하는 것이 주로 세 단계로 나뉘어져 있는지 확인해 보겠습니다.
1 단계 : 생성 할 모든 프록시 방법을 수집하고 근위 예의 객체로 싸서 맵 컬렉션에 등록하십시오.
2 단계 : 클래스 파일에 대해 생성 할 모든 필드 정보 및 방법 정보를 수집하십시오.
3 단계 : 위의 작업을 완료 한 후 클래스 파일을 조립하십시오.
우리는 클래스의 핵심 부분이 그 분야와 방법이라는 것을 알고 있습니다. 두 번째 단계에 중점을 두어 프록시 클래스에 어떤 필드와 메소드가 생성하는지 확인합시다. 두 번째 단계에서는 다음 4 가지 작업이 순서대로 수행되었습니다.
1. 프록시 클래스의 매개 변수 생성자를 생성하고 invocationHandler 인스턴스를 참조하고 상위 클래스의 매개 변수 생성자를 호출합니다.
2. 프록시 메소드의 맵 컬렉션을 반복하고 각 프록시 방법에 대한 해당 방법 유형 정적 도메인을 생성 한 다음 필드 컬렉션에 추가하십시오.
3. 프록시 메소드의 맵 컬렉션을 반복하고 각 프록시 방법에 해당하는 메소드 인포 객체를 생성 한 다음 메소드 컬렉션에 추가하십시오.
4. 프록시 클래스의 정적 초기화 방법을 생성합니다. 이 정적 초기화 방법은 주로 각 프록시 메소드의 참조를 해당 정적 필드에 할당합니다.
위의 분석을 통해 JDK Dynamic Proxy는 결국 다음 구조의 프록시 클래스를 생성 할 것임을 대략 알 수 있습니다.
Public Class Proxy0 확장 프록시 구현 userdao {// 1 단계, 생성자 보호 proxy0 (invocationHandler h) {super (h); } // 2 단계, 정적 도메인 생성 비공개 정적 메소드 M1; // 해시 코드 메소드 개인 정적 메소드 M2; // 동등한 메소드 개인 정적 메소드 M3; // TOSTRING 메소드 개인 정적 메소드 M4; // ... // 3 단계, 프록시 메소드 생성 @Override public int hashcode () {try {return (int) h.invoke (this, m1, null); } catch (Throwable e) {새로운 미등성 무적 외과 (e); }} @override public boolean equals (object obj) {try {object [] args = new Object [] {obj}; return (부울) H.Invoke (this, m2, args); } catch (Throwable e) {새로운 미등성 무적 외과 (e); }} @override public String toString () {try {return (string) h.invoke (this, m3, null); } catch (Throwable e) {새로운 미등성 무적 외과 (e); }} @override public void save (user user) {try {// 매개 변수 배열을 구성하십시오. H.invoke (this, m4, args); } catch (Throwable e) {새로운 미등성 무적 외과 (e); }} // 4 단계, 정적 초기화 메소드를 생성 static {try {class c1 = class.forname (object.class.getName ()); class c2 = class.forname (userdao.class.getName ()); m1 = c1.getMethod ( "Hashcode", null); m2 = c1.getMethod ( "equals", new class [] {object.class}); m3 = c1.getMethod ( "tostring", null); m4 = c2.getMethod ( "save", new class [] {user.class}); // ...} catch (예외 e) {e.printstacktrace (); }}}이 시점에서 JDK 소스 코드의 계층화 된 분석 및 심층 탐색 후, 우리는 동적으로 생성 된 프록시 클래스의 원래 모양을 복원했으며, 이전 질문 중 일부도 잘 설명되었습니다.
1. 프록시 클래스는 기본적으로 Porxy 클래스를 상속합니다. Java는 단일 상속만을 지원하기 때문에 JDK Dynamic Proxy는 인터페이스 만 구현할 수 있습니다.
2. 프록시 메소드는 invoctionHandler의 invoke () 메소드를 호출하므로 invoke () 메소드의 invoctionHandler를 다시 작성해야합니다.
3. invoke () 메소드, 프록시 인스턴스 자체, 대상 메소드 및 대상 메소드 매개 변수를 호출 할 때, invoke () 메소드의 매개 변수가 어떻게 나오는지 설명합니다.
새로 구성된 proxy0을 프록시 클래스로 사용하여 다시 테스트하면 최종 결과가 JDK를 사용하여 동적으로 생성 된 프록시 클래스와 동일하다는 것을 알 수 있습니다. 다시 한번, 우리의 분석은 신뢰할 수 있고 정확합니다. 이 시점에서 JDK Dynamic Proxy 시리즈 기사가 종료되도록 발표되었습니다. 이 시리즈의 분석을 통해 저자는 그의 마음에 오랫동안 의심을 불러 일으켰으며, JDK 다이나믹 프록시에 대한 독자의 이해가 한 걸음 더 나아 갔다고 생각합니다. 그러나 종이에 대한 지식은 항상 얕습니다. JDK Dynamic Proxy 기술을 더 잘 마스터하려면 독자는이 일련의 기사를 참조하여 JDK 소스 코드를 스스로 확인하거나 저자와의 학습 경험을 교환하고 저자의 부적절한 분석을 지적하고 함께 배우고 함께 진행할 수 있습니다.
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.