Java入门

Before all

拖了好久还是想学一学java。

idea

在IntelliJ IDEA中调试Java应用程序可以按照以下步骤进行:

  1. 设置断点:在你认为有问题的代码行上设置断点。你可以在代码编辑器中单击行号旁边的空白处来设置断点,或者使用快捷键Ctrl + F8。
  2. 启动调试模式:在IntelliJ IDEA中,你可以通过多种方式启动调试模式,包括:
    • 在代码编辑器中单击断点行号旁边的调试按钮(绿色的虫子图标)。
    • 使用快捷键Shift + F9。
    • 在菜单栏中选择”Run” -> “Debug”。
  3. 配置调试配置:如果你是第一次进行调试,可能需要配置调试配置。IntelliJ IDEA会为你的应用程序自动创建一个调试配置,你可以在弹出的对话框中选择相应的配置。
  4. 访问应用程序:在调试模式下,你可以通过多种方式运行应用程序,包括:
    • 在IDEA中使用右上角的运行按钮(绿色的三角形图标)。
    • 使用快捷键Shift + F10。
    • 在菜单栏中选择”Run” -> “Run”。
  5. 调试过程:一旦应用程序进入断点,调试器会暂停应用程序的执行。你可以使用调试器的各种工具和功能来查看和修改变量的值,跳过代码行,单步执行代码等。常用的调试工具包括:
    • 变量观察窗口:显示当前作用域的变量及其值。
    • 断点窗口:显示已设置的断点列表,并提供断点的管理功能。
    • 控制台窗口:可以在断点暂停时执行命令,并查看输出结果。
  6. 观察和分析:在调试过程中,观察应用程序的行为和变量的值。通过查看变量的值并与预期值进行比较,你可以找到问题所在。
  7. 修改代码:根据观察和分析的结果,你可以决定是否需要修改代码来修复问题。在这种情况下,你可以在IDEA中编辑代码并保存更改。
  8. 重新运行:一旦你修改了代码并保存更改,你可以停止调试模式并重新运行应用程序,以测试你的更改是否解决了问题。

简单的调试如下:

java

java简介

Java是一种面向对象的编程语言,具有简单、可靠、安全和可移植等特点。下面是一些Java基础语法的介绍:

类和对象:Java是面向对象的语言,所有的代码都必须在类中定义。一个类是对象的模版,用于创建对象。可以使用关键字class来定义类,例如:

1
2
3
public class MyClass {
// 类的成员变量和方法
}

方法:方法是类中执行特定任务的一组语句。每个方法都有一个名称和一组参数(可选),并可以返回一个值(也可以是void表示无返回值)。方法的定义包括方法的修饰符、返回类型、名称和参数列表,例如:

1
2
3
public int add(int a, int b) {
return a + b;
}

变量:变量用于存储数据,可以是基本类型或引用类型。在Java中,变量需要先声明,然后才能使用。声明变量时,需要指定变量的类型和名称,例如:

1
int age; // 声明一个名为age的整数变量

数据类型:Java有两种数据类型,即基本类型和引用类型。基本类型包括整数类型(byteshortintlong)、浮点类型(floatdouble)、字符类型(char)和布尔类型(boolean)。引用类型包括类、接口、数组等。

控制流语句:Java提供了一些控制流语句来控制程序的执行流程,包括条件语句(if-elseswitch)、循环语句(forwhiledo-while)和跳转语句(breakcontinuereturn)等。

数组:数组是一组具有相同类型的数据元素的集合。在Java中,可以使用数组来存储和操作多个数据。数组的声明和初始化可以通过以下方式进行:

1
int[] numbers = new int[5]; // 声明一个包含5个整数的数组

输入输出:Java提供了System.out用于标准输出,可以使用System.out.println()打印文本到控制台。可以使用Scanner类来获取用户的输入,例如:

1
2
3
4
5
6
7
8
9
10
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数:");
int num = scanner.nextInt();
System.out.println("你输入的整数是:" + num);
}
}

好了我们学会1+1了,开始挖洞吧。(x

标识符与关键字

以上为常用关键字。
以HelloWOrld文件来分析。

1
2
3
4
5
6
public class Hello {
public static void main(String[] args) {
string a = "1azy_fish"
System.out.println("Hello World!"+a+"你好");
}
}

public class Hello表示定义一个类。
public static void main(String[] args)表示定义一个方法。
String a定义一个变量。

以上这几种命名变量名的方式都可以,还可以中文变量,标识符对大小写敏感

数据类型详解

Java是一个强类型语言,要求规范严格,要求变量使用严格规定,需要定义后使用

基本数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo {
public static void main(String[] args) {
int num1=10;
byte num2=20;
short num3=30;
long num4=4444444444444444444L;//在末尾加L表示这是个长整数型
float num5=1.1F; //F强调是一个小数,不加可能以为是double类型,或者用f
double num6=3.141592653589793;
char string1='A';
String string2="1azy_fish";
boolean a=true;
boolean b=false;
}
}

可以用逗号隔开同时对多个变量赋值。

整数型

整数型有四个类型byte,short,int,long

byte占一个字节 范围-128-127
short占两个字节 范围-32768-32767
int占四个字节 范围-2147483648-2147483647
long占八个字节 范围-9223372036854775808-9223372036854775807

浮点型

浮点型也有2个类型doublefloat

float占四个字节
double占八个字节

字符类型

类型为char

占两个字节。

定义字符用char,用单引号包裹。

定义字符串用String并且一定要用双引号包裹,不可以使用单引号。

字符串用+做连接符

布尔类型

类型为boolean,只有true,false

占一个字节

引用数据类型

类(Class)

用户自定义的引用数据类型,通过定义类可以创建对象实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
private String name;
private int age;

// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}

// 方法
public void sayHello() {
System.out.println("Hello, my name is " + name);
}
}

Person 是一个自定义的类,它具有 nameage 两个属性,以及 sayHello() 方法。我们可以通过创建 Person 类的对象实例来访问和操作这些属性和方法。

接口(Interface)

一种抽象的引用数据类型,定义了一组方法的签名,可以被类实现(implement)。

1
2
3
public interface Drawable {
void draw();
}

Drawable 是一个接口,它定义了一个 draw() 方法。其他类可以实现(implement)这个接口,并提供自己的实现逻辑。

数组(Array)

引用数据类型的一种,可以存储多个相同类型的元素。

1
2
3
4
5
6
int[] numbers = new int[5];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;

numbers 是一个整型数组,它可以存储五个整数。我们可以通过索引访问和修改数组中的元素。

字符串(String)

是Java提供的引用数据类型,用于表示文本字符串。

1
2
String message = "Hello, World!";
System.out.println(message);

message 是一个字符串变量,存储了文本消息。我们可以使用字符串的方法来操作和处理文本数据。

包装类(Wrapper Class)

包装类为基本数据类型提供了对应的引用数据类型,使其具有对象特性。

1
2
Integer number = Integer.valueOf(42);
System.out.println(number);

Integer 是一个包装类,它将基本数据类型 int 包装成一个对象。通过包装类,我们可以在需要使用对象的场景中操作基本数据类型。

枚举(Enum)

一种特殊的引用数据类型,用于定义一组具名的常量。

1
2
3
4
5
6
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Day today = Day.MONDAY;
System.out.println(today);

Day 是一个枚举类型,它定义了一周中的每一天。我们可以使用枚举类型来表示一组具名的常量,如星期几、月份等。

数据类型拓展

整数拓展

进制问题

在Java中,常见的整数进制有二进制(binary)、八进制(octal)、十进制(decimal)和十六进制(hexadecimal)。它们在Java中的表示方式如下:

二进制:以 0b0B 开头,后面跟着二进制数字(0或1)。
例如:int binary = 0b010;

八进制:以 0 开头,后面跟着八进制数字(0-7)。
例如:int octal = 010;

十进制:直接使用数字表示,没有特殊的前缀。
例如:int decimal = 10;

十六进制:以 0x0X 开头,后面跟着十六进制数字(0-9,A-F或a-f)。
例如:int hexadecimal = 0x10;

浮点数拓展

浮点数在Java中有两种类型:floatdouble。需要注意以下几点拓展:

  • 浮点数的精度是有限的,因此存在舍入误差。
  • 浮点数是近似值,接近但不完全等于所表示的数。
  • 在进行浮点数比较时,应该使用范围或误差值来判断相等性,而不是直接使用 == 运算符。
1
2
3
4
5
6
7
float c = 0.1f;
double d = 1/10;
System.out.println(c == d); // false

float e = 121313213213f;
double f = e + 1;
System.out.println(e == f); // true

第一个比较结果为 false 是因为浮点数的舍入误差导致精度不一致。第二个比较结果为 true 是因为超过了浮点数的有效范围,所以即使增加了1,仍然相等。

字符拓展

字符类型 char 在Java中表示单个字符。除了表示字符本身外,还可以进行一些拓展操作:

  • 可以将字符转换为对应的数字表示,即转换为它的 ASCII 码值。
1
2
char character = 'A';
System.out.println((int) character); // 65
  • 可以使用 Unicode 转义序列来表示特殊字符。
1
2
char unicodeChar = '\u0061'; // Unicode码为 'a'
System.out.println(unicodeChar); // a
  • 字符串中可以使用转义字符(如 \n\t 等)来表示特殊字符。
1
2
String message = "Hello\nWorld";
System.out.println(message);

注意:以上特性在其他编程语言中也普遍存在,因此这些概念容易理解和使用。

类型转换

在Java中,可以进行不同类型之间的转换,但需要注意以下几点:

  • 从低优先级到高优先级的转换不需要进行强制类型转换。
  • 从高优先级到低优先级的转换需要进行强制类型转换。
  • 一些特定的转换可能会导致精度损失或溢出的问题。

以下是一些示例:

1
2
3
4
5
6
7
8
int i = 100;
double j = i; // 从int到double的自动类型转换

double x = 3.14;
int y = (int) x; // 从double到int的强制类型转换

byte b = (byte) 128; // 强制类型转换,可能导致溢出
System.out.println(b); // 输出 -128,溢出结果为负数

需要注意的事项:

  • 布尔类型不能进行类型转换。
  • 对象类型不能转换为不相关的类型。
  • 在进行类型转换时,可能会遇到精度损失的问题,例如将小数转换为整数时可能会直接取整。
  • 在计算过程中,要考虑溢出的情况,否则可能得到负数或其他不正确的结果。

数字之间可以使用下划线 _ 进行分隔,以提高可读性(JDK 7 新特性)。

1
2
3
4
5
int million = 1_000_000;
System.out.println(million); // 1000000

long creditCardNumber = 1234_5678_9012_3456L;
System.out.println(creditCardNumber); // 1234567890123456

需要注意的是,int 类型无法直接通过 String 进行转换。

常量

在Java中,使用 final 关键字声明的变量是常量,其值不能被修改。常量的命名通常使用大写字符。

1
final double PI = 3.14;

常量一旦被赋值后,其值不能再改变。常量的声明可以与其他修饰符无先后顺序关系,例如 staticfinal 的顺序可以任意。

测试结果如下

基本运算符

如上。

还有三元运算符:a=x?y:z,表示如果x为true(也就是非0),那么a=y,否则a=z

math类

利用math类,我们可以获得很多方便的运算。

例如:

幂运算

包机制

在实际应用的时候,新建包之后就会自动给我们分区:

可以使用import导入我们需要的包。

*表示通配符,会导入当前包下所有的类,比如可以 import com.example.demo1.model.*导入所有model包中的类。

JAVADOC

可以帮我们的类,函数写上注释信息,加在类上就是类注释,方法上面就是方法的注释

1
javadoc -encoding UTF-8 -charset UTF-8 Hello.java

encoding表示对文件用UTF8编码,charset表示对字节用UTF8编码

可以看到一些概况

用户交互Scanner

也就是python里的input和c里的scanf了
这里有两种输入方式next(),nextline()

next()方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.demo1;

import java.util.Scanner;
public class input {
public static void main(String[] args) {
//创建一个扫描器对象,用于接受键盘数据
Scanner test=new Scanner(System.in);
System.out.println("使用next方式接受:");
//判断用户有没有输入,可以省略,一般不省
if(test.hasNext()){
//使用next方式接受
String str=test.next();
System.out.printf("输出的内容为:%s",str);
}
test.close();//关闭输入流
}
}

可以看到只输出了hello!

nextline():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.demo1;

import java.util.Scanner;
public class input {
public static void main(String[] args) {
//创建一个扫描器对象,用于接受键盘数据
Scanner test=new Scanner(System.in);
System.out.println("使用next方式接受:");
//判断用户有没有输入,可以省略,一般不省
if(test.hasNext()){
//使用next方式接受
String str=test.nextline();
System.out.printf("输出的内容为:%s",str);
}
test.close();//关闭输入流
}
}

完全输出,这两者有以下区别:

言简意赅的说,next以空格为结束符,nextline以回车为结束符

Scanner进阶使用

判断类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.Scanner;
public class input {
public static void main(String[] args) {
//创建一个扫描器对象,用于接受键盘数据
Scanner test=new Scanner(System.in);
System.out.println("请输入:");
//判断用户有没有输入,可以省略,一般不省
if(test.hasNextInt()){
//使用next方式接受
int num=test.nextInt();
System.out.printf("输出的内容为:%d\n",num);
}
else{
System.out.println("请输入整数型");
}
System.out.println("请输入:");
if(test.hasNextDouble()){
Double num=test.nextDouble();
System.out.printf("输出的内容为%f\n",num);
}
else{
System.out.println("请输入小数");
}
test.close();//关闭输入流
}
}

输入几个n个数据,求平均数,通过输入一个非数字来结束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;
public class input {
public static void main(String[] args) {
//创建一个扫描器对象,用于接受键盘数据
Scanner input=new Scanner(System.in);
int i=1;
double sum=0;
System.out.printf("请输入第%d个数:\n",1);
while(input.hasNextDouble()){
i+=1;
System.out.printf("请输入第%d个数:\n",i);
double number=input.nextDouble();
sum+=number;
}
System.out.printf("你一共输入了%d个数字,这%d个数的平均数为%f",i-1,i-1,sum/i);
input.close();//关闭输入流
}
}

注意,hasNextDouble()函数也是可以进行一次输出,如果hasNextDouble进行了,name里面的NextDouble就是用来获取值的,如果没有hasNextDouble(),那里面的NextDouble也可以进行一次输入并且获取值。

各种判断语句

if

1
2
3
4
5
6
7
8
9
if(条件){//为true就执行
执行的代码
}
else if(){

}
else(){

}

switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;
public class input {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
String str = scanner.nextLine();
switch (str){
case "apple":
System.out.println("给你苹果");
break;
case "fish":
System.out.println("给你鱼鱼");
break;
default:
System.out.println("匹配不到算了");
}
scanner.close();
}
}

java7开始switch支持string类型,case不能放变量,放常量。
这儿的break是需要的,和其他语言一样,没了break就会继续往下判断。

while和do while

1
2
3
4
while(i<=100){
i++;
sum+=i
}
1
2
3
4
do{
//代码
}
while(xxxx);

和c里的一样,先执行一次do

for

和C和PHP简直是一模一样啊

1
2
3
for(初始值;布尔判断;更新){
xxx
}

奇数和偶数的和:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class input {
public static void main(String[] args) {
int sum1=0;
int sum2=0;
for(int i=1;i<=100;i++){
if(i%2==0){
sum1+=i;//偶数和
}
if(i%2!=0){
sum2+=i;//奇数和
}
}
System.out.printf("偶数和为%d,奇数和为%d",sum1,sum2);
}
}

1-1000能被5整除的,每行输出3个

1
2
3
4
5
6
7
8
9
10
11
12
public class input {
public static void main(String[] args) {
for(int i=5;i<=1000;i++){
if(i%5==0){
System.out.print(i+"\t");
}
if(i%(5*3)==0){
System.out.println();
}
}
}
}

System.out.print输出不会换行
System.out.printin会换行,和python的print一样
九九乘法表

1
2
3
4
5
6
7
8
9
10
public class input {
public static void main(String[] args) {
for(int i=1;i<=9;i++){
for(int j=1;j<=i;j++){
System.out.printf("%d*%d=%d\t",i,j,i*j);
}
System.out.println();
}
}
}

增强For循环

1
2
3
for(声明语句:表达式){
//代码
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class input {
public static void main(String[] args) {
int[] number={10,20,30,40,50};
for(int i=0;i<number.length;i++){
System.out.print(number[i]+"\t");
}
//等价于
System.out.println();
for(int i:number){
System.out.print(i+"\t");
}
}
}

for enhance只不过是为了遍历数组

Break,continue,goto

  • break

直接跳出当前整个循环体

  • continue

跳过本次循环,进入下一次循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class input {
public static void main(String[] args) {
int[] number={10,20,30,40,50};
System.out.println();
int count=0;
for(int i:number){
count++;
System.out.print(i+"\t");
if (count == 2) {
break;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class input {
public static void main(String[] args) {
int[] number={10,20,30,40,50};
System.out.println();
int count=0;
for(int i:number){
count++;
if (count == 2) {
continue;
}
System.out.print(i+"\t");
}
}
}
  • goto

java里没有c里的goto,但是有一些残留的影子

1
2
3
4
5
6
7
8
9
10
11
12
public class input {
public static void main(String[] args) {
outer:for(int i=101;i<=150;i++){
for(int j=2;j<i/2;j++){
if(i%j==0){
continue outer;
}
}
System.out.printf(i+"\t");
}
}
}

Maven

Maven是一个项目管理工具,用于构建、部署和管理Java项目。它提供了一种统一的方式来管理项目的依赖、编译、测试和部署等过程。

以下是Maven的一些基本特点和使用方式:

  1. 项目对象模型(Project Object Model,POM):Maven使用POM文件来描述项目的配置和依赖关系。POM是一个XML文件,定义了项目的坐标、依赖、构建配置等信息。
  2. 依赖管理:Maven通过POM文件中的依赖配置来管理项目的依赖库。你可以在POM文件中指定所需的依赖库和版本号,Maven会自动下载并管理这些依赖库。
  3. 构建生命周期:Maven定义了一套构建生命周期,包括一系列的阶段(Phase)和插件(Plugin)。通过在POM文件中配置构建插件和阶段,可以在构建过程中执行特定的任务,例如编译、测试、打包等。
  4. 中央仓库:Maven维护了一个中央仓库,包含了大量的开源Java库和插件。当你在POM文件中声明依赖时,Maven会自动从中央仓库下载相应的库文件。
  5. 插件系统:Maven具有强大的插件系统,可以通过插件扩展和定制构建过程。你可以使用现有的插件,也可以编写自己的插件来满足特定需求。
  6. 命令行工具和集成开发环境(IDE)支持:Maven提供了命令行工具和与主流IDE(如Eclipse、IntelliJ IDEA)的集成支持,方便项目的构建和管理。

安装配置:下载配置环境变量就ok

如何加速

由于默认情况下执行 mvn 各种命令是去国外的 mvn 官方镜像源获取需要安装的具体软件信息,所以在不使用代理、不FQ的情况下,从国内访问国外服务器的速度相对比较慢

如何修改镜像源

阿里旗下维护着一个国内 maven 镜像源。感谢阿里为开源届,为开发者做的贡献,之前发布的两篇关于 npm, yarn 的国内加速镜像源也是使用阿里提供的,之后会发布一篇介绍 gradle 的国内加速方法,依旧会使用阿里提供的镜像源

配置只在当前项目生效

在 pom.xml 文件内添加以下配置

1
2
3
4
5
6
<repositories>
<repository>
<id>ali-maven</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</repository>
</repositories>

配置全局生效

修改 settings.xml 文件

找到 mirrors 标签,在里面加入以下内容

1
2
3
4
5
6
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>

可以在以下路径查找到 settings.xml 文件

  1. (用户根目录)/.m2/settings.xml
  2. (maven安装目录)/conf/settings.xml,

如果以上两个位置同时存在 settings.xml 文件,用户根目录的配置文件权重更高

各种常见Maven插件

我们可以通过mvn help:describe命令来查看插件、命令等操作的详细说明,比如我们在命令行执行mvn help:describe -Dplugin=org.apache.maven.plugins:maven-jar-plugin,便可以看到maven-jar-plugin插件的详细介绍

maven-compiler-plugin

该插件用于编译项目的源代码,用于设置和控制Java编译器的版本和参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>18</source>
<target>18</target>
</configuration>
</plugin>

或者
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>18</maven.compiler.source> <!--这里写的是jdk版本,通常是1.8-->
<maven.compiler.target>18</maven.compiler.target> <!--这里写的是jdk版本,通常是1.8-->
</properties>

maven-dependency-plugin

用于复制依赖的jar包到指定的文件夹里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory><!--输出的文件夹-->
</configuration>
</execution>
</executions>
</plugin>

maven-jar-plugin

jar项目默认的打包工具,默认情况下只会将项目源码编译生成的class文件和资源文件打包进来,不会打包进项目依赖的jar包。

打成jar时,设定manifest的参数,比如指定运行的Main class,还有依赖的jar包,加入classpath中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath><!--指定信息-->
<classpathPrefix>/data/lib</classpathPrefix>
<mainClass>com.zhang.spring.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>

maven-antrun-plugin

maven-antrun-plugin 是 Maven 的一个官方插件,主要用于在 Maven 构建过程中执行 Apache Ant 任务。下面是对这个插件的详细解释:

Maven 与 Ant

  • Maven 是一个项目管理和综合工具,提供了项目对象模型 (POM)、依赖管理和构建生命周期等功能。
  • Ant 是一个用于构建 Java 应用程序的工具,类似于 make,但是使用 XML 格式的构建文件。

为什么需要 maven-antrun-plugin

  • 尽管 Maven 有其自己的构建生命周期和任务系统,但有时,开发者可能希望或需要使用 Ant 中的某些特定功能或任务,这可能是因为它们在 Maven 中不容易实现,或者团队已经有现成的 Ant 脚本。
  • maven-antrun-plugin 允许开发者在 Maven 构建中嵌入这些 Ant 任务。

功能

  • 使用 maven-antrun-plugin,开发者可以定义要在 Maven 构建过程的特定阶段执行的 Ant 任务。
  • 这提供了一个桥梁,允许在 Maven 构建中利用 Ant 的强大和灵活性。

常见用途

  • 文件操作,如复制、移动和删除。
  • 文本处理,如替换文件中的文本。
  • 执行系统命令。
  • 任何其他 Ant 可以执行但 Maven 不直接支持的任务。

总的来说,maven-antrun-plugin 提供了一种在 Maven 构建中利用 Apache Ant 功能的方法。它为那些需要或想要在 Maven 项目中运行 Ant 任务的开发者提供了一个方便的工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target name="copy">
<delete>
<fileset dir="target" includes="*.properties"></fileset>
</delete>
<copy todir="target">
<fileset dir="files"></fileset>
</copy>
</target>
</configuration>
</execution>
</executions>
</plugin>

wagon-maven-plugin

用于一键部署,把本地打包的jar文件,上传到远程服务器上,并执行服务器上的shell命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<serverId>crawler</serverId>
<fromDir>target</fromDir>
<includes>*.jar,*.properties,*.sh</includes>
<url>sftp://服务器ip地址/home/dream</url>
<commands>
<command>chmod 755 /home/dream/update.sh</command><!--shell-->
<command>/home/dream/update.sh</command>
</commands>
<displayCommandOutputs>true</displayCommandOutputs>
</configuration>
</plugin>

maven-shade-plugin

用于把多个jar包,打成1个jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>me.dream.App</Main-Class>
<X-Compile-Source-JDK>${maven.compile.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compile.target}</X-Compile-Target-JDK>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

maven-archetype-plugin

Archtype指项目的骨架,Maven初学者最开始执行的Maven命令可能就是mvn archetype:generate,这实际上就是让maven-archetype-plugin生成一个很简单的项目骨架,帮助开发者快速上手。

可能也有人看到一些文档写了mvn archetype:create,但实际上create目标已经被弃用了,取而代之的是generate目标,该目标使用交互式的方式提示用户输入必要的信息以创建项目,体验更好。

maven-archetype-plugin还有一些其他目标帮助用户自己定义项目原型,例如你由一个产品需要交付给很多客户进行二次开发,你就可以为他们提供一个Archtype,帮助他们快速上手。

Maven Archetype Plugin是一个用于创建项目原型(archetype)的Maven插件。项目原型是一种用于快速生成新项目结构的模板。通过使用Maven Archetype Plugin,您可以定义并创建自己的项目原型,以便在需要时轻松生成项目结构。

以下是Maven Archetype Plugin的一些常见用法:

  1. create:创建一个新的项目原型。您可以使用该目标指定原型的groupId、artifactId、version等信息,并选择要包含的文件和目录结构。
  2. generate:根据指定的项目原型生成一个新项目。您可以使用该目标指定原型的groupId、artifactId、version等信息,并选择要包含的文件和目录结构。
  3. list:列出所有可用的项目原型。使用该目标可以查看本地或远程仓库中可用的项目原型列表。
  4. delete:删除本地仓库中的指定项目原型。

使用示例:

创建项目原型:

1
2
3
4
5
6
mvn archetype:generate
-DgroupId=com.example
-DartifactId=my-project
-Dversion=1.0.0
-DarchetypeGroupId=org.apache.maven.archetypes
-DarchetypeArtifactId=maven-archetype-quickstart

上述命令将使用”Maven Quickstart Archetype”原型创建一个名为”my-project”的新项目。可以根据需要修改groupId、artifactId和version参数。

  1. 选择项目原型:
    当执行上述命令时,Maven会列出可用的项目原型列表供您选择。您可以根据列表中的编号选择相应的原型。例如,选择编号为1的原型。
  2. 生成新项目:
    根据您的选择,Maven Archetype Plugin将在当前目录中生成一个新的项目结构,包括源代码、测试代码、资源文件等。

groupId、artifactId和version是Maven项目的核心标识和版本信息。

  1. groupId:groupId代表项目所属的组织或团队的唯一标识。通常使用反向域名的方式来命名,例如”com.example”。groupId用于确保项目的唯一性,并帮助组织和识别项目。
  2. artifactId:artifactId代表项目的唯一标识符。通常使用项目名称来命名,例如”my-project”。artifactId用于识别和定位项目,它是项目构建产物(如JAR包、WAR包)的名称。
  3. version:version代表项目的版本号。它是一个字符串,可以是数字和字符的组合,例如”1.0.0”。version用于标识和管理项目的不同版本,以便开发者和用户可以区分不同的发布版本。

这些参数在Maven项目的构建、依赖管理和部署过程中非常重要。它们可以确保项目的唯一性、版本控制和协作开发。在Maven的pom.xml文件中,这些参数通常作为项目的基本信息和属性定义。通过使用这些参数,Maven可以根据项目标识和版本信息来管理和构建项目。

maven-assembly-plugin

该插件的用途是制作项目分发包,该分发包可能包含了项目的可执行文件、源代码、readme、平台脚本等等。

一些常用配置和用法:

  1. 配置assembly描述符:在项目的pom.xml文件中,您可以定义一个assembly描述符,用于指定要打包的文件和目录结构、包含/排除的规则、文件的权限等。描述符通常以assembly.xml或assembly.xml文件的形式存在。
  2. 定义组件:在assembly描述符中,您可以定义一个或多个组件。每个组件可以指定一个或多个文件和目录,以及打包到发布包中的路径和文件名。
  3. 指定发布包类型:Maven Assembly Plugin支持多种发布包类型,如JAR、ZIP、TAR等。您可以在assembly描述符中指定所需的发布包类型。
  4. 配置附加构建阶段:通过配置Maven Assembly Plugin的execution元素,您可以指定插件在项目构建的特定阶段执行。例如,您可以配置插件在package阶段之后执行,以便在打包完成后生成自定义的发布包。
  5. 自定义文件、目录、权限等:通过配置assembly描述符,您可以自定义发布包中的文件、目录和文件权限。您可以包括额外的资源文件、配置文件,以及调整文件的权限和属性。

maven-assembly-plugin要求用户使用一个名为assembly.xml的元数据文件来表述打包,它的single目标可以直接在命令行调用,也可以被绑定至生命周期。

下面是一个使用Maven Assembly Plugin创建可执行JAR包的示例:

在项目的pom.xml文件中,添加Maven Assembly Plugin的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptor>src/assembly/assembly.xml</descriptor>
</configuration>
<executions>
<execution>
<id>create-executable-jar</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

在项目中创建一个名为assembly.xml的assembly描述符,指定要打包的文件和目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>executable-jar</id>
<formats>
<format>jar</format>
</formats>
<files>
<file>
<source>target/my-project.jar</source>
<outputDirectory>/</outputDirectory>
</file>
</files>
<dependencySets>
<dependencySet>
<outputDirectory>/lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
</assembly>

上述描述符指定了一个名为”executable-jar”的组件,将target目录下的my-project.jar文件打包到发布包的根目录,并将依赖的JAR文件放入lib目录。

运行以下命令生成可执行JAR包:

mvn package

执行完上述命令后,Maven Assembly Plugin将在target目录下生成一个名为my-project-assembly.jar的可执行JAR包,包含了您在assembly.xml中定义的文件和目录结构。

maven-dependency-plugin

最大的用途是帮助分析项目依赖

  1. list:列出项目的依赖关系。可以使用该目标查看项目的直接依赖、传递依赖和冲突依赖。
  2. tree:以树状结构展示项目的依赖关系。可以使用该目标查看项目依赖的层级结构和依赖传递路径。
  3. analyze:分析项目的依赖关系。可以使用该目标找出项目中未使用的依赖、潜在的冲突依赖和过时的依赖。
  4. copy:复制项目的依赖到指定目录。可以使用该目标将项目的依赖复制到指定的目录,便于离线构建或其他用途。
  5. unpack:解压项目的依赖。可以使用该目标将项目依赖的JAR文件解压到指定的目录,以便查看或修改依赖的内容。

Maven Dependency Plugin还支持其他配置和参数,例如指定依赖的范围、排除依赖、过滤依赖等。

  1. 列出项目的依赖关系:mvn dependency:list

    上述命令将列出项目的直接依赖和传递依赖,包括每个依赖的groupId、artifactId、版本号等信息。

  2. 以树状结构展示项目的依赖关系:mvn dependency:tree

    这个命令将以树状结构展示项目的依赖关系,显示每个依赖的groupId、artifactId、版本号及其传递路径。

  3. 分析项目的依赖关系:mvn dependency:analyze

    该命令将分析项目的依赖关系,找出未使用的依赖、潜在的冲突依赖和过时的依赖,并提供相应的建议。

  4. 复制项目的依赖到指定目录:mvn dependency:copy-dependencies -DoutputDirectory=/path/to/directory

    上述命令将项目的依赖复制到指定的目录,便于离线构建或其他用途。

  5. 解压项目的依赖:mvn dependency:unpack -Dartifact=groupId:artifactId:version -DoutputDirectory=/path/to/directory

    这个命令将指定的依赖JAR文件解压到指定的目录,以便查看或修改依赖的内容。

maven-enforcer-plugin

Maven Enforcer Plugin是一个用于强制执行构建规则和限制的Maven插件。它提供了一系列目标,可以帮助您在构建过程中强制执行各种规则和限制,以确保项目的构建和质量符合预期。

用法:

  1. enforce:强制执行规则和限制。可以使用该目标配置和应用各种规则,例如强制使用特定的Maven版本、限制依赖的版本范围、强制执行特定的构建配置等。
  2. display-info:显示构建信息。可以使用该目标显示有关构建环境、插件版本、配置信息等的详细信息。
  3. display-requirement:显示规则和限制信息。可以使用该目标显示已配置的规则和限制的详细信息。
  4. display-properties:显示项目属性。可以使用该目标显示项目中定义的属性及其值。
  5. display-plugin-updates:显示插件更新信息。可以使用该目标显示项目中可用的插件的最新版本和更新说明。

Maven Enforcer Plugin还支持其他配置和参数,例如自定义规则、忽略规则、设置规则的错误级别等。

示例:

在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>enforce-maven-version</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>3.6.3</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们指定了一个执行阶段(execution),并配置了一个要求Maven版本的规则。该规则要求使用的Maven版本必须是3.6.3。

接下来,执行以下命令来验证规则是否被强制执行:

mvn clean install

如果当前使用的Maven版本不符合规定的要求(不是3.6.3),则构建将失败,并显示类似以下的错误信息:

1
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-enforcer-plugin:3.0.0-M3:enforce (enforce-maven-version) on project your-project: Some Enforcer rules have failed. Look above for specific messages explaining why the rule failed. -> [Help 1]

maven-help-plugin

maven-help-plugin是一个小巧的辅助工具。

最简单的mvn help:system可以打印所有可用的环境变量和Java系统属性。

mvn help:effective-pommvn help:effective-settings最为有用,它们分别打印项目的有效POM和有效settings,有效POM是指合并了所有父POM(包括Super POM)后的XML,

当你不确定POM的某些信息从何而来时,就可以查看有效POM。

有效settings同理,特别是当你发现自己配置的settings.xml没有生效时,就可以用mvn help:effective-settings来验证。

此外,maven-help-plugin的describe目标可以帮助你描述任何一个Maven插件的信息,还有all-profiles目标和active-profiles目标帮助查看项目的Profile。

maven-release-plugin

该插件的用途是帮助自动化项目版本发布,它依赖于POM中的SCM信息。

用法:

  1. prepare:准备发布。执行该目标将自动更新项目的版本号、生成发布文档、打上版本标签等。
  2. perform:执行发布。执行该目标将自动构建项目、生成发布包,并将发布包发布到远程Maven仓库。
  3. rollback:回滚发布。执行该目标将回滚之前准备的发布操作,恢复项目到之前的状态。
  4. release:执行完整的发布流程。执行该目标将依次执行prepare和perform目标,实现自动化的版本管理和发布过程。

示例配置和命令:

首先,在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<tagNameFormat>@{project.version}</tagNameFormat>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Maven Release Plugin的版本为3.0.0-M5,并配置了一些参数。tagNameFormat参数指定了标签名的格式,使用了项目的版本号作为标签名。autoVersionSubmodules参数指定是否自动版本化子模块。

接下来,执行以下命令来执行版本管理和发布操作:

mvn release:prepare

上述命令会执行准备发布的操作,包括更新版本号、生成发布文档、打上版本标签等。根据配置的参数,插件会自动化处理子模块的版本管理。

完成准备发布后,可以执行以下命令来执行实际的发布操作:

mvn release:perform

上述命令会构建项目、生成发布包,并将发布包发布到远程Maven仓库。

如果需要回滚之前的发布操作,可以执行以下命令:

mvn release:rollback

上述命令会回滚之前准备的发布操作,恢复项目到之前的状态。

maven-resources-plugin

为了使项目结构更为清晰,Maven区别对待Java代码文件和资源文件,maven-compiler-plugin用来编译Java代码,maven-resources-plugin则用来处理资源文件。

以下是Maven Resources Plugin的一些常见用法:

  1. resources:复制项目资源。执行该目标将复制项目中的资源文件到指定的输出目录。
  2. testResources:复制测试资源。执行该目标将复制测试代码中的资源文件到指定的输出目录。
  3. copy-resources:复制指定资源。执行该目标可以复制指定的资源文件或目录到指定的输出目录。
  4. filtering:过滤资源。执行该目标将对资源文件进行变量替换,替换掉配置文件中的占位符等。

Maven Resources Plugin还支持其他配置和参数,例如指定资源文件的包含或排除规则、设置输出目录、配置变量替换等。

示例配置和命令:

复制项目资源:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<outputDirectory>${project.build.directory}/custom-resources</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Maven Resources Plugin的版本为3.2.0,并配置了一个outputDirectory参数,指定资源文件的输出目录为${project.build.directory}/custom-resources

然后,执行以下命令来复制项目资源:

mvn resources:resources

上述命令将会复制项目中的资源文件到指定的输出目录。

复制测试资源:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-test-resources</id>
<phase>generate-test-resources</phase>
<goals>
<goal>testResources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/custom-test-resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们配置了一个testResources目标,并将其绑定到generate-test-resources阶段。我们还指定了一个outputDirectory参数,指定测试资源文件的输出目录为${project.build.directory}/custom-test-resources

然后,执行以下命令来复制测试资源:

mvn resources:testResources

上述命令将会复制测试代码中的资源文件到指定的输出目录。

maven-surefire-plugin

Maven Surefire Plugin是一个用于执行项目测试的Maven插件。它提供了一组目标,用于自动化运行测试代码、生成测试报告以及处理测试覆盖率等。

以下是Maven Surefire Plugin的一些常见用法:

  1. test:运行测试。执行该目标将自动运行项目中的测试代码。
  2. report:生成测试报告。执行该目标将生成测试结果的报告文件,例如HTML、XML等格式。
  3. test-compile:编译测试代码。执行该目标将编译项目中的测试代码。
  4. test-coverage:生成测试覆盖率报告。执行该目标将生成测试代码的覆盖率报告。

Maven Surefire Plugin还支持其他配置和参数,例如指定测试代码的包含或排除规则、设置测试报告的输出目录、配置并行执行等。

示例配置和命令:

运行测试:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Maven Surefire Plugin的版本为3.0.0-M5,并配置了一个includes参数,指定要运行的测试类的包含规则。

然后,执行以下命令来运行测试:

mvn surefire:test

上述命令将会自动运行项目中符合包含规则的测试代码。

生成测试报告:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
<executions>
<execution>
<id>surefire-report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们配置了一个report目标,并将其绑定到test阶段。我们还指定了一个includes参数,指定要生成测试报告的测试类的包含规则。

然后,执行以下命令来生成测试报告:

mvn surefire:report

上述命令将会生成测试结果的报告文件,通常以HTML格式呈现。

build-helper-maven-plugin

Build Helper Maven Plugin是一个用于扩展Maven构建的插件。它提供了一组目标,用于动态添加、修改和管理Maven项目的构建信息。

以下是Build Helper Maven Plugin的一些常见用法:

  1. add-source:添加源代码目录。执行该目标将向项目中添加额外的源代码目录,使其能够被编译和构建。
  2. add-test-source:添加测试源代码目录。执行该目标将向项目中添加额外的测试源代码目录,使其能够被编译和运行测试。
  3. add-resource:添加资源目录。执行该目标将向项目中添加额外的资源目录,使其能够被复制到构建输出目录。
  4. add-test-resource:添加测试资源目录。执行该目标将向项目中添加额外的测试资源目录,使其能够被复制到测试构建输出目录。

Build Helper Maven Plugin还支持其他配置和参数,例如指定源代码或资源的目录位置、配置过滤器等。

示例配置和命令:

添加源代码目录:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/java2</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Build Helper Maven Plugin的版本为3.2.0,并配置了一个add-source目标,并将其绑定到generate-sources阶段。我们指定了一个sources参数,指定要添加的源代码目录为src/main/java2

然后,执行以下命令来添加源代码目录:

mvn build-helper:add-source

上述命令将会向项目中添加指定的源代码目录。

添加资源目录:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>add-resource</id>
<phase>generate-resources</phase>
<goals>
<goal>add-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources2</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Build Helper Maven Plugin的版本为3.2.0,并配置了一个add-resource目标,并将其绑定到generate-resources阶段。我们指定了一个resources参数,指定要添加的资源目录为src/main/resources2

然后,执行以下命令来添加资源目录:

mvn build-helper:add-resource

上述命令将会向项目中添加指定的资源目录。

exec-maven-plugin

Exec Maven Plugin是一个用于在Maven构建过程中执行外部命令的插件。它提供了一组目标,用于在构建过程中执行自定义的命令行脚本或外部程序。

以下是Exec Maven Plugin的一些常见用法:

  1. exec:执行命令行脚本或外部程序。通过配置该目标,可以在构建过程中执行自定义的命令行脚本或外部程序。
  2. java:执行Java程序。通过配置该目标,可以在构建过程中执行Java程序,例如运行测试类、启动应用程序等。
  3. execSet:执行一组命令。通过配置该目标,可以执行多个命令行脚本或外部程序,按顺序执行。

Exec Maven Plugin还支持其他配置和参数,例如指定命令行参数、设置工作目录、配置环境变量等。

示例配置和命令:

执行命令行脚本:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>execute-command</id>
<phase>process-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>echo</executable>
<arguments>
<argument>Hello, 1azy_fish!</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Exec Maven Plugin的版本为3.0.0,并配置了一个exec目标,并将其绑定到process-resources阶段。我们使用executable参数指定要执行的命令行脚本,然后使用arguments参数指定脚本的参数。

然后,执行以下命令来执行命令行脚本:mvn exec:exec

上述命令将会执行我们指定的命令行脚本,输出”Hello, 1azy_fish!”。

执行Java程序:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>execute-java</id>
<phase>test</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.example.MyApp</mainClass>
<arguments>
<argument>arg1</argument>
<argument>arg2</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Exec Maven Plugin的版本为3.0.0,并配置了一个java目标,并将其绑定到test阶段。我们使用mainClass参数指定要执行的Java程序的主类,然后使用arguments参数指定程序的参数。

然后,执行以下命令来执行Java程序:mvn exec:java

上述命令将会执行我们指定的Java程序,传递参数”arg1”和”arg2”给程序。

jetty-maven-plugin

Jetty Maven Plugin是一个用于在Maven构建过程中启动和管理Jetty Web服务器的插件。它提供了一组目标,用于在开发和测试阶段快速启动和部署Web应用程序。

以下是Jetty Maven Plugin的一些常见用法:

  1. jetty:run:启动Jetty服务器。执行该目标将启动内嵌的Jetty服务器,并将当前项目部署到该服务器上。可以通过访问http://localhost:8080来访问部署的应用程序。
  2. jetty:run-forked:以独立进程方式启动Jetty服务器。执行该目标将启动一个独立的Jetty服务器进程,并将当前项目部署到该服务器上。
  3. jetty:deploy:部署Web应用程序。执行该目标将构建和打包项目,并将生成的WAR文件部署到Jetty服务器中。可以通过访问http://localhost:8080来访问部署的应用程序。

Jetty Maven Plugin还支持其他配置和参数,例如指定端口号、上下文路径、配置SSL等。

示例配置和命令:

启动Jetty服务器:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.43.v20210629</version>
<configuration>
<webApp>
<contextPath>/</contextPath>
</webApp>
</configuration>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Jetty Maven Plugin的版本为9.4.43.v20210629,并配置了一个jetty-maven-plugin插件。我们通过configuration元素可以配置插件的参数。在上述示例中,我们指定了Web应用程序的上下文路径为/

然后,执行以下命令来启动Jetty服务器:mvn jetty:run

上述命令将会启动Jetty服务器,并将当前项目部署到该服务器上。可以通过访问http://localhost:8080来访问部署的应用程序。

部署Web应用程序:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.43.v20210629</version>
<configuration>
<webApp>
<contextPath>/myapp</contextPath>
</webApp>
</configuration>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Jetty Maven Plugin的版本为9.4.43.v20210629,并配置了一个jetty-maven-plugin插件。我们通过configuration元素可以配置插件的参数。在上述示例中,我们指定了Web应用程序的上下文路径为/myapp

然后,执行以下命令来部署Web应用程序:mvn jetty:deploy

上述命令将会构建和打包项目,并将生成的WAR文件部署到Jetty服务器中。可以通过访问http://localhost:8080/myapp来访问部署的应用程序。

versions-maven-plugin

很多Maven用户遇到过这样一个问题,当项目包含大量模块的时候,为他们集体更新版本就变成一件烦人的事情,到底有没有自动化工具能帮助完成这件事情呢?

(当然你可以使用sed之类的文本操作工具,不过不在本文讨论范围)答案是肯定的,versions-maven- plugin提供了很多目标帮助你管理Maven项目的各种版本信息。

以下是Versions Maven Plugin的一些常见用法:

  1. versions:display-dependency-updates:显示更新的依赖版本。执行该目标将显示当前项目依赖的库中可用的新版本。
  2. versions:display-plugin-updates:显示更新的插件版本。执行该目标将显示当前项目中可用的新插件版本。
  3. versions:update-properties:更新项目属性中的依赖版本。执行该目标将根据可用的新版本更新项目属性中的依赖版本。

Versions Maven Plugin还支持其他配置和参数,例如指定要排除的依赖、配置更新策略等。

示例:

显示更新的依赖版本:
执行以下命令来显示项目中可用的更新的依赖版本:mvn versions:display-dependency-updates

显示更新的插件版本:
执行以下命令来显示项目中可用的更新的插件版本:mvn versions:display-plugin-updates

更新项目属性中的依赖版本:
执行以下命令来更新项目属性中的依赖版本:mvn versions:update-properties

maven-war-plugin

war项目默认的打包工具,默认情况下会打包项目编译生成的.class文件、资源文件以及项目依赖的所有jar包。

示例:

将项目打包为WAR文件:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Maven War Plugin的版本为3.3.1,并配置了一个maven-war-plugin插件。我们通过configuration元素可以配置插件的参数。在上述示例中,我们指定了Web资源目录为src/main/webapp,并设置failOnMissingWebXml参数为false,表示允许项目中没有web.xml文件。

然后,执行以下命令来将项目打包为WAR文件:mvn war:war

将项目解压为目录结构:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
<webappDirectory>${project.build.directory}/exploded</webappDirectory>
</configuration>
</plugin>
</plugins>
</build>

上述配置中,我们添加了一个webappDirectory配置,将解压后的文件和目录结构输出到${project.build.directory}/exploded目录。

然后,执行以下命令来将项目解压为目录结构:mvn war:exploded

maven-shade-plugin

需要在pom文件的plugin元素中引入才可以使用,它可以让用户配置Main-Class的值,然后在打包的时候将值填入/META-INF/MANIFEST.MF文件。关于项目的依赖,它很聪明地将依赖的JAR文件全部解压后,再将得到的.class文件连同当前项目的.class文件一起合并到最终的CLI包(可以直接运行的jar包)中,这样,在执行CLI JAR文件的时候,所有需要的类就都在Classpath中了。

示例配置和命令:

将项目及其依赖库打包为可执行的JAR文件:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们指定了Maven Shade Plugin的版本为3.2.4,并配置了一个maven-shade-plugin插件。我们通过configuration元素可以配置插件的参数。在上述示例中,我们设置createDependencyReducedPomfalse,表示不生成精简的POM文件。我们还添加了一个transformers配置,指定了一个ManifestResourceTransformer转换器,其中指定了主类为com.example.Main

然后,执行以下命令来将项目及其依赖库打包为可执行的JAR文件:mvn package

上述命令将会构建和打包项目,并生成一个包含所有依赖库的JAR文件。

对依赖库进行重定位:
在项目的pom.xml文件中添加以下插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<relocations>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>myapp.shaded.com.google.common</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

上述配置中,我们添加了一个relocations配置,指定了一个重定位规则,将com.google.common包重命名为myapp.shaded.com.google.common

然后,执行以下命令来对依赖库进行重定位:mvn package

上述命令将会构建和打包项目,并将符合重定位规则的依赖库进行重命名。

Maven多模块项目

Maven多模块项目是指由多个子模块组成的项目结构。每个子模块都是一个独立的项目,可以有自己的源代码、资源文件、依赖等。这种项目结构使得大型项目能够更好地组织和管理,提高了代码的可维护性和复用性。

在Maven中,多模块项目通过父子关系来组织。通常,一个Maven多模块项目会有一个顶级的父项目,以及多个子项目(也称为模块)。父项目负责统一管理子项目的依赖、构建配置等,子项目可以继承父项目的配置,也可以有自己的特定配置。

多模块项目的好处包括:

  1. 提供了更好的代码组织结构:多模块项目可以将相关的功能模块或组件划分为独立的子模块,使得代码更加有序和可维护。
  2. 支持代码复用和共享:子模块之间可以共享代码、资源和依赖,提高了代码的复用性和共享性。
  3. 管理依赖更加方便:父项目可以集中管理所有子项目的依赖库版本,避免版本冲突和重复引入。
  4. 允许并行构建和测试:由于子模块之间是相互独立的,因此可以并行地构建和测试各个子模块,提高构建速度和效率。

Maven多模块项目的结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
my-parent-project
├── module1
│ ├── src
│ ├── pom.xml
├── module2
│ ├── src
│ ├── pom.xml
├── module3
│ ├── src
│ ├── pom.xml
├── pom.xml

在父项目的pom.xml中,使用<modules>元素列出所有的子模块。

1
2
3
4
5
<modules>
<module>module1</module>
<module>module2</module>
<module>module3</module>
</modules>

每个子模块都有自己的pom.xml文件,用于配置该模块的构建和依赖等信息。父项目和子项目之间可以通过Maven的继承机制来共享和继承配置。

Gradle是啥???

Gradle是一种开源的自动化构建工具,用于构建、测试和部署软件项目。它是基于Groovy编程语言和DSL(领域特定语言)的构建工具,具有强大的灵活性和可扩展性。

以下是Gradle的一些特点和优势:

  1. 声明式构建脚本:Gradle使用基于Groovy的DSL来定义构建脚本,使得构建逻辑更易于理解和维护。构建脚本可以声明项目的依赖、任务和构建流程等。
  2. 高度可定制:Gradle提供了丰富的插件和扩展机制,可以根据项目的需求定制构建逻辑。开发人员可以编写自己的插件,或者使用现有的插件来扩展Gradle的功能。
  3. 多项目支持:Gradle支持多项目构建,可以轻松管理和构建复杂的多模块项目。通过定义项目之间的依赖关系,可以实现模块化的构建和自动化任务的执行。
  4. 并行构建:Gradle能够根据项目的依赖关系和任务之间的依赖关系,自动进行任务的并行执行,提高构建效率。
  5. 强大的依赖管理:Gradle支持各种依赖管理工具,如Maven和Ivy,可以轻松管理项目的依赖库。它还支持动态版本解析和增量构建,提供了更高效的依赖管理机制。
  6. 跨平台支持:Gradle可以在各种操作系统上运行,包括Windows、Linux和Mac OS等。它通过使用Java虚拟机(JVM)来实现跨平台的支持。

Gradle被广泛应用于Java、Android和Kotlin等项目的构建和自动化。

(不想再学下去,暂时先学maven

Java Web

Servlet Tomcat JSP

Servlet、Tomcat和JSP是Java Web开发中常用的技术组合,用于构建动态的Web应用程序。

  1. Servlet(服务器端小程序):Servlet是Java编写的服务器端程序,用于处理Web请求和生成动态的Web内容。它可以接收HTTP请求并生成响应,通常用于处理表单提交、用户身份验证、数据查询等任务。Servlet通常被部署在Servlet容器中(如Tomcat),并通过URL映射来处理特定的请求。
  2. Tomcat(Apache Tomcat):Tomcat是一个开源的Servlet容器和Web服务器,用于运行Java Web应用程序。它是Java Servlet和JavaServer Pages(JSP)规范的参考实现之一。Tomcat提供了一个运行环境,可以加载和执行Servlet和JSP,并处理HTTP请求和响应。开发人员可以将他们的Servlet和JSP部署到Tomcat中,以提供Web应用程序的服务。
  3. JSP(JavaServer Pages):JSP是一种用于在服务器端生成动态Web内容的技术。它允许开发人员在HTML页面中嵌入Java代码,以便动态地生成页面内容。JSP可以与Servlet一起使用,Servlet负责处理请求和业务逻辑,而JSP负责生成动态的HTML页面。JSP在被访问时会被容器翻译为Servlet,并在运行时执行。

这些技术在Java Web开发中相互配合,共同构建出具有动态功能的Web应用程序。Servlet负责处理请求和业务逻辑,Tomcat作为Servlet容器提供运行环境,而JSP用于生成动态的HTML内容。通过使用这些技术,开发人员可以构建出灵活、可交互的Web应用程序。

传统XML配置SSM/SSH

在传统的SSM(SpringMVC + Spring + MyBatis)或SSH(Struts2 + Spring + Hibernate)框架中,配置文件通常使用XML来进行配置。下面是一个简单的步骤指南,帮助你了解如何进行传统XML配置。

  1. 创建项目和目录结构:
    • 使用IDE(如Eclipse)创建一个动态Web项目。
    • 创建以下目录结构:
      • src/main/java:用于存放Java代码。
      • src/main/resources:用于存放配置文件和资源文件。
      • src/main/resources/mapper:用于存放MyBatis的SQL映射文件。
      • src/main/resources/spring:用于存放Spring的配置文件。
      • src/main/resources/sql:用于存放SQL脚本文件。
      • src/main/webapp:用于存放前端静态资源。
      • src/main/webapp/resources:用于存放项目的静态资源,如JavaScript、CSS和图片等。
      • src/main/webapp/WEB-INF:用于存放JSP文件和web.xml配置文件。
  2. 配置SpringMVC:
    • 在src/main/webapp/WEB-INF目录下创建一个名为springmvc-servlet.xml的SpringMVC配置文件。
    • 在配置文件中进行SpringMVC的相关配置,如扫描包、视图解析器、处理器映射等。
  3. 配置Spring:
    • 在src/main/resources/spring目录下创建一个名为spring-context.xml的Spring配置文件。
    • 在配置文件中进行Spring的相关配置,如扫描包、数据源配置、事务管理等。
  4. 配置MyBatis:
    • 在src/main/resources/spring目录下创建一个名为spring-mybatis.xml的MyBatis配置文件。
    • 在配置文件中进行MyBatis的相关配置,如数据源配置、Mapper扫描等。
    • 在src/main/resources/mapper目录下创建Mapper接口对应的XML文件,用于编写SQL语句。
  5. 配置web.xml:
    • 在src/main/webapp/WEB-INF目录下创建一个名为web.xml的配置文件。
    • 在配置文件中进行Servlet、Filter和Listener的配置,以及SpringMVC的DispatcherServlet配置。
  6. 编写业务代码:
    • 在src/main/java目录下创建相应的包和类,包括dao、entity、service等。
    • 在dao包中创建接口,定义数据访问方法。
    • 在entity包中创建实体类,与数据库表对应。
    • 在service包中创建接口和实现类,定义业务逻辑。
    • 在controller包中创建控制器类,处理用户请求。
  7. 部署和运行:
    • 将项目部署到Web服务器中(如Tomcat)。
    • 启动Web服务器,访问项目的URL,测试功能是否正常。

这些步骤只是一个简单的指南,实际配置过程可能会因项目需求和框架版本而有所不同。

SpringBoot

Spring Boot是一个基于Spring的套件,它帮助我们以尽可能少的代码和配置来开发基于Spring的Java应用程序。它预组装了Spring的一系列组件,使开发变得更加简单和高效。

以下是使用Spring Boot的基本步骤:

  1. 创建一个Spring Boot项目:可以使用Maven或Gradle构建工具创建一个新的Spring Boot项目。可以使用IDE(如IntelliJ IDEA或Eclipse)的Spring Initializr插件来快速生成项目结构。
  2. 添加Spring Boot依赖:在项目的pom.xml(如果使用Maven)或build.gradle(如果使用Gradle)文件中,添加Spring Boot的依赖项。这些依赖项包括Spring Boot的核心库和其他需要的功能库。
  3. 编写应用程序代码:在src/main/java目录下编写应用程序的Java代码。可以使用Spring Boot的注解和自动配置来简化开发过程。例如,使用@SpringBootApplication注解标记主类,使用@RestController注解创建一个RESTful API。
  4. 配置应用程序:可以使用application.properties或application.yml文件来配置应用程序的属性,如数据库连接、端口号等。Spring Boot会自动加载这些配置。
  5. 运行应用程序:可以使用IDE的运行按钮或命令行工具来运行Spring Boot应用程序。Spring Boot会自动启动嵌入式的Web服务器,并将应用程序部署到该服务器上。
  6. 测试应用程序:编写单元测试和集成测试来验证应用程序的功能和性能。Spring Boot提供了一些测试工具和注解,使测试变得更加简单和方便。
  7. 打包和部署应用程序:使用Maven或Gradle的打包命令将应用程序打包成可执行的JAR文件或WAR文件。可以将该文件部署到服务器上,或使用Docker等容器技术进行部署。

SpringCloud (了解即可)

Spring Cloud 是一个开发分布式系统的框架,为开发者提供了一系列的工具集,用于构建分布式系统。它提供了一套简单易用的解决方案,包括服务注册与发现、配置中心、服务网关、负载均衡、断路器、分布式消息队列等等。Spring Cloud 通过封装复杂的配置和实现原理,使开发者能够快速启动服务或构建应用,并与云平台资源进行对接。

Spring Cloud 包含多个子项目,其中最常用的是 Spring Cloud Netflix 和 Spring Cloud Alibaba。

Spring Cloud Netflix

Spring Cloud Netflix 是 Spring Cloud 的第一代实现,它基于 Netflix 公司的开源组件,提供了以下工具包:

  • Netflix Eureka:一个基于 REST 服务的服务治理组件,包括服务注册中心和服务发现机制的实现。
  • Netflix Ribbon:客户端负载均衡的服务调用组件。
  • Netflix Hystrix:容错管理工具,实现断路器模式,提供强大的容错能力。
  • Netflix Feign:基于 Ribbon 和 Hystrix 的声明式服务调用组件。
  • Netflix Zuul:微服务网关,提供动态路由和访问过滤等功能。
  • Netflix Archaius:配置管理 API,提供动态类型化属性、线程安全配置操作等功能。

Spring Cloud Alibaba

Spring Cloud Alibaba 是 Spring Cloud 的第二代实现,它是一套微服务解决方案,提供了以下组件:

  • Nacos:一个动态服务发现、配置管理和服务管理平台。
  • Sentinel:面向分布式服务架构的流量控制产品,保护服务的稳定性。
  • RocketMQ:一款高可靠的分布式消息系统。
  • Dubbo:一款高性能的 Java RPC 框架,用于实现服务通信。
  • Seata:一个易于使用的高性能微服务分布式事务解决方案。

Spring Cloud Alibaba 还包含了阿里云商业化组件,如 Alibaba Cloud ACM、Alibaba Cloud OSS、Alibaba Cloud SchedulerX、Alibaba Cloud SMS 等。

常用组件

除了 Netflix 和 Alibaba 的组件,Spring Cloud 还提供了其他常用的组件,包括:

  • Spring Cloud Zookeeper:服务注册中心。
  • Spring Cloud Consul:服务注册和配置管理中心。
  • Spring Cloud Gateway:基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开发的网关,提供统一的 API 路由管理方式。

总之,Spring Cloud 是一个强大的分布式系统开发框架,通过提供丰富的工具集,帮助开发者快速构建和管理分布式系统。

反射

Java反射是Java语言的一种特性,它允许在程序运行时动态地获取和操作类的信息。通过反射,我们可以在运行时获取类的属性、方法和构造函数等信息,并且可以动态地调用这些属性和方法。这种动态获取和调用的功能对于框架设计和一些特定场景非常有用。

反射的基本概念和作用

Java的反射机制允许我们在运行时获取一个类的信息,包括类的成员变量、方法和构造函数等。通过反射,我们可以实现以下功能:

  • 动态创建对象:可以在运行时根据类的信息动态地创建对象。
  • 动态调用方法:可以在运行时根据方法名动态地调用对象的方法。
  • 动态修改属性:可以在运行时根据属性名动态地修改对象的属性值。

反射的常用类和方法:

在Java中,反射相关的类主要包括以下几个:

  • Class类:表示一个类的信息,可以通过该类获取类的属性、方法和构造函数等信息。
  • Constructor类:表示一个类的构造函数,可以通过该类创建对象。
  • Field类:表示一个类的成员变量,可以通过该类修改对象的属性值。
  • Method类:表示一个类的方法,可以通过该类调用对象的方法。

常用的反射方法包括:

  • Class.forName(String className):根据类的全限定名获取Class对象。
  • Class.getDeclaredFields():获取类的所有成员变量。
  • Class.getDeclaredMethods():获取类的所有方法。
  • Class.getDeclaredConstructors():获取类的所有构造函数。
  • Constructor.newInstance(Object… args):根据构造函数创建对象。
  • Field.set(Object obj, Object value):设置对象的属性值。
  • Method.invoke(Object obj, Object… args):调用对象的方法。

反射调用方法与反射修改字段

动态创建对象、调用方法和修改属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Person.java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//ReflectionExample.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取Person类的Class对象
Class<?> personClass = Class.forName("com.example.Person");

// 动态创建对象
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
Object person = constructor.newInstance("John", 25);

// 动态调用方法
Method method = personClass.getMethod("sayHello");
method.invoke(person);

// 动态修改属性
Field field = personClass.getDeclaredField("age");
field.setAccessible(true);
field.setInt(person, 30);

System.out.println(person);
}
}

JAVA反射的常规使用步骤

反射调用一般分为3个步骤:

  • 得到要调用类的class

  • 得到要调用的类中的方法(Method)

  • 方法调用(invoke)

    代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Student.java
public class Student {
private int age;
private String name;

public Student(int age, String name) {
this.name = name;
this.age = age;
}

public void hi(int age, String name) {
System.out.println("Hi, I'm " + name + ", " + age + " years old.");
}

public void setAge(int age) {
this.age = age;
System.out.println("Hi, I'm " + name + ", " + age + " years old.");
}

public static void sayHello() {
System.out.println("Hello, I'm a static method in Student class.");
}

public int getAge() {
return age;
}
}
1
2
3
4
5
6
Class<?> Stu = Class.forName("Student");
Constructor<?> constructor = Stu.getConstructor(int.class,String.class);
Object student = constructor.newInstance(19,"lazy_fish");
Method lms = Stu.getDeclaredMethod("hi",new Class[]{int.class,String.class});
lms.invoke(student,20,"lms");
System.out.println(student);

方法调用中的参数类型

​ 在方法调用中,参数类型必须正确,这里需要注意的是不能使用包装类替换基本类型,比如不能使用Integer.class代替int.class。

​ 如我要调用Student的setAge方法,下面的调用是正确的:

1
2
3
4
5
6
Class<?> Stu = Class.forName("Student");
Constructor<?> constructor = Stu.getConstructor(int.class,String.class);
Object student = constructor.newInstance(19,"lazy_fish");
Method lms = Stu.getDeclaredMethod("setAge",new Class[]{int.class});
lms.invoke(student,20);
System.out.println(student);

而如果我们用Integer.class替代int.class就会出错,如:

1
2
3
4
5
6
Class<?> Stu = Class.forName("Student");
Constructor<?> constructor = Stu.getConstructor(int.class,String.class);
Object student = constructor.newInstance(19,"lazy_fish");
Method lms = Stu.getDeclaredMethod("setAge",new Class[]{Integer.class});
lms.invoke(student,20);
System.out.println(student);

jvm会报出如下异常:

static方法的反射调用

static方法调用时,不必得到对象示例,如下:

1
2
3
4
5
Class<?> clazz = Class.forName("Student");
// 获取静态方法
Method method = clazz.getMethod("sayHello");
// 调用静态方法
method.invoke(null);

private的成员变量赋值

如果直接通过反射给类的private成员变量赋值,是不允许的,这时我们可以通过setAccessible方法解决。代码示例:

1
2
3
4
5
6
7
8
9
Class<?> Stu = Class.forName("Student");
Constructor<?> constructor = Stu.getConstructor(int.class,String.class);
Object student = constructor.newInstance(19,"lazy_fish");
Method lms = Stu.getDeclaredMethod("setAge",new Class[]{int.class});
lms.invoke(student,20);
Field field = Stu.getDeclaredField("age");
field.set(student, 10);
System.out.println(field.get(student));
System.out.println(student);

运行如上代码,系统会报出如下异常:

解决方法:

1
2
3
4
5
6
7
8
9
10
Class<?> Stu = Class.forName("Student");
Constructor<?> constructor = Stu.getConstructor(int.class,String.class);
Object student = constructor.newInstance(19,"lazy_fish");
Method lms = Stu.getDeclaredMethod("setAge",new Class[]{int.class});
lms.invoke(student,20);
Field field = Stu.getDeclaredField("age");
field.setAccessible(true);//设置允许访问
field.set(student, 10);
System.out.println(field.get(student));
System.out.println(student);

其实,在某些场合下(类中有get,set方法),可以先反射调用set方法,再反射调用get方法达到如上效果,代码示例:

1
2
3
4
5
6
7
8
9
10
11
Student student = new Student(20, "Alice");

// 反射调用setAge方法设置年龄
Method setAgeMethod = Student.class.getDeclaredMethod("setAge", int.class);
setAgeMethod.invoke(student, 25);

// 反射调用getAge方法获取年龄
Method getAgeMethod = Student.class.getDeclaredMethod("getAge");
int age = (int) getAgeMethod.invoke(student);

System.out.println("Age: " + age);

反射修改final的问题

  1. Java 8及更早版本:在Java 8及更早的版本中,可以使用反射机制绕过final修饰的变量的限制来修改它的值。通过获取final变量的Field对象,并将其可访问性设置为true,然后使用Field对象的set()方法来修改final变量的值。但是,需要注意的是,这种做法是不安全的,并且可能导致不可预测的行为。
  2. Java 9及更高版本:从Java 9开始,对于final修饰的变量,Java引入了一个新的概念叫做”Effectively Final”。”Effectively Final”意味着虽然变量被声明为final,但在实际使用中可以修改其值。在这种情况下,反射机制无法修改”Effectively Final”变量的值。因此,从Java 9开始,使用反射机制修改final变量的值可能会导致编译错误或运行时异常。

修改static final属性值,关键在于通过反射将字段的final修饰符去掉

通过修改Field对象的修饰符可以实现使用反射修改final字段的值。

在JDK<=11,可以按照如下流程修改:

  1. 通过反射获取java.lang.reflect.Field内部的modifiers Field
  2. 将修改目标字段的 modifiers 修改为非 final
  3. 设置目标字段为新的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Student.java
public class Student {
private static final int age;
private static final String name;

public Student(int age, String name) {
this.name = name;
this.age = age;
}

public int getAge() {
return age;
}

public String getName() {
return name;
}
}

简单来说,我们通过反射,拿到某个属性的Field对象,再将Field对象中的修饰符modifiers修改成非final,最后再调用反射去修改就可以成功了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class Main {
public static void main(String[] args) throws Exception {
// 创建Student对象
Student student = new Student(20, "Alice");

// 反射获取final字段
Field nameField = Student.class.getDeclaredField("name");

// 设置字段的可访问性
nameField.setAccessible(true);

// 修改字段的修饰符为非final
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

// 修改字段的值
nameField.set(student, "Bob");

// 打印修改后的名字
System.out.println("Name: " + student.getName());
}
}

在上面的示例中,我们首先使用反射获取name字段,并通过调用setAccessible(true)方法设置字段的可访问性。然后,我们获取modifiers字段并设置其值,将字段的修饰符中的final标志位设为0,从而使字段变为非final。最后,我们使用set方法将字段的值设置为新的名字。最后,我们打印修改后的名字。

但自从 Java 12 开始,直接获取 Field 的 modifiers 字段会得到以下错误

1
Exception java.lang.NoSuchFieldException: modifiers at java.base/java.lang.Class.getDeclaredField

但是实际可以通过getDeclaredFields0获取到Fields所有字段

getDeclaredFields0java.lang.Class下的成员,恰好上面的fieldFilterMap只过滤了java.lang.Class下的classLoader成员,因此我们直接反射调用getDeclaredFields0就能获取到Fields的所有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
Field modifierField = null;
for (Field f : fields) {
if ("modifiers".equals(f.getName())) {
modifierField = f;
break;
}
}
modifierField.setAccessible(true);
// 创建Student对象
Student student = new Student(20, "Alice");
// 反射获取final字段
Field nameField = Student.class.getDeclaredField("name");
// 设置字段的可访问性
nameField.setAccessible(true);
// 修改字段的修饰符为非final
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);
// 修改字段的值
nameField.set(student, "Bob");
// 打印修改后的名字
System.out.println("Name: " + student.getName());

利用反射命令执行

最简单的命令执行

1
Runtime.getRuntime().exec("calc");

跟踪调用栈可以发现,Runtime#exec并不是命令执行的最终点

java.lang.Runtime#exec

-> java.lang.ProcessBuilder#start

-> java.lang.ProcessImpl#start

-> ProcessImpl#

-> native create

所以,理论上,下面的方法都可以命令执行

ProcessBuilder.start()

Runtime.exec()

ProcessImpl.start()

ProcessImpl

java.lang.Runtime

1
2
3
Class clazz = Class.forName("java.lang.Runtime");
Object runTime = clazz.getMethod("getRuntime").invoke(null);
clazz.getMethod("exec", String.class).invoke(runTime, "calc");

也可以获取Runtime的私有构造器去构造Runtime对象

1
2
3
4
Class clazz = Class.forName("java.lang.Runtime");
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(constructor.newInstance(), "calc");

java.lang.ProcessBuilder

除了Runtime.exec()ProcessBuilder.start()是另外一种命令执行的方式。

java.lang.ProcessBuilder有两个构造函数

public ProcessBuilder(List command)

public ProcessBuilder(String… command)

1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc")));
1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc"}}));

java.lang.ProcessImpl

start非public类,需要反射调用

1
2
3
4
Class clazz = Class.forName("java.lang.ProcessImpl");
Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
start.setAccessible(true);
start.invoke(clazz, new String[]{"calc"}, null, null, null, false);

ProcessImpl.start()最终调用的是ProceImpl的构造方法

1
2
3
4
Class clazz = Class.forName("java.lang.ProcessImpl");
Constructor constructor = clazz.getDeclaredConstructor(String[].class, String.class, String.class, long[].class, boolean.class);
constructor.setAccessible(true);
constructor.newInstance(new String[]{"calc"}, null, null, new long[]{-1,-1,-1}, false);

通过上述的方法就可以达成命令注入的效果。

java反射加载字节码GETshell

介绍

Java不像其他脚本语言,如js、php、python等有eval函数,可以把字符串当作代码来执行。

因此在Java中最直接的任意代码执行的方式就是加载字节码了。

加载字节码的几种方法:

  1. URLClassLoader#loadClass:需要出网或文件落地
  2. TransletClassLoader#defineClass:一般通过反序列化漏洞打进来
  3. ClassLoader#defineClass:需要通过反射调用

加载字节码
Java中的类加载器负责将字节码文件加载到内存中,并生成对应的Class对象。可以通过自定义ClassLoader来实现动态加载字节码文件。

动态加载字节码获取shell
通过反射和动态加载字节码,可以实现在Java程序中执行恶意代码,从而获取shell权限。具体步骤如下:

  1. 创建一个自定义的ClassLoader,继承自java.lang.ClassLoader类。
  2. 在自定义ClassLoader中定义一个方法,接收字节数组类型的参数,并调用父类的defineClass方法动态解析字节码,返回对应的Class对象。
  3. 使用反射调用ClassLoader的defineClass方法,传入字节数组参数,实现动态加载字节码。
  4. 实例化加载的类,并调用其中的方法来执行恶意代码,获取shell权限。

测试实例:

1
2
3
4
5
6
7
8
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("java.lang.ClassLoader");
Method method = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] bytes = getEvilCode("calc");
Class<?> aClass = (Class<?>) method.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
aClass.newInstance();
}

modifiers Bypass

ClassLoader为PUBLIC,我们只需将defineClass方法的修饰符修改为PUBLIC即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void bypassModifier() throws Exception {
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field field = clazz.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);

Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
Field modifiers = defineClassMethod.getClass().getDeclaredField("modifiers");
unsafe.putShort((Object) defineClassMethod, unsafe.objectFieldOffset(modifiers), (short) Modifier.PUBLIC);

byte[] bytes = getEvilCode("calc");
Class<?> aClass = (Class<?>) defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
aClass.newInstance();
}

override Bypass

注意setAccessible的调用逻辑

1
2
3
4
5
6
7
@Override
@CallerSensitive
public void setAccessible(boolean flag) {
AccessibleObject.checkPermission();
if (flag) checkCanSetAccessible(Reflection.getCallerClass());
setAccessible0(flag);
}
1
2
3
4
5
//checkCanSetAccessible去判断目标的修饰符、目标类是否对调用类开放...最后返回flag再传给setAccessible0
boolean setAccessible0(boolean flag) {
this.override = flag;
return flag;
}

setAccessible0直接将flag赋值给this.overrideoverride是其父类AccessibleObject的属性

因此也可以直接修改java.lang.reflect.AccessibleObjectoverride属性

注意:

JDK>=12之后(JDK 12/13/14),反射也过滤java.lang.reflect.AccessibleObject类的所有成员,得通过getDeclaredFields0去获取override属性

JDK>=12报错提示:没有modifiers字段

1
Caused by: java.lang.NoSuchFieldException: modifiers at java.base/java.lang.Class.getDeclaredField
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void bypassOverride(Object accessibleObject) throws Exception {
Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
Field override = Class.forName("java.lang.reflect.AccessibleObject").getDeclaredField("override");
unsafe.putBoolean(accessibleObject, unsafe.objectFieldOffset(override), true);
}
Class<?> c = Class.forName("java.lang.ClassLoader");
Method method = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
bypassOverride(method);
byte[] bytes = getEvilCode("calc");
Class<?> aClass = (Class<?>) method.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
aClass.newInstance();

fieldFilterMap Bypass

置空fieldFilterMap置空了。

通过unsafe.defineAnonymousClass创建匿名内部类,由此匿名类来获取类成员偏移量,最后再通过unsafe.putObject修改原来Reflection类的静态成员fieldFilterMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void bypassFieldFilterMap() throws Exception {
Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

Class<?> reflectionClass = Class.forName("jdk.internal.reflect.Reflection");
byte[] classBuffer = reflectionClass.getResourceAsStream("Reflection.class").readAllBytes();
Class<?> reflectionAnonymousClass = unsafe.defineAnonymousClass(reflectionClass, classBuffer, null);
Field fieldFilterMap = reflectionAnonymousClass.getDeclaredField("fieldFilterMap");

if (fieldFilterMap.getType().isAssignableFrom(HashMap.class)) {
unsafe.putObject(reflectionClass, unsafe.staticFieldOffset(fieldFilterMap), new HashMap<>());
}

byte[] clz = Class.class.getResourceAsStream("Class.class").readAllBytes();
Class<?> classAnonymousClass = unsafe.defineAnonymousClass(Class.class, clz, null);
Field reflectionData = classAnonymousClass.getDeclaredField("reflectionData");
unsafe.putObject(Class.class, unsafe.objectFieldOffset(reflectionData), null);
}

defineAnonymousClass Bypass

虽然JDK11把Unsafe#defineClass移除了,但Unsafe#defineAnonymousClass还在

1
2
3
4
Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
unsafe.defineAnonymousClass(Class.class, getEvilCode("calc"), null).newInstance();

序列化与反序列化

Java的序列化和反序列化是将Java对象转换为字节序列和将字节序列恢复为Java对象的过程。序列化可以将对象保存到文件或在网络中传输,而反序列化则可以将字节序列转换回原始对象。

序列化和反序列化的概念

序列化是将对象转换为字节序列的过程,而反序列化是将字节序列恢复为对象的过程。

序列化和反序列化的必要性

序列化和反序列化在以下情况下非常有用:

  • 分布式对象:序列化可以实现分布式对象,例如在远程调用中运行对象。
  • 对象复制:序列化不仅可以保存一个对象的数据,还可以递归保存对象引用的每个对象的数据,实现对象的深复制。
  • 持久化存储:序列化可以将内存中的对象写入文件或数据库中,实现数据的持久化存储。
  • 跨平台传输:序列化后的对象可以以字节流的形式进行通用的格式传输或保存,传输结束后可以进行反序列化还原。

实现Java序列化和反序列化的方法

要实现Java序列化和反序列化,需要按照以下步骤进行:

实现Serializable接口

要使一个类可以序列化,需要让该类实现Serializable接口。

1
2
3
4
5
import java.io.Serializable;

public class MyClass implements Serializable {
// 类的成员和方法
}

序列化对象

使用ObjectOutputStream类将对象序列化为字节序列。

1
2
3
4
5
6
7
8
9
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

MyClass obj = new MyClass();
FileOutputStream fileOut = new FileOutputStream("object.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(obj);
out.close();
fileOut.close();

反序列化对象

使用ObjectInputStream类将字节序列反序列化为对象。

1
2
3
4
5
6
7
8
import java.io.FileInputStream;
import java.io.ObjectInputStream;

FileInputStream fileIn = new FileInputStream("object.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
MyClass obj = (MyClass) in.readObject();
in.close();
fileIn.close();

注意事项

  • 要进行序列化和反序列化的类必须实现Serializable接口。
  • 静态变量不会被序列化。
  • transient关键字可以用于标记不需要序列化的成员变量。

反序列化实现漏洞利用

readObject()方法

特地提到这个方法是因为在反序列化漏洞中它起到了关键作用,readObject()方法被重写的的话,反序列化该类时调用便是重写后的readObject()方法。如果该方法书写不当的话就有可能引发恶意代码的执行,如

1
2
3
4
5
6
7
8
9
10
11
package evilSerialize;

import java.io.*;

public class Evil implements Serializable{
public String cmd;
private void readObject(java.io.ObjectInputStream stream) throws Exception {
stream.defaultReadObject();
Runtime.getRuntime().exec(cmd);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package evilSerialize;

import java.io.*;

public class Main {
public static void main(String[] args) throws Exception {
Evil evil=new Evil();
evil.cmd="calc";

byte[] serializeData=serialize(evil);
unserialize(serializeData);
}
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}

但肯定不会有程序员写出这样的代码,所以往往实际中反序列化漏洞的构造比较复杂,而且需要借助Java的一些特性如Java的反射,上文已经较为详细的介绍了反射的使用。

RMI

在Java里面简单来说使用Java调用远程Java程序使用的就是RMI

RMI中分为三大部分:Server、Client、Registry 。

Server: 提供远程的对象
Client: 调用远程的对象
Registry: 一个注册表,存放着远程对象的位置(ip、端口、标识符)

ASM/Javassist

字节码是什么???

字节码是一种中间代码,通常指的是经过编译器编译后的、与特定机器码无关的代码。它是一种由编码后的数值常量、引用和指令构成的序列,不像源代码那样容易阅读。字节码的主要目的是实现特定软件的运行和软件环境,与硬件环境无关。它的实现方式是通过编译器和虚拟机器。编译器将源代码编译成字节码,而特定平台上的虚拟机器将字节码转译为可以直接执行的指令。

Java字节码是Java虚拟机(JVM)执行的一种指令格式,它是Java程序编译后的中间代码。Java字节码具有跨平台的特性,可以在不同的操作系统和硬件平台上运行。它是一种与具体硬件平台无关的中间表示形式,使得Java程序可以在不同的平台上运行而无需重新编译。

Java字节码是由Java编译器将Java源代码编译成的一系列指令,这些指令被称为字节码指令。每个字节码指令都对应着一种特定的操作,例如存储、加载、算术运算、类型转换、方法调用等。这些指令以二进制形式存储在.class文件中,并由JVM解释执行。

Java字节码具有以下特点:

  1. 中间表示形式:Java字节码是一种中间表示形式,它不直接运行在物理硬件上,而是由JVM解释执行。
  2. 跨平台性:由于Java字节码与具体硬件平台无关,可以在任何支持Java虚拟机的平台上运行。
  3. 安全性:Java字节码在执行之前会经过严格的安全检查,确保代码的安全性和可靠性。
  4. 可移植性:由于Java字节码可以在不同的平台上运行,使得Java程序具有很高的可移植性。

Java字节码的执行过程如下:

  1. Java源代码通过Java编译器编译成字节码文件(.class文件)。
  2. JVM将字节码文件加载到内存中,并进行解析。
  3. JVM逐条解释执行字节码指令,执行相应的操作。
  4. 在执行过程中,JVM会进行一些优化操作,如即时编译(Just-In-Time Compilation)等。

总结起来,Java字节码是一种中间表示形式,它使得Java程序具有跨平台的特性,可以在不同的操作系统和硬件平台上运行。它是由Java编译器将Java源代码编译成的一系列指令,这些指令被JVM解释执行。

如何用asm修改字节码

导入ASM库
首先,需要导入ASM库到你的项目中。你可以在Maven或Gradle中添加以下依赖项:

1
2
3
4
5
6
<!--Maven-->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
</dependency>

创建ClassVisitor
ASM使用访问者模式来处理字节码。你需要创建一个继承自ClassVisitor的类,用于访问和修改字节码的不同部分。你可以重写visitMethod方法来修改方法的字节码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyClassVisitor extends ClassVisitor {

public MyClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM9, classVisitor);
}

@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("process")) {
// 在process方法前插入代码
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("start");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
return methodVisitor;
}
}

修改字节码
在你的代码中,使用ClassReader读取原始的字节码文件,然后使用MyClassVisitor来修改字节码,最后使用ClassWriter将修改后的字节码写回到文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import java.io.FileOutputStream;
import java.io.IOException;

public class BytecodeModifier {

public static void main(String[] args) throws IOException {
// 读取原始的字节码文件
ClassReader classReader = new ClassReader("com.example.Base");

// 创建ClassWriter
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

// 创建自定义的ClassVisitor
MyClassVisitor classVisitor = new MyClassVisitor(classWriter);

// 修改字节码
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);

// 获取修改后的字节码
byte[] modifiedClass = classWriter.toByteArray();

// 将修改后的字节码写回到文件中
FileOutputStream outputStream = new FileOutputStream("path/to/modified/Bytecode.class");
outputStream.write(modifiedClass);
outputStream.close();
}
}

这样,你就可以使用ASM修改Java字节码了。在上面的示例中,我们通过MyClassVisitor类的visitMethod方法,在process方法前插入了一段输出代码。

如何使用javassist生成字节码

使用Javassist生成字节码可以通过以下步骤实现:

导入Javassist库:首先,需要在项目中导入Javassist库。可以通过Maven或手动下载jar包的方式导入。

1
2
3
4
5
<dependency><!--maven-->
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.1-GA</version>
</dependency>

创建类池(ClassPool):使用Javassist进行字节码操作时,需要创建一个类池对象来存储和管理CtClass对象。类池是Javassist中的核心概念之一。

1
ClassPool pool = ClassPool.getDefault();

创建CtClass对象:CtClass对象代表一个Java类。可以通过类池对象创建CtClass对象。

1
CtClass ctClass = pool.makeClass("com.example.MyClass");

添加类的属性和方法:可以使用CtClass对象的方法来添加类的属性和方法。

1
2
3
4
5
6
7
// 添加字段
CtField field = new CtField(CtClass.intType, "myField", ctClass);
ctClass.addField(field);

// 添加方法
CtMethod method = CtNewMethod.make("public void myMethod() { System.out.println(\"Hello, Javassist!\"); }", ctClass);
ctClass.addMethod(method);

生成字节码文件:可以使用CtClass对象的writeFile方法将生成的字节码文件写入磁盘。

1
ctClass.writeFile();