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