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