Java入门
Java入门
1azy_fish.Before all
拖了好久还是想学一学java。
idea
在IntelliJ IDEA中调试Java应用程序可以按照以下步骤进行:
- 设置断点:在你认为有问题的代码行上设置断点。你可以在代码编辑器中单击行号旁边的空白处来设置断点,或者使用快捷键Ctrl + F8。
- 启动调试模式:在IntelliJ IDEA中,你可以通过多种方式启动调试模式,包括:
- 在代码编辑器中单击断点行号旁边的调试按钮(绿色的虫子图标)。
- 使用快捷键Shift + F9。
- 在菜单栏中选择”Run” -> “Debug”。
- 配置调试配置:如果你是第一次进行调试,可能需要配置调试配置。IntelliJ IDEA会为你的应用程序自动创建一个调试配置,你可以在弹出的对话框中选择相应的配置。
- 访问应用程序:在调试模式下,你可以通过多种方式运行应用程序,包括:
- 在IDEA中使用右上角的运行按钮(绿色的三角形图标)。
- 使用快捷键Shift + F10。
- 在菜单栏中选择”Run” -> “Run”。
- 调试过程:一旦应用程序进入断点,调试器会暂停应用程序的执行。你可以使用调试器的各种工具和功能来查看和修改变量的值,跳过代码行,单步执行代码等。常用的调试工具包括:
- 变量观察窗口:显示当前作用域的变量及其值。
- 断点窗口:显示已设置的断点列表,并提供断点的管理功能。
- 控制台窗口:可以在断点暂停时执行命令,并查看输出结果。
- 观察和分析:在调试过程中,观察应用程序的行为和变量的值。通过查看变量的值并与预期值进行比较,你可以找到问题所在。
- 修改代码:根据观察和分析的结果,你可以决定是否需要修改代码来修复问题。在这种情况下,你可以在IDEA中编辑代码并保存更改。
- 重新运行:一旦你修改了代码并保存更改,你可以停止调试模式并重新运行应用程序,以测试你的更改是否解决了问题。
简单的调试如下:
java
java简介
Java是一种面向对象的编程语言,具有简单、可靠、安全和可移植等特点。下面是一些Java基础语法的介绍:
类和对象:Java是面向对象的语言,所有的代码都必须在类中定义。一个类是对象的模版,用于创建对象。可以使用关键字class
来定义类,例如:
1 | public class MyClass { |
方法:方法是类中执行特定任务的一组语句。每个方法都有一个名称和一组参数(可选),并可以返回一个值(也可以是void
表示无返回值)。方法的定义包括方法的修饰符、返回类型、名称和参数列表,例如:
1 | public int add(int a, int b) { |
变量:变量用于存储数据,可以是基本类型或引用类型。在Java中,变量需要先声明,然后才能使用。声明变量时,需要指定变量的类型和名称,例如:
1 | int age; // 声明一个名为age的整数变量 |
数据类型:Java有两种数据类型,即基本类型和引用类型。基本类型包括整数类型(byte
、short
、int
、long
)、浮点类型(float
、double
)、字符类型(char
)和布尔类型(boolean
)。引用类型包括类、接口、数组等。
控制流语句:Java提供了一些控制流语句来控制程序的执行流程,包括条件语句(if-else
、switch
)、循环语句(for
、while
、do-while
)和跳转语句(break
、continue
、return
)等。
数组:数组是一组具有相同类型的数据元素的集合。在Java中,可以使用数组来存储和操作多个数据。数组的声明和初始化可以通过以下方式进行:
1 | int[] numbers = new int[5]; // 声明一个包含5个整数的数组 |
输入输出:Java提供了System.out
用于标准输出,可以使用System.out.println()
打印文本到控制台。可以使用Scanner
类来获取用户的输入,例如:
1 | import java.util.Scanner; |
好了我们学会1+1了,开始挖洞吧。(x
标识符与关键字
以上为常用关键字。
以HelloWOrld文件来分析。
1 | public class Hello { |
public class Hello
表示定义一个类。public static void main(String[] args)
表示定义一个方法。String a
定义一个变量。
以上这几种命名变量名的方式都可以,还可以中文变量,标识符对大小写敏感。
数据类型详解
Java是一个强类型语言,要求规范严格,要求变量使用严格规定,需要定义后使用
基本数据类型
1 | public class Demo { |
可以用逗号隔开同时对多个变量赋值。
整数型
整数型有四个类型byte
,short
,int
,long
byte占一个字节 范围-128-127
short占两个字节 范围-32768-32767
int占四个字节 范围-2147483648-2147483647
long占八个字节 范围-9223372036854775808-9223372036854775807
浮点型
浮点型也有2个类型double
,float
float占四个字节
double占八个字节
字符类型
类型为char
占两个字节。
定义字符用char,用单引号包裹。
定义字符串用String并且一定要用双引号包裹,不可以使用单引号。
字符串用+做连接符
布尔类型
类型为boolean
,只有true,false
占一个字节
引用数据类型
类(Class)
用户自定义的引用数据类型,通过定义类可以创建对象实例。
1 | public class Person { |
Person
是一个自定义的类,它具有 name
和 age
两个属性,以及 sayHello()
方法。我们可以通过创建 Person
类的对象实例来访问和操作这些属性和方法。
接口(Interface)
一种抽象的引用数据类型,定义了一组方法的签名,可以被类实现(implement)。
1 | public interface Drawable { |
Drawable
是一个接口,它定义了一个 draw()
方法。其他类可以实现(implement)这个接口,并提供自己的实现逻辑。
数组(Array)
引用数据类型的一种,可以存储多个相同类型的元素。
1 | int[] numbers = new int[5]; |
numbers
是一个整型数组,它可以存储五个整数。我们可以通过索引访问和修改数组中的元素。
字符串(String)
是Java提供的引用数据类型,用于表示文本字符串。
1 | String message = "Hello, World!"; |
message
是一个字符串变量,存储了文本消息。我们可以使用字符串的方法来操作和处理文本数据。
包装类(Wrapper Class)
包装类为基本数据类型提供了对应的引用数据类型,使其具有对象特性。
1 | Integer number = Integer.valueOf(42); |
Integer
是一个包装类,它将基本数据类型 int
包装成一个对象。通过包装类,我们可以在需要使用对象的场景中操作基本数据类型。
枚举(Enum)
一种特殊的引用数据类型,用于定义一组具名的常量。
1 | enum Day { |
Day
是一个枚举类型,它定义了一周中的每一天。我们可以使用枚举类型来表示一组具名的常量,如星期几、月份等。
数据类型拓展
整数拓展
进制问题
在Java中,常见的整数进制有二进制(binary)、八进制(octal)、十进制(decimal)和十六进制(hexadecimal)。它们在Java中的表示方式如下:
二进制:以 0b
或 0B
开头,后面跟着二进制数字(0或1)。
例如:int binary = 0b010;
八进制:以 0
开头,后面跟着八进制数字(0-7)。
例如:int octal = 010;
十进制:直接使用数字表示,没有特殊的前缀。
例如:int decimal = 10;
十六进制:以 0x
或 0X
开头,后面跟着十六进制数字(0-9,A-F或a-f)。
例如:int hexadecimal = 0x10;
浮点数拓展
浮点数在Java中有两种类型:float
和 double
。需要注意以下几点拓展:
- 浮点数的精度是有限的,因此存在舍入误差。
- 浮点数是近似值,接近但不完全等于所表示的数。
- 在进行浮点数比较时,应该使用范围或误差值来判断相等性,而不是直接使用
==
运算符。
1 | float c = 0.1f; |
第一个比较结果为 false
是因为浮点数的舍入误差导致精度不一致。第二个比较结果为 true
是因为超过了浮点数的有效范围,所以即使增加了1,仍然相等。
字符拓展
字符类型 char
在Java中表示单个字符。除了表示字符本身外,还可以进行一些拓展操作:
- 可以将字符转换为对应的数字表示,即转换为它的 ASCII 码值。
1 | char character = 'A'; |
- 可以使用 Unicode 转义序列来表示特殊字符。
1 | char unicodeChar = '\u0061'; // Unicode码为 'a' |
- 字符串中可以使用转义字符(如
\n
、\t
等)来表示特殊字符。
1 | String message = "Hello\nWorld"; |
注意:以上特性在其他编程语言中也普遍存在,因此这些概念容易理解和使用。
类型转换
在Java中,可以进行不同类型之间的转换,但需要注意以下几点:
- 从低优先级到高优先级的转换不需要进行强制类型转换。
- 从高优先级到低优先级的转换需要进行强制类型转换。
- 一些特定的转换可能会导致精度损失或溢出的问题。
以下是一些示例:
1 | int i = 100; |
需要注意的事项:
- 布尔类型不能进行类型转换。
- 对象类型不能转换为不相关的类型。
- 在进行类型转换时,可能会遇到精度损失的问题,例如将小数转换为整数时可能会直接取整。
- 在计算过程中,要考虑溢出的情况,否则可能得到负数或其他不正确的结果。
数字之间可以使用下划线 _
进行分隔,以提高可读性(JDK 7 新特性)。
1 | int million = 1_000_000; |
需要注意的是,int
类型无法直接通过 String
进行转换。
常量
在Java中,使用 final
关键字声明的变量是常量,其值不能被修改。常量的命名通常使用大写字符。
1 | final double PI = 3.14; |
常量一旦被赋值后,其值不能再改变。常量的声明可以与其他修饰符无先后顺序关系,例如 static
和 final
的顺序可以任意。
测试结果如下
基本运算符
如上。
还有三元运算符: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 | package com.example.demo1; |
可以看到只输出了hello!
nextline():
1 | package com.example.demo1; |
完全输出,这两者有以下区别:
言简意赅的说,next以空格为结束符,nextline以回车为结束符
Scanner进阶使用
判断类型:
1 | import java.util.Scanner; |
输入几个n个数据,求平均数,通过输入一个非数字来结束:
1 | import java.util.Scanner; |
注意,
hasNextDouble()
函数也是可以进行一次输出,如果hasNextDouble进行了,name里面的NextDouble就是用来获取值的,如果没有hasNextDouble()
,那里面的NextDouble也可以进行一次输入并且获取值。
各种判断语句
if
1 | if(条件){//为true就执行 |
switch
1 | import java.util.Scanner; |
java7开始switch支持string类型,case不能放变量,放常量。
这儿的break是需要的,和其他语言一样,没了break就会继续往下判断。
while和do while
1 | while(i<=100){ |
1 | do{ |
和c里的一样,先执行一次do
for
和C和PHP简直是一模一样啊
1 | for(初始值;布尔判断;更新){ |
奇数和偶数的和:
1 | public class input { |
1-1000能被5整除的,每行输出3个
1 | public class input { |
System.out.print
输出不会换行System.out.printin
会换行,和python的print一样
九九乘法表
1 | public class input { |
增强For循环
1 | for(声明语句:表达式){ |
1 | public class input { |
for enhance只不过是为了遍历数组
Break,continue,goto
- break
直接跳出当前整个循环体
- continue
跳过本次循环,进入下一次循环
1 | public class input { |
1 | public class input { |
- goto
java里没有c里的goto,但是有一些残留的影子
1 | public class input { |
Maven
Maven是一个项目管理工具,用于构建、部署和管理Java项目。它提供了一种统一的方式来管理项目的依赖、编译、测试和部署等过程。
以下是Maven的一些基本特点和使用方式:
- 项目对象模型(Project Object Model,POM):Maven使用POM文件来描述项目的配置和依赖关系。POM是一个XML文件,定义了项目的坐标、依赖、构建配置等信息。
- 依赖管理:Maven通过POM文件中的依赖配置来管理项目的依赖库。你可以在POM文件中指定所需的依赖库和版本号,Maven会自动下载并管理这些依赖库。
- 构建生命周期:Maven定义了一套构建生命周期,包括一系列的阶段(Phase)和插件(Plugin)。通过在POM文件中配置构建插件和阶段,可以在构建过程中执行特定的任务,例如编译、测试、打包等。
- 中央仓库:Maven维护了一个中央仓库,包含了大量的开源Java库和插件。当你在POM文件中声明依赖时,Maven会自动从中央仓库下载相应的库文件。
- 插件系统:Maven具有强大的插件系统,可以通过插件扩展和定制构建过程。你可以使用现有的插件,也可以编写自己的插件来满足特定需求。
- 命令行工具和集成开发环境(IDE)支持:Maven提供了命令行工具和与主流IDE(如Eclipse、IntelliJ IDEA)的集成支持,方便项目的构建和管理。
安装配置:下载配置环境变量就ok
如何加速
由于默认情况下执行 mvn 各种命令是去国外的 mvn 官方镜像源获取需要安装的具体软件信息,所以在不使用代理、不FQ的情况下,从国内访问国外服务器的速度相对比较慢
如何修改镜像源
阿里旗下维护着一个国内 maven 镜像源。感谢阿里为开源届,为开发者做的贡献,之前发布的两篇关于 npm, yarn 的国内加速镜像源也是使用阿里提供的,之后会发布一篇介绍 gradle 的国内加速方法,依旧会使用阿里提供的镜像源
配置只在当前项目生效
在 pom.xml 文件内添加以下配置
1 | <repositories> |
配置全局生效
修改 settings.xml 文件
找到 mirrors 标签,在里面加入以下内容
1 | <mirror> |
可以在以下路径查找到 settings.xml 文件
- (用户根目录)/.m2/settings.xml
- (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 | <plugin> |
maven-dependency-plugin
用于复制依赖的jar包到指定的文件夹里
1 | <plugin> |
maven-jar-plugin
jar项目默认的打包工具,默认情况下只会将项目源码编译生成的class文件和资源文件打包进来,不会打包进项目依赖的jar包。
打成jar时,设定manifest的参数,比如指定运行的Main class,还有依赖的jar包,加入classpath中
1 | <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 | <plugin> |
wagon-maven-plugin
用于一键部署,把本地打包的jar文件,上传到远程服务器上,并执行服务器上的shell命令
1 | <plugin> |
maven-shade-plugin
用于把多个jar包,打成1个jar包
1 | <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的一些常见用法:
- create:创建一个新的项目原型。您可以使用该目标指定原型的groupId、artifactId、version等信息,并选择要包含的文件和目录结构。
- generate:根据指定的项目原型生成一个新项目。您可以使用该目标指定原型的groupId、artifactId、version等信息,并选择要包含的文件和目录结构。
- list:列出所有可用的项目原型。使用该目标可以查看本地或远程仓库中可用的项目原型列表。
- delete:删除本地仓库中的指定项目原型。
使用示例:
创建项目原型:
1 | mvn archetype:generate |
上述命令将使用”Maven Quickstart Archetype”原型创建一个名为”my-project”的新项目。可以根据需要修改groupId、artifactId和version参数。
- 选择项目原型:
当执行上述命令时,Maven会列出可用的项目原型列表供您选择。您可以根据列表中的编号选择相应的原型。例如,选择编号为1的原型。 - 生成新项目:
根据您的选择,Maven Archetype Plugin将在当前目录中生成一个新的项目结构,包括源代码、测试代码、资源文件等。
groupId、artifactId和version是Maven项目的核心标识和版本信息。
- groupId:groupId代表项目所属的组织或团队的唯一标识。通常使用反向域名的方式来命名,例如”com.example”。groupId用于确保项目的唯一性,并帮助组织和识别项目。
- artifactId:artifactId代表项目的唯一标识符。通常使用项目名称来命名,例如”my-project”。artifactId用于识别和定位项目,它是项目构建产物(如JAR包、WAR包)的名称。
- version:version代表项目的版本号。它是一个字符串,可以是数字和字符的组合,例如”1.0.0”。version用于标识和管理项目的不同版本,以便开发者和用户可以区分不同的发布版本。
这些参数在Maven项目的构建、依赖管理和部署过程中非常重要。它们可以确保项目的唯一性、版本控制和协作开发。在Maven的pom.xml文件中,这些参数通常作为项目的基本信息和属性定义。通过使用这些参数,Maven可以根据项目标识和版本信息来管理和构建项目。
maven-assembly-plugin
该插件的用途是制作项目分发包,该分发包可能包含了项目的可执行文件、源代码、readme、平台脚本等等。
一些常用配置和用法:
- 配置assembly描述符:在项目的pom.xml文件中,您可以定义一个assembly描述符,用于指定要打包的文件和目录结构、包含/排除的规则、文件的权限等。描述符通常以assembly.xml或assembly.xml文件的形式存在。
- 定义组件:在assembly描述符中,您可以定义一个或多个组件。每个组件可以指定一个或多个文件和目录,以及打包到发布包中的路径和文件名。
- 指定发布包类型:Maven Assembly Plugin支持多种发布包类型,如JAR、ZIP、TAR等。您可以在assembly描述符中指定所需的发布包类型。
- 配置附加构建阶段:通过配置Maven Assembly Plugin的execution元素,您可以指定插件在项目构建的特定阶段执行。例如,您可以配置插件在package阶段之后执行,以便在打包完成后生成自定义的发布包。
- 自定义文件、目录、权限等:通过配置assembly描述符,您可以自定义发布包中的文件、目录和文件权限。您可以包括额外的资源文件、配置文件,以及调整文件的权限和属性。
maven-assembly-plugin要求用户使用一个名为assembly.xml的元数据文件来表述打包,它的single目标可以直接在命令行调用,也可以被绑定至生命周期。
下面是一个使用Maven Assembly Plugin创建可执行JAR包的示例:
在项目的pom.xml文件中,添加Maven Assembly Plugin的配置:
1 | <build> |
在项目中创建一个名为assembly.xml的assembly描述符,指定要打包的文件和目录结构:
1 | <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" |
上述描述符指定了一个名为”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
最大的用途是帮助分析项目依赖
- list:列出项目的依赖关系。可以使用该目标查看项目的直接依赖、传递依赖和冲突依赖。
- tree:以树状结构展示项目的依赖关系。可以使用该目标查看项目依赖的层级结构和依赖传递路径。
- analyze:分析项目的依赖关系。可以使用该目标找出项目中未使用的依赖、潜在的冲突依赖和过时的依赖。
- copy:复制项目的依赖到指定目录。可以使用该目标将项目的依赖复制到指定的目录,便于离线构建或其他用途。
- unpack:解压项目的依赖。可以使用该目标将项目依赖的JAR文件解压到指定的目录,以便查看或修改依赖的内容。
Maven Dependency Plugin还支持其他配置和参数,例如指定依赖的范围、排除依赖、过滤依赖等。
列出项目的依赖关系:
mvn dependency:list
上述命令将列出项目的直接依赖和传递依赖,包括每个依赖的groupId、artifactId、版本号等信息。
以树状结构展示项目的依赖关系:
mvn dependency:tree
这个命令将以树状结构展示项目的依赖关系,显示每个依赖的groupId、artifactId、版本号及其传递路径。
分析项目的依赖关系:
mvn dependency:analyze
该命令将分析项目的依赖关系,找出未使用的依赖、潜在的冲突依赖和过时的依赖,并提供相应的建议。
复制项目的依赖到指定目录:
mvn dependency:copy-dependencies -DoutputDirectory=/path/to/directory
上述命令将项目的依赖复制到指定的目录,便于离线构建或其他用途。
解压项目的依赖:
mvn dependency:unpack -Dartifact=groupId:artifactId:version -DoutputDirectory=/path/to/directory
这个命令将指定的依赖JAR文件解压到指定的目录,以便查看或修改依赖的内容。
maven-enforcer-plugin
Maven Enforcer Plugin是一个用于强制执行构建规则和限制的Maven插件。它提供了一系列目标,可以帮助您在构建过程中强制执行各种规则和限制,以确保项目的构建和质量符合预期。
用法:
- enforce:强制执行规则和限制。可以使用该目标配置和应用各种规则,例如强制使用特定的Maven版本、限制依赖的版本范围、强制执行特定的构建配置等。
- display-info:显示构建信息。可以使用该目标显示有关构建环境、插件版本、配置信息等的详细信息。
- display-requirement:显示规则和限制信息。可以使用该目标显示已配置的规则和限制的详细信息。
- display-properties:显示项目属性。可以使用该目标显示项目中定义的属性及其值。
- display-plugin-updates:显示插件更新信息。可以使用该目标显示项目中可用的插件的最新版本和更新说明。
Maven Enforcer Plugin还支持其他配置和参数,例如自定义规则、忽略规则、设置规则的错误级别等。
示例:
在项目的pom.xml文件中添加以下插件配置:
1 | <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-pom
和mvn 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信息。
用法:
- prepare:准备发布。执行该目标将自动更新项目的版本号、生成发布文档、打上版本标签等。
- perform:执行发布。执行该目标将自动构建项目、生成发布包,并将发布包发布到远程Maven仓库。
- rollback:回滚发布。执行该目标将回滚之前准备的发布操作,恢复项目到之前的状态。
- release:执行完整的发布流程。执行该目标将依次执行prepare和perform目标,实现自动化的版本管理和发布过程。
示例配置和命令:
首先,在项目的pom.xml文件中添加以下插件配置:
1 | <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的一些常见用法:
- resources:复制项目资源。执行该目标将复制项目中的资源文件到指定的输出目录。
- testResources:复制测试资源。执行该目标将复制测试代码中的资源文件到指定的输出目录。
- copy-resources:复制指定资源。执行该目标可以复制指定的资源文件或目录到指定的输出目录。
- filtering:过滤资源。执行该目标将对资源文件进行变量替换,替换掉配置文件中的占位符等。
Maven Resources Plugin还支持其他配置和参数,例如指定资源文件的包含或排除规则、设置输出目录、配置变量替换等。
示例配置和命令:
复制项目资源:
在项目的pom.xml文件中添加以下插件配置:
1 | <build> |
上述配置中,我们指定了Maven Resources Plugin的版本为3.2.0,并配置了一个outputDirectory
参数,指定资源文件的输出目录为${project.build.directory}/custom-resources
。
然后,执行以下命令来复制项目资源:
mvn resources:resources
上述命令将会复制项目中的资源文件到指定的输出目录。
复制测试资源:
在项目的pom.xml文件中添加以下插件配置:
1 | <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的一些常见用法:
- test:运行测试。执行该目标将自动运行项目中的测试代码。
- report:生成测试报告。执行该目标将生成测试结果的报告文件,例如HTML、XML等格式。
- test-compile:编译测试代码。执行该目标将编译项目中的测试代码。
- test-coverage:生成测试覆盖率报告。执行该目标将生成测试代码的覆盖率报告。
Maven Surefire Plugin还支持其他配置和参数,例如指定测试代码的包含或排除规则、设置测试报告的输出目录、配置并行执行等。
示例配置和命令:
运行测试:
在项目的pom.xml文件中添加以下插件配置:
1 | <build> |
上述配置中,我们指定了Maven Surefire Plugin的版本为3.0.0-M5,并配置了一个includes
参数,指定要运行的测试类的包含规则。
然后,执行以下命令来运行测试:
mvn surefire:test
上述命令将会自动运行项目中符合包含规则的测试代码。
生成测试报告:
在项目的pom.xml文件中添加以下插件配置:
1 | <build> |
上述配置中,我们配置了一个report
目标,并将其绑定到test
阶段。我们还指定了一个includes
参数,指定要生成测试报告的测试类的包含规则。
然后,执行以下命令来生成测试报告:
mvn surefire:report
上述命令将会生成测试结果的报告文件,通常以HTML格式呈现。
build-helper-maven-plugin
Build Helper Maven Plugin是一个用于扩展Maven构建的插件。它提供了一组目标,用于动态添加、修改和管理Maven项目的构建信息。
以下是Build Helper Maven Plugin的一些常见用法:
- add-source:添加源代码目录。执行该目标将向项目中添加额外的源代码目录,使其能够被编译和构建。
- add-test-source:添加测试源代码目录。执行该目标将向项目中添加额外的测试源代码目录,使其能够被编译和运行测试。
- add-resource:添加资源目录。执行该目标将向项目中添加额外的资源目录,使其能够被复制到构建输出目录。
- add-test-resource:添加测试资源目录。执行该目标将向项目中添加额外的测试资源目录,使其能够被复制到测试构建输出目录。
Build Helper Maven Plugin还支持其他配置和参数,例如指定源代码或资源的目录位置、配置过滤器等。
示例配置和命令:
添加源代码目录:
在项目的pom.xml文件中添加以下插件配置:
1 | <build> |
上述配置中,我们指定了Build Helper Maven Plugin的版本为3.2.0,并配置了一个add-source
目标,并将其绑定到generate-sources
阶段。我们指定了一个sources
参数,指定要添加的源代码目录为src/main/java2
。
然后,执行以下命令来添加源代码目录:
mvn build-helper:add-source
上述命令将会向项目中添加指定的源代码目录。
添加资源目录:
在项目的pom.xml文件中添加以下插件配置:
1 | <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的一些常见用法:
- exec:执行命令行脚本或外部程序。通过配置该目标,可以在构建过程中执行自定义的命令行脚本或外部程序。
- java:执行Java程序。通过配置该目标,可以在构建过程中执行Java程序,例如运行测试类、启动应用程序等。
- execSet:执行一组命令。通过配置该目标,可以执行多个命令行脚本或外部程序,按顺序执行。
Exec Maven Plugin还支持其他配置和参数,例如指定命令行参数、设置工作目录、配置环境变量等。
示例配置和命令:
执行命令行脚本:
在项目的pom.xml文件中添加以下插件配置:
1 | <build> |
上述配置中,我们指定了Exec Maven Plugin的版本为3.0.0,并配置了一个exec
目标,并将其绑定到process-resources
阶段。我们使用executable
参数指定要执行的命令行脚本,然后使用arguments
参数指定脚本的参数。
然后,执行以下命令来执行命令行脚本:mvn exec:exec
上述命令将会执行我们指定的命令行脚本,输出”Hello, 1azy_fish!”。
执行Java程序:
在项目的pom.xml文件中添加以下插件配置:
1 | <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的一些常见用法:
- jetty:run:启动Jetty服务器。执行该目标将启动内嵌的Jetty服务器,并将当前项目部署到该服务器上。可以通过访问
http://localhost:8080
来访问部署的应用程序。- jetty:run-forked:以独立进程方式启动Jetty服务器。执行该目标将启动一个独立的Jetty服务器进程,并将当前项目部署到该服务器上。
- jetty:deploy:部署Web应用程序。执行该目标将构建和打包项目,并将生成的WAR文件部署到Jetty服务器中。可以通过访问
http://localhost:8080
来访问部署的应用程序。
Jetty Maven Plugin还支持其他配置和参数,例如指定端口号、上下文路径、配置SSL等。
示例配置和命令:
启动Jetty服务器:
在项目的pom.xml文件中添加以下插件配置:
1 | <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 | <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的一些常见用法:
- versions:display-dependency-updates:显示更新的依赖版本。执行该目标将显示当前项目依赖的库中可用的新版本。
- versions:display-plugin-updates:显示更新的插件版本。执行该目标将显示当前项目中可用的新插件版本。
- 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 | <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 | <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 | <build> |
上述配置中,我们指定了Maven Shade Plugin的版本为3.2.4,并配置了一个maven-shade-plugin
插件。我们通过configuration
元素可以配置插件的参数。在上述示例中,我们设置createDependencyReducedPom
为false
,表示不生成精简的POM文件。我们还添加了一个transformers
配置,指定了一个ManifestResourceTransformer
转换器,其中指定了主类为com.example.Main
。
然后,执行以下命令来将项目及其依赖库打包为可执行的JAR文件:mvn package
上述命令将会构建和打包项目,并生成一个包含所有依赖库的JAR文件。
对依赖库进行重定位:
在项目的pom.xml文件中添加以下插件配置:
1 | <build> |
上述配置中,我们添加了一个relocations
配置,指定了一个重定位规则,将com.google.common
包重命名为myapp.shaded.com.google.common
。
然后,执行以下命令来对依赖库进行重定位:mvn package
上述命令将会构建和打包项目,并将符合重定位规则的依赖库进行重命名。
Maven多模块项目
Maven多模块项目是指由多个子模块组成的项目结构。每个子模块都是一个独立的项目,可以有自己的源代码、资源文件、依赖等。这种项目结构使得大型项目能够更好地组织和管理,提高了代码的可维护性和复用性。
在Maven中,多模块项目通过父子关系来组织。通常,一个Maven多模块项目会有一个顶级的父项目,以及多个子项目(也称为模块)。父项目负责统一管理子项目的依赖、构建配置等,子项目可以继承父项目的配置,也可以有自己的特定配置。
多模块项目的好处包括:
- 提供了更好的代码组织结构:多模块项目可以将相关的功能模块或组件划分为独立的子模块,使得代码更加有序和可维护。
- 支持代码复用和共享:子模块之间可以共享代码、资源和依赖,提高了代码的复用性和共享性。
- 管理依赖更加方便:父项目可以集中管理所有子项目的依赖库版本,避免版本冲突和重复引入。
- 允许并行构建和测试:由于子模块之间是相互独立的,因此可以并行地构建和测试各个子模块,提高构建速度和效率。
Maven多模块项目的结构如下所示:
1 | my-parent-project |
在父项目的pom.xml中,使用<modules>
元素列出所有的子模块。
1 | <modules> |
每个子模块都有自己的pom.xml文件,用于配置该模块的构建和依赖等信息。父项目和子项目之间可以通过Maven的继承机制来共享和继承配置。
Gradle是啥???
Gradle是一种开源的自动化构建工具,用于构建、测试和部署软件项目。它是基于Groovy编程语言和DSL(领域特定语言)的构建工具,具有强大的灵活性和可扩展性。
以下是Gradle的一些特点和优势:
- 声明式构建脚本:Gradle使用基于Groovy的DSL来定义构建脚本,使得构建逻辑更易于理解和维护。构建脚本可以声明项目的依赖、任务和构建流程等。
- 高度可定制:Gradle提供了丰富的插件和扩展机制,可以根据项目的需求定制构建逻辑。开发人员可以编写自己的插件,或者使用现有的插件来扩展Gradle的功能。
- 多项目支持:Gradle支持多项目构建,可以轻松管理和构建复杂的多模块项目。通过定义项目之间的依赖关系,可以实现模块化的构建和自动化任务的执行。
- 并行构建:Gradle能够根据项目的依赖关系和任务之间的依赖关系,自动进行任务的并行执行,提高构建效率。
- 强大的依赖管理:Gradle支持各种依赖管理工具,如Maven和Ivy,可以轻松管理项目的依赖库。它还支持动态版本解析和增量构建,提供了更高效的依赖管理机制。
- 跨平台支持:Gradle可以在各种操作系统上运行,包括Windows、Linux和Mac OS等。它通过使用Java虚拟机(JVM)来实现跨平台的支持。
Gradle被广泛应用于Java、Android和Kotlin等项目的构建和自动化。
(不想再学下去,暂时先学maven
Java Web
Servlet Tomcat JSP
Servlet、Tomcat和JSP是Java Web开发中常用的技术组合,用于构建动态的Web应用程序。
- Servlet(服务器端小程序):Servlet是Java编写的服务器端程序,用于处理Web请求和生成动态的Web内容。它可以接收HTTP请求并生成响应,通常用于处理表单提交、用户身份验证、数据查询等任务。Servlet通常被部署在Servlet容器中(如Tomcat),并通过URL映射来处理特定的请求。
- Tomcat(Apache Tomcat):Tomcat是一个开源的Servlet容器和Web服务器,用于运行Java Web应用程序。它是Java Servlet和JavaServer Pages(JSP)规范的参考实现之一。Tomcat提供了一个运行环境,可以加载和执行Servlet和JSP,并处理HTTP请求和响应。开发人员可以将他们的Servlet和JSP部署到Tomcat中,以提供Web应用程序的服务。
- 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配置。
- 创建项目和目录结构:
- 使用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配置文件。
- 配置SpringMVC:
- 在src/main/webapp/WEB-INF目录下创建一个名为springmvc-servlet.xml的SpringMVC配置文件。
- 在配置文件中进行SpringMVC的相关配置,如扫描包、视图解析器、处理器映射等。
- 配置Spring:
- 在src/main/resources/spring目录下创建一个名为spring-context.xml的Spring配置文件。
- 在配置文件中进行Spring的相关配置,如扫描包、数据源配置、事务管理等。
- 配置MyBatis:
- 在src/main/resources/spring目录下创建一个名为spring-mybatis.xml的MyBatis配置文件。
- 在配置文件中进行MyBatis的相关配置,如数据源配置、Mapper扫描等。
- 在src/main/resources/mapper目录下创建Mapper接口对应的XML文件,用于编写SQL语句。
- 配置web.xml:
- 在src/main/webapp/WEB-INF目录下创建一个名为web.xml的配置文件。
- 在配置文件中进行Servlet、Filter和Listener的配置,以及SpringMVC的DispatcherServlet配置。
- 编写业务代码:
- 在src/main/java目录下创建相应的包和类,包括dao、entity、service等。
- 在dao包中创建接口,定义数据访问方法。
- 在entity包中创建实体类,与数据库表对应。
- 在service包中创建接口和实现类,定义业务逻辑。
- 在controller包中创建控制器类,处理用户请求。
- 部署和运行:
- 将项目部署到Web服务器中(如Tomcat)。
- 启动Web服务器,访问项目的URL,测试功能是否正常。
这些步骤只是一个简单的指南,实际配置过程可能会因项目需求和框架版本而有所不同。
SpringBoot
Spring Boot是一个基于Spring的套件,它帮助我们以尽可能少的代码和配置来开发基于Spring的Java应用程序。它预组装了Spring的一系列组件,使开发变得更加简单和高效。
以下是使用Spring Boot的基本步骤:
- 创建一个Spring Boot项目:可以使用Maven或Gradle构建工具创建一个新的Spring Boot项目。可以使用IDE(如IntelliJ IDEA或Eclipse)的Spring Initializr插件来快速生成项目结构。
- 添加Spring Boot依赖:在项目的pom.xml(如果使用Maven)或build.gradle(如果使用Gradle)文件中,添加Spring Boot的依赖项。这些依赖项包括Spring Boot的核心库和其他需要的功能库。
- 编写应用程序代码:在src/main/java目录下编写应用程序的Java代码。可以使用Spring Boot的注解和自动配置来简化开发过程。例如,使用@SpringBootApplication注解标记主类,使用@RestController注解创建一个RESTful API。
- 配置应用程序:可以使用application.properties或application.yml文件来配置应用程序的属性,如数据库连接、端口号等。Spring Boot会自动加载这些配置。
- 运行应用程序:可以使用IDE的运行按钮或命令行工具来运行Spring Boot应用程序。Spring Boot会自动启动嵌入式的Web服务器,并将应用程序部署到该服务器上。
- 测试应用程序:编写单元测试和集成测试来验证应用程序的功能和性能。Spring Boot提供了一些测试工具和注解,使测试变得更加简单和方便。
- 打包和部署应用程序:使用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 | //Person.java |
1 | //ReflectionExample.java |
JAVA反射的常规使用步骤
反射调用一般分为3个步骤:
得到要调用类的class
得到要调用的类中的方法(Method)
方法调用(invoke)
代码示例:
1 | //Student.java |
1 | Class<?> Stu = Class.forName("Student"); |
方法调用中的参数类型
在方法调用中,参数类型必须正确,这里需要注意的是不能使用包装类替换基本类型,比如不能使用Integer.class代替int.class。
如我要调用Student的setAge方法,下面的调用是正确的:
1 | Class<?> Stu = Class.forName("Student"); |
而如果我们用Integer.class替代int.class就会出错,如:
1 | Class<?> Stu = Class.forName("Student"); |
jvm会报出如下异常:
static方法的反射调用
static方法调用时,不必得到对象示例,如下:
1 | Class<?> clazz = Class.forName("Student"); |
private的成员变量赋值
如果直接通过反射给类的private成员变量赋值,是不允许的,这时我们可以通过setAccessible方法解决。代码示例:
1 | Class<?> Stu = Class.forName("Student"); |
运行如上代码,系统会报出如下异常:
解决方法:
1 | Class<?> Stu = Class.forName("Student"); |
其实,在某些场合下(类中有get,set方法),可以先反射调用set方法,再反射调用get方法达到如上效果,代码示例:
1 | Student student = new Student(20, "Alice"); |
反射修改final的问题
- Java 8及更早版本:在Java 8及更早的版本中,可以使用反射机制绕过final修饰的变量的限制来修改它的值。通过获取final变量的Field对象,并将其可访问性设置为true,然后使用Field对象的set()方法来修改final变量的值。但是,需要注意的是,这种做法是不安全的,并且可能导致不可预测的行为。
- Java 9及更高版本:从Java 9开始,对于final修饰的变量,Java引入了一个新的概念叫做”Effectively Final”。”Effectively Final”意味着虽然变量被声明为final,但在实际使用中可以修改其值。在这种情况下,反射机制无法修改”Effectively Final”变量的值。因此,从Java 9开始,使用反射机制修改final变量的值可能会导致编译错误或运行时异常。
修改static final
属性值,关键在于通过反射将字段的final
修饰符去掉
通过修改Field
对象的修饰符可以实现使用反射修改final
字段的值。
在JDK<=11,可以按照如下流程修改:
- 通过反射获取
java.lang.reflect.Field
内部的modifiers
Field- 将修改目标字段的
modifiers
修改为非final
- 设置目标字段为新的值
1 | //Student.java |
简单来说,我们通过反射,拿到某个属性的Field对象,再将Field对象中的修饰符modifiers
修改成非final,最后再调用反射去修改就可以成功了。
1 | import java.lang.reflect.Field; |
在上面的示例中,我们首先使用反射获取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
所有字段
getDeclaredFields0
是java.lang.Class
下的成员,恰好上面的fieldFilterMap
只过滤了java.lang.Class
下的classLoader
成员,因此我们直接反射调用getDeclaredFields0
就能获取到Fields
的所有成员
1 | Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); |
利用反射命令执行
最简单的命令执行
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 | Class clazz = Class.forName("java.lang.Runtime"); |
也可以获取Runtime
的私有构造器去构造Runtime
对象
1 | Class clazz = Class.forName("java.lang.Runtime"); |
java.lang.ProcessBuilder
除了Runtime.exec()
,ProcessBuilder.start()
是另外一种命令执行的方式。
java.lang.ProcessBuilder
有两个构造函数
public ProcessBuilder(List command)
public ProcessBuilder(String… command)
1 | Class clazz = Class.forName("java.lang.ProcessBuilder"); |
1 | Class clazz = Class.forName("java.lang.ProcessBuilder"); |
java.lang.ProcessImpl
start非public类,需要反射调用
1 | Class clazz = Class.forName("java.lang.ProcessImpl"); |
ProcessImpl.start()最终调用的是ProceImpl的构造方法
1 | Class clazz = Class.forName("java.lang.ProcessImpl"); |
通过上述的方法就可以达成命令注入的效果。
java反射加载字节码GETshell
介绍
Java不像其他脚本语言,如js、php、python等有eval函数,可以把字符串当作代码来执行。
因此在Java中最直接的任意代码执行的方式就是加载字节码了。
加载字节码的几种方法:
- URLClassLoader#loadClass:需要出网或文件落地
- TransletClassLoader#defineClass:一般通过反序列化漏洞打进来
- ClassLoader#defineClass:需要通过反射调用
加载字节码
Java中的类加载器负责将字节码文件加载到内存中,并生成对应的Class对象。可以通过自定义ClassLoader来实现动态加载字节码文件。
动态加载字节码获取shell
通过反射和动态加载字节码,可以实现在Java程序中执行恶意代码,从而获取shell权限。具体步骤如下:
- 创建一个自定义的ClassLoader,继承自java.lang.ClassLoader类。
- 在自定义ClassLoader中定义一个方法,接收字节数组类型的参数,并调用父类的defineClass方法动态解析字节码,返回对应的Class对象。
- 使用反射调用ClassLoader的defineClass方法,传入字节数组参数,实现动态加载字节码。
- 实例化加载的类,并调用其中的方法来执行恶意代码,获取shell权限。
测试实例:
1 | public static void main(String[] args) throws Exception { |
modifiers Bypass
ClassLoader
为PUBLIC,我们只需将defineClass
方法的修饰符修改为PUBLIC
即可
1 | public static void bypassModifier() throws Exception { |
override Bypass
注意setAccessible
的调用逻辑
1 |
|
1 | //checkCanSetAccessible去判断目标的修饰符、目标类是否对调用类开放...最后返回flag再传给setAccessible0 |
setAccessible0
直接将flag赋值给this.override
,override
是其父类AccessibleObject
的属性
因此也可以直接修改java.lang.reflect.AccessibleObject
的override
属性
注意:
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 | public static void bypassOverride(Object accessibleObject) throws Exception { |
fieldFilterMap Bypass
置空fieldFilterMap
置空了。
通过unsafe.defineAnonymousClass
创建匿名内部类,由此匿名类来获取类成员偏移量,最后再通过unsafe.putObject
修改原来Reflection
类的静态成员fieldFilterMap
1 | public static void bypassFieldFilterMap() throws Exception { |
defineAnonymousClass Bypass
虽然JDK11把Unsafe#defineClass
移除了,但Unsafe#defineAnonymousClass
还在
1 | Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); |
序列化与反序列化
Java的序列化和反序列化是将Java对象转换为字节序列和将字节序列恢复为Java对象的过程。序列化可以将对象保存到文件或在网络中传输,而反序列化则可以将字节序列转换回原始对象。
序列化和反序列化的概念
序列化是将对象转换为字节序列的过程,而反序列化是将字节序列恢复为对象的过程。
序列化和反序列化的必要性
序列化和反序列化在以下情况下非常有用:
- 分布式对象:序列化可以实现分布式对象,例如在远程调用中运行对象。
- 对象复制:序列化不仅可以保存一个对象的数据,还可以递归保存对象引用的每个对象的数据,实现对象的深复制。
- 持久化存储:序列化可以将内存中的对象写入文件或数据库中,实现数据的持久化存储。
- 跨平台传输:序列化后的对象可以以字节流的形式进行通用的格式传输或保存,传输结束后可以进行反序列化还原。
实现Java序列化和反序列化的方法
要实现Java序列化和反序列化,需要按照以下步骤进行:
实现Serializable接口
要使一个类可以序列化,需要让该类实现Serializable接口。
1 | import java.io.Serializable; |
序列化对象
使用ObjectOutputStream类将对象序列化为字节序列。
1 | import java.io.FileOutputStream; |
反序列化对象
使用ObjectInputStream类将字节序列反序列化为对象。
1 | import java.io.FileInputStream; |
注意事项
- 要进行序列化和反序列化的类必须实现Serializable接口。
- 静态变量不会被序列化。
- transient关键字可以用于标记不需要序列化的成员变量。
反序列化实现漏洞利用
readObject()方法
特地提到这个方法是因为在反序列化漏洞中它起到了关键作用,readObject()方法被重写的的话,反序列化该类时调用便是重写后的readObject()方法。如果该方法书写不当的话就有可能引发恶意代码的执行,如
1 | package evilSerialize; |
1 | package evilSerialize; |
但肯定不会有程序员写出这样的代码,所以往往实际中反序列化漏洞的构造比较复杂,而且需要借助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字节码具有以下特点:
- 中间表示形式:Java字节码是一种中间表示形式,它不直接运行在物理硬件上,而是由JVM解释执行。
- 跨平台性:由于Java字节码与具体硬件平台无关,可以在任何支持Java虚拟机的平台上运行。
- 安全性:Java字节码在执行之前会经过严格的安全检查,确保代码的安全性和可靠性。
- 可移植性:由于Java字节码可以在不同的平台上运行,使得Java程序具有很高的可移植性。
Java字节码的执行过程如下:
- Java源代码通过Java编译器编译成字节码文件(.class文件)。
- JVM将字节码文件加载到内存中,并进行解析。
- JVM逐条解释执行字节码指令,执行相应的操作。
- 在执行过程中,JVM会进行一些优化操作,如即时编译(Just-In-Time Compilation)等。
总结起来,Java字节码是一种中间表示形式,它使得Java程序具有跨平台的特性,可以在不同的操作系统和硬件平台上运行。它是由Java编译器将Java源代码编译成的一系列指令,这些指令被JVM解释执行。
如何用asm修改字节码
导入ASM库
首先,需要导入ASM库到你的项目中。你可以在Maven或Gradle中添加以下依赖项:
1 | <!--Maven--> |
创建ClassVisitor
ASM使用访问者模式来处理字节码。你需要创建一个继承自ClassVisitor
的类,用于访问和修改字节码的不同部分。你可以重写visitMethod
方法来修改方法的字节码。
1 | import org.objectweb.asm.ClassVisitor; |
修改字节码
在你的代码中,使用ClassReader
读取原始的字节码文件,然后使用MyClassVisitor
来修改字节码,最后使用ClassWriter
将修改后的字节码写回到文件中。
1 | import org.objectweb.asm.ClassReader; |
这样,你就可以使用ASM修改Java字节码了。在上面的示例中,我们通过MyClassVisitor
类的visitMethod
方法,在process
方法前插入了一段输出代码。
如何使用javassist生成字节码
使用Javassist生成字节码可以通过以下步骤实现:
导入Javassist库:首先,需要在项目中导入Javassist库。可以通过Maven或手动下载jar包的方式导入。
1 | <dependency><!--maven--> |
创建类池(ClassPool):使用Javassist进行字节码操作时,需要创建一个类池对象来存储和管理CtClass对象。类池是Javassist中的核心概念之一。
1 | ClassPool pool = ClassPool.getDefault(); |
创建CtClass对象:CtClass对象代表一个Java类。可以通过类池对象创建CtClass对象。
1 | CtClass ctClass = pool.makeClass("com.example.MyClass"); |
添加类的属性和方法:可以使用CtClass对象的方法来添加类的属性和方法。
1 | // 添加字段 |
生成字节码文件:可以使用CtClass对象的writeFile方法将生成的字节码文件写入磁盘。
1 | ctClass.writeFile(); |