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

  • 2019 年 11 月 8 日
  • 筆記

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

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'

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

源程式碼如下:

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

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

我們都知道JDK只能代理介面,對於非介面的類的代理,應該使用CGLIB

因為CGLIB是通過繼承代理類實現,而JDK是通過實現介面實現。

但是我這裡Inter1分明就是一個介面。後來仔細檢查了程式碼,發現其實使用Java代理也行,只要改如下一行程式碼即可:

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

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


為什麼Java代理只支援介面代理,這裡我們來深扒一下:

首先定義一個介面:

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進階的文章,感謝關注