오늘 우연히 JDK의 동적 프록시를 살펴보고 싶었습니다. 이전에 그것에 대해 조금 알고 있었고 단지 사용법을 테스트하고 싶었기 때문에 다음 인터페이스와 클래스를 신속하게 작성했습니다.
인터페이스 클래스: UserService.java
다음과 같이 코드 코드를 복사합니다 .
패키지 com.yixi.proxy;
공개 인터페이스 UserService {
공개 int 저장();
공개 무효 업데이트(int id);
}
구현 클래스: UserServiceImpl.java
다음과 같이 코드 코드를 복사합니다 .
패키지 com.yixi.proxy;
공개 클래스 UserServiceImpl은 UserService를 구현합니다.
@보수
공개 int 저장() {
System.out.println("사용자 저장....");
1을 반환합니다.
}
@보수
공개 무효 업데이트(int id) {
System.out.println("사용자 업데이트 " + id);
}
}
그런 다음 나는 내가 원했던 InvocationHandler를 걱정스럽게 작성했습니다. 이 함수의 기능은 매우 간단하며 메서드 실행의 시작 시간과 종료 시간을 기록하는 것입니다.
TimeInvocationHandler.java
다음과 같이 코드 코드를 복사합니다 .
패키지 com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
공개 클래스 TimeInvocationHandler는 InvocationHandler를 구현합니다.
@보수
공용 객체 호출(객체 프록시, 메소드 메소드, Object[] args)
Throwable {를 던집니다.
System.out.println("startTime : " +System.currentTimeMillis());
객체 obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
반환 객체;
}
}
모든 준비가 완료되면 당연히 테스트 작성을 시작할 시간입니다!
Test.java
다음과 같이 코드 코드를 복사합니다 .
패키지 com.yixi.proxy;
import java.lang.reflect.Proxy;
공개 클래스 테스트 {
공용 정적 void main(String[] args) { 9 TimeInvocationHandler timeHandler = new TimeInvocationHandler();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.저장();
}
}
행복하게 실행되었지만 아무런 표정도 나오지 않았습니다. 결과는 화면을 가득 채운 예외였습니다.
다음과 같이 코드 코드를 복사합니다 .
시작 시간: 1352877835040
시작 시간: 1352877835040
시작 시간: 1352877835040
스레드 "main"의 예외 java.lang.reflect.UndeclaredThrowableException
$Proxy0.update(출처를 알 수 없음)
com.yixi.proxy.Test.main(Test.java:11)에서
원인: java.lang.reflect.InvocationTargetException
sun.reflect.NativeMethodAccessorImpl.invoke0(네이티브 메소드)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)에서
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)에서
com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)에서
... 2개 더 보기
com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12) 예외는 TimeInvocationHandle의 12번째 줄에 있는 문제를 명확하게 알려줍니다.
다음과 같이 코드 코드를 복사합니다 .
공용 객체 호출(객체 프록시, 메소드 메소드, Object[] args)
Throwable {를 던집니다.
System.out.println("startTime : " +System.currentTimeMillis());
객체 obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
반환 객체;
}
방법에는 문제가 없습니다! Invoke() 메소드는 method.invoke(Object, Object[])에 필요한 모든 매개변수를 제공하는 것처럼 보이기 때문에 당연히 이를 사용할 것입니다. 그렇게 생각한다면 JDK를 속인 것입니다. 먼저 올바른 작성 방법을 살펴보겠습니다. 일부 학생들이 다음 내용을 읽을 기분이 아닐 경우 최소한 올바른 해결책을 알려주세요.
TimeInvocationHandler.java 수정
다음과 같이 코드 코드를 복사합니다 .
패키지 com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
공개 클래스 TimeInvocationHandler는 InvocationHandler를 구현합니다.
개인 개체 o;
공개 TimeInvocationHandler(객체 o){
this.o = o;
}
@보수
공용 객체 호출(객체 프록시, 메소드 메소드, Object[] args)
Throwable {를 던집니다.
System.out.println("startTime : " +System.currentTimeMillis());
객체 obj = method.invoke(o, args);
System.out.println("endTime : " +System.currentTimeMillis());
반환 객체;
}
}
Test.java 수정
다음과 같이 코드 코드를 복사합니다 .
패키지 com.yixi.proxy;
import java.lang.reflect.Proxy;
공개 클래스 테스트 {
공개 정적 무효 메인(String[] args) {
TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.저장();
}
}
이제 올바른 출력은 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다 .
시작 시간: 1352879531334
사용자 2 업데이트
종료 시간: 1352879531334
시작 시간: 1352879531334
사용자 저장....
종료 시간: 1352879531335
더 적은 양의 코드를 원한다면 익명 클래스를 직접 작성할 수 있습니다.
패키지 com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
공개 클래스 테스트 {
공개 정적 무효 메인(String[] args) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
새로운 InvocationHandler() {
@보수
공용 객체 호출(객체 프록시, 메소드 메소드, Object[] args)
Throwable {를 던집니다.
System.out.println("startTime : " +System.currentTimeMillis());
객체 obj = method.invoke(usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
반환 객체;
}
});
u.update(2);
u.저장();
}
}
method.invoke(target,args);의 첫 번째 매개변수가 대상 객체인데 왜 invocationHandler의 Invoke 메소드에 객체 프록시 매개변수가 필요합니까? 아래를 내려다보자!
가장 중요한 호출 메서드(내 개인적인 생각으로는)에 대해 JDK의 내용을 살펴보겠습니다.
다음과 같이 코드 코드를 복사합니다 .
부르다
객체 호출(객체 프록시,
방법 방법,
객체[] 인수)
Throwable은 프록시 인스턴스에 대한 메서드 호출을 처리하고 결과를 반환합니다. 이 메서드는 연결된 프록시 인스턴스에서 메서드가 호출될 때 호출 핸들러에서 호출됩니다.
매개변수:
프록시 - 메소드가 호출되는 프록시 인스턴스
method - 프록시 인스턴스에서 호출된 인터페이스 메서드에 해당하는 Method 인스턴스입니다. Method 객체의 선언 클래스는 메소드가 선언되는 인터페이스가 되며, 이는 프록시 클래스가 메소드를 상속하는 프록시 인터페이스의 상위 인터페이스일 수 있습니다.
args - 프록시 인스턴스의 메소드 호출에 전달된 인수 값을 포함하는 객체 배열이거나, 인터페이스 메소드가 인수를 취하지 않는 경우 null입니다. 기본 유형의 매개변수는 적절한 기본 래퍼 클래스(예: java.lang.Integer 또는 java.lang.Boolean)의 인스턴스로 래핑됩니다.
프록시 - 메소드가 호출되는 프록시 인스턴스? 이 문장은 무엇을 의미하나요? 연기? 방법은 프록시 방법입니까? 그러면 프록시 실행 방법이 Object obj = method.invoke(proxy, args);가 되어서는 안 되나요? 그때는 돌아서지 않고 토론방에 가서 구글에 가봤지만 아무런 영감도 얻지 못해서 소스코드를 보면 뭔가 보일 수도 있겠다는 생각이 들었습니다.
Proxy 클래스의 소스 코드를 열고 생성자가 무엇인지 알아보세요.
다음과 같이 코드 코드를 복사합니다 .
protectedInvocationHandler h;
보호된 프록시(InvocationHandler h) {
this.h = h;
}
InvocationHandler를 Proxy 생성자의 매개변수로 사용합니다.... 그렇다면 InvocationHandler는 무엇을 위해 사용됩니까? InvocationHandler의 호출() 메소드와 연결되어 있습니까?
내 첫 번째 생각은 Proxy가 내부적으로 다음 명령문을 호출한다는 것입니다.
다음과 같이 코드 코드를 복사합니다 .
h.invoke(this,methodName,args);
왜냐하면 해당 메서드를 실행하려면 Invoke 메서드를 호출해야 하기 때문입니다.
먼저 이것부터 살펴보자
여기에서 이해가 되는 내용을 찾을 수 있습니다. u.update(2)가 실행되면 àProxy는 handler.invoke(proxyClass,update,2) à를 호출합니다. 이는 ProxyClass.update(2)를 의미합니다.
u.save(); à프록시가 handler.invoke(proxyClass,save,null) à즉, proxyClass.save()를 호출할 때;
Test.java가 다음과 같이 변경되면:
다음과 같이 코드 코드를 복사합니다 .
공개 클래스 테스트 {
공개 정적 무효 메인(String[] args) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
새로운 InvocationHandler() {
@보수
공용 객체 호출(객체 프록시, 메소드 메소드, Object[] args)
Throwable {를 던집니다.
null을 반환;
}
});
u.update(2);
u.저장();
}
}
이때 익명 클래스의 메서드는 null을 반환합니다. 이를 실행하면 다음을 찾을 수 있습니다.
다음과 같이 코드 코드를 복사합니다 .
스레드 "main" java.lang.NullPointerException의 예외
$Proxy0.save(출처를 알 수 없음)
com.yixi.proxy.Test.main(Test.java:17)에서
17행에 널 포인터가 있습니다. 즉, 여기서 u.save() 메소드에 널 요소가 있을 수 있습니까? u가 null이면 u.update(2)는 거기에서 null 포인터 예외를 보고합니다. 17행을 주석 처리하면 예외가 사라져서 u.update()가 정상적으로 실행될 수 있음을 나타냅니다. 그렇다면 왜 이런가요?
실제로 이것이 호출 메소드가 null을 반환하는 이유입니다.
UserService 클래스의 두 가지 메서드에 주의하세요.
다음과 같이 코드 코드를 복사합니다 .
공개 인터페이스 UserService {
공개 int 저장();
공개 무효 업데이트(int id);
}
Save() 메소드는 int 유형을 반환하고 update 메소드는 void 유형을 반환합니다. 위의 추측에 따르면 handler.invoke()는 ProxyClass.update(2);를 구현하며 호출 메소드의 반환 메소드는 이에 상응합니다. 프록시 메소드의 값,
따라서 호출 메서드가 null을 반환하면 에이전트의 업데이트 메서드는 null 반환 값을 받고 원래는 void를 반환하므로 예외가 보고되지 않으며 에이전트 저장은 int 유형 값을 반환해야 합니다. 여기서 반환하는 것은 여전히 null입니다. JVM은 null을 변환할 수 없으므로 int 유형으로 변환하므로 예외가 보고됩니다. 이 설명은 이전 추측을 상대적으로 증명할 수도 있습니다.
InvocationHandler의 호출 메소드에 있는 첫 번째 매개변수 프록시는 ProxyClass가 완료해야 하는 비즈니스를 완료하기 위해 자체 InvocationHandler 객체의 참조로 메소드를 호출할 때 Proxy 클래스가 프록시 객체 ProxyClass의 참조를 전달할 수 있도록 허용하는 것 같습니다. .
문학적 재능이 좋지 않습니다! 능력은 제한되어 있습니다! 여러분이 저를 바로잡아 주셨으면 좋겠습니다...