Java——字节码技术

  1. 字节码

1.1 什么是字节码?

Java之所以可以”一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。

因此,也可以看出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而JVM以两个十六进制值为一组,即以字节为单位进行读取。

在Java中一般是用javac命令编译源代码为字节码文件,一个.java文件从编译到运行的示例如图:

Java——字节码技术

对于开发人员,了解字节码可以更准确、直观地理解Java语言中更深层次的东西,比如通过字节码,可以很直观地看到Volatile关键字如何在字节码上生效。

另外,字节码增强技术在Spring AOP、各种ORM框架、热部署中的应用屡见不鲜,深入理解其原理对于我们来说大有裨益。

除此之外,由于JVM规范的存在,只要最终可以生成符合规范的字节码就可以在JVM上运行,因此这就给了各种运行在JVM上的语言(如Scala、Groovy、Kotlin)一种契机,

可以扩展Java所没有的特性或者实现各种语法糖。理解字节码后再学习这些语言,可以”逆流而上”,从字节码视角看它的设计思路,学习起来也”易如反掌”。

1.2 字节码结构

.java文件通过javac编译后将得到一个.class文件,比如编写一个简单的ByteCodeDemo类,如下图2的左侧部分:

Java——字节码技术

编译后生成ByteCodeDemo.class文件,打开后是一堆十六进制数,按字节为单位进行分割后展示如图2右侧部分所示。上文提及过,JVM对于字节码是有规范要求的,那么看似杂乱的十六进制符合什么结构呢?

JVM规范要求每一个字节码文件都要由十部分按照固定的顺序组成,整体结构如下图所示:

Java——字节码技术

1.3 查看字节码工具

如果每次查看反编译后的字节码都使用javap命令的话,好非常繁琐。这里推荐一个Idea插件:jclasslib

使用效果如图15所示,代码编译后在菜单栏”View”中选择”Show Bytecode With jclasslib”,可以很直观地看到当前字节码文件的类信息、常量池、方法区等信息。

Java——字节码技术
  1. 字节码增强

在上文中,着重介绍了字节码的结构,这为我们了解字节码增强技术的实现打下了基础。字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。

接下来,我们将从最直接操纵字节码的实现方式开始深入进行剖析。

Java——字节码技术

2.1 ASM

对于需要手动操纵字节码的需求,可以使用ASM,它可以直接生产 .class字节码文件,也可以在类被加载入JVM之前动态修改类行为(如下图17所示)。

ASM的应用场景有 AOP(Cglib就是基于ASM)热部署修改其他jar包中的类等。当然,涉及到如此底层的步骤,实现起来也比较麻烦。

接下来,本文将介绍ASM的两种API,并用ASM来实现一个比较粗糙的AOP。但在此之前,为了让大家更快地理解ASM的处理流程,强烈建议读者先对访问者模式进行了解。

简单来说,访问者模式主要用于修改或操作一些数据结构比较稳定的数据,而通过第一章,我们知道字节码文件的结构是由JVM固定的,所以很适合利用访问者模式对字节码文件进行修改。

Java——字节码技术

2.1.1 ASM API

2.1.1.1 核心API

ASM Core API可以类比解析XML文件中的SAX方式,不需要把这个类的整个结构读取进来,就可以用流式的方法来处理字节码文件。好处是非常节约内存,但是编程难度较大。

然而出于性能考虑,一般情况下编程都使用Core API。在Core API中有以下几个关键类:

  • ClassReader:用于读取已经编译好的.class文件。
  • ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。
  • 各种 Visitor类:CoreAPI根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor,比如用于访问方法的MethodVisitor、用于访问类变量的FieldVisitor、用于访问注解的AnnotationVisitor等。为了实现AOP,重点要使用的是MethodVisitor。

2.1.1.2 树形API

ASM Tree API可以类比解析XML文件中的DOM方式,把整个类的结构读取到内存中,缺点是消耗内存多,但是编程比较简单。

TreeApi不同于CoreAPI,TreeAPI通过各种Node类来映射字节码的各个区域,类比DOM节点,就可以很好地理解这种编程方式。

2.1.2 利用ASM实现AOP

利用ASM的CoreAPI来增强类。这里不纠结于AOP的专业名词如切片、通知,只实现在方法调用前、后增加逻辑,通俗易懂且方便理解。

首先定义需要被增强的Base类:其中只包含一个process()方法,方法内输出一行”process”。增强后,我们期望的是,方法执行前输出”start”,之后输出”end”。

public class Base {
    public void process(){
        System.out.println("process");
    }
}

为了利用ASM实现AOP,需要定义两个类:一个是MyClassVisitor类,用于对字节码的visit以及修改;另一个是Generator类,在这个类中定义ClassReader和ClassWriter,

其中的逻辑是,classReader读取字节码,然后交给MyClassVisitor类处理,处理完成后由ClassWriter写字节码并将旧的字节码替换掉。

Generator类较简单,我们先看一下它的实现,如下所示:

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

public class Generator {
    public static void main(String[] args) throws Exception {
        //读取
        ClassReader classReader = new ClassReader("meituan/bytecode/asm/Base");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        //处理
        ClassVisitor classVisitor = new MyClassVisitor(classWriter);
        classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
        byte[] data = classWriter.toByteArray();
        //输出
        File f = new File("operation-server/target/classes/meituan/bytecode/asm/Base.class");
        FileOutputStream fout = new FileOutputStream(f);
        fout.write(data);
        fout.close();
        System.out.println("now generator cc success!!!!!");
    }
}

MyClassVisitor继承自ClassVisitor,用于对字节码的观察。它还包含一个内部类MyMethodVisitor,继承自MethodVisitor用于对类内方法的观察,它的整体代码如下:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyClassVisitor extends ClassVisitor implements Opcodes {
    public MyClassVisitor(ClassVisitor cv) {
        super(ASM5, cv);
    }
    @Override
    public void visit(int version, int access, String name, String signature,
                      String superName, String[] interfaces) {
        cv.visit(version, access, name, signature, superName, interfaces);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
                exceptions);
        //Base类中有两个方法:无参构造以及process方法,这里不增强构造方法
        if (!name.equals("") && mv != null) {
            mv = new MyMethodVisitor(mv);
        }
        return mv;
    }
    class MyMethodVisitor extends MethodVisitor implements Opcodes {
        public MyMethodVisitor(MethodVisitor mv) {
            super(Opcodes.ASM5, mv);
        }

        @Override
        public void visitCode() {
            super.visitCode();
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("start");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
        @Override
        public void visitInsn(int opcode) {
            if ((opcode >= Opcodes.IRETURN && opcode  Opcodes.RETURN)
                    || opcode == Opcodes.ATHROW) {
                //方法在返回之前,打印"end"
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("end");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            mv.visitInsn(opcode);
        }
    }
}

利用这个类就可以实现对字节码的修改。详细解读其中的代码,对字节码做修改的步骤是:

  • 首先通过MyClassVisitor类中的visitMethod方法,判断当前字节码读到哪一个方法了。跳过构造方法 后,将需要被增强的方法交给内部类MyMethodVisitor来进行处理。
  • 接下来,进入内部类MyMethodVisitor中的visitCode方法,它会在ASM开始访问某一个方法的Code区时被调用,重写visitCode方法,将AOP中的前置逻辑就放在这里。
  • MyMethodVisitor继续读取字节码指令,每当ASM访问到无参数指令时,都会调用MyMethodVisitor中的visitInsn方法。我们判断了当前指令是否为无参数的”return”指令,如果是就在它的前面添加一些指令,也就是将AOP的后置逻辑放在该方法中。
  • 综上,重写MyMethodVisitor中的两个方法,就可以实现AOP了,而重写方法时就需要用ASM的写法,手动写入或者修改字节码。通过调用methodVisitor的visitXXXXInsn()方法就可以实现字节码的插入,XXXX对应相应的操作码助记符类型,比如mv.visitLdcInsn(“end”)对应的操作码就是ldc “end”,即将字符串”end”压入栈。

完成这两个visitor类后,运行Generator中的main方法完成对Base类的字节码增强,增强后的结果可以在编译后的target文件夹中找到Base.class文件进行查看,可以看到反编译后的代码已经改变了(如图18左侧所示)。

然后写一个测试类MyTest,在其中new Base(),并调用base.process()方法,可以看到下图右侧所示的AOP实现效果:

Java——字节码技术

2.1.3 ASM工具

利用ASM手写字节码时,需要利用一系列visitXXXXInsn()方法来写对应的助记符,所以需要先将每一行源代码转化为一个个的助记符,然后通过ASM的语法转换为visitXXXXInsn()这种写法。

第一步将源码转化为助记符就已经够麻烦了,不熟悉字节码操作集合的话,需要我们将代码编译后再反编译,才能得到源代码对应的助记符。

第二步利用ASM写字节码时,如何传参也很令人头疼。ASM社区也知道这两个问题,所以提供了工具ASM ByteCode Outline

安装后,右键选择”Show Bytecode Outline”,在新标签页中选择”ASMified”这个tab,如图19所示,就可以看到这个类中的代码对应的ASM写法了。

图中上下两个红框分别对应AOP中的前置逻辑于后置逻辑,将这两块直接复制到visitor中的visitMethod()以及visitInsn()方法中,就可以了。

Java——字节码技术

2.2 Javassist

ASM是在指令层次上操作字节码的,阅读上文后,我们的直观感受是在指令层次上操作字节码的框架实现起来比较晦涩。故除此之外,我们再简单介绍另外一类框架:强调源代码层次操作字节码的框架Javassist。

利用Javassist实现字节码增强时,可以无须关注字节码刻板的结构,其 优点就在于编程简单。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构或者动态生成类。

其中最重要的是ClassPool、CtClass、CtMethod、CtField这四个类:

  • CtClass(compile-time class):编译时类信息,它是一个class文件在代码中的抽象表现形式,可以通过一个类的全限定名来获取一个CtClass对象,用来表示这个类文件。
  • ClassPool:从开发视角来看,ClassPool是一张保存CtClass信息的HashTable,key为类名,value为类名对应的CtClass对象。当我们需要对某个类进行修改时,就是通过pool.getCtClass(“className”)方法从pool中获取到相应的CtClass。
  • CtMethodCtField:这两个比较好理解,对应的是类中的方法和属性。

了解这四个类后,我们可以写一个小Demo来展示Javassist简单、快速的特点。我们依然是对Base中的process()方法做增强,在方法调用前后分别输出”start”和”end”,实现代码如下。

我们需要做的就是从pool中获取到相应的CtClass对象和其中的方法,然后执行method.insertBefore和insertAfter方法,参数为要插入的Java代码,再以字符串的形式传入即可,实现起来也极为简单。

import com.meituan.mtrace.agent.javassist.*;

public class JavassistTest {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, IOException {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get("meituan.bytecode.javassist.Base");
        CtMethod m = cc.getDeclaredMethod("process");
        m.insertBefore("{ System.out.println(\"start\"); }");
        m.insertAfter("{ System.out.println(\"end\"); }");
        Class c = cc.toClass();
        cc.writeFile("/Users/zen/projects");
        Base h = (Base)c.newInstance();
        h.process();
    }
}

2.3 ASM & Javassist 对比

  • Javassist源代码级API比ASM中实际的字节码操作更容易使用
  • Javassist在复杂的字节码级操作上提供了更高级别的抽象层。Javassist源代码级API只需要很少的字节码知识,甚至不需要任何实际字节码知识,因此实现起来更容易、更快。
  • Javassist使用反射机制,这使得它比运行时使用Classworking技术的ASM慢。
  • 总的来说ASM比Javassist快得多,并且提供了更好的性能。Javassist使用Java源代码的简化版本,然后将其编译成字节码。这使得Javassist非常容易使用,但是它也将字节码的使用限制在Javassist源代码的限制之内。
  • 总之,如果有人需要更简单的方法来动态操作或创建Java类,那么应该使用Javassist API 。如果需要注重性能地方,应该使用ASM库。

  • 运行时类的重载

3.1 问题引出

上一章重点介绍了两种不同类型的字节码操作框架,且都利用它们实现了较为粗糙的AOP。其实,为了方便大家理解字节码增强技术,在上文中我们避重就轻将ASM实现AOP的过程分为了两个main方法:

第一个是利用MyClassVisitor对已编译好的class文件进行修改,第二个是new对象并调用。这期间并不涉及到JVM运行时对类的重加载,

而是在第一个main方法中,通过ASM对已编译类的字节码进行替换,在第二个main方法中,直接使用已替换好的新类信息。另外在Javassist的实现中,我们也只加载了一次Base类,也不涉及到运行时重加载类。

如果我们在一个JVM中,先加载了一个类,然后又对其进行字节码增强并重新加载会发生什么呢?

模拟这种情况,只需要我们在上文中Javassist的Demo中main()方法的第一行添加Base b=new Base(),即在增强前就先让JVM加载Base类,然后在执行到c.toClass()方法时会抛出错误,

如下图20所示。跟进c.toClass()方法中,我们会发现它是在最后调用了ClassLoader的native方法defineClass()时报错。也就是说,JVM是不允许在运行时动态重载一个类的。

Java——字节码技术

显然, 如果只能在类加载前对类进行强化,那字节码增强技术的使用场景就变得很窄了。我们期望的效果是: 在一个持续运行并已经加载了所有类的JVM中,还能利用字节码增强技术对其中的类行为做替换并重新加载

为了模拟这种情况,我们将Base类做改写,在其中编写main方法,每五秒调用一次process()方法,在process()方法中输出一行”process”。

import java.lang.management.ManagementFactory;

public class Base {
    public static void main(String[] args) {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        String s = name.split("@")[0];
        //打印当前Pid
        System.out.println("pid:"+s);
        while (true) {
            try {
                Thread.sleep(5000L);
            } catch (Exception e) {
                break;
            }
            process();
        }
    }

    public static void process() {
        System.out.println("process");
    }
}

3.2 Instrument

instrument是JVM提供的一个可以修改已加载类的类库,专门为Java语言编写的插桩服务提供支持。它需要依赖JVMTI的Attach API机制实现,JVMTI这一部分,我们将在下一小节进行介绍。

在JDK 1.6以前,instrument只能在JVM刚启动开始加载类时生效,而在JDK 1.6之后,instrument支持了在运行时对类定义的修改。要使用instrument的类修改功能,我们需要实现它提供的ClassFileTransformer接口,定义一个类文件转换器。

接口中的transform()方法会在类文件被加载时调用,而在transform方法里,我们可以利用上文中的ASM或Javassist对传入的字节码进行改写或替换,生成新的字节码数组后返回。

我们定义一个实现了ClassFileTransformer接口的类TestTransformer,依然在其中利用Javassist对Base类中的process()方法进行增强,在前后分别打印”start”和”end”,代码如下:

import java.lang.instrument.ClassFileTransformer;

public class TestTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        System.out.println("Transforming " + className);
        try {
            ClassPool cp = ClassPool.getDefault();
            CtClass cc = cp.get("meituan.bytecode.jvmti.Base");
            CtMethod m = cc.getDeclaredMethod("process");
            m.insertBefore("{ System.out.println(\"start\"); }");
            m.insertAfter("{ System.out.println(\"end\"); }");
            return cc.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

现在有了Transformer,那么它要如何注入到正在运行的JVM呢?还需要定义一个Agent,借助Agent的能力将Instrument注入到JVM中。我们将在下一小节介绍Agent,现在要介绍的是Agent中用到的另一个类Instrumentation。

在JDK 1.6之后,Instrumentation可以做启动后的Instrument、本地代码(Native Code)的Instrument,以及动态改变Classpath等等。

我们可以向Instrumentation中添加上文中定义的Transformer,并指定要被重加载的类,代码如下所示。这样,当Agent被Attach到一个JVM中时,就会执行类字节码替换并重载入JVM的操作。

import java.lang.instrument.Instrumentation;

public class TestAgent {
    public static void agentmain(String args, Instrumentation inst) {
        //指定我们自己定义的Transformer,在其中利用Javassist做字节码替换
        inst.addTransformer(new TestTransformer(), true);
        try {
            //重定义类并载入新的字节码
            inst.retransformClasses(Base.class);
            System.out.println("Agent Load Done.");
        } catch (Exception e) {
            System.out.println("agent load failed!");
        }
    }
}

3.3 JVMTI & Agent & Attach API

上一小节中,我们给出了Agent类的代码,追根溯源需要先介绍JPDA(Java Platform Debugger Architecture)。如果JVM启动时开启了JPDA,那么类是允许被重新加载的。

在这种情况下,已被加载的旧版本类信息可以被卸载,然后重新加载新版本的类。正如JDPA名称中的Debugger,JDPA其实是一套用于调试Java程序的标准,任何JDK都必须实现该标准。

JPDA定义了一整套完整的体系,它将调试体系分为三部分,并规定了三者之间的通信接口。三部分由低到高分别是Java 虚拟机工具接口(JVMTI),Java 调试协议(JDWP)以及 Java 调试接口(JDI),

三者之间的关系如下图所示:

Java——字节码技术

现在回到正题,我们可以借助JVMTI的一部分能力,帮助动态重载类信息。JVM TI(JVM TOOL INTERFACE,JVM工具接口)是JVM提供的一套对JVM进行操作的工具接口。

通过JVMTI,可以实现对JVM的多种操作,它通过接口注册各种事件勾子,在JVM事件触发时,同时触发预定义的勾子,以实现对各个JVM事件的响应,事件包括 类文件加载

异常产生与捕获线程启动和结束进入和退出临界区成员变量修改GC开始和结束方法调用进入和退出临界区竞争与等待VM启动与退出等等。

Agent 就是JVMTI 的一种实现,Agent有两种启动方式:

  • 一是随Java进程启动而启动,经常见到的java -agentlib就是这种方式;
  • 二是运行时载入,通过attach API,将模块(jar包)动态地Attach到指定进程id的Java进程内。

Attach API 的作用是提供J VM进程间通信的能力,比如说我们为了让另外一个JVM进程把线上服务的线程Dump出来,会运行jstack或jmap的进程,并传递pid的参数,告诉它要对哪个进程进行线程Dump,这就是Attach API做的事情。

在下面,我们将通过Attach API的loadAgent()方法,将打包好的Agent jar包动态Attach到目标JVM上。具体实现起来的步骤如下:

  • 定义Agent,并在其中实现AgentMain方法,如上一小节中定义的代码块7中的TestAgent类;
  • 然后将TestAgent类打成一个包含MANIFEST.MF的jar包,其中MANIFEST.MF文件中将Agent-Class属性指定为TestAgent的全限定名,如下图所示;

Java——字节码技术
  • 最后利用Attach API,将我们打包好的jar包Attach到指定的JVM pid上,代码如下:
import com.sun.tools.attach.VirtualMachine;

public class Attacher {
    public static void main(String[] args) throws AttachNotSupportedException, IOException, AgentLoadException, AgentInitializationException {
        // 传入目标 JVM pid
        VirtualMachine vm = VirtualMachine.attach("39333");
        vm.loadAgent("/Users/zen/operation_server_jar/operation-server.jar");
    }
}
  • 由于在MANIFEST.MF中指定了Agent-Class,所以在Attach后,目标JVM在运行时会走到TestAgent类中定义的agentmain()方法,而在这个方法中,我们利用Instrumentation,将指定类的字节码通过定义的类转化器TestTransformer做了Base类的字节码替换(通过javassist),并完成了类的重新加载。由此,我们达成了”在JVM运行时,改变类的字节码并重新载入类信息”的目的。

以下为运行时重新载入类的效果:先运行Base中的main()方法,启动一个JVM,可以在控制台看到每隔五秒输出一次”process”。接着执行Attacher中的main()方法,并将上一个JVM的pid传入。

此时回到上一个main()方法的控制台,可以看到现在每隔五秒输出”process”前后会分别输出”start”和”end”,也就是说完成了运行时的字节码增强,并重新载入了这个类。

Java——字节码技术

3.4 使用场景

至此,字节码增强技术的可使用范围就不再局限于JVM加载类前了。通过上述几个类库,我们可以在运行时对JVM中的类进行修改并重载了。通过这种手段,可以做的事情就变得很多了:

  • 热部署:不部署服务而对线上服务做修改,可以做打点、增加日志等操作。
  • Mock:测试时候对某些服务做Mock。
  • 性能诊断工具:比如bTrace就是利用Instrument,实现无侵入地跟踪一个正在运行的JVM,监控到类和方法级别的状态信息。

  • 总结

字节码增强技术相当于是一把打开运行时JVM的钥匙,利用它可以动态地对运行中的程序做修改,也可以跟踪JVM运行中程序的状态。

此外,我们平时使用的动态代理、AOP也与字节码增强密切相关,它们实质上还是利用各种手段生成符合规范的字节码文件。

综上所述,掌握字节码增强后可以高效地定位并快速修复一些棘手的问题(如线上性能问题、方法出现不可控的出入参需要紧急加日志等问题),也可以在开发中减少冗余代码,大大提高开发效率。

转载:

Original: https://www.cnblogs.com/caoweixiong/p/15213739.html
Author: 曹伟雄
Title: Java——字节码技术

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/538824/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 聊聊redis的主从复制吧

    聊聊基础概念 主从复制与主从替换 主从复制不同于主从替换,主从复制是正常情况下主节点同步数据到从节点;主从替换是主节点挂了之后,把从节点替换为主节点; 从节点存在的意义:备份主节点…

    Java 2023年6月8日
    075
  • 栈的定义和应用(数组模拟)

    1、定义 栈是一个 先入后出的有序列表 栈(stack)是限制线性表中元素的插入和删除只能在线性表的 同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶,另一…

    Java 2023年6月16日
    083
  • 【Linux】教学视频推荐以及一些经验分享

    好的教学视频和工具真的可以让你学习的很舒服很透彻,就如古人云:工欲善其事必先利其器。 一、Linux视频推荐 1、没错又是韩老师的视频,韩保姆的视频真的可以放一万个心去看,而且内容…

    Java 2023年6月13日
    083
  • Mysql 去回车空格

    MySQL 去除字段中的换行和回车符 解决方法:UPDATE tablename SET xxx= REPLACE(REPLACE(xxx, CHAR(10), ”),…

    Java 2023年6月13日
    073
  • 操作系统 文件管理 实验(C语言)

    实验要求和提示 1、利用内存或外存存储结构实现文件系统的树型目录结构,并通过交互式命令完成文件与目录管理。 2 、至少提供如下命令(大小写都能识别):MD(创建空目录)、CD(切换…

    Java 2023年6月5日
    092
  • MySQL排序原理与案例分析

    转载:MySQL排序原理与案例分析 posted @2021-04-04 18:12 轨迹320 阅读(34 ) 评论() 编辑 Original: https://www.cnb…

    Java 2023年6月5日
    091
  • Sharepoint 2013 安装部署系列篇 第三篇 — 安装和配置网络负载均衡在前端web服务器

    第一部分 系统集群安装 第二部分 SQL集群安装 第四部分 安装和配置sharepoint 场(三层拓扑部署) 接下来一步一步开始配置NLB吧, 以下开始讲解如何配置NLB集群作为…

    Java 2023年6月7日
    0157
  • 抽象类和接口,你了解多少?

    现在的java开发一般都说面向接口编程,在开发过程中使用的最多的是给service层每个方法写一个接口,如果用到了DAO层,那么也是一个Mapper接口,之后的事情就交给mybat…

    Java 2023年6月9日
    076
  • 数据库篇:mysql事务原理之MVCC视图+锁

    前言 数据库的事务特性 数据并发读写时遇到的一致性问题 mysql事务的隔离级别 MVCC的实现原理 锁和隔离级别 关注公众号,一起交流,微信搜一搜: 潜行前行 1 数据库的事务特…

    Java 2023年6月5日
    0114
  • Spring 中经典的 9 种设计模式,打死也要记住啊!

    来源:blog.csdn.net/caoxiaohong1005 转载: https://mp.weixin.qq.com/s/HdOKIp_rFgX-h65M0pRK9Q 1.简…

    Java 2023年5月30日
    070
  • etcd v3版本生产级集群搭建以及实现一键启动脚本

    本专栏的上一篇文章写了《长篇图解etcd核心应用场景及编码实战》,本文继续。后续计划章节内容如下: 《长篇图解etcd核心应用场景及编码实战》 《搭建高可用etcd集群》 《基于e…

    Java 2023年6月15日
    074
  • nginx重新整理——————nginx 模块[十]

    前言 简单介绍一下nginx的模块。 正文 https://nginx.org/en/docs/ 这里面可以看到官方模块。 比如打开这个模块: https://nginx.org/…

    Java 2023年5月30日
    073
  • JVM快速入门

    JVM的位置 JVM是运行在操作系统之上的。 JVM体系结构 类加载器 类加载器的作用:加载class文件 加载器:①.ApplicationClassLoader应用程序类加载器…

    Java 2023年6月8日
    072
  • 如何从0到1设计一个类Dubbo的RPC框架

    之前分享了如何从0到1设计一个MQ消息队列,今天谈谈”如何从0到1设计一个Dubbo的RPC框架”,重点考验: 你对RPC框架的底层原理掌握程度。 以及考验…

    Java 2023年6月15日
    076
  • LeetCode题解—-两数之和

    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答…

    Java 2023年6月6日
    075
  • Spring源码学习笔记4——BeanFactoryPostProcessor执行

    一丶BeanFactoryPostProcessor是什么 Spring留给我们的一个扩展接口,在BeanDefinition加载注册完之后,并执行一些前置操作(笔记3)之后会反射…

    Java 2023年6月14日
    082
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球