分析为什么java动态代理是基于接口

前言

最近一直在研究Spring的源码,就看到了一些有关Java动态代理的博客。发现有些针对Java原生动态代理的博客没有写的很详细,不少都是一知半解互相粘贴的。所以写了一个小东西去研究,就诞生了这篇博客。希望对大家理解动态代理有一个新的帮助。更多Spring内容进入【Spring解读系列目录】

从一个异常开始

我们先把问题提出来,比如我有以下的代码。一个接口Dao,一个实现类DemoDao,一个切面类用来产生代理的

MyAspect,一个测试类MainTest。
public interface Dao {
    
    
    void print();
}
@Repository("demoDao")
public class DemoDao implements Dao{
    
    
    @Override
    public void print() {
    
    
        System.out.println("print Empty");
    }
}
@Component
@Aspect
public class MyAspect {
    
    
    @Before("within(com.demo.dao.*)")
    public void beforeDao(){
    
    
        System.out.println("this is a beforeDao");
    }
}
public class MainTest {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
        DemoDao dao= (DemoDao) anno.getBean("demoDao");
		//DemoDao dao= (DemoDao) anno.getBean(DemoDao.class);
        dao.print();
    }
}

这样直接运行,一定报错。如果通过名字拿出来就报代理类的转换错误。

DemoDao dao= (DemoDao) anno.getBean("demoDao");
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy20 cannot be cast to com.demo.dao.DemoDao
	at com.demo.main.MainTest.main(MainTest.java:11)

如果直接通过类拿出来报的却是DemoDao这个bean不可用。

DemoDao dao= (DemoDao) anno.getBean(DemoDao.class);
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.demo.dao.DemoDao' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
	at com.demo.main.MainTest.main(MainTest.java:11)

但是如果我们把MyAspect类中的@Component注销掉,再运行就没问题了。

//@Component
@Aspect
public class MyAspect {
    
    
    @Before("within(com.demo.dao.*)")
    public void beforeDao(){
    
    
        System.out.println("this is a beforeDao");
    }
}
打印结果输出
print Empty

这就说明,一旦给当前的程序加上JDK动态代理,也就是说把动态代理的类交给Spring管理就会报错,这是为什么呢?

分析

那么我们去看一下报错:No qualifying bean of type 'com.demo.dao.DemoDao' available。Spring说找不到这个DemoDao类,这就很矛盾,因为我们明明用@Repository("demoDao")把这个类交给Spring容器了,但是Spring却说没有。为了解决这个问题,我们改造一下测试类。

public class MainTest {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao= (Dao) anno.getBean("demoDao");
        System.out.println(dao instanceof DemoDao);
        dao.print();
    }
}

我们直接用接口Dao去对接这个demoDao,即让它的实现类去对接。然后我们再打印一下这个类的类型,因为DemoDao实现了Dao,正常情况下应该会打印出来true,因为本质上这俩应该是一样的,那么我们运行一下:

false
this is a beforeDao
print Empty

这次确实没有报错了,我们的切面也代理成功了。但是我们的dao的类型变了,JDK判定dao不是DemoDao的类型。换句话说,我拿出来的dao根本就不是DemoDao。真是见了鬼了,不是DemoDao谁运行的print()方法呢?

这个问题我们先放一放,我们去修改另一个地方,让这个dao变成DemoDao。这次修改的地方就是我们的配置类AppConfig,再运行。

@Configuration
@ComponentScan("com")
//@EnableAspectJAutoProxy 修改成下面的样子
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig {
    
    
}
运行结果:
true
this is a beforeDao
print Empty

大家看这次就正常运行,而且实例对象的判定也是true。这是为什么呢,proxyTargetClass又是个啥属性呢?其实官网【JavaBean Properties】有解释:

proxyTargetClass: true if the target class is to be proxied, rather than the target class’s interfaces. If this property value is set to true, then CGLIB proxies are created (but see also JDK- and CGLIB-based proxies).

大意:默认就是false,一旦是true,就会开启CGLIB作为动态代理,而不开的时候用的才是JDK原生的动态代理。所以我们这里的现象可以归结为用CGLIB就可以通过动态代理,但是用JDK原生的动态代理就不行。

一个问题

我们的例子中,已经把DemoDao放在Spring容器中了,结果再从Spring中拿出来的时候它就不是原来的它了,它变了,不是DemoDao了,这是不是很有问题?而且当我们使用CGLIB的时候,这个问题就不产生了。那么问题就来了,当我们使用JDK动态代理的时候,执行的dao instanceof DemoDaofalse的时候,这个instanceof后面的类是谁呢?

首先,Dao肯定是没问题的,很多博客都说过JDK的动态代理底层是基于接口的,修改成Dao肯定是true,运行起来完全没有问题。

那既然说是动态代理,那动态代理也是代理对不对,Java的代理类是谁呢?Proxy对吧,按照这个分析,我们直接用Proxy也应该是true,换了试试:

true
this is a beforeDao
print Empty

果然也是true。这说明我们拿出来的dao确实是一个代理。对代理有些了解的小伙伴一定知道,代理可以用继承实现,可以用接口聚合实现。但是为什么都说Java的动态代理是基于接口聚合,而不是基于继承实现呢?

探查JDK动态代理

为了搞清楚这个问题,我们继续改造MainTest,指定让Dao产生一个代理,然后还原动态代理产生的字节码。

public class MainTest {
    
    
    public static void main(String[] args) {
    
    
        Class[] classes= new Class[]{
    
    Dao.class};
        //这句代码就是为了给Dao生成一个代理对象
        byte[] bytes= ProxyGenerator.generateProxyClass("MyDao",classes);
        File file=new File("d:\\Mydao.class");
        try {
    
    
            FileOutputStream fos=new FileOutputStream(file);
            fos.write(bytes);
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

为什么要这么做呢,因为generateProxyClass()这个方法返回的就是字节,也就是我们这个类的字节码。也就是说bytes这个字节数组变量,存的就是这个类加载到内存中的样子。然后我们再写到文件中让它产生一个class文件,我们要看的就是:当我手动产生一个代理的时候,产生出来的字节码是什么,再经过反编译成class文件中的内容又是什么。那么我们运行一下,产生这个class文件,然后再拉到Idea中:

在这里插入图片描述

神奇的一幕,大家看这个代理对象。这个MyDao的类对象就是我们自己命名的MyDao,这个就是我们的代理对象。当我们new出来Dao的时候,这里因为实现了Dao所以可以接收到这个Dao,而Dao这个接口中有个print()方法,方法在哪里呢?往下找:

public final void print() throws  {
    
    
    try {
    
    
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
    
    
        throw var2;
    } catch (Throwable var3) {
    
    
        throw new UndeclaredThrowableException(var3);
    }
}

就找到了这个print()方法,而且方法里面,有super.h.invoke(this, m3, (Object[])null);这句话去执行业务逻辑和代理逻辑的(这里涉及到真正的源码了,大家知道这个作用就好了,如果展开一篇博客讲是讲不完的),所以它可以完成代理。

但是重点就在于这个生成的代理对象MyDao确实是基于接口的因为implements Dao,那么到这里有没有同学明白了为什么JDK动态代理不能基于继承实现的?

重点就在于这个动态代理对象的类上:public final class MyDao extends Proxy implements Dao。因为生成代理对象的过程中已经自动继承了一个Proxy类了,java不能多继承,所以不可能再继承目标对象,只能使用去实现目标对象的接口。

总结

那么我们拿出来的Dao dao= (Dao) anno.getBean("demoDao"); 它等于Dao,同时它还等于Proxy。但是唯独不等于DemoDao,这也就是为什么直接拿出来会报错的原因,因为拿出来的就不是目标对象DemoDao,只是一个代理对象而已。而且因为在生成动态代理的时候JDK自动生成了一个Proxy的继承,因此为了不违反Java的语法,JDK的动态代理必须基于接口。那为什么CGLIB能用呢?因为CGLIB是基于继承实现的,不过这就又是另一个故事了。

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/108156217