> Java > java지도 시간 > 본문

프록시 모드의 Java 동적 프록시 구현 방법

高洛峰
풀어 주다: 2017-02-07 13:49:19
원래의
1327명이 탐색했습니다.

오늘 우연히 JDK의 동적 프록시를 살펴보고 싶었습니다. 이전에 조금 알고 있었고, 짧은 시간에 여러 인터페이스와 클래스를 작성해 보고 싶었기 때문입니다. :
인터페이스 클래스: UserService.java

package com.yixi.proxy;
public interface UserService {
    public int save() ;
    public void update(int id);
}
로그인 후 복사

구현 클래스: UserServiceImpl.java

package com.yixi.proxy;
public class UserServiceImpl implements UserService {
    @Override
    public int save() {
        System.out.println("user save....");
        return 1;
    }
    @Override
    public void update(int id) {
        System.out.println("update a user " + id);
    }
}
로그인 후 복사

그런 다음 원하는 InvocationHandler를 빠르게 작성했습니다. 이 기능은 시작을 기록하는 것이 매우 간단합니다. 메소드 실행 시간 및 종료 시간
TimeInvocationHandler.java

package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("startTime : " +System.currentTimeMillis());
        Object obj = method.invoke(proxy, args);
        System.out.println("endTime : " +System.currentTimeMillis());
        return obj;
    }
}
로그인 후 복사

모든 준비가 완료되면 당연히 테스트 작성을 시작할 시간입니다!
Test.java

package com.yixi.proxy;
import java.lang.reflect.Proxy;
public class Test {
    public static 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.save();
    }
}
로그인 후 복사

행복하게 실행되지만 결과는 화면 전체 예외입니다.

startTime : 1352877835040
startTime : 1352877835040
startTime : 1352877835040
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.update(Unknown Source)
    at com.yixi.proxy.Test.main(Test.java:11)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)
    ... 2 more
로그인 후 복사

com.yixi.proxy.TimeInvocationHandler.invoke( TimeInvocationHandler.java:12) 예외는 문제가 TimeInvocationHandle의 12번째 라인에 있음을 분명히 알려줍니다. 즉,

public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("startTime : " +System.currentTimeMillis());
        Object obj = method.invoke(proxy, args);
        System.out.println("endTime : " +System.currentTimeMillis());
        return obj;
    }
로그인 후 복사
메소드에 잘못된 것이 없습니다! Invoke() 메소드는 method.invoke(Object, Object[])에 필요한 모든 매개변수를 제공하는 것처럼 보이기 때문에 당연히 이를 사용하게 됩니다. 그렇게 생각한다면 JDK를 속인 것입니다. 함정, 먼저 올바른 작성 방법을 살펴보겠습니다. 일부 학생들이 다음 내용을 읽을 기분이 아닌 경우 최소한 올바른 해결책을 알려주십시오.

Modify TimeInvocationHandler.java

package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
    private Object o;
    public TimeInvocationHandler(Object o){
        this.o = o;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("startTime : " +System.currentTimeMillis());
        Object obj = method.invoke(o, args);
        System.out.println("endTime : " +System.currentTimeMillis());
        return obj;
    }
}
로그인 후 복사

Test.java 수정

package com.yixi.proxy;
import java.lang.reflect.Proxy;
public class Test {
    public static void main(String[] args) {
        TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
        UserService u =  (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
        u.update(2);
        u.save();
    }
}
로그인 후 복사

이제 출력이 정확합니다:

startTime : 1352879531334
update a user 2
endTime : 1352879531334
startTime : 1352879531334
user save....
endTime : 1352879531335
로그인 후 복사

코드를 덜 사용하려면 익명 클래스를 직접 작성할 수 있습니다:
package com.yixi. 프록시;
가져오기 java.lang.reflect.InvocationHandler ;
가져오기 java.lang.reflect.Method;
가져오기 java.lang.reflect.Proxy;
공용 클래스 테스트 {
공용 정적 void main(String[] args) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
새로운 InvocationHandler() {
                                                           ~                 @Override를 통해 🎜>                 System.out.println("startTime: " +System.currentTimeMillis());
객체 obj = method.invoke(usi, args);
                   return obj;
}
                ); 첫 번째 매개변수는 전달된 대상 개체인데, invocationHandler의 Invoke 메서드에 개체 프록시 매개변수가 필요한 이유는 무엇입니까? 아래를 내려다보자!
가장 중요한 호출 메소드(제 생각에는)에 대해 JDK가 말하는 내용을 살펴보겠습니다.

invoke
Object invoke(Object proxy,
              Method method,
              Object[] args)
              throws Throwable在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。 
参数:
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
로그인 후 복사

프록시 - 메소드가 호출되는 프록시 인스턴스? 이 문장은 무엇을 의미하나요? 연기? 방법은 프록시 방법입니까? 그러면 프록시를 실행하는 방법이 Object obj = method.invoke(proxy, args);가 되어서는 안 되나요? 그때는 돌아보지 않고 토론방에 가서 구글에 가봤지만 아무런 영감도 찾을 수 없었고 소스코드를 보면 뭔가 보일 수도 있겠다는 생각이 들었습니다.
Proxy 클래스의 소스 코드를 열고 그것이 어떤 종류의 생성자인지 알아보세요.
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
    this.h = h;
    }
로그인 후 복사

InvocationHandler를 Proxy 생성자 메소드의 매개변수로 사용합니다.... 그렇다면 InvocationHandler는 무엇을 위해 사용됩니까? ? InvocationHandler의 호출() 메소드와 어떤 연관이 있습니까?
프록시가 내부적으로 다음 명령문을 호출한다고 생각합니다.
h.invoke(this,methodName,args);
로그인 후 복사
해당 메소드를 실행하려면 호출 메소드를 호출해야 하기 때문입니다.

이것을 먼저 살펴보겠습니다

프록시 모드의 Java 동적 프록시 구현 방법

在这里你就会发现貌似有点感觉了:当u.update(2)时 àProxy就会调用 handler.invoke(proxyClass,update,2) à 也就是调用了proxyClass.update(2);
当u.save();时àProxy就会调用handler.invoke(proxyClass,save,null) à也就是调用了proxyClass.save();

当Test.java改成这样时:

public class Test {
    public static void main(String[] args) {
        final UserServiceImpl usi = new UserServiceImpl();
        UserService u =  (UserService) Proxy.newProxyInstance(
                usi.getClass().getClassLoader(),
                usi.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        return null;
                    }
                });
        u.update(2);
        u.save();
    }
}
로그인 후 복사

注意这时候的匿名类的方法的返回的是null,运行一下就会发现:

Exception in thread "main" java.lang.NullPointerException
    at $Proxy0.save(Unknown Source)
    at com.yixi.proxy.Test.main(Test.java:17)
로그인 후 복사

17行有空指针 也就是这里的u.save()方法有为null的元素 难道是u是空的? 不应该啊如果u是null的话那么u.update(2)在那里就会报空指针异常了,当我把17行注释掉以后异常没了说明u.update()能正常执行。那这到底是为什么呢?
其实这就是invoke方法返回null的缘故:
注意一下UserService类中的两个方法:

public interface UserService {
    public int save() ;
    public void update(int id);
}
로그인 후 복사
Save()方法返回的是int型的 而update方法返回的是void型的;根据上面的猜测是 handler.invoke()是实现 proxyClass.update(2);的,invoke方法中的return方法的是相应的代理方法的返回值,
所以在invoke方法返回null的时候代理的update方法接收到返回值是null, 而它本来就是返回void 所以没有报异常, 而代理save必须返回int型的数值 我们这返回的还是null,JVM无法将null转化为int型 所以就报了异常了
这样解释就能解释通了,也能相对证明前面的猜测。

InvocationHandler中invoke方法中第一个参数proxy貌似只是为了让Proxy类能给自己的InvocationHandler对象的引用调用方法时能传入代理对象proxyClass的引用,来完成proxyClass需要完成的业务。

更多프록시 모드의 Java 동적 프록시 구현 방법相关文章请关注PHP中文网!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿