Spring 動態代理 之 but was actually of type 'com.sun.proxy.$Proxy14 Exception

今天在寫Spring的引介代理的時候,報了一個錯:java

Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'inter1' is expected to be of type 'com.dengchengchao.springtest.intertest.Inter1Impl' but was actually of type 'com.sun.proxy.$Proxy14'

大概的意思是類型轉換錯誤。spring

源代碼以下:微信

ApplicationContext  ctx = new AnnotationConfigApplicationContext(Conf.class);
Inter1 inter1 = ctx.getBean("inter1", Inter1Impl.class);

inter1.say1();

Inter2 inter2=(Inter2) inter1;
inter2.say2();

後來google了一下發現把代理方式改爲CGLIB就行。ide

咱們都知道JDK只能代理接口,對於非接口的類的代理,應該使用CGLIBui

由於CGLIB是經過繼承代理類實現,而JDK是經過實現接口實現。this

可是我這裏Inter1分明就是一個接口。後來仔細檢查了代碼,發現其實使用Java代理也行,只要改以下一行代碼便可:google

Inter1 inter1 = ctx.getBean("inter1", Inter1.class);

也就是說,須要轉換成類型應該是Inter1.class而不能是具體的類Inter1Impl.net


爲何Java代理只支持接口代理,這裏咱們來深扒一下:代理

首先定義一個接口:code

public interface People {
    void eat();
}

而後定義一個實現類:

public class Student implements People{

    @Override
    public void eat() {
        System.out.println("用手吃");
    }
}

接着定義一個代理類:

public class StudentInvokeHandler implements InvocationHandler {

    private Object target;

    public StudentInvokeHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable    {

        System.out.println("飯前洗手");
        Object retVal = method.invoke(target, args);
        System.out.println("飯後吃水果");
        return retVal;
    }
}

接下來,經過代理來調用Student

public static void main(String[] args) {
    //初始化Student
    Student student = new Student();
    //初始化Student代理類
    StudentInvokeHandler studentInvokeHandler = new StudentInvokeHandler(student);
    //經過代理獲取代理獨享
    People studentProxy = (People) Proxy.newProxyInstance(StudentInvokeHandler.class.getClassLoader(), new Class[]{People.class}, studentInvokeHandler);
    //經過代理對象調用eat方法
    studentProxy.eat();
}

能夠看見,Java的代理很是簡單,可是底層是如何實現的呢?

參照細說JDK動態代理的實現原理,咱們在main中設置一下JVM屬性

public static void main(String[] args) {
    //將生成的代理類文件保存
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    Student student = new Student();
    StudentInvokeHandler studentInvokeHandler = new StudentInvokeHandler(student);
    People studentProxy = (People) Proxy.newProxyInstance(StudentInvokeHandler.class.getClassLoader(), new Class[]{People.class}, studentInvokeHandler);
    studentProxy.eat();
}

運行以後,能夠在項目根目錄中找到com/sun/proxy/$Proxy0.class文件,這個文件即是代理Student生成的對象的.class文件:

public final class $Proxy0 extends Proxy implements People {
    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});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void eat() 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);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.dengchengchao.springtest.proxy.People").getMethod("eat");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

經過以上文件咱們能夠發現:

  • 生成的代理類繼承了Proxy,實現了People接口

    這也就是爲何JDK代理只能代理接口,不能代理具體的類,由於Java不能多繼承,所以只能實現接口

  • 因爲實現的是接口,所以對於生成的代理對象proxy

    proxy instanceof People  //true
    proxy instanceof Student //false

這即是開始咱們所遇到的問題的根源所在,proxy僅僅是實現了People接口,卻不是繼承自Student類,所以沒法將proxy對象轉換爲Student類型,因此才報的錯。

明白了這個問題,之後使用底層爲JDK代理的類,就不會再出錯了。


若是以爲寫得不錯,歡迎掃描下面二維碼關注微信公衆號:逸遊Java ,天天不定時發佈一些有關Java進階的文章,感謝關注

相關文章
相關標籤/搜索