博客内容来源于 刘欣老师的课程,刘欣老师的公众号 码农翻身

博客内容来源于 Java虚拟机规范(JavaSE7)

博客内容的源码 https://gitee.com/zumengjie/litejvm

阅读此博客请配合源码食用。

ClassLoader

ClassLoader就是根据类名去classpath路径上找到Class文件然后解析Class文件形成Class类对象。

package com.datang.litejvm.loader;

import com.datang.litejvm.clz.Class;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import java.io.*;
import java.util.ArrayList;
import java.util.List; /**
* @author: 顶风少年
* @Description: 类加载器
* @date: 13:54 2022/6/10
**/
public class ClassLoader { /**
* @author: 顶风少年
* @Description: 存放环境变量
* @date: 21:58 2022/6/8
**/
private List<String> clzPaths = new ArrayList<String>(); /**
* @author: 顶风少年
* @Description: 添加环境变量 classPath
* @date: 21:53 2022/6/8
**/
public void addClassPath(String path) {
if (this.clzPaths.contains(path)) {
return;
}
this.clzPaths.add(path);
} /**
* @author: 顶风少年
* @Description: 返回classPath, 多个中间使用 ; 拼接
* @date: 21:53 2022/6/8
**/
public String getClassPath() {
return StringUtils.join(this.clzPaths, ";");
} /**
* @author: 顶风少年
* @Description: 将classpath和className拼接获取类路径
* @date: 21:53 2022/6/8
**/
public byte[] readBinaryCode(String className) {
className = className.replace('.', File.separatorChar) + ".class";
for (String path : this.clzPaths) {
String clzFileName = path + File.separatorChar + className;
byte[] codes = loadClassFile(clzFileName);
if (codes != null) {
return codes;
}
}
return null;
} /**
* @author: 顶风少年
* @Description: 根据类路径读取class文件
* @date: 21:54 2022/6/8
**/
private byte[] loadClassFile(String clzFileName) {
File f = new File(clzFileName);
try {
return IOUtils.toByteArray(new FileInputStream(f));
} catch (IOException e) {
e.printStackTrace();
return null;
}
} /**
* @author: 顶风少年
* @Description: 解析class文件形成Class对象,在这里是形成ClassFile对象
* @date: 13:55 2022/6/10
**/
public Class loadClass(String className) {
byte[] codes = this.readBinaryCode(className);
ClassParser parser = new ClassParser();
return parser.parse(codes);
}
}

Class

首先我们对一个已经编译完成的class文件进行解析,解析的过程就是读取每一个字节,然后了解其结构以及含义,将其解析为一个Class类。使用javap -v xxx.class命令可以查看该class的结构。

Class字节码文件的内容十分紧凑,首先是魔数,小版本号,大版本号,常量池,类访问权限,类,父类,父接口,字段列表,方法列表。常量池占了大部分的字节码文件,其余的部分很多都会引用常量池项。

解析Class文件就是读取固定长度的字节,魔数为4个字节,小版本号2个字节,大版本号两个字节。接下来的常量池稍微复杂,两个字节标记了常量池项的总个数,其中每一个常量池项都有对应的数据结构。需要先读取1个字节判断它的结构,例如tag是1则它是一个CONSTANT_Utf8_info,这是一个最简单的结构,2个字节的长度表示后续字符串的长度,然后再读取对应长度的字节数。常量池项是可以引用常量池项的,例如第7项是一个CONSTANT_Class_info这个结构包含了2个字节的index它指向常量池的第40项目,是Class对应的类名。解析常量池时需要根据tag判断常量池项是什么结构,然后再根据具体的结构读取字节。

接下来是读取2个字节类的访问权限,无论是类,字段,方法都有访问权限,注意访问权限可能有多个,例如类可以是 <public> <final>

接下来是2个字节的类名,2个字节的父类名,2个字节的接口个数,每个接口名也是2个字节。

之后是类成员变量也就是字段,2个字节的成员个数,每个字段里包含2个字节的访问权限,2个字节的字段名,2个字节的字段类型,两个字节的的属性个数,这个属性也是有多种结构的,方法里也有,放到方法里说。

字节码文件的最后一部分是方法,2个字节的方法个数,每个方法包含2个字节的访问权限,2个字节的方法名,2个字节的方法签名(入参和返回值)。2个字节的属性个数,每个属性中包含,2个字节的属性名称。在方法中只有一个属性就是Code。

jvm定义的属性有以下结构,字段的属性也在其中。

首先是2个字节的属性名称,根据属性名判断属性。每个属性都有4个字节的属性长度,它标记了接下来的属性内容的字节数。Code属性中包含2个字节的栈深度,2个字节的局部变量表,4个字节的字节码长度,字节码长度为N则表示有N个字节的字节码指令,每个指令1个字节。对于字节码指令需要展开说,每个字节码指令根据其含义的不同可能带有不同的参数,例如bb含义为new对象,bb的后边有2个字节是它的参数,这两个参数指向了常量池中的常量项是需要new的对象类名。

在字节码指令后是2个字节的异常长度,异常的相关的内容,我本次没有解析,只是手动跳过了异常。

Code属性中还包含了2个其他属性。LineNumberTable和LocalVariableTable其一是行号相关,其二是参数列表。

2个字节区分属性的名称,4个字节表示属性内容的长度。LineNumberTable有2个字节的行数,每一行中包含2个字节的起始位,2个字节的行号。

LocalVariableTable有2个字节的参数个数,每个参数有2个字节的起始位,2个字节的长度,2个字节的属性名,2个字节的下标。

以上是Class文件中可以解析出来的内容,当然根据Class的复杂程度,解析出来的内容不同,我这里是最基本的Class属性,将其解析完毕后,形成一个Class类。

package com.datang.litejvm.clz;

import com.datang.litejvm.constant.ConstantClassInfo;
import com.datang.litejvm.constant.ConstantPool;
import com.datang.litejvm.constant.ConstantUTF8Info; import java.util.Iterator;
import java.util.List; /**
* @author: 顶风少年
* @Description: Class类
* @date: 13:55 2022/6/10
**/
public class Class { //魔数
private String magic; //小版本号
private int minorVersion; //大版本号
private int majorVersion; //常量池
private ConstantPool pool; //访问标记
private ClassAccessFlag classAccessFlag; //类
private int thisClassIndex; //父类
private int superClassIndex; private String superClassName; //接口引用
private List<Integer> interfaceIndexList; //属性列表
private List<Field> fieldList; //方法
private List<Method> methodList; //------------------------------------------------------- /**
* @author: 顶风少年
* @Description: 魔数
* @date: 11:39 2022/6/10
**/
public String getMagic() {
return magic;
} /**
* @author: 顶风少年
* @Description: 魔数
* @date: 11:39 2022/6/10
**/
public void setMagic(String magic) {
this.magic = magic;
} /**
* @author: 顶风少年
* @Description: 小版本号
* @date: 11:28 2022/6/10
**/
public int getMinorVersion() {
return minorVersion;
} /**
* @author: 顶风少年
* @Description: 小版本号
* @date: 11:28 2022/6/10
**/
public void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
} /**
* @author: 顶风少年
* @Description: 大版本号
* @date: 11:28 2022/6/10
**/
public int getMajorVersion() {
return majorVersion;
} /**
* @author: 顶风少年
* @Description: 大版本号
* @date: 11:28 2022/6/10
**/
public void setMajorVersion(int majorVersion) {
this.majorVersion = majorVersion;
} /**
* @author: 顶风少年
* @Description: 常量池
* @date: 11:28 2022/6/10
**/
public ConstantPool getConstantPool() {
return pool;
} /**
* @author: 顶风少年
* @Description: 常量池
* @date: 11:28 2022/6/10
**/
public void setConstPool(ConstantPool pool) {
this.pool = pool;
} /**
* @author: 顶风少年
* @Description: 访问标记
* @date: 17:18 2022/6/10
**/
public ClassAccessFlag getAccessFlag() {
return classAccessFlag;
} /**
* @author: 顶风少年
* @Description: 访问标记
* @date: 17:18 2022/6/10
**/
public void setAccessFlag(ClassAccessFlag classAccessFlag) {
this.classAccessFlag = classAccessFlag;
} /**
* @author: 顶风少年
* @Description: 类
* @date: 17:18 2022/6/10
**/
public int getThisClassIndex() {
return thisClassIndex;
} public void setThisClassIndex(int thisClassIndex) {
this.thisClassIndex = thisClassIndex;
} /**
* @author: 顶风少年
* @Description: 父类
* @date: 17:18 2022/6/10
**/
public int getSuperClassIndex() {
return superClassIndex;
} public void setSuperClassIndex(int superClassIndex) {
this.superClassIndex = superClassIndex;
ConstantClassInfo constantClassInfo = (ConstantClassInfo) pool.getConstantInfo(superClassIndex);
this.superClassName = constantClassInfo.getClassName();
} public String getSuperClassName() {
return superClassName;
} /**
* @author: 顶风少年
* @Description: 接口
* @date: 17:18 2022/6/10
**/
public List<Integer> getInterfaceIndexList() {
return interfaceIndexList;
} public void setInterfaceIndexList(List<Integer> interfaceIndexList) {
this.interfaceIndexList = interfaceIndexList;
} /**
* @author: 顶风少年
* @Description: 属性列表
* @date: 11:22 2022/6/12
**/
public List<Field> getFieldList() {
return fieldList;
} /**
* @author: 顶风少年
* @Description: 属性列表
* @date: 11:22 2022/6/12
**/
public void setFieldList(List<Field> fieldList) {
this.fieldList = fieldList;
} /**
* @author: 顶风少年
* @Description: 方法
* @date: 18:31 2022/6/12
**/
public List<Method> getMethodList() {
return methodList;
} /**
* @author: 顶风少年
* @Description: 方法
* @date: 18:31 2022/6/12
**/
public void setMethodList(List<Method> methodList) {
this.methodList = methodList;
} /**
* @author: 顶风少年
* @Description: 根据方法名和方法签名查询方法
* @date: 10:47 2022/6/16
**/
public Method getMethod(String methodName, String paramAndResultType) {
Method rMethod = null;
Iterator<Method> iter = methodList.iterator();
while (iter.hasNext()) {
Method method = iter.next();
int nameIndex = method.getNameIndex();
int descriptorIndex = method.getDescriptorIndex(); ConstantUTF8Info nameInfo = (ConstantUTF8Info) pool.getConstantInfo(nameIndex);
ConstantUTF8Info descriptorInfo = (ConstantUTF8Info) pool.getConstantInfo(descriptorIndex); if (nameInfo.getBytes().equals(methodName) && descriptorInfo.getBytes().equals(paramAndResultType)) {
rMethod = method;
}
}
return rMethod;
} /**
* @author: 顶风少年
* @Description: 查询main方法
* @date: 10:36 2022/6/16
**/
public Method getMainMethod() {
return getMethod("main", "([Ljava/lang/String;)V");
}
}

MethodArea

方法区中有个Map它的key是class类名,value是Class对象。使用ClassLoader解析后的Class对象都存在这里。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Field;
import com.datang.litejvm.clz.Method;
import com.datang.litejvm.constant.ConstantFieldRefInfo;
import com.datang.litejvm.constant.ConstantMethodRefInfo;
import com.datang.litejvm.loader.ClassLoader;
import com.datang.litejvm.clz.Class;
import java.util.HashMap;
import java.util.Map; /**
* @author: 顶风少年
* @Description: 方法区
* @date: 10:49 2022/6/16
**/
public class MethodArea { public static final MethodArea instance = new MethodArea(); /**
* 注意:我们做了极大的简化, ClassLoader 只有一个, 实际JVM中的ClassLoader,是一个双亲委托的模型
*/ private ClassLoader classLoader = null; /**
* @author: 顶风少年
* @Description: 存放所有的Class
* @date: 10:39 2022/6/16
**/
Map<String, Class> map = new HashMap<String, Class>(); private MethodArea(){
} /**
* @author: 顶风少年
* @Description: 单例,获取常量池
* @date: 10:39 2022/6/16
**/
public static MethodArea getInstance(){
return instance;
} public void setClassFileLoader(ClassLoader clzLoader){
this.classLoader = clzLoader;
} /**
* @author: 顶风少年
* @Description: 获取main方法
* @date: 10:39 2022/6/16
**/
public Method getMainMethod(String className){
Class clz = this.findClass(className);
return clz.getMainMethod();
} /**
* @author: 顶风少年
* @Description: 从指定class中 根据名称获取方法
* @date: 22:23 2022/6/16
**/
public Method getMethod(ConstantMethodRefInfo constantMethodRefInfo){
Class aClass = findClass(constantMethodRefInfo.getClassName());
Method method = aClass.getMethod(constantMethodRefInfo.getMethodName(), constantMethodRefInfo.getParamAndResult());
return method;
} /**
* @author: 顶风少年
* @Description: 创建Class
* @date: 10:38 2022/6/16
**/
public Class findClass(String className){
if(map.get(className) != null){
return map.get(className);
}
// 看来该class 文件还没有load过
Class aClass = this.classLoader.loadClass(className);
map.put(className, aClass);
return aClass;
}
}

ExecutorEngine

执行引擎中主要有一个栈结构,栈中的每个元素是StackFrame栈帧。执行引擎执行第一步将main方法压入栈中,然后就是执行栈帧。每个栈帧其实就是一个method当方法执行结束后返回ExecutionResult里边封装了该方法是暂存运行其他method还是方法运行结束出栈。如果是运行其他的method则将跳转方法压入栈中,并且传递参数,如果是方法执行结束则将当前方法出栈。执行引擎就是在不断的判断栈内是否还有元素,如果栈为空则表示当前程序执行结束。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack; /**
* @author: 顶风少年
* @Description: 执行引擎
* @date: 15:19 2022/6/16
**/
public class ExecutorEngine { /**
* @author: 顶风少年
* @Description: 栈
* @date: 15:41 2022/6/16
**/
private Stack<StackFrame> stack = new Stack<StackFrame>(); public void execute(Method mainMethod) {
//创建main栈帧
StackFrame stackFrame = StackFrame.create(mainMethod);
//入栈
stack.push(stackFrame);
//接下来就是对栈做操作了,入栈,出栈
while (!stack.isEmpty()) {
//拿到栈顶栈帧
StackFrame frame = stack.peek();
//执行栈帧
ExecutionResult result = frame.execute();
//暂停,并运行新的栈帧.有函数调用了
if (result.isPauseAndRunNewFrame()) {
//下一个method
Method nextMethod = result.getNextMethod();
//形成新的栈帧
StackFrame nextFrame = StackFrame.create(nextMethod);
nextFrame.setCallerFrame(frame);
setupFunctionCallParams(frame, nextFrame);
//将新的栈帧也入栈
stack.push(nextFrame);
} else {
//出栈
stack.pop();
}
}
} /**
* @author: 顶风少年
* @Description: 给下个调用方法设置参数
* @date: 16:07 2022/6/16
**/
private void setupFunctionCallParams(StackFrame currentFrame, StackFrame nextFrame) {
Method nextMethod = nextFrame.getMethod();
//获取参数列表
List<String> parameterList = nextMethod.getParameterList();
List<JavaObject> values = new ArrayList<>();
//要添加 this
int paramNum = parameterList.size() + 1; while (paramNum > 0) {
values.add(currentFrame.getOperandStack().pop());
paramNum--;
} List<JavaObject> params = new ArrayList<>();
for (int i = values.size() - 1; i >= 0; i--) {
params.add(values.get(i));
}
//设置局部变量表
nextFrame.setLocalVariableTable(params);
} }

StackFrame

每个方法入栈都会形成一个StackFrame,栈帧中包含两个数据结构。局部变量表和操作数栈,局部变量表是用来存放方法的入参,方法内的计算结果,操作数栈则是真正运算的地方,我们经常说的 1 + 2 = 3 的操作其实是在操作数栈进行的,先将 1 和 2 压入操作数栈,将 1 和 2 出栈进行运算最后得出的 3 将其再次压入操作数栈。StackFrame执行时就是从method总取出Code属性中的操作指令,一条一条的执行。每条指令执行结束后会对ExecutionResult进行设置。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;
import com.datang.litejvm.cmd.ByteCodeCommand; import java.util.ArrayList;
import java.util.List;
import java.util.Stack; /**
* @author: 顶风少年
* @Description: 函数栈帧
* @date: 15:41 2022/6/16
**/
public class StackFrame {
//局部变量表
private List<JavaObject> localVariableTable = new ArrayList<JavaObject>();
//操作数栈
private Stack<JavaObject> operandStack = new Stack<JavaObject>(); //字节码指令偏移量,指向下一个操作指令
int index = 0; //当前方法
private Method m = null; //上一个函数栈帧
private StackFrame callerFrame = null; private StackFrame(Method m) {
this.m = m;
} //创建函数栈帧
public static StackFrame create(Method m) {
StackFrame frame = new StackFrame(m);
return frame;
} /**
* @author: 顶风少年
* @Description: 上一个函数栈帧
* @date: 16:44 2022/6/16
**/
public StackFrame getCallerFrame() {
return callerFrame;
} public void setCallerFrame(StackFrame callerFrame) {
this.callerFrame = callerFrame;
} /**
* @author: 顶风少年
* @Description: 栈帧所属方法
* @date: 16:45 2022/6/16
**/
public Method getMethod() {
return m;
} /**
* @author: 顶风少年
* @Description: 设置局部变量表
* @date: 16:40 2022/6/16
**/
public void setLocalVariableTable(List<JavaObject> values) {
this.localVariableTable = values;
} /**
* @author: 顶风少年
* @Description: 获取局部变量表中的某个变量
* @date: 10:22 2022/6/17
**/
public JavaObject getLocalVariableValue(int index) {
return this.localVariableTable.get(index);
} /**
* @author: 顶风少年
* @Description: 向局部变量表设置值
* @date: 10:38 2022/6/17
**/
public void setLocalVariableValue(int index, JavaObject jo) {
//问题: 为什么要这么做??
if (this.localVariableTable.size() - 1 < index) {
for (int i = this.localVariableTable.size(); i <= index; i++) {
this.localVariableTable.add(null);
}
}
this.localVariableTable.set(index, jo);
} /**
* @author: 顶风少年
* @Description: 执行方法, 就是执行字节码指令
* @date: 17:11 2022/6/16
**/
public ExecutionResult execute() {
List<ByteCodeCommand> cmds = m.getCodeAttr().getCmds();
while (index < cmds.size()) {
//执行结果
ExecutionResult result = new ExecutionResult();
//下一条字节码指令
ByteCodeCommand cmd = cmds.get(index);
System.out.println(cmd.toString());
//执行
cmd.execute(this, result);
//运行下一条
if (result.isRunNextCmd()) {
index++;
} else if (result.isExitCurrentFrame()) {
//退出当前栈帧,return 剩余的栈帧不执行了
return result;
} else if (result.isPauseAndRunNewFrame()) {
//暂停当前栈帧,执行新的函数栈帧
index++;
return result;
} else if (result.isJump()) {
//跳转指令,跳转到下一个字节码指令
int offset = result.getNextCmdOffset();
//设置下一个指令的偏移量
this.index = getNextCommandIndex(offset);
} else {
index++;
}
}
//如果循环走完了,说明没有任何的跳转,停止,表示当前StackFrame的指令全部执行完毕,可以退出了
ExecutionResult result = new ExecutionResult();
result.setNextAction(ExecutionResult.EXIT_CURRENT_FRAME);
return result;
} /**
* @author: 顶风少年
* @Description: 根据偏移量查询字节码指令
* @date: 17:48 2022/6/16
**/
public int getNextCommandIndex(int offset) {
List<ByteCodeCommand> cmds = m.getCodeAttr().getCmds();
for (int i = 0; i < cmds.size(); i++) {
if (cmds.get(i).getOffset() == offset) {
return i;
}
}
throw new RuntimeException("Can't find next command");
} /**
* @author: 顶风少年
* @Description: 获取操作数栈
* @date: 19:04 2022/6/16
**/
public Stack<JavaObject> getOperandStack() {
return this.operandStack;
} }

ExecutionResult

操作指令执行结束后会设置ExecutionResult,这个类标记了当前字节码执行后的行为,默认的是RUN_NEXT_CMD继续执行下一条字节码指令,也可能是JUMP跳转到指定的指令号,EXIT_CURRENT_FRAME停止执行,PAUSE_AND_RUN_NEW_FRAME暂存当前栈帧,执行新的栈帧。如果是JUMP或RUN_NEXT_CMD则当前栈帧继续执行。如果是EXIT_CURRENT_FRAME和PAUSE_AND_RUN_NEW_FRAME就要返回到执行引擎层。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;

/**
* @author: 顶风少年
* @Description: 执行结果
* @date: 17:29 2022/6/16
**/
public class ExecutionResult {
//默认值 执行下一条指令
public static final int RUN_NEXT_CMD = 1;
//跳转指令
public static final int JUMP = 2;
//退出当前栈帧,剩余指令不执行了
public static final int EXIT_CURRENT_FRAME = 3;
//暂停当前栈帧,执行新的函数栈帧
public static final int PAUSE_AND_RUN_NEW_FRAME = 4; //下一次的行为
private int nextAction = RUN_NEXT_CMD; //如果是跳转指令 JUMP ,则会记录下一个指令偏移量
private int nextCmdOffset = 0; //如果是执行新的栈帧 EXIT_CURRENT_FRAME 则需要设置栈帧对应的方法
private Method nextMethod; /**
* @author: 顶风少年
* @Description: 获取下一个执行的方法
* @date: 17:46 2022/6/16
**/
public Method getNextMethod() {
return nextMethod;
} public void setNextMethod(Method nextMethod) {
this.nextMethod = nextMethod;
} /**
* @author: 顶风少年
* @Description: 设置字节码执行后的行为, 默认为 RUN_NEXT_CMD 执行下一条指令
* @date: 17:31 2022/6/16
**/
public void setNextAction(int action) {
this.nextAction = action;
} /**
* @author: 顶风少年
* @Description: 下一个字节码指令偏移量
* @date: 17:43 2022/6/16
**/
public int getNextCmdOffset() {
return nextCmdOffset;
} public void setNextCmdOffset(int nextCmdOffset) {
this.nextCmdOffset = nextCmdOffset;
} /**
* @author: 顶风少年
* @Description: 执行结果
* @date: 17:44 2022/6/16
**/
public boolean isPauseAndRunNewFrame() {
return this.nextAction == PAUSE_AND_RUN_NEW_FRAME;
} public boolean isExitCurrentFrame() {
return this.nextAction == EXIT_CURRENT_FRAME;
} public boolean isRunNextCmd() {
return this.nextAction == RUN_NEXT_CMD;
} public boolean isJump() {
return this.nextAction == JUMP;
}
}

Heap

堆这个对象用来模拟创建不同类型的对象,当前可以创建String,int,float,Object。如果创建的Object则还可以给Object设置属性。

package com.datang.litejvm.engin;

/**
* @author: 顶风少年
* @Description: 堆,可以有多种类型,对象,字符串,int float
* @date: 21:02 2022/6/16
**/
public class Heap { /**
* 没有实现垃圾回收, 所以对于下面新创建的对象, 并没有记录到一个数据结构当中
*/ private static Heap instance = new Heap();
private Heap() {
}
public static Heap getInstance(){
return instance;
} public JavaObject newObject(String clzName){
JavaObject jo = new JavaObject(JavaObject.OBJECT);
jo.setClassName(clzName);
return jo;
} public JavaObject newString(String value){
JavaObject jo = new JavaObject(JavaObject.STRING);
jo.setStringValue(value);
return jo;
} public JavaObject newFloat(float value){
JavaObject jo = new JavaObject(JavaObject.FLOAT);
jo.setFloatValue(value);
return jo;
}
public JavaObject newInt(int value){
JavaObject jo = new JavaObject(JavaObject.INT);
jo.setIntValue(value);
return jo;
} }

jvm造轮子的更多相关文章

  1. 别在重复造轮子了,几个值得应用到项目中的 Java 开源库送给你

    我是风筝,公众号「古时的风筝」.文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面.公众号回复『666』获取高清大图. 风筝我作为一个野路子开发者,直到 ...

  2. 避免重复造轮子的UI自动化测试框架开发

    一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...

  3. 【疯狂造轮子-iOS】JSON转Model系列之二

    [疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...

  4. 【疯狂造轮子-iOS】JSON转Model系列之一

    [疯狂造轮子-iOS]JSON转Model系列之一 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗 ...

  5. h5engine造轮子

    基于学习的造轮子,这是一个最简单,最基础的一个canvas渲染引擎,通过这个引擎架构,可以很快的学习canvas渲染模式! 地址:https://github.com/RichLiu1023/h5en ...

  6. 我为什么还要造轮子?欠踹?Monk.UI表单美化插件诞生记!

    背景 目前市场上有很多表单美化的UI,做的都挺不错,但是他们都有一个共同点,那就是90%以上都是前端工程师开发的,导致我们引入这些UI的时候,很难和程序绑定.所以作为程序员的我,下了一个决定!我要自己 ...

  7. 「iOS造轮子」之UIButton 用Block响应事件

    俗语说 一个不懒的程序员不是好程序员 造轮子,也只是为了以后更好的coding. coding,简易明了的代码更是所有程序员都希望看到的 无论是看自己的代码,还是接手别人的代码 都希望一看都知道这代码 ...

  8. 重复造轮子感悟 – XLinq性能提升心得

    曾经的两座大山 1.EF 刚接触linq那段时间,感觉这家伙好神奇,语法好优美,好厉害.后来经历了EF一些不如意的地方,就想去弥补,既然想弥补,就必须去了解原理.最开始甚至很长一段时间都搞不懂IQue ...

  9. GitHub Android 最火开源项目Top20 GitHub 上的开源项目不胜枚举,越来越多的开源项目正在迁移到GitHub平台上。基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要。利用这些项目,有时能够让你达到事半功倍的效果。

    1. ActionBarSherlock(推荐) ActionBarSherlock应该算得上是GitHub上最火的Android开源项目了,它是一个独立的库,通过一个API和主题,开发者就可以很方便 ...

随机推荐

  1. Model, HttpServletRequest, ModelMap区别

    看了spring mvc的底层会发现,model数据最终还是写到HttpServletRequest属性中,只是model的写法更体现了MVC思想减少各层间耦合 写法: 1.request.setAt ...

  2. zabbix监控SSL证书有效期

    想给公司网站加上证书的监控,发现agent无此监控项.科普之后发现需要自行添加脚本以及一些操作. 环境信息 系统版本: Ubuntu20.04 zabbix server版本:5.4 (这个自定义貌似 ...

  3. .NET Core(.NET6)中gRPC使用

    一.简介 简单解析一下gRPC,gRPC 是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架. 特点: 跨语言 内容protobuf格式(比json体积小),网络传输快 使用HT ...

  4. 公私钥 SSH 数字证书

    公私钥 SSH 数字证书 小菜鸟今天买了华为云一台服务器,在使用公私钥远程登录服务器的时候,忘记了相关公钥私钥的原理和一些应用了,今天复习一波做个记录. 相关概念 公钥:公钥用来给数据加密,用公钥加密 ...

  5. R语言_格兰因果检验

    #当前文件路径 getwd() #设置当前路径,注意转译 setwd("C://Users//Administrator//Desktop//R_test") #导入数据 data ...

  6. 前后端分离,简单JWT登录详解

    前后端分离,简单JWT登录详解 目录 前后端分离,简单JWT登录详解 JWT登录流程 1. 用户认证处理 2. 前端登录 3. 前端请求处理 4. 后端请求处理 5. 前端页面跳转处理 6. 退出登录 ...

  7. 【HashMap】浅析HashMap的构造方法及put方法(JDK1.7)

    目录 引言 代码讲解 属性 HashMap的空参构造方法 HashMap的put方法 put inflateTable initHashSeedAsNeeded putForNullKey hash ...

  8. MongoDB 常用运维实践总结

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 一.MongoDB 集群简介 MongoDB是一个基于分布式文件存储的数据库,其目的在于为WE ...

  9. 如何使用Shell写一个显示目录结构的命令?

    公众号关注 「开源Linux」 回复「学习」,有我为您特别筛选的学习资料~ 在Linux中使用Shell写一个显示目录结构的命令,快速寻找目录结构. 1.代码 #!/usr/bin/env bash ...

  10. 一个登录点两个逻辑漏洞-edusrc

    最近呢, 也是基础漏洞学的差不多了, 就在edusrc上面实战, 刚开始搞一些信息泄漏啥的, 提交了十几个, 结果就他娘的通过了一个. 咱也就不碰信息泄漏了, 没得意思. 关于这个学校测试时也是有坑的 ...