자바 JDK 동적 에이전트 의 기본 원리 상세 소개
본 고 는 주로 JDK 동적 에이전트 의 기본 원 리 를 소개 하여 사람들 로 하여 금 JDK Proxy 를 더욱 깊이 이해 하 게 하고 그 이 유 를 알 게 한다.JDK 동적 에이전트 의 진정한 원리 와 생 성 과정 을 알 고 있 습 니 다.앞으로 JDK Proxy 를 쓰 면 demo 를 찾 지 않 아 도 완벽 한 Proxy 를 맨손으로 쓸 수 있 습 니 다.다음은 먼저 간단 한 Demo 를 살 펴 보 겠 습 니 다.후속 적 인 분석 과정 은 모두 이 Demo 에 의 해 소개 되 고 예 를 들 어 JDK 1.8 로 실 행 됩 니 다.
JDK Proxy HelloWorld
package com.yao.proxy;
/**
* Created by robin
*/
public interface Helloworld {
void sayHello();
}
package com.yao.proxy;
import com.yao.HelloWorld;
/**
* Created by robin
*/
public class HelloworldImpl implements HelloWorld {
public void sayHello() {
System.out.print("hello world");
}
}
package com.yao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Created by robin
*/
public class MyInvocationHandler implements InvocationHandler{
private Object target;
public MyInvocationHandler(Object target) {
this.target=target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method :"+ method.getName()+" is invoked!");
return method.invoke(target,args);
}
}
package com.yao.proxy;
import com.yao.HelloWorld;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
/**
* Created by robin
*/
public class JDKProxyTest {
public static void main(String[]args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// , , 。
Class<?> proxyClass= Proxy.getProxyClass(JDKProxyTest.class.getClassLoader(),HelloWorld.class);
final Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class);
final InvocationHandler ih = new MyInvocationHandler(new HelloworldImpl());
HelloWorld helloWorld= (HelloWorld)cons.newInstance(ih);
helloWorld.sayHello();
// ,
/*
HelloWorld helloWorld=(HelloWorld)Proxy.
newProxyInstance(JDKProxyTest.class.getClassLoader(),
new Class<?>[]{HelloWorld.class},
new MyInvocationHandler(new HelloworldImpl()));
helloWorld.sayHello();
*/
}
}
위의 코드 를 실행 하면 간단 한 JDK Proxy 가 실 현 됩 니 다.에이전트 생 성 과정
우리 가 매일 JDK 동적 에이전트 라 고 부 르 는 이 유 는 이 에이전트 클 라 스 가 JDK 가 실행 할 때 동적 으로 생 성 되 기 때 문 입 니 다.프 록 시 생 성 과정 을 설명 하기 전에-dsun.misc.ProxyGenerator.saveGenerated Files=true 라 는 매개 변 수 를 JVM 시작 매개 변수 에 추가 합 니 다.JDK 가 동적 으로 생 성 한 proxy class 의 바이트 코드 를 하 드 디스크 에 저장 하여 구체 적 으로 proxy 를 생 성 하 는 내용 을 볼 수 있 도록 도와 주 는 역할 을 합 니 다.제 가 사용 하 는 Intellij IDEA 는 프 록 시 클 라 스 가 생 성 된 후에 프로젝트 의 루트 디 렉 터 리 에 직접 놓 여 있 고 구체 적 인 가방 이름 을 디 렉 터 리 구조 로 합 니 다.
프 록 시 클래스 생 성 과정 은 주로 두 부분 을 포함한다.
프 록 시 바이트 생 성
4.567917.바이트 코드 를 들 어 오 는 클래스 로 더 를 통 해 가상 컴퓨터 에 불 러 옵 니 다Proxy 클래스 의 getProxyClass 방법 입구:클래스 로 더 와 interface 가 필요 합 니 다.
그리고 getProxyClass 0 방법 을 호출 합 니 다.현재 인터페이스의 프 록 시 클래스 가 존재 하면 캐 시 에서 되 돌아 오고 존재 하지 않 으 면 ProxyClassFactory 를 통 해 만 듭 니 다.인터페이스 인터페이스 수량 에 대한 제한 이 있어 65535 를 초과 해 서 는 안 된다 는 것 을 분명히 볼 수 있다.그 중에서 proxyClassCache 의 구체 적 인 초기 화 정 보 는 다음 과 같 습 니 다.
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
그 중에서 프 록 시 클래스 를 만 드 는 구체 적 인 논 리 는 ProxyClassFactory 의 apply 방법 을 통 해 만 든 것 입 니 다.ProxyClassFactory 의 논 리 는 패키지 이름 의 생 성 논 리 를 포함 하고 ProxyGenerator 를 호출 합 니 다.generateProxyClass 는 프 록 시 클래스 를 생 성하 고 프 록 시 바이트 코드 를 JVM 에 불 러 옵 니 다.
1.패키지 이름 생 성 논 리 는 기본적으로 com.sun.proxy 입 니 다.프 록 시 클래스 가 non-Public proxy interface 라면 프 록 시 인터페이스 와 같은 패키지 이름 을 사용 합 니 다.클래스 이름 은 기본적으로$Proxy 에 자체 증가 하 는 정수 치 를 추가 합 니 다.
2.패키지 이름 클래스 가 준비 되면 프 록 시 Generator.generateProxyClass 를 통 해 구체 적 으로 들 어 오 는 인터페이스 에 따라 프 록 시 바이트 코드 를 만 듭 니 다.-dsun.misc.ProxyGenerator.saveGenerated Files=true 이 매개 변 수 는 이 방법 에 작용 합 니 다.true 라면 디스크 에 바이트 코드 를 저장 합 니 다.프 록 시 클래스 에서 모든 프 록 시 방법 논 리 는 invocationHander 를 호출 하 는 invoke 방법 입 니 다.이것 은 뒤에 구체 적 인 프 록 시 컴 파일 결 과 를 볼 수 있 습 니 다.
3.들 어 오 는 클래스 로 더 를 통 해 JVM 에 바이트 코드 를 불 러 옵 니 다:defineClass 0(loader,proxy Name,proxy ClassFile,0,proxy ClassFile.length).
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
//
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
* -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// JVM
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
우 리 는 프 록 시 클래스 의 바이트 코드 에 따라 역 컴 파일 할 수 있 습 니 다.다음 과 같은 결 과 를 얻 을 수 있 습 니 다.그 중에서 Hello World 는 sayHello 방법 만 있 지만 프 록 시 클래스 에는 Object 의 세 가지 방법 이 있 습 니 다.equals,toString,hashCode.대리 의 대략적인 구 조 는 4 부분 을 포함한다.
4.567917.정적 필드:대 리 된 인터페이스 모든 방법 에 대응 하 는 정적 방법 변수 가 있 습 니 다4.567917.정적 블록:주로 반사 로 정적 방법 변 수 를 초기 화 합 니 다
package com.sun.proxy;
import com.yao.HelloWorld;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements HelloWorld {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.yao.HelloWorld").getMethod("sayHello", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
일반적인 질문:1.toString()hashCode()equal()방법 은 논 리 를 호출 합 니 다.이 세 가지 Object 의 방법 은 다른 인터페이스 방법 으로 논 리 를 처리 하 는 것 과 마찬가지 로 invocationHandler 논 리 를 거 쳐 위의 바이트 코드 결 과 를 통 해 뚜렷하게 볼 수 있 습 니 다.다른 Object 의 방법 은 대리 처리 논리 로 가지 않 고 Proxy 가 계승 하 는 Object 의 방법 논리 로 갑 니 다.
2.interface 는 equals,toString hashCode 방법 을 포함 할 때 일반 인터페이스 방법 을 처리 하 는 것 과 마찬가지 로 invocation handler 논 리 를 실행 하고 대상 이 다시 쓴 논 리 를 기준 으로 트리거 방법 논 리 를 실행 합 니 다.
3.interface 는 중복 되 는 방법 으로 서명 합 니 다.인터페이스 가 들 어 오 는 순 서 를 기준 으로 앞에서 누 구 를 사용 하 는 방법 입 니 다.대리 류 에 서 는 하나만 보류 하고 중복 되 는 방법 으로 서명 하지 않 습 니 다.
읽 어 주 셔 서 감사합니다. 여러분 에 게 도움 이 되 기 를 바 랍 니 다.본 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
【Java】 STS (Eclipse)에 AdoptOpen JDK 설정· Eclipse를 2020-09로 업데이트하면 jre로 Eclipse를 움직이고 있습니다! 라는 메시지가 나온다. ・메모리 상태의 파악을 위해 MissionControl 넣으려고 하면 JDK로 움직이지 않으면 안 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.