btrace快速入门

简介:

BTrace是Java的安全可靠的动态跟踪工具。 它的工作原理是通过 instrument + asm 来对正在运行的java程序中的class类进行动态增强。也就是说btrace可以在Java程序运行时,动态地向目标应用程序的字节码注入追踪代码。

说他是安全可靠的,是因为它对正在运行的程序是只读的。也就是说,他可以插入跟踪语句来检测和分析运行中的程序,不允许对其进行修改。因此他存在一些限制:

不能创建对象

不能创建数组

不能抛出和捕获异常

不能调用任何对象方法和静态方法

不能给目标程序中的类静态属性和对象的属性进行赋值

不能有外部、内部和嵌套类

不能有同步块和同步方法

不能有循环(for, while, do..while)

不能继承任何的类

不能实现接口

不能包含assert断言语句

这些限制其实是可以使用unsafe模式绕过。通过在BTrace脚本中声明 @BTrace(unsafe = true) 注解 ,并且使用 -u 选项,指定以 unsafe 模式运行btrace即可

注:实际使用非安全模式跟踪时,发现一个问题,一个进程如果被安全模式btrace探测过一次, 后面再使用非安全模式进行探测时非安全模式不生效。

btrace的github地址:

安装btrace:

到github上下载btrace的压缩包,我这里下载的是1.3.11版本,下载地址如下:

解压下载的压缩包,解压后可以看到目录如下:

然后就是配置环境变量,首先创建一个BTRACE_HOME,该环境变量的值就是btrace的安装目录:

接着在path变量里,配置BTRACE_HOME环境变量下的bin目录:

配置好btrace后,我们有两种方式可以运行BTrace脚本:

一是在JVisualVM中添加BTrace插件,添加classpath

二是直接使用命令行运行:btrace < pid > < trace_script >

我们先来演示第二种使用命令行的运行方式,在工程里新建一个简单的controller,用于演示如何利用BTrace脚本来实时获取方法的参数值:

package org.zero01.monitor_tuning.controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

/**

* @program: monitor_tuning

* @description: BTrace演示

* @author: 01

* @create: 2018-07-14 22:33

**/

@RestController

@RequestMapping("/btrace")

public class BTraceController {

@RequestMapping("/arg1")

public String arg1(@RequestParam("name") String name) {

return "hello: " + name;

}

}

编写完这个Controller后就可以启动项目了,为了方便演示,我们直接将BTrace脚本写在这个工程里,实际上BTrace脚本与我们的项目代码是分离的,没有依赖关系的。但我们得引入编写BTrace脚本所需的jar包,pom.xml配置的依赖如下

com.sun.btrace

btrace-agent

1.3.11

jar

system

E:\BTrace\btrace-bin-1.3.11\build\btrace-agent.jar

com.sun.btrace

btrace-boot

1.3.11

jar

system

E:\BTrace\btrace-bin-1.3.11\build\btrace-boot.jar

com.sun.btrace

btrace-client

1.3.11

jar

system

E:\BTrace\btrace-bin-1.3.11\build\btrace-client.jar

注:systemPath配置的是BTrace安装目录里的jar包路径,如果不想指向路径的话,可以使用maven命令,把这些jar包安装到本地的maven仓库中,命令示例如下:

mvn install:install-file -Dfile=E:\BTrace\btrace-bin-1.3.11\build\btrace-boot.jar -DgroupId=com.sun.btrace -DartifactId=btrace-boot -Dversion=1.3.11 -Dpackaging=jar

然后新建一个btrace包,在该包内新建一个 PrintArgSimple 类,代码如下:

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.AnyType;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.Kind;

import com.sun.btrace.annotations.Location;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.ProbeClassName;

import com.sun.btrace.annotations.ProbeMethodName;

/**

* @program: monitor_tuning

* @description: BTrace脚本

* @author: 01

* @create: 2018-07-14 22:33

**/

@BTrace // 注明这是一个BTrace脚本

public class PrintArgSimple {

// 指定需要拦截的方法

@OnMethod(

// 类的路径

clazz = "org.zero01.monitor_tuning.controller.BTraceController",

// 方法名

method = "arg1",

// 在什么时候进行拦截

location = @Location(Kind.ENTRY)

)

public static void anyRead(@ProbeClassName String pcn, // 被拦截的类名

@ProbeMethodName String pmn, // 被拦截的方法名

AnyType[] args // 被拦截的方法的参数值

) {

// 打印数组

BTraceUtils.printArray(args);

// 打印行

BTraceUtils.println("className: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.println();

}

}

打开命令行,进入到PrintArgSimple.java文件所在的路径,然后使用命令运行该BTrace脚本:

C:\Users\admin>e:

E:\>cd E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace\

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>jps

11300

7396 Jps

8504 MonitorTuningApplication

5656 RemoteMavenServer

12188 Main

13916 Launcher

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>btrace 8504 PrintArgSimple.java

注:因为代码中有中文注释,可能会报:编码GBK的不可映射字符 错误,但是并不影响运行,可以不用管它

在命令行中运行了BTrace脚本后,到浏览器上,访问我们之前所编写的接口,如下:

访问成功后,命令行输出如下,可以看到BTrace脚本成功拦截了我们输入的参数值,以及访问的controller和方法:

[小明, ]

className: org.zero01.monitor_tuning.controller.BTraceController

MethodName: arg1

以上就是如何在命令行里运行BTrace脚本。接着我们再来演示如何在JVisualVM里运行BTrace脚本,在此之前我们还需要安装一个BTrace的插件。关于BTrace插件的安装,我已经在 基于JVisualVM的可视化监控 一文中介绍过了,这里就不赘述了。复制好之前编写的BTrace脚本,然后按照下图粘贴到JVisualVM里运行即可:

注:在JVisualVM里运行BTrace脚本其实会有很多迷之问题,所以一般推荐使用命令行的运行方式

拦截构造函数、同名函数

在上一小节中,我们简单介绍了BTrace的安装以及两种运行方式。并且也写了一个小demo用于动态地拦截方法参数,而本节将介绍一下如何拦截构造函数和同名方法。

首先我们新建一个实体类,代码如下:

package org.zero01.monitor_tuning.vo;

public class User {

private int id;

private String name;

public User(int id, String name) {

super(); // 必须加上super不然无法拦截到参数

this.id = id;

this.name = name;

}

... gtter setter 略 ...

}

在BTraceController类中,新增如下方法,并启动项目:

@RequestMapping("/constructor")

public User constructor(User user) {

return user;

}

然后和之前一样,在btrace包下新建一个BTrace脚本,代码如下:

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.AnyType;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.ProbeClassName;

import com.sun.btrace.annotations.ProbeMethodName;

/**

* @program: monitor_tuning

* @description: 演示拦截构造函数

* @author: 01

* @create: 2018-07-14 22:33

**/

@BTrace

public class PrintConstructor {

@OnMethod(

clazz = "org.zero01.monitor_tuning.vo.User",

method = "" // 指定拦截构造函数

)

public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {

BTraceUtils.println("ClassName: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.printArray(args);

BTraceUtils.println();

}

}

同样的,在命令行中运行该脚本:

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>jps

1184 MonitorTuningApplication

11300

15208 Launcher

5656 RemoteMavenServer

1052 Jps

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>btrace 1184 PrintConstructor.java

使用postman或浏览器访问接口:

访问成功后,命令行输出如下,则是成功拦截到了:

ClassName: org.zero01.monitor_tuning.vo.User

MethodName:

[1, 小明, ]

演示完构造函数的拦截后,然后我们来看看如何拦截同名的方法,在 BTraceController 类里,增加如下两个同名方法:

@RequestMapping("/same1")

public String same(@RequestParam("name") String name) {

return "hello: " + name;

}

@RequestMapping("/same2")

public User same(@RequestParam("id") int id,

@RequestParam("name") String name) {

return new User(id, name);

}

然后在btrace包下新建一个BTrace脚本,代码如下:

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.ProbeClassName;

import com.sun.btrace.annotations.ProbeMethodName;

@BTrace

public class PrintSame {

@OnMethod(

clazz = "org.zero01.monitor_tuning.controller.BTraceController",

method = "same"

)

public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name) {

BTraceUtils.println("ClassName: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.println("name: " + name);

BTraceUtils.println();

}

@OnMethod(

clazz = "org.zero01.monitor_tuning.controller.BTraceController",

method = "same"

)

public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int id, String name) {

BTraceUtils.println("ClassName: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.println("id: " + id);

BTraceUtils.println("name: " + name);

BTraceUtils.println();

}

}

从以上的BTrace脚本中可以看到,其实拦截重载方法的话,只需要在BTrace脚本的方法中声明与之对应的参数即可。

接着就是在命令行里运行该脚本,访问相应的接口后,输出如下:

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>jps

9024 MonitorTuningApplication

11300

3956 Launcher

5656 RemoteMavenServer

9272 Jps

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>btrace 9024 PrintSame.java

ClassName: org.zero01.monitor_tuning.controller.BTraceController

MethodName: same

name: 小明

ClassName: org.zero01.monitor_tuning.controller.BTraceController

MethodName: same

id: 1

name: 小明

拦截返回值、异常、行号

本小节将介绍常用的拦截方式,如何去拦截返回值、异常以及行号。

我们通过Kind来指定拦截方式,常用的拦截方式如下:

Kind.ENTRY // 入口拦截,默认值

Kind.RETURN // 拦截返回值

Kind.THROW // 发生异常时拦截

Kind.LINE // 拦截某一行

我们先来演示拦截返回值,就以之前编写的arg1方法作为示例。

在btrace包下新建一个BTrace脚本,代码如下:

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.AnyType;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.Kind;

import com.sun.btrace.annotations.Location;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.ProbeClassName;

import com.sun.btrace.annotations.ProbeMethodName;

import com.sun.btrace.annotations.Return;

@BTrace

public class PrintReturn {

@OnMethod(

clazz="org.zero01.monitor_tuning.controller.BTraceController",

method="arg1",

location=@Location(Kind.RETURN) // 拦截返回值

)

public static void anyRead(@ProbeClassName String pcn,

@ProbeMethodName String pmn,

@Return AnyType result // 接收返回值) {

BTraceUtils.println("ClassName: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.println("ResultValue: " + result);

BTraceUtils.println();

}

}

在命令行里运行该脚本,访问相应的接口后,输出如下,可以看到成功拦截到了该方法的返回值:

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>btrace 9024 PrintReturn.java

ClassName: org.zero01.monitor_tuning.controller.BTraceController

MethodName: arg1

ResultValue: hello: 小明

已经工作了的小伙伴们,应该会遇到一个常见的现象。那就是在很多旧项目的遗留代码中,总是能看到很多不妥的处理异常的方式。例如经常能看到把异常使用try-catch包起来,但是又不打印异常堆栈,也不抛出去。结果就是把异常隐藏了起来,没能让异常终止逻辑,导致了代码继续执行,还无法定位问题的所在。就如同以下这个方法一样:

@RequestMapping("/exception")

public String exception() {

try {

System.out.println("start...");

System.out.println(1 / 0);

System.out.println("end...");

} catch (Exception e) {

}

return "success";

}

我们访问以上这个接口,会看到返回的值是正确的,但是也会发现有部分逻辑没被执行,查看日志或控制台输出也没有异常信息。当出现这种情况,就真的两眼一抹黑了,根本无法排查问题(这里作为演示代码很简单,但是实际的项目代码可不是这样)。

还好我们现在知道了BTrace这样的调试工具,那么就可以利用BTrace脚本来拦截异常并打印异常堆栈,从而定位问题。

在btrace包下新建一个BTrace脚本,代码如下,这是我直接从官方示例里拿来的代码:

/*

* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.

* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.

*

* This code is free software; you can redistribute it and/or modify it

* under the terms of the GNU General Public License version 2 only, as

* published by the Free Software Foundation. Oracle designates this

* particular file as subject to the Classpath exception as provided

* by Oracle in the LICENSE file that accompanied this code.

*

* This code is distributed in the hope that it will be useful, but WITHOUT

* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or

* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License

* version 2 for more details (a copy is included in the LICENSE file that

* accompanied this code).

*

* You should have received a copy of the GNU General Public License version

* 2 along with this work; if not, write to the Free Software Foundation,

* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.

*

* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA

* or visit www.oracle.com if you need additional information or have any

* questions.

*/

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.Kind;

import com.sun.btrace.annotations.Location;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.Self;

import com.sun.btrace.annotations.TLS;

@BTrace

public class PrintOnThrow {

// store current exception in a thread local

// variable (@TLS annotation). Note that we can't

// store it in a global variable!

@TLS

static Throwable currentException;

// introduce probe into every constructor of java.lang.Throwable

// class and store "this" in the thread local variable.

@OnMethod(

clazz="java.lang.Throwable",

method=""

)

public static void onthrow(@Self Throwable self) { // @Self其实就是拦截了this

//new Throwable()

currentException = self;

}

@OnMethod(

clazz="java.lang.Throwable",

method=""

)

public static void onthrow1(@Self Throwable self, String s) {

//new Throwable(String msg)

currentException = self;

}

@OnMethod(

clazz="java.lang.Throwable",

method=""

)

public static void onthrow1(@Self Throwable self, String s, Throwable cause) {

//new Throwable(String msg, Throwable cause)

currentException = self;

}

@OnMethod(

clazz="java.lang.Throwable",

method=""

)

public static void onthrow2(@Self Throwable self, Throwable cause) {

//new Throwable(Throwable cause)

currentException = self;

}

// when any constructor of java.lang.Throwable returns

// print the currentException's stack trace.

@OnMethod(

clazz="java.lang.Throwable",

method="",

location=@Location(Kind.RETURN)

)

public static void onthrowreturn() {

if (currentException != null) {

// 打印异常堆栈

BTraceUtils.Threads.jstack(currentException);

BTraceUtils.println("=====================");

// 打印完之后就置空

currentException = null;

}

}

}

在命令行里运行该脚本,访问相应的接口后,输出的异常堆栈如下:

可以看到,即便异常被隐藏了起来,而我们通过btrace脚本一样能把异常重新给揪出来,这样即便是线上正在运行的项目,我们也能够不关闭、不重启服务,就能够监控到项目里是否有发生异常。

我们还可以使用BTrace拦截某一行代码,以此判断该行代码是否有被执行。在btrace包下新建一个BTrace脚本,代码如下:

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.Kind;

import com.sun.btrace.annotations.Location;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.ProbeClassName;

import com.sun.btrace.annotations.ProbeMethodName;

@BTrace

public class PrintLine {

@OnMethod(

clazz="org.zero01.monitor_tuning.controller.BTraceController",

method="exception",

location=@Location(value=Kind.LINE, line=43) // 拦截第43行

)

public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {

BTraceUtils.println("ClassName: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.println("line: " + line);

BTraceUtils.println();

}

}

在命令行里运行该脚本,访问相应的接口后,输出的信息如下:

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>btrace 14952 PrintLine.java

ClassName: org.zero01.monitor_tuning.controller.BTraceController

MethodName: exception

line: 43

如果没有任何输出的话,就代表那一行没有被执行到,所以没被拦截。这种拦截某一行的方式,不适用于判断是否有异常,只能单纯用于判断某一行是否被执行了。

拦截复杂参数、环境变量、正则匹配拦截

在以上小节中,我们已经知道了如何拦截简单的参入,本小节中,将介绍如何拦截复杂参数、环境变量以及使用正则匹配拦截。

拦截复杂参数就是拦截实体对象类型的参数,在 BTraceController 类里,增加如下方法:

@RequestMapping("/arg2")

public User arg2(User user) {

return user;

}

使用BTrace拦截复杂参数,需要使用反射的方式进行拦截,也就是需要传递包名+属性名。在btrace包下新建一个BTrace脚本,代码如下:

package org.zero01.monitor_tuning.btrace;

import java.lang.reflect.Field;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.Kind;

import com.sun.btrace.annotations.Location;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.ProbeClassName;

import com.sun.btrace.annotations.ProbeMethodName;

import org.zero01.monitor_tuning.vo.User;

@BTrace

public class PrintArgComplex {

@OnMethod(

clazz = "org.zero01.monitor_tuning.controller.BTraceController",

method = "arg2",

location = @Location(Kind.ENTRY)

)

public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user) {

//print all fields

BTraceUtils.print("print all fields: ");

BTraceUtils.printFields(user);

//print one field

Field oneFiled = BTraceUtils.field("org.zero01.monitor_tuning.vo.User", "name");

BTraceUtils.println("print one field: " + BTraceUtils.get(oneFiled, user));

BTraceUtils.println("ClassName: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.println();

}

}

在命令行里运行该脚本,访问相应的接口后,输出的信息如下:

E:\Java_IDEA\monitor_tuning\src\main\java\org\zero01\monitor_tuning\btrace>btrace -cp "E:\Java_IDEA\monitor_tuning\target\classes" 13336 PrintArgComplex.java

print all fields: {id=0, name=小明, }

print one field: 小明

ClassName: org.zero01.monitor_tuning.controller.BTraceController

MethodName: arg2

注:这里使用到了一个 -cp 参数,该参数表示指定一个classpath路径

其实我们在编写BTrace脚本时,是可以使用正则表达式匹配类名和方法名的,并非必须要指定一个完整的名称。如下示例:

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

import com.sun.btrace.annotations.OnMethod;

import com.sun.btrace.annotations.ProbeClassName;

import com.sun.btrace.annotations.ProbeMethodName;

@BTrace

public class PrintRegex {

@OnMethod(

// 类名也可以使用正则表达式进行匹配

clazz = "org.zero01.monitor_tuning.controller.BTraceController",

// 正则表达式需要写在两个斜杠内

method = "/.*/"

)

public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn) {

BTraceUtils.println("ClassName: " + pcn);

BTraceUtils.println("MethodName: " + pmn);

BTraceUtils.println();

}

}

通过编写BTrace脚本我们可以打印出JVM的信息以及环境变量等,就类似于jinfo命令一样。代码示例:

package org.zero01.monitor_tuning.btrace;

import com.sun.btrace.BTraceUtils;

import com.sun.btrace.annotations.BTrace;

@BTrace

public class PrintJinfo {

static {

// 打印系统属性

BTraceUtils.println("System Properties:");

BTraceUtils.printProperties();

// 打印JVM参数

BTraceUtils.println("VM Flags:");

BTraceUtils.printVmArguments();

// 打印环境变量

BTraceUtils.println("OS Enviroment:");

BTraceUtils.printEnv();

// 退出脚本

BTraceUtils.exit(0);

}

}

在命令行里运行该脚本,输出的信息如下:

注意事项

在以上小节中,我们介绍了BTrace的常见用法,也编写了相应的demo进行一一演示。但是还有一些需要注意事项,在此说明一下:

BTrace脚本默认只能本地运行,也就是只能调试本地的Java进程。如果需要在本地调试远程的Java进程的话,是需要自己去修改BTrace源码的

BTrace脚本在生产环境下可以使用,但是被修改的字节码不会被还原。所以我们需要先在本地调试好BTrace脚本,然后才能放到生产环境下使用。并且需要注意BTrace脚本中不能含有影响性能或消耗资源较多的代码,不然会导致线上的服务性能降低。

参考文档

btrace下载

java btrace_基于Btrace的监控调试相关推荐

  1. Java生产环境下性能监控与调优详解 第4章 基于Btrace的监控调试

    第4章 基于Btrace的监控调试 4-1 btrace入门 4-2 拦截构造函数.同名函数 4-3 拦截返回值.异常.行号 4-4 拦截复杂参数.坏境变量.正则匹配拦截 4-5 注意事项

  2. 【基于Btrace的监控调试】

    Btrace简介(Docker可选择阿里arthas) 建议使用JDK8 Btrace可以动态地向目标应用程序的字节码注入追踪代码. JavaComplierApi.JVMTI.Instrumenta ...

  3. Java生产环境下性能监控与调优详解 大纲 学习感悟

    Java生产环境下性能监控与调优详解 生产环境发生了内存溢出如何处理? 生产环境应该给服务器分配多少内存合适? 如何对垃圾收集器的性能进行调优? 4.生产环境CPU负载飙高该如何处理? 5.生产环境应 ...

  4. java毕业设计基于的校园头条新闻管理系统的设计与实现mybatis+源码+调试部署+系统+数据库+lw

    java毕业设计基于的校园头条新闻管理系统的设计与实现mybatis+源码+调试部署+系统+数据库+lw java毕业设计基于的校园头条新闻管理系统的设计与实现mybatis+源码+调试部署+系统+数 ...

  5. java毕业设计基于Web的人事管理系统的设计与实现mybatis+源码+调试部署+系统+数据库+lw

    java毕业设计基于Web的人事管理系统的设计与实现mybatis+源码+调试部署+系统+数据库+lw java毕业设计基于Web的人事管理系统的设计与实现mybatis+源码+调试部署+系统+数据库 ...

  6. java毕业设计基于web的学校工资管理系统Mybatis+系统+数据库+调试部署

    java毕业设计基于web的学校工资管理系统Mybatis+系统+数据库+调试部署 java毕业设计基于web的学校工资管理系统Mybatis+系统+数据库+调试部署 本源码技术栈: 项目架构:B/S ...

  7. java毕业设计基于的高校学生综合素质评价系统Mybatis+系统+数据库+调试部署

    java毕业设计基于的高校学生综合素质评价系统Mybatis+系统+数据库+调试部署 java毕业设计基于的高校学生综合素质评价系统Mybatis+系统+数据库+调试部署 本源码技术栈: 项目架构:B ...

  8. java毕业设计基于JS的租房网站mybatis+源码+调试部署+系统+数据库+lw

    java毕业设计基于JS的租房网站mybatis+源码+调试部署+系统+数据库+lw java毕业设计基于JS的租房网站mybatis+源码+调试部署+系统+数据库+lw 本源码技术栈: 项目架构:B ...

  9. java毕业设计基于Web生鲜O2O订购平台的设计与实现mybatis+源码+调试部署+系统+数据库+lw

    java毕业设计基于Web生鲜O2O订购平台的设计与实现mybatis+源码+调试部署+系统+数据库+lw java毕业设计基于Web生鲜O2O订购平台的设计与实现mybatis+源码+调试部署+系统 ...

最新文章

  1. tree类型题目需要用到的头文件tree.h
  2. 运维开发笔记整理-前后端分离
  3. 在ASP.NET中指定出错页面,不让代码外泄!
  4. 剑指 Offer 17. 打印从1到最大的n位数
  5. Leetcode970. Powerful Integers强整数
  6. Readyfor4GB帮你的32位Win7用4G内存
  7. 高效算法——E - 贪心-- 区间覆盖
  8. web service方法进行全文检索_SpringMVC(Web应用)配置教程终章项目实战
  9. linux之如何快速在文本里面写入内容
  10. 正则表达式: input框禁止输入空格:不能只有空格、不能有空格、不能为空的判断
  11. MySQL水平分区代理Spock Proxy(一)
  12. [vb]一些窗口消息的详解
  13. ans函数python_#12 Python函数
  14. atitit.提升开发效率---MDA 软件开发方式的革命(4)----编辑表单建模
  15. python——numpy——roll()函数
  16. Java开发QQ机器人
  17. python数据可视化matplotlib之grid
  18. 浏览器打开exe(IE和谷歌)
  19. 学习笔记1——常用的注意力机制(即插即用)
  20. MIMO技术杂谈(三):知己知彼,百战不殆--信道信息的获取和应用

热门文章

  1. Java流程控制语序 循环
  2. 惠普裁员 9000 人:普通人,不把平台当本事
  3. AI开发之——Leonardo—用户工具-AI图形制作(2)
  4. Git回退到指定版本
  5. 好教程推荐系列:张小方的《高性能服务器开发》以及《30天自制C++服务器》
  6. 探讨css中repaint和reflow
  7. 跨域/中央计算进入「攻坚」阶段,TOP10全域智能化Tier1「抢市」
  8. C# 中什么是GUID?
  9. css3光影闪过效果
  10. 案例 2:Hadoop集群SSL证书制作