java面试知识总结--spring工作原理+动态代理知识

目标

汇总spring中ioc和aop核心思想,jdk和cglib原理及区别

ioc分析

IoC不是一种spring独有的技术,只是一种设计思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。ioc的思想给予依赖导致原则,如典型的先造汽车还是先早轮子的话题:(以下内容引用出处,看参考资料)

image

这样的设计看起来没问题,但是可维护性却很低。假设设计完工之后,上司却突然说根据市场需求的变动,要我们把车子的轮子设计都改大一码。这下我们就蛋疼了:因为我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改;同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改——整个设计几乎都得改!

我们现在换一种思路。我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。

image

这时候,上司再说要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。

这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。

aop思想

  1. 静态代理 ,不方便,使用麻烦

    • 针对每个具体类分别编写代理类;
    • 针对一个接口编写一个代理类;
  2. 动态代理

    1. JDK实现动态代理,通过
      Proxy.newProxyInstance(class.tInterfaces,this);
      要求:只能对实现了接口的类生成代理,而不能针对类
    2. cglib实现:针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法, 因为是继承,所以该类或方法最好不要声明成final

jdk动态代理实现

动态代理原理(字节码重组,下面为JDK动态代理,cglib类似区别在于它是通过继承实现的):

                1.拿到被代理对象的引用,并且通过反射获取到它的所有接口

                2.JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类的所有实现方法

                3.动态生产java代码,把新加的业务逻辑方法有一定的逻辑代码取调用

                4.编译新生成的iava代码.class

                5.再重新加载到jvm中运行

                6.以上为字节码重组的全过程

4.JDK动态代理源码分析:

  手写JDK动态代理过程,关键代码:

(1)代理类的newInstance方法

public static Object newInstance(GPClassLoader cl, Class<?>[] interfaces, GPInvocationHandler invokehandler) throws Throwable {

    //0.动态生成java文件

    String s = ganeteFile(interfaces);

    //1.写入磁盘

    String path = GPProxy.class.getResource("").getPath();

    System.out.println(path);

    File f=new File(path+"KYProxy$0.java");

    FileOutputStream fileOutputStream=new FileOutputStream(f);

    fileOutputStream.write(s.getBytes());

    fileOutputStream.close();

    //2.把java文件编译成.class文件

    JavaCompiler compiler= ToolProvider.getSystemJavaCompiler();

    StandardJavaFileManager manager=compiler.getStandardFileManager(null,null,null);

    Iterable<? extends JavaFileObject> javaFileObjects = manager.getJavaFileObjects(f);

  JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, javaFileObjects);

    task.call();

    manager.close();

    //3.将class文件加载到jvm中

    Class<?> aClass = cl.findClass("KYProxy$0");

    //4.返回字节码生成到代理对象

    Constructor<?>[] constructors = aClass.getConstructors();

    return constructors[0].newInstance(invokehandler);

}

(2)classloader加载类方法


public class GPClassLoader extends ClassLoader{

    private File classPathFile;

    public GPClassLoader() {

        String path = GPClassLoader.class.getResource("").getPath();

        this.classPathFile = new File(path);

    }

    @Override

    protected Class<?> findClass(String name) throws ClassNotFoundException {

       String className= GPClassLoader.class.getPackage().getName()+"."+ name;

        try {

            if(classPathFile!=null){

                File file=new File(classPathFile,name.replaceAll(".","/"));

                if(file.exists()){

                    FileInputStream fis=new FileInputStream(file+"/"+name+".class");

                    ByteArrayOutputStream byteArrayInputStream=new ByteArrayOutputStream();

                    byte[] bt=new byte[1024];

                    int lend;

                    while ( (lend = fis.read(bt))!=-1){

                        byteArrayInputStream.write(bt,0,lend);

                    }

                    return defineClass(className,byteArrayInputStream.toByteArray(),0,byteArrayInputStream.size());

                }

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

        return null;

    }

}

(2)拼代理类字节码的方法,此处我们可以通过ProxyGenerator.generateProxyClass生成对应的代理源码然后仿写代理类,如: byte[] bytes = ProxyGenerator.generateProxyClass("proxy.XieMu$$EnhancerByCGLIB$$6e794e95@3d012ddd”, FileOutputStream os=new FileOutputStream("/Users/kai.yang/Desktop/test.class");

public static String ganeteFile(Class<?>[] classes) {

    StringBuilder sb = new StringBuilder();

    append(sb, "package proxy.custom;");

    append(sb, "import java.lang.reflect.*;");

    append(sb, "public class KYProxy$0 implements");

    StringBuilder clName = new StringBuilder();

    for (Class<?> clazz : classes

            ) {

        clName.append(" ").append(clazz.getName()).append(",");

    }

    sb.append(clName.toString().substring(0, clName.length() - 1)).append("{\n");

    sb.append("public KYProxy$0(GPInvocationHandler handler){\n" +

            "this.h=handler;" +

            "\n}\n");

    append(sb, "proxy.custom.GPInvocationHandler h=null;");

    for (Class<?> clazz : classes

            ) {

        Method[] methods = clazz.getMethods();

        for (int i=0;i<methods.length;i++) {

            sb.append("private static Method m"+i+";\n");

            sb.append("public "+methods[i].getReturnType()+" ")

                    .append(methods[i].getName()).append("(").

                    append(")").append("throws Throwable{\n").

                    append("h.invoke(this,m"+i+",(Object[])null);\n").

                    append("}\n");

        }

    }

    sb.append("static{" +

            "try{\n");

    for (Class<?> clazz : classes

            ) {

        Method[] methods = clazz.getMethods();

        for (int i=0;i<methods.length;i++) {

            sb.append(" m"+i+"=Class.forName(\""+clazz.getName()+"\").getMethod(\""+methods[i].getName()+"\",new Class[0]);\n");

        }

    }

    sb.append("}catch (Throwable e){\n" +

            "  e.printStackTrace();\n" +

            " }");

    sb.append("}\n");

    append(sb, "}");

    return sb.toString();

}


cglib动态代理实现:

先说下两种动态代理区别:

  1. JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
  2. JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
  3. JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

(1)FastClass机制
Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。

这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。

//根据方法签名获取index

 public int getIndex(Signature var1) {
      String var10000 = var1.toString();
      switch(var10000.hashCode()) {
      case -2077043409:
         if(var10000.equals("getPerson(Ljava/lang/String;)Lcom/demo/pojo/Person;")) {
            return 21;
         }
         break;
      case -2055565910:
         if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
            return 12;
         }
         break;
      case -1902447170:
         if(var10000.equals("setPerson()V")) {
            return 7;
         }
         break;
   //省略部分代码.....

 

 //根据index直接定位执行方法
 public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
      eaaaed75 var10000 = (eaaaed75)var2;
      int var10001 = var1;

      try {
         switch(var10001) {
         case 0:
            return new Boolean(var10000.equals(var3[0]));
         case 1:
            return var10000.toString();
         case 2:
            return new Integer(var10000.hashCode());
         case 3:
            return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
         case 4:
            return var10000.newInstance((Callback)var3[0]);
         case 5:
            return var10000.newInstance((Callback[])var3[0]);
         case 6:
            var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
            return null;
         case 7:
            var10000.setPerson();
            return null;
         case 8:
            var10000.setCallbacks((Callback[])var3[0]);
            return null;
         case 9:
            return var10000.getCallback(((Number)var3[0]).intValue());
         case 10:
            return var10000.getCallbacks();
         case 11:
            eaaaed75.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
            return null;
         case 12:
            eaaaed75.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
            return null;
         case 13:
            return eaaaed75.CGLIB$findMethodProxy((Signature)var3[0]);
         case 14:
            return var10000.CGLIB$toString$3();
         case 15:
            return new Boolean(var10000.CGLIB$equals$2(var3[0]));
         case 16:
            return var10000.CGLIB$clone$5();
         case 17:
            return new Integer(var10000.CGLIB$hashCode$4());
         case 18:
            var10000.CGLIB$finalize$1();
            return null;
         case 19:
            var10000.CGLIB$setPerson$0();
            return null;
        //省略部分代码....
      } catch (Throwable var4) {
         throw new InvocationTargetException(var4);
      }

      throw new IllegalArgumentException("Cannot find matching method/constructor");
   }

参考资料:
1.ioc理解:https://www.zhihu.com/question/23277575

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341