동적 프록시 클래스

목차

소개

소개

동적 프록시 클래스는 클래스 인스턴스의 인터페이스 중 하나를 통해 메서드 호출이 인코딩되고 균일 한 인터페이스를 통해 다른 개체에 전달되도록 런타임에 지정된 인터페이스 목록을 구현하는 클래스입니다. 따라서 동적 프록시 클래스를 사용하여 컴파일 타임 도구와 같이 프록시 클래스를 미리 생성하지 않고도 인터페이스 목록에 대한 형식 안전 프록시 개체를 만들 수 있습니다.동적 프록시 클래스의 인스턴스에 대한 메서드 호출은 인스턴스의 호출 핸들러에서 단일 메서드에 패치되며,호출된 메서드를 식별하는java.lang.reflect.Method개체와 인수를 포함하는Object형식의 배열로 인코딩됩니다.

동적 프록시 클래스는 응용 프로그램 또는 라이브러리에서 유용합니다. 예를 들어 응용 프로그램에서 동적 프록시 클래스를 사용하여 여러 임의 이벤트 리스너 인터페이스를 구현하는 개체를 만들 수 있습니다.

동적 프록시 클래스(아래 프록시클래스라고도 함)는 클래스가 생성될 때 런타임에 지정된 인터페이스 목록을 구현하는 클래스입니다.

프록시 인터페이스는 프록시 클래스에 의해 구현되는 인터페이스입니다.

프록시 인스턴스는 프록시 클래스의 인스턴스입니다.

프록시 클래스 만들기

프록시 클래스와 그 인스턴스는 자바 클래스의 정적 메서드를 사용하여 만들어집니다.랭반영.프록시.

Proxy.getProxyClass메서드는 클래스 로더 및 인터페이스 배열이 지정된 프록시 클래스에 대한java.lang.Class개체를 반환합니다. 프록시 클래스는 지정된 클래스 로더에서 정의되며 지원되는 모든 인터페이스를 구현합니다. 인터페이스의 동일한 순열에 대한 프록시 클래스가 클래스 로더에 이미 정의된 경우 기존 프록시 클래스가 반환됩니다.

다음과 같은 매개 변수에 대한 몇 가지 제한 사항이 있습니다.Proxy.getProxyClass:

  • interfaces배열의Class개체는 모두 클래스나 기본 형식이 아닌 인터페이스를 나타내야 합니다.
  • interfaces배열의 두 요소는 동일한Class개체를 참조할 수 없습니다.
  • 지정된 클래스 로더를 통해 모든 인터페이스 유형을 이름으로 표시해야 합니다. 즉,클래스 로더cl및 모든 인터페이스i의 경우 다음 표현식이 참이어야 합니다:
     Class.forName(i.getName(), false, cl) == i
  • 그렇지 않으면 프록시 클래스가 정의된 패키지에 관계없이 모든 인터페이스를 구현할 수 없습니다.
  • 지정된 인터페이스의 멤버 메서드 집합에 대해 동일한 서명을 갖습니다:
    • 모든 메서드의 반환 형식이 기본 형식 또는 보이드 인 경우 모든 메서드는 동일한 반환 형식을 가져야합니다.
    • 그렇지 않으면 메서드 중 하나에 나머지 메서드의 모든 반환 형식에 할당할 수 있는 반환 형식이 있어야 합니다.
  • 결과 프록시 클래스는 가상 컴퓨터에 의해 클래스에 부과된 제한을 초과해서는 안 됩니다. 이 경우interfaces배열의 크기는 65535 를 초과할 수 없습니다.

이러한 제한 사항 중 하나라도 위반되면Proxy.getProxyClassIllegalArgumentException을 발생시킵니다. interfaces배열 인수 또는 해당 요소 중 하나가null이면NullPointerException이 재생됩니다.

지정된 프록시 인터페이스의 순서는 다음과 같습니다. 프록시 클래스는 둘 이상의 프록시 인터페이스가 동일한 이름 및 매개 변수 서명을 가진 메서드를 공유하는 경우에 결정적 방법 호출 인코딩을 제공하기 위해 해당 프록시 인터페이스의 이론으로 구분됩니다.프록시클래스의 캐시는 해당 로더와 인터페이스 리스트에 의해 키 입력된다.이 구현은 클래스 로더,인터페이스 및 프록시 클래스를 참조하여 클래스 로더 및 해당 클래스의 모든 클래스가 적절한 경우 복식 수집되는 것을 방지하지 않도록 주의해야 합니다.

프록시 클래스 속성

프록시 클래스의 속성은 다음과 같습니다:

  • 프록시 클래스는 공개적이고 최종적이며 추상이 아닙니다.
  • 프록시 클래스의 정규화되지 않은 이름이 지정되지 않았습니다. 그러나"$Proxy"문자열로 시작하는 클래스 이름의 공백은 프록시 클래스 용으로 예약됩니다.
  • 프록시 클래스가java.lang.reflect.Proxy확장됩니다.
  • 프록시 클래스는 동일한 순서로 지정된 인터페이스를 정확하게 구현합니다.
  • 프록시 클래스가 비공개 인터페이스를 구현하면 해당 인터페이스와 동일한 패키지에 정의됩니다. 그렇지 않으면 프록시 클래스의 패키지도 지정되지 않습니다. 패키지 봉인에서는 프록시 클래스가 런타임에 특정 패키지에서 성공적으로 정의되는 것을 방지할 수 없으며 동일한 클래스 로더와 특정 서명자와 동일한 패키지에 이미 정의된 클래스도 없습니다.
  • 프록시 클래스는 만들 때 지정된 모든 인터페이스를 구현하므로Class객체에서getInterfaces를 호출하면 해당 인터페이스의 모든 메서드를 포함하는Method개체의 배열을 반환하고Class객체에서를 호출하면 해당 인터페이스의 모든 메서드를 포함하는Method개체의 배열을 반환합니다.예상.
  • Proxy.isProxyClass메서드는 프록시 클래스(Proxy.getProxyClass에 의해 반환 된 클래스 또는Proxy.newProxyInstance에 의해 반환 된 객체의 클래스)를 전달하고 그렇지 않으면 거짓입니다. 이 메서드의 신뢰성은 보안 결정을 내리는 데 사용할 수 있는 기능에 중요하므로 구현은 해당 클래스가java.lang.reflect.Proxy확장되는지 테스트해서는 안 됩니다.
  • 프록시 클래스의java.security.ProtectionDomainjava.lang.Object과 같은 부트스트랩 클래스 로더에 의해 로드된 시스템 클래스의 코드와 동일합니다. 이 보호 도메인은 일반적으로java.security.AllPermission이 부여됩니다.

프록시 인스턴스 만들기

각 프록시 클래스에는 인터페이스InvocationHandler의 구현인 하나의 공용 생성자가 있습니다.

각 프록시 인스턴스에는 해당 생성자에 전달된 호출 처리기 개체가 연결되어 있습니다. 이 메서드는Proxy.getProxyClass을 호출하는 동작과 호출 처리기를 사용하여 생성자를 호출하는 동작을 결합합니다.Proxy.newProxyInstanceProxy.getProxyClass과 같은 이유로IllegalArgumentException을 던졌습니다.

프록시 인스턴스 속성

프록시 인스턴스의 속성은 다음과 같습니다:

  • 프록시 인스턴스proxy과 프록시 클래스Foo에 의해 구현된 인터페이스 중 하나가 주어지면 다음 식은 참 반환됩니다.ClassCastException):
     (Foo) proxy
  • 정적Proxy.getInvocationHandler메서드는 인수로 전달된 프록시 인스턴스와 연결된 호출 처리기를 반환합니다. Proxy.getInvocationHandler로 전달된 개체가 프록시 인스턴스가 아닌 경우IllegalArgumentException이 발생합니다.
  • 프록시 인스턴스의 인터페이스 메서드 호출이 인코딩되어 호출 처리기의invoke메서드에 전달됩니다.

    프록시 인스턴스 자체는Object형식의invoke의 첫 번째 인수로 전달됩니다.

    invoke에 전달된 두 번째 인수는 프록시 인스턴스에서 호출된 인터페이스 메서드에 해당하는java.lang.reflect.Method인스턴스가 됩니다. 이 인터페이스는 프록시 클래스가 메서드를 통해 상속하는 프록시 인터페이스의 슈퍼인터페이스일 수 있습니다.

    invoke에 전달된 세 번째 인수는 프록시 인스턴스에서 메서드 호출에 전달된 인수의 값을 포함하는 개체의 배열입니다. 기본 형식의 인수는java.lang.Integer또는java.lang.Boolean과 같이 적절한 기본 래퍼 클래스의 인스턴스에 래핑됩니다. invoke메서드의 구현은 이 배열의 내용을 자유롭게 수정할 수 있습니다.

    invoke메서드에서 반환된 값은 프록시 인스턴스에서 메서드 호출의 반환 값이 됩니다. 그렇지 않으면 선언된 반환 형식에 할당할 수 있는 형식이어야 합니다. invoke에서 반환된 값이null이고 인터페이스 메서드의 반환 형식이 기본 형식이면NullPointerException이 프록시 인스턴스의 메서드 침입에 의해 발생합니다. 그렇지 않으면invoke에 의해 반환된 값이 위에서 설명한 대로 메서드의 선언된 반환 형식과 호환되지 않으면ClassCastException가 프록시에 의해 발생합니다.

    invoke메서드에서 예외가 발생하면 프록시 인스턴스의 메서드 호출에서도 예외가 발생합니다.예외 형식은 인터페이스 메서드의 시그니처에서 선언된 예외 형식 중 하나에 할당하거나 체크되지 않은 예외 형식java.lang.RuntimeException또는java.lang.Error에 할당할 수 있어야 합니다. 인터페이스 방법의throws절에 선언된 예외 형식에 할당할 수 없는invoke에 의해 확인된 예외가 발생하면 프록시 인스턴스의 메서드 호출에 의해UndeclaredThrowableException이 발생합니다. UndeclaredThrowableExceptioninvoke방법에 의해 발생한 예외로 구성됩니다.

  • 프록시 인스턴스에서java.lang.Object에 선언된hashCode,equals또는toString메서드의 호출은 위에서 설명한 것처럼 인터페이스 메서드 호출이 인코딩되고 디스패치되는 것과 같은 방식으로 호출 처리기의invoke메서드에 인코딩되고 디스패치됩니다. invoke로 전달된Method개체의 선언 클래스가java.lang.Object이 됩니다. java.lang.Object에서 상속 된 프록시 인스턴스의 다른 공용 메서드는 프록시 클래스에 의해 오버 라이드되지 않으므로 이러한 메서드의 호출은java.lang.Object의 인스턴스에 대해 수행하는 것처럼 작동합니다.

다중 인터페이스에 중복 된 메서드

프록시 클래스의 두 개 이상의 인터페이스에 동일한 이름 및 매개 변수 서명 메서드가 포함 된 경우 프록시클래스의 인터페이스 순서가 중요 합니다. 프록시의 메서드를 통해 호출된 인터페이스의 참조 형식에서 선언 클래스를 할당할 수 있습니다. 생성된 프록시 클래스에서 해당 메서드 구현이 호출된 인터페이스를 확인할 수 없기 때문에 이 제한이 존재합니다. 따라서 프록시 인스턴스에서 중복 메서드를 호출할 때 메서드 호출이 발생한 참조 유형에 관계없이 프록시 클래스의 인터페이스 목록에서 메서드를 포함하는 가장 중요한 인터페이스의 메서드에 대한Method개체가 호출 처리기의invoke메서드에 전달됩니다.이러한 메서드를 호출할 때,Method개체는 위치 지정 처리기에 전달된java.lang.Object을 선언 클래스로 갖습니다. 즉,java.lang.Object의 최종적이지 않은 공개 메서드는 호출 핸들러에 전달할 개체를 결정하기 위해 모든 프록시 인터페이스보다 논리적으로 앞에 옵니다.

참고 또한 중복 메서드를 통해 호출할 수 있는 프록시 인터페이스의 모든 메서드의throws절에서 예외 형식 중 하나에 할당할 수 있는invoke메서드는 검사된 예외 형식만 던질 수 있습니다. invoke메서드에서 호출할 수 있는 프록시 인터페이스 중 하나의 메서드에서 선언한 예외 형식에 할당할 수 없는 확인된 예외가 발생하면 확인되지 않은UndeclaredThrowableException이 프록시 인스턴스의 호출에 의해 발생합니다. 이 제한은invoke메서드로 전달된Method객체에서getExceptionTypes을 호출하여 반환된 모든 예외 형식을invoke메서드에서 반드시 성공적으로 던질 수 없음을 의미합니다.

직렬화

java.lang.reflect.Proxyjava.io.Serializable을 구현하므로 프록시 인스턴스는 이 섹션에 설명된 대로 직렬화할 수 있습니다. 그러나java.io.Serializable에 할당할 수 없는 호출 처리기가 프록시 인스턴스에 포함되어 있는 경우java.io.ObjectOutputStream에 해당 인스턴스가 기록되면java.io.NotSerializableException가 발생합니다. 유 proxyclasses,구현하는java.io.Externalizable는 같은 효과를 존중하는 직렬화로 구현하는java.io.Serializable:는writeExternalreadExternal의 방법을Externalizable인터페이스코 호출에 aproxy 인스턴스(또는 호출 처리기)의 일부로 itsserialization 과정입니다. 모든Class개체와 마찬가지로 프록시 클래스의Class개체는 항상 직렬화 가능합니다.

프록시 클래스에는 직렬화 가능 필드와0LserialVersionUID가 없습니다. 즉,프록시 클래스에 대한Class개체가java.io.ObjectStreamClass의 정적lookup메서드에 전달되면 반환된ObjectStreamClass인스턴스는 다음과 같은 속성을 갖습니다:

  • getSerialVersionUID메서드를 호출하면0L이 반환됩니다.
  • getFields메서드를 호출하면 길이가 0 인 배열이 반환됩니다.
  • String인수를 사용하여getField메서드를 호출하면null이 반환됩니다.

개체 직렬화를 위한 스트림 프로토콜은TC_PROXYCLASSDESC라는 형식 코드를 지원합니다.; 그것의 종류와 값을 aredefined 여 다음과 같은 일정한 분야에서java.io.ObjectStreamConstants인터페이스:

 final static byte TC_PROXYCLASSDESC = (byte)0x7D;

문법을 포함한 다음과 같은 두 가지 규칙,firstbeing 대체의 확장은 원래 newClassDescrule:

newClassDesc:
TC_PROXYCLASSDESCnewHandle proxyClassDescInfo

proxyClassDescInfo:
(int)<count>proxyInterfaceName classAnnotationsuperClassDesc

proxyInterfaceName:
(utf)

Class개체를Proxy.isProxyClass메서드로 우회하는 것으로 결정된 대로ObjectOutputStream이 프록시 클래스인 클래스에 대한 클래스 설명자를 직렬화하면 위의 규칙에 따라TC_CLASSDESC대신TC_PROXYCLASSDESC형식 코드를 사용합니다. 프록시 클래스에서 구현된 모든 인터페이스의 이름은Class개체의getInterfaces메서드를 호출하여 반환되는 순서입니다. 클래스 내포 및 슈퍼클래스 항목은 클래스 설명 규칙에서와 동일한 의미를 갖습니다. 이 설명자를 포함하면 프록시 인스턴스에 대한 클래스Proxy의 직렬화 표현의 진화를 허용합니다.

비 프록시 클래스의 경우ObjectOutputStream는 하위 클래스가 특정 클래스의 스트림에 사용자 지정 데이터를 쓸 수 있도록 보호 된annotateClass메서드를 호출합니다. 프록시 클래스의 경우annotateClass대신java.io.ObjectOutputStream의 다음 방법을 프록시 클래스의Class개체와 함께 호출합니다:

 protected void annotateProxyClass(Class cl) throws IOException;

ObjectOutputStream에서annotateProxyClass의 기본 구현은 아무 것도 수행하지 않습니다.

ObjectInputStream형식 코드TC_PROXYCLASSDESC가 발생하면 위에서 설명한 대로 형식이 지정된 스트림에서 프록시 클래스에 대한 클래스 설명자를 역직렬화합니다. 대신 호출 하는resolveClass메서드Class개체를 확인 하려면 클래스 설명자에 대 한java.io.ObjectInputStream에서 다음 메서드를 호출 합니다:

 protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;

프록시 클래스 설명자에서 역직렬화된 인터페이스 이름 목록은interfaces인수로resolveProxyClass로 전달됩니다.

ObjectInputStream에서resolveProxyClass의 기본 구현은interfaces매개 변수에 이름이 지정된 인터페이스의Class개체 목록과 함께Proxy.getProxyClass을 호출한 결과를 반환합니다. 각 인터페이스 이름i에 사용되는Class객체는

 Class.forName(i, false, loader)

를 호출하여 다시 튜닝된 값입니다. 이 메서드는resolveClass메서드의 기본 동작으로 만든 동일한 클래스 로더 선택입니다. 이 같은 값loaderProxy.getProxyClass으로 전달 된 클래스 로더이기도합니다.Proxy.getProxyClassIllegalArgumentException

을 던지면resolveClassIllegalArgumentException을 포함하는ClassNotFoundException를 던집니다.

프록시 클래스에는 직렬화 가능한 자체 필드가 없기 때문에 프록시 인스턴스의 스트림 표현에서 클래스 데이터는 수퍼 클래스java.lang.reflect.Proxy에 대한 인스턴스 데이터로 구성됩니다. Proxy에는 프록시 인스턴스에 대한 호출 핸들러가 포함된 1 개의 직렬화 가능 필드h가 있습니다.

예제

다음은 인터페이스의 임의리스트를 구현하는 개체에 대한 메서드 호출 전후에 메시지를 출력하는 간단한 예제입니다:

public interface Foo { Object bar(Object obj) throws BazException;}public class FooImpl implements Foo { Object bar(Object obj) throws BazException { // ... }}public class DebugProxy implements java.lang.reflect.InvocationHandler { private Object obj; public static Object newInstance(Object obj) { return java.lang.reflect.Proxy.newProxyInstance( obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new DebugProxy(obj)); } private DebugProxy(Object obj) { this.obj = obj; } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Object result; try { System.out.println("before method " + m.getName()); result = m.invoke(obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (Exception e) { throw new RuntimeException("unexpected invocation exception: " + e.getMessage()); } finally { System.out.println("after method " + m.getName()); } return result; }}

Foo인터페이스를 구현하기 위해DebugProxy를 구성하고 해당 메서드 중 하나를 호출합니다:

 Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);

다음은java.lang.Object에서 상속된 메서드에 대한 기본 프록시 동작을 제공하고 호출된 메서드의 인터페이스에 따라 고유 개체에 대한 특정 프록시 메서드 호출의 위임을 구현하는 유틸리티 호출 처리기 클래스의 예입니다:

import java.lang.reflect.*;public class Delegator implements InvocationHandler { // preloaded Method objects for the methods in java.lang.Object private static Method hashCodeMethod; private static Method equalsMethod; private static Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode", null); equalsMethod = Object.class.getMethod("equals", new Class { Object.class }); toStringMethod = Object.class.getMethod("toString", null); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } private Class interfaces; private Object delegates; public Delegator(Class interfaces, Object delegates) { this.interfaces = (Class) interfaces.clone(); this.delegates = (Object) delegates.clone(); } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Class declaringClass = m.getDeclaringClass(); if (declaringClass == Object.class) { if (m.equals(hashCodeMethod)) { return proxyHashCode(proxy); } else if (m.equals(equalsMethod)) { return proxyEquals(proxy, args); } else if (m.equals(toStringMethod)) { return proxyToString(proxy); } else { throw new InternalError( "unexpected Object method dispatched: " + m); } } else { for (int i = 0; i < interfaces.length; i++) { if (declaringClass.isAssignableFrom(interfaces)) { try { return m.invoke(delegates, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } } return invokeNotDelegated(proxy, m, args); } } protected Object invokeNotDelegated(Object proxy, Method m, Object args) throws Throwable { throw new InternalError("unexpected method dispatched: " + m); } protected Integer proxyHashCode(Object proxy) { return new Integer(System.identityHashCode(proxy)); } protected Boolean proxyEquals(Object proxy, Object other) { return (proxy == other ? Boolean.TRUE : Boolean.FALSE); } protected String proxyToString(Object proxy) { return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode()); }}

Delegator의 하위 클래스는invokeNotDelegated를 재정의하여 다른 개체에 직접 위임되지 않도록 프록시 방법 호출 동작을 구현할 수 있으며proxyHashCode,proxyEqualsproxyToString도 재정의할 수 있습니다.

Foo인터페이스 구현을 위해Delegator를 구성하려면:

 Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));

위의Delegator클래스의 구현은 최적화 된 것보다 더 설명하기위한 것입니다; 예를 들어hashCode,equalstoString메서드에 대한Method개체를 캐싱하고 비교하는 대신java.lang.Object에서 메서드 이름이 오버로드되지 않기 때문에 문자열 이름으로 개체를 일치시킬 수 있습니다.

답글 남기기

이메일 주소는 공개되지 않습니다.