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

今天在写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进阶的文章,感谢关注

posted @ 2019-11-08 22:31 逸游Java 阅读(...) 评论(...) 编辑 收藏