zoukankan      html  css  js  c++  java
  • 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进阶的文章,感谢关注
    ![](https://img2018.cnblogs.com/blog/1363061/201911/1363061-20191108153402308-1399272640.png)
  • 相关阅读:
    OpenCV 在android studio 中的用法
    Python中用requests处理cookies的3种方法
    Jmeter利用正则表达式提取器提取登录cookie供下一步使用
    vue项目 el-tree的界面自定义 实现增删改查操作
    PostgreSQL高可用方案-patroni+etcd+vipmanager(二)
    PostgreSQL高可用方案-patroni+etcd+vipmanager(一)
    【转载】Linux下PostgreSQL主备环境搭建和切换
    一些自定义 PostgreSQL 随机数据生成器 —— Some self-defined PostgreSQL random data generators
    ClickHouse 简单使用(六)
    ClickHouse 简单使用(五)
  • 原文地址:https://www.cnblogs.com/dengchengchao/p/11823687.html
Copyright © 2011-2022 走看看