목차
소개
예
소개
동적 프록시 클래스는 클래스 인스턴스의 인터페이스 중 하나를 통해 메서드 호출이 인코딩되고 균일 한 인터페이스를 통해 다른 개체에 전달되도록 런타임에 지정된 인터페이스 목록을 구현하는 클래스입니다. 따라서 동적 프록시 클래스를 사용하여 컴파일 타임 도구와 같이 프록시 클래스를 미리 생성하지 않고도 인터페이스 목록에 대한 형식 안전 프록시 개체를 만들 수 있습니다.동적 프록시 클래스의 인스턴스에 대한 메서드 호출은 인스턴스의 호출 핸들러에서 단일 메서드에 패치되며,호출된 메서드를 식별하는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.getProxyClass
은IllegalArgumentException
을 발생시킵니다. 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.ProtectionDomain
은java.lang.Object
과 같은 부트스트랩 클래스 로더에 의해 로드된 시스템 클래스의 코드와 동일합니다. 이 보호 도메인은 일반적으로java.security.AllPermission
이 부여됩니다.
프록시 인스턴스 만들기
각 프록시 클래스에는 인터페이스InvocationHandler
의 구현인 하나의 공용 생성자가 있습니다.
각 프록시 인스턴스에는 해당 생성자에 전달된 호출 처리기 개체가 연결되어 있습니다. 이 메서드는Proxy.getProxyClass
을 호출하는 동작과 호출 처리기를 사용하여 생성자를 호출하는 동작을 결합합니다.Proxy.newProxyInstance
은Proxy.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
이 발생합니다.UndeclaredThrowableException
은invoke
방법에 의해 발생한 예외로 구성됩니다. - 프록시 인스턴스에서
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.Proxy
는java.io.Serializable
을 구현하므로 프록시 인스턴스는 이 섹션에 설명된 대로 직렬화할 수 있습니다. 그러나java.io.Serializable
에 할당할 수 없는 호출 처리기가 프록시 인스턴스에 포함되어 있는 경우java.io.ObjectOutputStream
에 해당 인스턴스가 기록되면java.io.NotSerializableException
가 발생합니다. 유 proxyclasses,구현하는java.io.Externalizable
는 같은 효과를 존중하는 직렬화로 구현하는java.io.Serializable
:는writeExternal
및readExternal
의 방법을Externalizable
인터페이스코 호출에 aproxy 인스턴스(또는 호출 처리기)의 일부로 itsserialization 과정입니다. 모든Class
개체와 마찬가지로 프록시 클래스의Class
개체는 항상 직렬화 가능합니다.
프록시 클래스에는 직렬화 가능 필드와0L
의serialVersionUID
가 없습니다. 즉,프록시 클래스에 대한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_PROXYCLASSDESC
newHandle 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
메서드의 기본 동작으로 만든 동일한 클래스 로더 선택입니다. 이 같은 값loader
은Proxy.getProxyClass
으로 전달 된 클래스 로더이기도합니다.Proxy.getProxyClass
이IllegalArgumentException
을 던지면resolveClass
는IllegalArgumentException
을 포함하는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
,proxyEquals
및proxyToString
도 재정의할 수 있습니다.
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
,equals
및toString
메서드에 대한Method
개체를 캐싱하고 비교하는 대신java.lang.Object
에서 메서드 이름이 오버로드되지 않기 때문에 문자열 이름으로 개체를 일치시킬 수 있습니다.