宣传一波 更好的阅读体验 👉 个人blog
前提
我自己的环境是jdk8,idea2023.3.6(比较基础请自行配置好环境)
-
JAVA安装
-
Maven安装
-
MySQL
-
Tomcat
-
IDEA安装
- ChatGPT 的灵活使用
Java 基础语法一课通
一 基础知识
1 项目搭建
对于下面代码练习,我们需要先创建一个项目。
我个人习惯于直接基础的 SpringBoot 项目,在进行一些代码的调试。
创建方法很简单,在你的第一个 IDEA 项目中也讲到过了。
点击下一步,选择依赖项页面,默认内容即可。
最后点击创建。
我们相关练习代码在src\main\java\com\example\javademo
下右键选中该目录后,新建相关 Java Class 即可,如下图所示:
2 Java 关键字
Java 关键字是对 Java编译器有特殊含义的字符串,是编译器和程序员的一个约定,程序员利用关键字来告诉编译器其声明的变量类型、类、方法特性等信息。
关键字 | 关键字 | 关键字 | 关键字 |
---|---|---|---|
abstract | assert | boolean | break |
byte | case | catch | char |
class | const | continue | default |
do | double | else | enum |
extends | final | finally | float |
for | goto | if | implements |
import | instanceof | int | interface |
long | native | new | package |
private | protected | public | return |
short | static | strictfp | super |
switch | synchronized | this | throw |
throws | transient | try | void |
volatile | while |
3 修饰符
public(公开的):
- 含义: 使用
public
修饰符的类是完全公开的,可以在任何地方访问。 - 使用场景: 当你希望类的实例能够在任何地方都能被创建和访问时,或者当你编写的是一个库(library)的一部分,希望让其他开发者能够使用这个类。
public class MyClass {
// 类的定义
}
没有修饰符(default):
- 含义: 如果没有使用任何修饰符,默认情况下,类的可见性为包级别,只能在同一个包中访问。
- 使用场景: 当你希望类在同一个包中的其他类能够访问,但不希望被包外的类访问时使用。
class MyClass {
// 类的定义
}
protected(保护的):
- 含义: 使用
protected
修饰符的类可以被同一包中的其他类访问,以及该类的子类(无论子类在哪个包中)。 - 使用场景: 通常较少用于修饰类,更多地用于修饰成员变量和方法。当你希望子类能够访问类的成员,但其他类不能访问时使用。
protected class MyClass {
// 类的定义
}
private(私有的):
- 含义: 使用
private
修饰符的类只能被同一类中的其他类访问,通常用于嵌套类或内部实现细节。 - 使用场景: 当你希望将类的细节隐藏在同一类的内部,不让其他类直接访问时使用。
private class MyClass {
// 类的定义
}
举个例子:
public class OuterClass {
private int outerData;
// 构造函数
public OuterClass(int data) {
this.outerData = data;
}
// 内部类
private class InnerClass {
private int innerData;
// 构造函数
public InnerClass(int data) {
this.innerData = data;
}
public void display() {
System.out.println("Outer data: " + outerData);
System.out.println("Inner data: " + innerData);
}
}
// 外部类的方法
public void outerMethod() {
InnerClass innerObj = new InnerClass(20);
innerObj.display();
}
public static void main(String[] args) {
OuterClass Obj = new OuterClass(10);
outerObj.outerMethod();
}
}
4 数据类型
4.1 基础概念
在 Java 中,数据类型是用来定义变量的类型,以决定变量可以存储的数据种类。 Java 的数据类型主要分为两大类:基本数据类型和引用数据类型。基本数据类型包括整型(byte short int long) 浮点型(float double) 字符型(char)和布尔型(boolean)。
4.2 代码案例
// 整型示例
int integerVariable = 42;
// 浮点型示例
double doubleVariable = 3.14;
// 字符型示例
char charVariable = 'A';
// 布尔型示例
boolean booleanVariable = true;
5 变量
5.1 基础概念
变量是用于存储数据的内存空间的标识符。在 Java 中,变量必须先声明后使用,并指定其数据类型。变量的命名要符合 Java 的命名规范,遵循驼峰命名法。
5.2 代码案例
// 声明和初始化整型变量
int x = 10;
// 声明和初始化字符串变量
String name = "John";
6 基本运算符
6.1 基础概念
基本运算符包括算术运算符(+、-、*、/、%)、关系运算符(==、!=、<、>、<=、>=)、逻辑运算符(&&、||、!)等。它们用于执行常见的数学和逻辑运算。
6.2 代码案例
// 算术运算
int a = 5, b = 3;
int sum = a + b;
int difference = a - b;
int product = a * b;
double quotient = (double) a / b; // 注意类型转换
int remainder = a % b;
// 关系运算
boolean isEqual = (a == b);
boolean isGreaterThan = (a > b);
// 逻辑运算
boolean logicalAnd = (true && false);
boolean logicalOr = (true || false);
boolean logicalNot = !true;
7 顺序结构
7.1 基础概念
顺序结构是程序中最简单的结构,代码按照书写的顺序一行一行地执行。每一行代码都在前一行代码执行完毕后执行。
7.2 代码案例
// 顺序结构示例
int a = 5;
int b = 3;
int sum = a + b;
System.out.println("Sum: " + sum);
8 选择结构
8.1 基础概念
选择结构允许根据条件选择执行不同的代码块。在 Java 中,常见的选择结构有if语句、if-else语句和switch语句。
8.2 代码案例
// if语句示例
int number = 6;
if (number / 2 == 3) {
System.out.println("1");
} else {
System.out.println("2");
}
public class NestedIfExample {
public static void main(String[] args) {
int x = 5;
int y = 10;
if (x > 0) {
System.out.println("X 大于 0.");
if (y > 0) {
System.out.println("Y 也大于 0.");
} else {
System.out.println("y 小于 0.");
}
} else {
System.out.println("X 小于0.");
}
}
}
9 循环结构
9.1 基础概念
循环结构允许多次执行相同的代码块,直到满足退出条件。在 Java 中,常见的循环结构有 for 循环、while 循环和 do-while 循环。
9.2 代码案例
for (初始化; 循环条件; 循环迭代) {
// 循环体
}
// for循环示例
for (int i = 0; i < 5; i++) {
System.out.println("当前值为" + i);
}
10 数组
10.1 基础概念
数组是一种存储相同类型数据的集合。在 Java 中,数组是固定大小的,可以通过索引访问其中的元素。
10.2 代码案例
// 数组示例
int[] numbers = {1, 2, 3, 4, 5};
System.out.println("First element: " + numbers[0]);
int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
System.out.println("数组元素为:" + numbers[i]);
}
11 函数方法
在 Java 中,函数方法是一组执行特定任务的代码块。方法提供了程序的模块化和重用性。方法由方法名、参数列表、返回类型和方法体组成。 Java 中的方法可以分为普通方法和静态方法,普通方法需要实例化对象调用,而静态方法属于类,可以通过类名直接调用。
public class MyClass {
// 普通方法
public void printMessage(String message) {
System.out.println(message);
}
public static void main(String[] args) {
MyClass myObject = new MyClass();
myObject.printMessage("Hello, World!");
}
}
/** 返回两个整型变量数据的较大值 */
public static int max(int num1, int num2) {
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
如果我们想要使用,只需max(1,2)
即可。
public class VoidMethodExample {
public static void main(String[] args) {
// 调用无返回值的方法
displayMessage();
}
// 一个无返回值的方法,用于显示一条简单的消息
public static void displayMessage() {
System.out.println("这是一个无返回值的方法代码案例。");
}
}
12 Java 异常处理
异常是指在程序运行时发生的意外情况,可能导致程序中断或产生不可预知的结果。Java 中的异常处理机制通过 try、catch、finally 块来捕获和处理异常。异常分为检查异常(Checked Exception)和非检查异常(Unchecked Exception)。
public class ExceptionHandling {
public static void main(String[] args) {
try {
int result = 10 / 0; // 会抛出ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero.");
} finally {
System.out.println("Finally block always executes.");
}
}
}
try
块: 在try
块内,包含可能引发异常的代码。在这里,int result = 10 / 0;
尝试进行除法运算,但由于除数是0,这会导致算术异常。
catch
块: 如果在try
块中发生了异常,控制流会跳转到对应的catch
块。在这里,catch (ArithmeticException e)
捕获了ArithmeticException
异常,表示发生了除以零的情况。catch
块内的代码会被执行,输出提示信息:”Cannot divide by zero.”。
finally
块: 无论是否发生异常,finally
块中的代码总是会被执行。在这里,输出信息:”Finally block always executes.”。finally
块通常用于确保资源的释放或清理工作,无论是否发生异常。
二 面向对象
面向对象(Object-Oriented,简称OOP)是一种编程思想和程序设计范式,它将程序中的数据和操作数据的方法组织成对象。在面向对象编程中,对象是程序的基本单元,每个对象可以包含数据(称为属性)和方法(称为行为)。
1 类
在编程中,类(Class)是一种抽象数据类型,用于描述具有相似属性和行为的对象集合。它是面向对象编程(Object-Oriented Programming,OOP)的核心概念之一,通过类可以创建对象,而对象则是类的实例。
我们初中生物讲过生物类,鸟类,鱼类等。这两个不同的类别,它们有着不同的特征和行为。同样,在编程中,类是一种将数据和方法组合在一起的结构,用于描述某种抽象概念或实体。
简单来说,类是一种用于描述对象的蓝图或模板。它定义了对象的属性(成员变量)和行为(方法)。
// 定义一个Bird类
public class Bird {
// 成员变量,描述鸟类的属性
private String feathers;
private String beakShape;
// 构造方法,用于初始化对象的属性
public Bird(String feathers, String beakShape) {
this.feathers = feathers;
this.beakShape = beakShape;
}
// 成员方法,描述鸟类的行为
public void fly() {
System.out.println("The bird is flying.");
}
// Getter方法,用于获取羽毛属性
public String getFeathers() {
return feathers;
}
// Setter方法,用于设置羽毛属性
public void setFeathers(String feathers) {
this.feathers = feathers;
}
}
// 在另一个类中使用Bird类
public class BirdExample {
public static void main(String[] args) {
// 创建一个鹰对象
Bird eagle = new Bird("brown", "hooked");
// 调用鹰对象的飞行方法
eagle.fly();
// 获取并输出鹰的羽毛属性
String feathers = eagle.getFeathers();
System.out.println("Feathers: " + feathers);
// 设置新的羽毛属性并输出
eagle.setFeathers("golden");
System.out.println("New Feathers: " + eagle.getFeathers());
}
}
2 对象
前面说到通过类可以创建对象,而对象则是类的实例。
实例化是为了创建对象,也就是我们使用类这个模板,以及可以进行自己所需的改动。
举个简单例子,我们从网上使用寻找 PPT 模板,最后下载使用,改成自己所需的内容。
这个过程和创建对象有些类似。
在 Java 中,实例化一个对象的过程通常包括使用 new
关键字来调用类的构造方法,并为对象分配内存空间。以下是一个简单的例子,演示如何在 Java 中实例化对象:
public class Dog {
// 类的属性
String name;
int age;
// 类的方法
public void bark() {
System.out.println("Woof! Woof!");
}
}
public class Main {
public static void main(String[] args) {
// 实例化一个 Dog 类的对象
Dog myDog = new Dog();
// 设置对象的属性值
myDog.name = "Buddy";
myDog.age = 3;
// 调用对象的方法
myDog.bark();
// 输出对象的属性值
System.out.println("Name: " + myDog.name);
System.out.println("Age: " + myDog.age);
}
}
// 定义一个简单的类
class PPTTemplate {
// 成员变量
String templateName;
// 构造方法
public PPTTemplate(String name) {
this.templateName = name;
}
// 成员方法
public void customize(String content) {
System.out.println("Customizing PPT template '" + templateName + "' with content: " + content);
}
}
// 在另一个类中使用 PPTTemplate 类
public class PPTCreator {
public static void main(String[] args) {
// 实例化 PPTTemplate 类,创建一个对象
PPTTemplate myTemplate = new PPTTemplate("SimpleTemplate");
// 调用对象的成员方法
myTemplate.customize("This is my custom content.");
// 创建另一个对象
PPTTemplate anotherTemplate = new PPTTemplate("FancyTemplate");
anotherTemplate.customize("Adding some fancy graphics and animations.");
// ...
}
}
在这个例子中,我们定义了一个简单的 PPTTemplate
类,其中包含了一个构造方法用于初始化对象的成员变量 templateName
,以及一个成员方法 customize
用于自定义模板内容。然后,在 PPTCreator
类中,我们实例化了两个不同的 PPTTemplate
对象,每个对象代表一个不同的PPT模板,并通过调用对象的方法来进行自定义。
关键步骤:
① PPTTemplate
类的定义包括构造方法和成员方法。
② 在 PPTCreator
类的 main
方法中,通过 new PPTTemplate("SimpleTemplate")
实例化一个 PPTTemplate
对象,并指定模板名称为 “SimpleTemplate”。
③ 通过调用对象的成员方法 myTemplate.customize("This is my custom content.");
来自定义模板内容。
④ 同样地,创建另一个对象 anotherTemplate
并进行自定义。
实例化对象的过程涉及到为对象分配内存 调用构造方法进行初始化等步骤,这样就可以创建多个相互独立的对象,每个对象都有自己的状态(成员变量值)和行为(成员方法)。
3 继承
基础概念:
继承是面向对象编程中的概念,允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以继承父类的行为,并且可以通过添加新的属性和方法来扩展其功能。
解决的问题:
继承解决了代码重用和扩展的问题。通过继承,子类可以复用父类的代码,而不必重复实现相同的功能。同时,子类可以在保留父类功能的基础上,添加新的功能或修改部分功能,实现功能的扩展和定制。
代码案例:
// 父类
class Animal {
void eat() {
System.out.println("动物正在吃");
}
}
// 子类继承父类
class Dog extends Animal {
void bark() {
System.out.println("狗在叫");
}
}
public class Main {
public static void main(String[] args) {
// 创建子类对象
Dog myDog = new Dog();
// 调用继承自父类的方法
myDog.eat();
// 调用子类自己的方法
myDog.bark();
}
}
4 封装
基础概念:
封装是将对象的内部状态和实现细节隐藏起来,只对外提供访问接口。在 Java 中,通过访问修饰符(如private
public
)来实现封装。
解决的问题:
封装解决了对象的安全性和灵活性问题。通过将内部细节隐藏,防止外部直接访问对象的属性,从而保护对象的状态。同时,通过提供公共的方法,使得对象能够以受控制的方式被外部访问和修改。
代码案例:
public class Person {
// 私有属性
private String name;
private int age;
// Getter方法,用于获取name属性值
public String getName() {
return this.name;
}
// Setter方法,用于设置name属性值,并进行非空验证
public void setName(String newName) {
if (newName != null && !newName.isEmpty()) {
this.name = newName;
} else {
System.out.println("Name cannot be null or empty.");
}
}
// Getter方法,用于获取age属性值
public int getAge() {
return this.age;
}
// Setter方法,用于设置age属性值,并进行年龄范围验证
public void setAge(int newAge) {
if (newAge >= 0 && newAge <= 150) {
this.age = newAge;
} else {
System.out.println("Age must be between 0 and 150.");
}
}
public static void main(String[] args) {
// 创建一个Person对象
Person person = new Person();
// 使用setter设置属性值
person.setName("John");
person.setAge(25);
// 使用getter获取属性值并输出
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
// 尝试设置无效值
person.setName(""); // 输出错误信息
person.setAge(200); // 输出错误信息
}
}
5 构造函数
基础概念:
构造函数是一个特殊的方法,与类同名,没有返回类型。它在对象被创建时调用,用于执行初始化操作。构造函数的主要目的是确保对象在被使用之前处于一个合理的状态。
构造函数可以显示的定义,也就是我们根据所需设置构造函数。
如果没有显示的提供构造函数,类仍然是可以被实例化的。
因为,如果你没有为类定义任何构造函数, Java 编译器会为你生成一个默认的无参数构造函数。这个构造函数会执行以下操作:
- 将类的实例变量初始化为默认值(数值型为0,布尔型为false,对象型为null等)。
- 如果类继承自其他类,会调用父类的无参数构造函数。
在构造函数中,又分为有参构造函数和无参构造函数。
都很好理解,就是是否需要传入参数。
如果你显式地提供了自定义的构造函数(无论是有参数的还是无参数的),并且没有提供任何无参数构造函数,那么默认的无参数构造函数就不再自动生成。
也就是说除非目标类显示的自定义了无参构造函数,否则如果目标类只定义了有参构造函数的话,那就不会默认生成无参构造函数了。
解决的问题:
构造函数解决了对象初始化的问题。通过构造函数,可以为对象的属性赋予初始值,执行必要的设置,使对象能够在创建时就具备正确的状态。
代码案例(无参构造函数):
public class Dog {
private String name;
private int age;
// 构造函数,初始值
public Dog() {
this.name = "juzi";
this.age = 3;
}
// 获取狗的名字
public String getName() {
return name;
}
// 获取狗的年龄
public int getAge() {
return age;
}
public static void main(String[] args) {
// 实例化一个Dog对象,使用初始值
Dog myDog = new Dog();
// 获取狗的信息并打印
System.out.println("Dog's name: " + myDog.getName() + ", Age: " + myDog.getAge());
}
}
代码案例(有参构造函数):
public class Dog {
private String name;
private int age;
// 无参构造函数,使用默认值
public Dog() {
this.name = "juzi";
this.age = 3;
}
// 有参构造函数,接受名字和年龄参数
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
// 获取狗的名字
public String getName() {
return name;
}
// 获取狗的年龄
public int getAge() {
return age;
}
public static void main(String[] args) {
// 使用无参构造函数实例化一个Dog对象,使用默认值
Dog defaultDog = new Dog();
System.out.println("Default Dog's name: " + defaultDog.getName() + ", Age: " + defaultDog.getAge());
// 使用有参构造函数实例化一个Dog对象,提供自定义值
Dog customDog = new Dog("Buddy", 5);
System.out.println("Custom Dog's name: " + customDog.getName() + ", Age: " + customDog.getAge());
}
}
5.1 继承中的构造函数
默认情况:
- 如果父类(基类)有一个无参数的构造函数,子类(派生类)会自动继承这个无参数构造函数。
- 如果子类没有显式定义构造函数,编译器会默认生成一个无参数构造函数,并在其中调用父类的无参数构造函数。
class Animal {
// 父类有一个无参数构造函数
}
class Dog extends Animal {
// 子类没有显式定义构造函数,编译器默认生成一个无参数构造函数
}
父类有有参数构造函数:
- 如果父类只提供了有参数的构造函数,子类需要显式定义构造函数,并通过
super()
调用适当的父类构造函数。此时super()
方法是必须的。
class Animal {
public Animal(String name) {
// 父类有一个有参数构造函数
}
}
class Dog extends Animal {
public Dog(String name, String breed) {
super(name); // 调用父类的有参数构造函数
// 初始化子类特有的属性
}
}
子类提供无参数构造函数:
- 如果父类没有提供无参数构造函数,但子类需要使用无参数构造函数,子类需要显式提供无参数构造函数,并通过
super()
调用适当的父类构造函数。此时super()
方法是必须的。
class Animal {
public Animal(String name) {
// 父类有一个有参数构造函数
}
}
class Dog extends Animal {
public Dog() {
super("DefaultName"); // 调用父类的有参数构造函数
// 子类提供无参数构造函数,并在其中调用父类构造函数
}
}
总的来说,继承和构造函数的关系取决于父类的构造函数情况。在设计时,需要考虑如何在子类中正确地初始化父类的状态。
6 函数方法重载
函数方法的重载是指在同一个类中可以定义多个方法,它们具有相同的方法名但具有不同的参数列表。编译器根据方法的参数数量、类型或顺序来选择合适的方法。
下面计算器示例代码也是一个比较经典的案例了,可以使用重载来实现任意加减乘除的运算。
public class Calculator {
// 重载方法
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public String add(String a, String b) {
return a + b;
}
public static void main(String[] args) {
Calculator myCalculator = new Calculator();
System.out.println(myCalculator.add(2, 3));
System.out.println(myCalculator.add(2.5, 3.5));
System.out.println(myCalculator.add("Hello", " World"));
}
}
7 构造函数中的重载
基础概念:
构造函数的重载是指在同一个类中定义多个构造函数,它们具有相同的名称但参数列表不同。通过构造函数的重载,可以提供多种初始化对象的方式。
解决的问题:
构造函数的重载解决了不同场景下对象初始化需求不同的问题。通过提供多个构造函数,使得用户能够选择适合自己需求的初始化方式。
代码案例:
public class Person {
private String name;
private int age;
// 构造函数重载
public Person() {
this.name = "Unknown";
this.age = 0;
}
public Person(String name) {
this.name = name;
this.age = 0;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 其他类成员和方法...
}
三 一些其他知识
1 HashMap
基础概念
HashMap
是 Java 中常用的集合类之一,用于存储Key-Value键值对的集合。它实现了 Map
接口,用于存储键值对。HashMap
的核心思想是通过散列算法将键映射到存储桶,提高查找效率。基本操作的时间复杂度为 O(1)。然而,需要注意 HashMap
不是线程安全的,如果在多线程环境中使用,可以考虑使用 ConcurrentHashMap
。
在 Java 中,键值对是一种常见的数据结构,通常用于表示关联关系。键值对包含两部分:键(key)和值(value)。键是唯一的,通过键可以访问对应的值。
在 HashMap 中,最常用的就是 get 和 put 了,通过名字也能知道,一个是获取键值,一个是存入键值。
代码案例
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// 创建HashMap实例
Map<String, Integer> hashMap = new HashMap<>();
// 添加键值对
hashMap.put("Java", 1);
hashMap.put("Python", 2);
hashMap.put("JavaScript", 3);
// 获取值
int javaValue = hashMap.get("Java");
System.out.println("Value for Java: " + javaValue);
// 遍历键值对
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
2 StringBuilder
基础概念
StringBuilder
是 java.lang
包中的一个类,用于在单线程环境下对字符串进行可变操作,避免了使用 String
类时的不断创建新字符串的开销。它提供了一系列方法用于修改字符串内容,是一个可变的字符序列。
代码案例
public class StringBuilderExample {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder("Hello");
// 追加字符串
stringBuilder.append(" World");
// 插入字符串
stringBuilder.insert(5, ", Java");
// 替换字符串
stringBuilder.replace(6, 11, "GPT");
// 删除字符串
stringBuilder.delete(12, 17);
// 输出结果
System.out.println(stringBuilder.toString());
}
}
3 StringBuffer
基础概念
StringBuffer
与 StringBuilder
类似,也是可变的字符序列。主要区别在于 StringBuffer
是线程安全的,因此在多线程环境中更适用。然而,由于同步的开销,StringBuilder
在单线程情况下可能更高效。
代码案例
public class StringBufferExample {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Hello");
// 追加字符串
stringBuffer.append(" World");
// 插入字符串
stringBuffer.insert(5, ", Java");
// 替换字符串
stringBuffer.replace(6, 11, "GPT");
// 删除字符串
stringBuffer.delete(12, 17);
// 输出结果
System.out.println(stringBuffer.toString());
}
}
4 IO 流
基础概念
输入输出流(IO流)是 Java 中用于处理输入和输出的机制。它分为字节流和字符流,以及输入流和输出流。常见的 IO 类有 FileInputStream
、FileOutputStream
、BufferedReader
、BufferedWriter
等。
可以进行文件读取,网络操作,缓冲操作读取字节流,对象序列化等操作,也是比较重要的。在第一阶段后面几个章节我们也会常接触的。
代码案例
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class IOExample {
public static void main(String[] args) {
try {
// 读取文件
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
reader.close();
// 写入文件
FileWriter writer = new FileWriter("output.txt");
writer.write("Hello, IO!");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5 Object
基础概念
Object
类是 Java 中所有类的根类,每个类都是 Object
类的子类。它定义了一些基本的方法,如 toString
、equals
和 hashCode
。在 Java 中,所有对象都可以被赋值给 Object
类型的变量。
https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html
toString()
方法:
- 用于返回对象的字符串表示。默认情况下,
toString()
返回类名后跟对象的哈希码。
equals(Object obj)
方法:
- 用于比较两个对象是否相等。默认实现是比较对象的引用地址,但通常在子类中会被重写以根据业务逻辑判断对象是否相等。
hashCode()
方法:
- 返回对象的哈希码。哈希码用于在哈希表等数据结构中快速查找对象。
getClass()
方法:
- 返回对象的运行时类,即对象所属的类。
notify()
、notifyAll()
和wait()
方法:
- 用于线程间的协调和通信。这些方法通常与多线程编程有关,用于实现线程的等待和唤醒机制。
finalize()
方法:
- 在垃圾回收器清理对象之前调用。子类可以重写此方法以执行资源清理等操作。
clone()
方法:
- 创建并返回一个对象的副本。默认情况下,
clone()
方法执行的是浅拷贝,但可以在子类中重写以实现深拷贝。
getClass()
方法:
- 返回对象的运行时类,即对象所属的类。
wait()
, notify()
, notifyAll()
:
- 用于线程间的协调和通信,通常与多线程编程相关。
Java Web,是用 Java 技术来解决相关 WEB 互联网领域的技术栈。
WEB 包括:WEB 服务端和 WEB 客户端两部分。Java 在客户端的应用有 Java Applet,不过使用得很少。
Java在服务器端的应用非常的丰富,比如 Servlet,JSP、SpringBoot 等等。
JavaWeb 架构演变过程大致分为以下几个阶段:
下面,我们通过案例学习这些技术点。
Servlet 以及过滤器、监听器和拦截器
Servlet
1.1、什么是Servlet
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
画个简易图,便于理解。
上面是个简易图,用语言描述下流程。
比如,我们打开一个网站有个注册功能,在填写完信息后,点击提交,所填写的信息传输到后端,根据所指向的路径,来匹配对应的servlet,专门用于处理注册流程。
当然,我们还可以有登录Servlet,个人信息Servlet等等。
从代码上来说,Servlets 是 Java 类,服务于 HTTP 请求并实现了 javax.servlet.Servlet 接口。
1.2、Servlet 生命周期
Servlet 生命周期就是从创建到毁灭的过程。
大致是四个阶段。
- init():初始化阶段,只被调用一次,也就是在第一次创建 Servlet 时被调用。
- service():服务阶段,主要处理来自客户端的请求,并可以根据 HTTP 请求类型来调用对应的方法,比如
doGet(),doPost(),doPut()
等等。 - doGet(),doPost():处理阶段,将主要代码逻辑写在此处。根据不同 HTTP 请求对应不同方法。
- destroy():销毁阶段,该方法只会被调用一次,即在 Servlet 生命期结束时被调用。一般都是在关闭系统时执行。
画个简易图,大致流程如下图所示:
1.3、你的第一个Servlet
光说不练假把式,我们动手写个 Servlet 案例,进一步理解它。
我们使用 Maven 创建一个 Servlet 案例,Maven在Java基础环境搭建
中有讲到,可以回顾一下。
1.3.1、创建项目
①、双击启动 IDEA,点击Create New Project
(如果默认进入某个项目,需要退出,点击左上角File -> Close Project
,左侧选择Maven
项目,选中Create from archetype
后选择maven-archetype-webapp
,点击Next,如下图所示:
选择archetype
可以简单理解为选择模板,选择不同的模板会有各自的规范。
②、点击创建即可,稍等片刻,自行会自行加载相关依赖,如果有报错请尝试退出重进。
④、在pom.xml
中引入 servlet 依赖,写在<dependencies>
标签内,然后可以点击右上角加载文件改动
的按钮(也就是重新加载安装依赖),如下图所示:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
1.3.2、配置 Tomcat 服务器
①、访问Tomcat官网https://tomcat.apache.org/download-80.cgi
,下载任意版本Tomcat,我选择的版本是8.5.81
,如下图所示:
选择Binary Distributions
下的Core
分类,这是 Tomcat 正式的二进制发布版本,一般学习和开发都用此版本。根据自己计算机系统选择下载项。
②、下载完成后,解压到一个固定常用的目录下。
③、进入 IDEA,配置 Tomcat。在右上侧选择Add Configuration...
,在新的界面点击左上角的+
按钮,滑到下面找到Tomcat Server
点击选择Local
,如下图所示:
[HTML_REMOVED]
④、在 Server 标签栏下点击 Configure...
,进入新的界面,在Tomcat Home
处添加 Tomcat,最后点击 OK。如下图所示:
[HTML_REMOVED]
⑤、配置部署方式,选择Deployment
标签栏,在右侧点击+
按钮,选择war exploded
方式,如下图所示:
[HTML_REMOVED]
war方式
:是发布模式,先打包成 war 包,再发布。war exploded方式
:常在开发的时候使用这种方式,它可以支持热部署,但需要设置。
跟本次案例调试无关,选择哪个都可以,对于我们练习影响不太大。
⑥、最后注意上图应用程序上下文出设置设置 URL 根路径后,一般就/
即可,最后点击应用,再点击确定即可。如下图所示:
如果在配置发现没有工件,那就需要再配置下。
第一步,点击编辑工件,点左上角文件 - 项目结构 - 工件
也可以,如下图所示:
第二步,点击加好,选择 WEB 应用程序,这两个看哪个有基于模块
点击这个即可。
然后点击应用确定即可,然后在转到部署工件处可成功部署。
最后,点击小三角运行,如果服务器没有报错,弹出浏览器输出了 hello world 及说明配成功,如下图所示:
至此,IDEA 中配置 Tomcat 就完成了。
下面我们开始编写代码案例。
1.3.3、编写Servlet并运行
接1.3.1、创建项目
,在创建好后项目进行下面步骤。
①、鼠标右键点击main
目录,选择New - Directory
,创建名为java
的主目录,后面我们的代码都写在此处。
②、鼠标右键点击java
目录,选择New - Java Class
,创建名为FirstServlet
的class文件,在这里面编写我们的代码,最终如下图所示:
③、想要真正运行起来 Servlet 程序,我们需要继承HTTPServlet
,重写部分方法,如下图所示:
④、编写代码,响应 Get 和 Post 请求内容,最终如下图所示:
⑤、在 servlet 中,需要根据 URL 路径匹配映射到对应的 servlet。即在web.xml
中注册 servlet,如下图所示:
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>FirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FirstServlet</servlet-name>
<url-pattern>/FirstServlet</url-pattern>
</servlet-mapping>
映射匹配流程:/FirstServlet
路径绑定的Servlet-name为FirstServlet
,而FirstServlet绑定的class是FirstServlet
,最终访问/FirstServlet
,调用的类也就是FirstServlet.class
。
多看两遍就明白了了。
servlet 注册有两种方式:一是通过上述web.xml
进行注册,二是在 servlet3 以后可以通过@WebServlet()
注解方式进行注册。
⑥、启动项目,可以点击右上侧运行按钮,稍等片刻,看到控制台输出以下信息即表明启动成功,如下图所示:
[HTML_REMOVED]
⑦、浏览器访问127.0.0.1:8080/FirstServlet
,即可看到代码中输出的信息,如下图所示:
至此,你的第一个 Servlet 程序成功运行启动起来了。
现阶段,你只需要关注知道 Servlet 是什么就好,我相信这个流程走下来你能明白不少。
如果想进一步了解,我从网上找了几个 JSP + Servlet 的系统,可以进一步研究下。
https://github.com/Hui4401/StudentManager
https://github.com/czwbig/Tmall_JavaEE
二、过滤器、监听器和拦截器
过滤器(Filter):在 servlet 中,过滤器也就是Filter
,它主要用于过滤字符编码,做一些统一的业务等等。是使用javax.servlet.Filter
接口进行实现的。在代码安全中,他常被用于防止 XSS,防 SQL注入,防任意文件上传等。再配置了Filter之后,它可以统一过滤危险字符,省时省力。
监听器(Listener):在servlet中,监听器也就是Listener
,它主要用于做一些初始化的内容。是使用javax.servlet.ServletContextListener
接口进行实现的。如果同时有监听器和过滤器,监听器是在过滤器之前启动。
拦截器(Interceptor):依赖 WEB 框架,在 SrpingMvc 中就依赖 SpringMVC 框架。是属于面向切面变成的一种运用。
过滤器和拦截器的区别,分为以下五种:
- 拦截器是基于 Java 的反射机制的,而过滤器是基于函数回调
- 过滤器依赖与 servlet 容器,而拦截器不依赖与 servlet 容器
- 拦截器只能对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用
- 拦截器可以访问 action 上下文、值栈里的对象,而过滤器不能
- 在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
我们现在主要关注一下过滤器。
2.1、过滤器代码
过滤器是使用javax.servlet.Filter
接口进行实现的。需要使用doFilter()
方法实现拦截。
以1.3.3、编写Servlet并运行
代码为基础,继续编写Filter代码。
①、在main/java
右键点击后选择New - package
,键入com.test.filter
新建个filter,编写过滤层代码,如下图所示:
我们重写了doFilter()
方法,代码逻辑如下:
首先通过String requestURI = request.getRequestURI();
获取URL路径。然后对路径进行判断,如果路径中包含/FirstServlet
,则放行。否则就跳转到根路径下。
②、然后在 web.xml 中配置注册过滤器,如下图所示:
<!--配置过滤器-->
<filter>
<filter-name>FilterTest</filter-name>
<filter-class>com.test.filter.FilterTest</filter-class>
</filter>
<!--映射过滤器-->
<filter-mapping>
<filter-name>FilterTest</filter-name>
<!--“/*”表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器标签需要要在servlet标签上面,程序会按照注册顺序进行执行。如果涉及多个过滤器,也是按照注册顺序进行执行的。
注册过滤器有两种方式:一是上面通过web.xml
进行注册,另一种是通过@WebFilter()
注解的方式进行注册。
③、运行项目,访问不通路径,观察不同效果。如下图所示:
JSP 基础
一、什么是 JSP
JSP 全称 Java Server Page,基于 Java 语言,是一种动态网页技术。
它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以<% code %>
显示。
JSP 本质是简化版的 Servlet,JSP 在编译后就变成了 Servlet。JVM 只能识别 Java 的类,是无法识别 JSP 代码的。所以 WEB 服务器会将 JSP 编译成 JVM 能识别的 Java类。
JSP 跟 Servlet 区别在于,JSP 常用于动态页面显示,Servlet 常用于逻辑控制。在代码中常使用 JSP 做前端动态页面,在接收到用户输入后交给对应的 Servlet 进行处理。当然 JSP 也可以当做后端代码进行逻辑控制。
二、JSP 基础知识
以下相关概念了解即可。后续可再根据自己兴趣拓展学习。
1、JSP 文件后缀名为 *.jsp
2、JSP 代码需要写在指定的标签之中,比如:
常用:
<%
out.println("hellpo JSP!");
%>
<jsp:scriptlet>
代码片段
</jsp:scriptlet>
3、JSP 生命周期:编译阶段 -> 初始化阶段 -> 执行阶段 -> 销毁阶段
,此处多了一个编译阶段,是将 JSP 编译成 Servlet 的阶段。
而这个阶段也是有三个步骤的:解析 JSP 文件 -> 将 JSP 文件转为 Servlet -> 编译 Servlet
。
4、JSP 指令:是用来设置 JSP 整个页面属性的。格式为:<%@ directive attribute="value" %>
。JSP 中的三种指令标签:
指令 | 描述 |
---|---|
<%@ page … %> | 定义网页依赖属性,比如脚本语言、error页面、缓存需求等等 |
<%@ include … %> | 包含其他文件 |
<%@ taglib … %> | 引入标签库的定义 |
5、JSP的九大内置对象(隐式对象),这九个对象,可以不用声明直接使用。
名称 | 类型 | 描述 |
---|---|---|
out | javax.Servlet.jsp.JspWriter | 页面输出 |
request | javax.Servlet.http.HttpServletRequest | 获得用户请求 |
response | javax.Servlet.http.HttpServletResponse | 服务器向客户端的回应信息 |
config | javax.Servlet.ServletConfig | 服务器配置,可以取得初始化参数 |
session | javax.Servlet.http.HttpSession | 保存用户的信息 |
application | javax.Servlet.ServletContext | 所有用户的共享信息 |
page | java.lang.Object | 指当前页面转换后的Servlet类的实例 |
pageContext | javax.Servlet.jsp.PageContext | JSP的页面容器 |
exception | java.lang.Throwable | 表示JSP页面所发生的异常,在错误页中才起作用 |
三、JSP程序运行
光说不练假把式。
通过创建 JSP 项目加上代码 demo 来进一步理解下吧。
2.1、创建项目
下面步骤讲解是基于 Maven 创建的 JSP 项目。
①、打开 IDEA 后,点击Create New Project
,选择Mavan,勾选Create from archetype
,选择的模板是maven-archetype-webapp
,如下图所示:
②、然后点击Next,给项目起个名字,其他默认即可。此处步骤和1.2.1 JavaWeb基础(一)Servlet以及过滤器、监听器和拦截器 中 1.3.1、创建项目
相同,不再演示。
③、下面开始配置Tomcat。此处步骤和1.2.1 JavaWeb基础(一)Servlet以及过滤器、监听器和拦截器 中 1.3.2、环境配置Tomcat
相同,不再演示。
④、配置完成后,点击运行,最终结果如下图所示:
2.2、代码 Demo
我们通过 JSP 模拟一下登录的过程。
首先引入一下依赖Jar包。点击左上角File - Project Structure - Libraries
,点击+
号,进入 Tomcat 的 lib 文件夹下,将依赖全部引入(对于我们这样最省事),最后点击 OK。如下图所示:
下面编写代码。
①、在webapp下面创建两个 JSP 文件,分别为login.jsp
和do_login.jsp
,如下图所示:
②、在login.jsp
键入以下代码,如下图所示:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>User to Register Page!</title>
</head>
<body>
<hr><br>登录页面<br>
<form action="do_login.jsp" method="get">
<br>
<h1>Please input your message:</h1><br>
Name:<input type="text" name="username"><br>
Pswd:<input type="password" name="password"><br>
<br><br><br>
<input type="submit"> <input type="reset"><br>
</body>
</html>
③、在do_login.jsp
键入以下代码,如下图所示:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ page import="java.sql.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Server to do the register page!</title>
</head>
<body>
<%
String username=request.getParameter("username");
String password=request.getParameter("password");
%>
<%
//模拟登录成功与否
if(username.equals("admin") && password.equals("password")){
response.getWriter().write("Success!");
}else {
response.getWriter().write("Error!");
}
%>
</body>
</html>
④、左上角运行代码,浏览器访问127.0.0.1:8080/login.jsp
,分别输入正确和错误的账号密码,观察操作结果。
以上仅是 Demo 案例,用于演示理解 JSP 代码。
四、JSP 木马
前几年是 JSP 木马的鼎盛时期,由于技术的迭代。现在大型企业使用 SprinBoot 框架来开发的系统了,该框架默认是不引入 JSP 解析的,需要引入特定依赖才可以。
而且现在前端大多使用vue,thymeleaf,freemarker等等。因此JSP木马也算逐渐没落了。
但我们还是得学习了解,毕竟网站数量基数很大,难免会在授权的测试中遇见。
JSP木马也可以称作JSP Webshell,如果对方在上传文件或其他功能没有做防护的话,攻击者可以利用任意文件上传漏洞将恶意代码传到后端,继而攻击者可以达到操作目标网站的目的。
切勿对未授权的系统进行非法测试,这是违法行为。
推荐一些较为老派的 JSP 木马的 github 仓库:
https://github.com/theralfbrown/WebShell-2/tree/master/jsp
近两年主流 webshell 管理工具:冰蝎,哥斯拉,蚁剑......
冰蝎:https://github.com/rebeyond/Behinder
蚁剑:https://github.com/AntSwordProject
哥斯拉:https://github.com/BeichenDream/Godzilla
1、JSP 大马
JSP 大马,在代码中写入了更多功能,可以实现在网页中进行一些危险操作。
以下演示案例中的JSP大马用的是https://github.com/theralfbrown/WebShell-2/tree/master/jsp
这个仓库中无密码的jsp大马.jsp
。
①、在webapp下新建个shell.jsp
,在该文件中复制粘贴以上代码。如下图所示:
②、点击运行,;浏览器访问127.0.0.1:8080/shell.jsp
,从中可以看到,我们可以进行的危险操作有上传文件、执行命令等等,如下图所示:
建议各位动手操作吧,动手操作加深理解。
感兴趣的可以自己私下阅读学习源码。
2、Godzilla(哥斯拉)JSP木马操作
Godzilla(哥斯拉),主要用于管理Webshell的客户端工具。近两年比较主流的工具。哥斯拉内置了 3 种 Payload 以及 6 种加密器,6 种支持脚本后缀,20 个内置插件。
Github地址:
https://github.com/BeichenDream/Godzilla
下载地址:
https://github.com/BeichenDream/Godzilla/releases/tag/v4.0.1-godzilla
下面我们在代码文件中再新建一个名为shell1.jsp
文件,配合哥斯拉的JSP木马进行操作。
①、下载完成后,启动哥斯拉,点击左上角管理,选择生成,此步骤是为了生成JSP木马代码,如下图所示:
然后点击生成,suffix 选择为 JSP,点击确定后选择存放目录。
②、将已生成的代码复制粘贴到shell1.jsp
中,然后启动项目。如下图所示:
③、打开哥斯拉,点击左上角目标,选择添加,将shell1.jsp地址添加进去后点击添加,如下图所示:
④、点击添加后,在管理页面即可看到该链接,右键选择进入,成功进入webshell管理页面,剩下的自己研究下吧。
JSP 章节到此结束了。关于以上代码建议大家动手操作,便于加深理解。
对于哥斯拉 JSP木马代码,在后续提升阶段可以进一步学习,目前理解即可。
感兴趣的可以私下进一步学习。
Spring 和 SpringMVC
一、什么是 Spring
Spring 是一个开源的框架,用于构建企业级 Java 应用程序。它提供了广泛的基础设施支持和一系列的解决方案,使开发者能够更轻松地构建和部署复杂的应用。
Spring 框架的核心特点包括:
轻量级: Spring 是一个轻量级框架,不需要大量的配置信息,使开发更加简单。
控制反转(IoC): Spring 实现了控制反转,即容器负责对象的创建、组装和管理。这样,开发者可以专注于业务逻辑而不是对象的创建和管理。
面向切面编程(AOP): Spring 支持面向切面编程,允许开发者定义横切关注点,并将其与主要业务逻辑分离。
容器: Spring 提供了一个容器,用于管理应用中的对象。这个容器负责对象的生命周期和配置。
数据访问: Spring 提供了对各种数据访问技术的支持,包括 JDBC、ORM(对象关系映射)等。
事务管理: Spring 支持声明式事务管理,简化了事务管理的配置。
模块化: Spring 框架被划分为多个模块,开发者可以选择使用特定的模块,以满足其特定需求。
Spring Framework官方介绍:
https://spring.io/projects/spring-framework
Spring 源码:
https://github.com/spring-projects/spring-framework
对于现阶段,大家理解 Spring 是一个 Java 开源框架接口,也是一个生态体系。其核心框架是 Spring Framework,在此基础上又衍生出来 Spring Boot、Spring Cloud、Spring Data、Spring Security 等一系列优秀项目。
二、什么是 SpringMVC
SpringMVC 是 Spring 基础之上的一个 MVC 框架,基于模型-视图-控制器(Model-View-Controller,简称 MVC)设计模式进行 JavaWeb 系统的开发,它是 Spring 的一个模块,通过 Dispatcher Servlet, ModelAndView 和 View Resolver 等,让应用开发变得很容易。
SpringMVC 是基于 Spring 功能之上添加的 Web 框架,想用 SpringMVC 必须先依赖 Spring。
拓展学习:
Spring,Spring MVC及Spring Boot区别
https://www.jianshu.com/p/42620a0a2c33
三、什么是 MVC
MVC 模式是一种软件框架模式,被广泛应用在 JavaEE 项目的开发中。MVC 模式通过提供一种良好的组织结构和分工方式,帮助解决了软件开发中的耦合、可维护性、可测试性和可扩展性等方面的问题。这使得开发者能够更有效地构建和维护复杂的应用程序。
MVC 即模型(Model) 、视图(View)、控制器(Controller)。
- 模型(Model)
模型是用于处理数据逻辑的部分。
所谓数据逻辑,也就是数据的映射以及对数据的增删改查,Bean、DAO(data access object,数据访问对象)等都属于模型部分。
- 视图(View)
视图负责数据与其它信息的显示,也就是给用户看到的页面。
html、JSP 等页面都可以作为视图。
- 控制器(controller)
控制器是模型与视图之间的桥梁,控制着数据与用户的交互。
控制器通常负责从视图读取数据,处理用户输入,并向模型发送数据,也可以从模型中读取数据,再发送给视图,由视图显示。
3.1、核心架构流程
在 Spring MVC 框架中,DispatcherServlet
、HandlerMapping
和 HandlerAdapter
是三个重要的组件,它们各自承担不同的角色,协同工作以处理客户端的请求并调度相应的处理程序。以下是它们的解释和作用:
DispatcherServlet(调度器Servlet):
- 作用:
DispatcherServlet
是 Spring MVC 中的前端控制器(Front Controller),负责接收客户端的所有请求并将其分派给适当的处理程序(Controller)。 - 解释: 当客户端发送请求时,
DispatcherServlet
接收请求,然后根据配置和规则找到合适的HandlerMapping
来确定请求对应的处理程序。一旦找到处理程序,DispatcherServlet
将请求交给该处理程序进行处理。它还负责处理异常、视图解析和其他与请求生命周期相关的任务。 - 源代码路径:
https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
HandlerMapping(处理程序映射):
- 作用:
HandlerMapping
负责将请求映射到相应的处理程序(Controller)。它确定了客户端请求应该由哪个处理程序来处理。 - 解释: 在 Spring MVC 中,可以有多个
HandlerMapping
实现,包括基于注解的映射、基于路径的映射等。HandlerMapping
将请求的 URL 映射到具体的控制器类和方法,以便DispatcherServlet
可以将请求分发给正确的处理程序。 - 源代码路径:
https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerMapping.java
HandlerAdapter(处理程序适配器):
- 作用:
HandlerAdapter
负责调用实际的处理程序(Controller)来处理请求,并将处理程序的执行结果返回给DispatcherServlet
。 - 解释: 不同的处理程序可能有不同的接口,
HandlerAdapter
的作用是适配各种不同类型的处理程序,使得它们能够被DispatcherServlet
统一调用。它将请求传递给处理程序,处理程序执行后,HandlerAdapter
还负责处理返回的结果,如视图解析、数据绑定等。 - 源代码路径:
https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerAdapter.java
这三个组件共同协作,实现了请求的分发和处理。DispatcherServlet
充当总管,HandlerMapping
负责找到处理程序,而 HandlerAdapter
则负责调用实际的处理程序执行业务逻辑。这种设计使得 Spring MVC 具有灵活性,允许通过配置来适应不同的业务需求和处理程序类型。
以上部分内容引用自下方链接,推荐大家拓展阅读
https://pdai.tech/md/spring/spring-x-framework-springmvc.html
四、IDEA 创建 Spring 项目
https://juejin.cn/post/6996542590820548639
https://github.com/Laverrr/bookstore
①、打开IDEA,选择Create New Project
。
②、左侧选择Srping,并勾选相关选项,并且勾选Create empty spirng-config.xml
,如下图所示:
[HTML_REMOVED]
③、点击next,给项目起个名字,以及设置存放目录地址后点击Finish
,稍等片刻,他会自动下载依赖的 JAR 包,如下图所示:
[HTML_REMOVED]
④、新建完成的项目结构如下图所示:
[HTML_REMOVED]
⑤、右键选中src目录下新建两个Java Class分别为HelloSpring
和Main
,如下图所示:
⑥、在HelloSpring
文件中键入以下代码。
public class HelloSpring {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
[HTML_REMOVED]
⑦、在Main
文件中键入以下代码。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
HelloSpring obj = (HelloSpring) context.getBean("HelloSpring");
obj.getMessage();
}
}
[HTML_REMOVED]
⑧、在spring-config.xml
配置文件中增加以下内容。
<bean id="HelloSpring" class="HelloSpring">
<property name="message" value="i am groot!!"/>
</bean>
⑨、最终点击运行,观察结果如下:
是不是感觉不知所以然?在这基础上,给大家推荐一篇文章,在进一步理解一下,https://juejin.cn/post/6844903912034533383
。
扩展学习,推荐这本书:
Spring实战(第五版),这是网上翻译版,尽可能买原版书籍。
https://potoyang.gitbook.io/spring-in-action-v5/
是不是感觉配置繁琐,当然这还只是基础案例,在实际开发中配置会更加繁琐。
因此由于人类的【懒惰】,衍生出来了Spring Boot框架。
我们将在下一章节学习。
五、拓展 Spring + Spring MVC 的项目
对于学习代码审计,也许我们代码写的不是很溜,甚至不会写。
但拿到一个项目一定要会看。
推荐几个 Spring + Spring MVC 的项目进一步学习。
- 某在线教育平台
https://github.com/impxiahuaxian/zaixianjiaoyu
- 某网上书店
https://github.com/Laverrr/bookstore
- 某在线课程学习网站
https://gitee.com/zhangjiazhao/online_course_learning_website
- 某汽车租赁系统
https://gitee.com/huang_xiao_feng/carrentalsystem
- 某人力资源管理系统
https://github.com/BradWenqiang/hr
SpingBoot 和 SpringCloud
一、什么是Srping Boot
SpringBoot 是一款基于 JAVA 的开源框架。目的是为了简化 Spring 应用搭建和开发流程。是目前比较流行,大中小型企业常用的框架。正因为极大简化了开发流程,才受到绝大开发人员的喜爱。
SpringBoot 核心原理是自动装配(自动配置)。
在这之前,开发一个JavaWeb,Spring 等项目要进行很多配置,使用了 SpringBoot 就不用在过多考虑这些方面。
并且在 SpringBoot 中还内置了 Tomcat。
官方介绍:
https://spring.io/projects/spring-boot
在 Spring Boot 中,自动装配是一项非常重要的功能,它帮助开发者简化了项目的配置和搭建过程。Spring Boot通过一系列的约定和默认配置,自动地将各种组件进行装配,使得开发者能够更专注于业务逻辑而不必过多关注框架的配置。
以下是 Spring Boot 自动装配体现的一些主要方面:
依赖管理: Spring Boot 通过 Maven 或 Gradle 等构建工具,引入了一系列默认的依赖项,以简化项目的搭建。例如,通过添加spring-boot-starter-web
依赖,你就能够使用Spring MVC来构建Web应用。
类路径扫描: Spring Boot会自动扫描项目中的特定包及其子包,寻找标有特殊注解的类,比如@Controller
、@Service
、@Repository
等,然后将它们注册为Spring的组件。
默认配置: Spring Boot 提供了大量的默认配置,当没有显式配置时,这些默认配置会自动应用。例如,如果项目中引入了数据库相关的依赖,Spring Boot 会根据类路径上的数据库驱动自动配置数据源。
条件化装配: Spring Boot 引入了条件化装配的概念,根据类路径上的特定条件来决定是否启用某些配置。这使得可以根据不同的环境或条件来自动装配不同的组件。
Spring Boot Starter: Spring Boot 提供了一系列的 Starter 依赖,这些 Starter 提供了一组常用的依赖项的整合,比如spring-boot-starter-data-jpa
、spring-boot-starter-security
等,它们能够一次性地引入一组相关的依赖和配置,简化了开发者的工作。
自定义Starter: 除了 Spring Boot 提供的 Starter 之外,开发者也可以创建自己的 Starter,将一组相关的依赖和配置封装起来,使得它们能够在不同的项目中被重复使用。
自动化配置类: Spring Boot通过自动化配置类来实现自动装配。这些配置类使用@Configuration
注解标记,并且通常包含有@ConditionalOn...
注解,用于指定生效的条件。
总体来说,Spring Boot通过一系列约定、默认配置、条件化装配等方式,实现了自动装配,使得开发者能够更加方便地构建和配置项目。
二、Spring Boot 项目简介
通过经典 HelloWorld 程序,来看看 Springboot 项目搭建多么简便。
①、打开 IDEA,点击新建项目,选择Spring Initializer
,配置信息如下图所示:
②、点击选择一些常用依赖,比如 Web 下的 Spring Web,如下图所示:
③、点击创建,稍等片刻,Maven 自动加载完所需依赖后,整体项目结构如下图所示:
@SpringBootApplication
注解表示这个类为 SpringBoot 的主配置类,SpringBoot 项目应运行这个类下面的 main 方法来启动 SpringBoot 应用。
也就是说这个 XXXApplication 是该项目的入口。
④、我们使用的是阿里云的脚手架创建的 SpringBoot 项目,可以看到在com\example\demo 目录下有个 demos\web 目录,里面是一些示例代码。如下图所示:
⑦、点击右上方运行,或者进入 XXXApplication 中,点击左侧绿色小按钮,即刻运行项目,打开浏览器输入http://127.0.0.1:8080/hello
,即可看到返回了一些内容,如下图所示:
⑧、下面我们进入 demos.web 目录下,看看他的 Controller 代码,也就是返回上面内容的接口代码,是位于该目录下的 BasicController,拿上述接口距离,访问 hello 会返回你输入的名字,默认值是 unkonown user,如下图所示:
其他一些注解含义:
@Controller
注解:标注该类为controller类,可以处理 http 请求。@Controller 一般要配合模版来使用。现在项目大多是前后端分离,后端处理请求,然后返回JSON格式数据即可,这样也就不需要模板了。
@ResponseBody
注解:将该注解写在类的外面,表示这个类所有方法的返回的数据直接给浏览器。@RestController 相当于 @ResponseBody 加上 @Controller
@RequestMapping
注解:配置URL映射
,可以作用于某个Controller类上,也可以作用于某Controller类下的具体方法中,说白了就是URL中请求路径会直接映射到具体方法中执行代码逻辑。
@PathVariable
注解:接受请求URL路径中占位符的值,示例代码如下图所示:
@Controller
@ResponseBody
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/whoami/{name}/{sex}")
public String hello(@PathVariable("name") String name, @PathVariable("sex") String sex){
return "Hello" + name + sex;
}
}
@RequestParam
注解:将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解),常用于POST请求处理表单。
综上演示,可以看到 Spring Boot 部署非常方便,并且在开发后端服务时也极大简化了各种配置,可以更专注于编写代码。这对于我们审计 Spring Boot 架构的系统也极为的便利。
三、什么是Spring Cloud
Spring Cloud 是一系列框架的有序集合。是一套基于 Spring Framework 的分布式系统开发工具,用于构建分布式应用程序中的各种模块化组件。
它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。
Spring Cloud 的诞生并不是为了解决微服务中的某一个问题,而是提供了一套解决微服务架构实施的综合性解决方案。
Spring Cloud 并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
大家也听过微服务吧。那它和 Spring Cloud 什么关系呢?
首先,什么是微服务?
微服务(英语:Microservices)是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关的API集相互通信。
简单来说,微服务就是将一个大型的应用拆分成很多个小的应用,这些应用之间一般通过基于HTTP的RESTful API进行通信协作,并且能够各自进行独立部署以及伸缩。由于微服务独立部署,可伸缩的特性,它能够迅速地大规模部署到云服务器上。
而使用 Spring Cloud 能够快速实现微服务架构。
官方介绍:
https://spring.io/projects/spring-cloud
Spring Cloud 提供了许多模块,以下是一些常用的模块及其主要作用:
Spring Cloud Config(配置中心):
- 作用:用于集中管理配置信息,可以将配置信息存储在版本控制系统中,并在需要时动态刷新。
Spring Cloud Netflix(服务治理):
- 作用:整合了Netflix开发的一些组件,包括Eureka(服务注册与发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Feign(声明式REST客户端)等,用于构建具有高可用性和弹性的微服务架构。
Spring Cloud Bus(消息总线):
- 作用:通过消息总线,实现微服务架构中配置的动态刷新,使得配置的修改能够快速传播到各个微服务实例。
Spring Cloud Sleuth(分布式追踪):
- 作用:用于跟踪分布式系统中的请求流程,生成跨服务的唯一标识,方便在分布式系统中进行日志跟踪和性能监控。
Spring Cloud Gateway(网关):
- 作用:提供了一种简单而有效的方式来进行路由、过滤以及转发请求,用于构建微服务架构中的API网关。
Spring Cloud Stream(消息驱动):
- 作用:简化了消息驱动的微服务开发,提供了一套统一的编程模型,支持多种消息中间件。
Spring Cloud Security(安全):
- 作用:提供了一些安全工具和特性,用于保护分布式系统中的微服务。
Spring Cloud Data Flow(数据流):
- 作用:用于构建和部署实时数据处理和分析的微服务,支持复杂的数据流操作。
Spring Cloud OpenFeign(声明式REST客户端):
- 作用:简化了微服务之间的REST调用,通过声明式的方式定义和调用服务接口。
Spring Cloud Task(任务调度):
- 作用:用于简化任务调度和执行,支持在分布式环境中进行批处理任务的调度和执行。
若依微服务版本:
https://gitee.com/y_project/RuoYi-Cloud
Java 分层思想与 MVC 模式
一、Java 分层思想
1、分层讲解
Java 分层思想是一种软件架构设计理念,旨在将一个复杂的系统划分为多个相对独立且互相关联的层次,每个层次负责不同的功能,以实现高内聚、低耦合的设计。这种思想有助于提高代码的可维护性、可扩展性,并使团队更容易协同工作。以下是Java分层思想的主要层次:
表现层(Presentation Layer): 主要负责与用户交互,处理用户界面和用户输入输出。在 Java 中,通常由 Servlet、JSP、或者更现代的框架如 Spring MVC 负责、或者 Springboot 下的 Controller 层。
业务层(Business Layer): 业务层包含应用程序的业务逻辑,处理业务规则和数据处理。这一层通常由 JavaBean、Service 等组成,负责执行具体的业务操作。
服务层(Service Layer): 服务层是业务层的一部分,提供业务逻辑的具体实现。在 Spring 框架中,使用 @Service 注解来表示服务层。
持久层(Persistence Layer): 持久层负责数据的持久化,通常与数据库交互。在 Java 中,常见的持久层技术包括 JDBC、Hibernate、MyBatis 等。
数据访问层(Data Access Layer): 这一层是持久层的一部分,负责封装数据访问细节,提供统一的接口给业务层。通常由 DAO(Data Access Object)组成。
通过明确划分这些层次,开发人员可以更容易地理解、维护和扩展代码。这种分层思想还有助于实现模块化开发,每个层次都可以独立测试和替换,从而提高系统的可测试性和灵活性。在实际应用中,可以根据项目的规模和需求进行适度的调整和扩展。
https://www.cnblogs.com/java-123/p/9174547.html
2、代码案列
以后面实战阶段第一套系统举例说明,这是一个架构非常简单清晰的系统。
首先,src/main
下面有两个目录,分别是java
和resources
java
目录中主要存放的是 Java 代码
resources
目录中主要存放的是静态资源文件,比如:html、js、css等。
在java
目录下还有其他一些常见目录,具体含义整理如下:
2.1、java 目录
annotation
:放置项目自定义注解
controller/
: 存放控制器,接收从前端传来的参数,对访问控制进行转发、各类基本参数校验或者不复用的业务简单处理等。
dao/
: 数据访问层,与数据库进行交互,负责数据库操作,在Mybaits框架中存放自定义的Mapper接口
entity/
: 存放实体类
interceptor/
: 拦截器
service/
: 存放服务类,负责业务模块逻辑处理。Service
层中有两种类,一是Service
,用来声明接口;二是ServiceImpl
,作为实现类实现接口中的方法。
utils/
: 存放工具类
dto/
: 存放数据传输对象(Data Transfer Object),如请求参数和返回结果
vo/
: 视图对象(View Object)用于封装客户端请求的数据,防止部分数据泄漏,保证数据安全
constant/
: 存放常量
filter/
: 存放过滤器
2.2、resources 目录
mapper/
: 存放Mybaits的mapper.xml文件
static/
: 静态资源文件目录(Javascript、CSS、图片等),在这个目录中的所有文件可以被直接访问
templates/
: 存放模版文件
application.properties
或application.yml
: Spring Boot默认配置文件
二、Java MVC 模式
1、模式讲解
MVC 即模型(Model) 、视图(View)、控制器(Controller)。
MVC(Model-View-Controller)是一种软件架构模式,用于设计和组织代码。它将一个应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。每个组件有不同的责任,以实现代码的分离和模块化,以便更容易维护和扩展应用程序。
通俗来说,各司其职高效完成任务。
- 模型(Model)
模型是用于处理数据逻辑的部分。
所谓数据逻辑,也就是数据的映射以及对数据的增删改查,Bean、DAO(data access object,数据访问对象)等都属于模型部分。
- 视图(View)
视图负责数据与其它信息的显示,也就是给用户看到的页面。
html、JSP 等页面都可以作为视图。
- 控制器(controller)
控制器是模型与视图之间的桥梁,控制着数据与用户的交互。
控制器通常负责从视图读取数据,处理用户输入,并向模型发送数据,也可以从模型中读取数据,再发送给视图,由视图显示。
2、代码案例
在前面我们简单学习了 Spring MVC,这就是基于 MVC 结合 Spring 形成的架构。
推荐几个 Spring + Spring MVC 的项目进一步学习。
- 某在线教育平台
https://github.com/impxiahuaxian/zaixianjiaoyu
- 某网上书店
https://github.com/Laverrr/bookstore
- 某在线课程学习网站
https://gitee.com/zhangjiazhao/online_course_learning_website
- 某汽车租赁系统
https://gitee.com/huang_xiao_feng/carrentalsystem
- 某人力资源管理系统
https://github.com/BradWenqiang/hr
对于 Java 代码审计来说,分层思想也好,MVC 模式也罢,我们主要关注的是整个请求流程的走向,以便更好地正向追踪,或是逆向追踪整个代码请求流程,实现 Java 代码审计闭环。
三、其他
推荐了解《阿里巴巴Java开发手册》:
《阿里巴巴Java开发手册》以Java开发者为中心视角,划分为编程规约、异常日志、单元测试、安全规约、MySQL 数据库、工程结构、设计规约七个维度,再根据内容特征,细分成若干二级子目录。
一份倡导性的 Java 项目开发规范文档,对于我们来说,从开发者角度了解开发规范,可以让我们在代码审计各个项目时对于细节更加的了解。
https://00fly.online/upload/alibaba/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E6%B3%B0%E5%B1%B1%E7%89%88%EF%BC%89.pdf
Java 文件操作之文件上传
此章节我们关注学习两种文件上传方式:Multipartfile方式文件上传和ServletFileUpload方式文件上传。
两种方式给出代码案例,从不同侧来学习Java中文件上传方式。
以下给出的文件上传代码均为示例代码,无安全防护,目前仅是为了让大家学习理解这几种上传方式。
提前铺垫下一阶段任意文件上传漏洞前置知识。
当然还有其他方式和组件的文件上传,比如文件流方式,smartupload组件等等。
本次仅讲述常见的两种。不论用那种方式都换汤不换药,方式的不同对于我们后面进行任意文件上传代码审计影响不会太大。到时再具体问题具体分析。
一、Multipartfile 方式文件上传
1、简介
MultipartFile 是 Spring 框架中的一个接口,它提供了一种方便的方式来处理文件上传。 它可以处理从表单中上传的文件,并提供了访问文件名称、大小、类型以及内容等信息的方法。 MultipartFile 接口还提供了一些实用的方法,如将文件保存到本地磁盘、将文件转换为字节数组等。
2、环境搭建
①、创建 SpringBoot 项目,打开 IDEA,点击新建项目,选择 Spring Initializr,使用以下配置后点击Next,如下图所示:
②、依赖选择添加 Web -> Spring Web,最后点击创建即可。如下图所示:
③、完善下项目结构。在 main 目录下新建webapp
目录,然后在 webapp 目录下新建WEB-INF
目录。如下图所示:
备注:WEB-INF
目录为 JAVA WEB 中安全目录,该目录仅允许服务端访问,客户端无法访问。该目录下有 web.xml 文件。
④、在pom.xml
文件中的<dependencies>...</dependencies>
标签内添加 JSP 依赖,并重载 maven 为了下载所添加的依赖。如下图所示:
<!-- servlet 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- tomcat 的支持.-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
⑤、在application.properties
添加一些配置信息,如下图所示:
server.port=7089
#视图前缀
spring.mvc.view.prefix=/jsp/
#视图后缀
spring.mvc.view.suffix=.jsp
⑧、在 webapp 文件下创建 jsp 目录,并新建一个 index.jsp 文件,键入以下信息,如下图所示:
⑨、启动运行,如成功搭建,响应如下图所示:
3、代码示例
①、在 jsp 文件夹下新建一个名为multipartfileUpload.jsp
的文件,键入以下代码。如下图所示:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>文件上传页面</title>
</head>
<body>
<h1>文件上传页面</h1>
<form method="post" action="/upload" enctype="multipart/form-data">
选择要上传的文件:<input type="file" name="file"><br>
<hr>
<input type="submit" value="提交">
</form>
</body>
</html>
②、在com.example.demo
目录下右键新建一个class名为multipartfileController
,键入以下代码,如下图所示:
package com.example.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@Controller
public class multipartfileController {
@Value("${file.upload.path}")
private String path;
@GetMapping("/")
public String uploadPage() {
return "upload";
}
@PostMapping("/upload")
@ResponseBody
public String create(@RequestPart MultipartFile file) throws IOException {
String fileName = file.getOriginalFilename();
String filePath = path + fileName;
File dest = new File(filePath);
Files.copy(file.getInputStream(), dest.toPath());
return "Upload file success : " + dest.getAbsolutePath();
}
}
③、在application.properties
配置文件中添加一条配置信息file.upload.path=C:/Users/powerful/Desktop/
,是上传文件存放的地址,如下图所示:
④、运行启动项目,访问127.0.0.1/jsp/multipartfileUpload.jsp
,上传文件观察结果(是否将上传的图片存放到了桌面处)。
此案例非常简单,使用了multipartfile方式进行文件上传,配合文件流保存上传内容。
二、ServletFileUpload 方式文件上传
1、简介
ServletFileUpload 方式文件上传依赖 commons-fileupload 组件。
对于 commons-fileupload 组件介绍:FileUpload依据规范RFC1867中”基于表单的 HTML 文件上载”对上传的文件数据进行解析,解析出来的每个项目对应一个 FileItem 对象。
每个 FileItem 都有我们可能所需的属性:获取contentType,获取原本的文件名,获取文件大小,获取FiledName(如果是表单域上传),判断是否在内存中,判断是否属于表单域等。
FileUpload使用FileItemFactory创建新的FileItem。该工厂可以控制每个项目的创建方式。目前提供的工厂实现可以将项目的数据存储临时存储在内存或磁盘上,具体取决于项目的大小(即数据字节,在指定的大小内时,存在内存中,超出范围,存在磁盘上)。
FileUpload 又依赖于 Commons IO。
官方网站:
https://commons.apache.org/proper/commons-fileupload/
常用的一些方法:
方法 | 描述 |
---|---|
FileItemFactory | 表单项工厂接口 |
ServletFileUpload | 文件上传类,用于解析上传的数据 |
FileItem | 表单项类,表示每一个表单项 |
public List[HTML_REMOVED] parseRequest(HttpServletRequest request) | 解析上传的数据,返回包含 表单项的 List 集合 |
String FileItem.getFieldName() | 获取表单项的 name 属性值 |
String FileItem.getString() | 获取当前表单项的值; |
String FileItem.getName() | 获取上传的文件名 |
void FileItem.write(file) | 将上传的文件写到 参数 file 所指向存取的硬盘位置 |
FileItemFactory 表单项工厂接口
ServletFileUpload 文件上传类,用于解析上传的数据
FileItem 表单项类,表示每一个表单项
boolean ServletFileUpload.isMultipartContent(HttpServletRequest request) 判断当前上传的数据格式是否是多段的格式,只有是多段数据,才能使用该方式
public List<FileItem> parseRequest(HttpServletRequest request) 解析上传的数据,返回包含 表单项的 List 集合
boolean FileItem.isFormField() 判断当前这个表单项,是否是普通的表单项,还是上传的文件类型,true 表示普通类型的表单项;false 表示上传的文件类型
String FileItem.getFieldName() 获取表单项的 name 属性值
String FileItem.getString() 获取当前表单项的值;
String FileItem.getName() 获取上传的文件名
void FileItem.write( file ) 将上传的文件写到 参数 file 所指向存取的硬盘位置
2、创建项目
①、启动 IDEA,新建个名为 servletDemo 的项目,选择 maven-webapp 模板,如下图所示:
②、在pom.xml
中引入 servlet 的依赖,写在<dependencies>
标签内,然后可以点击右上角加载文件改动
的按钮(也就是重新加载安装依赖),如下图所示:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
3、环境配置Tomcat
①、访问Tomcat官网https://tomcat.apache.org/download-80.cgi
,下载任意版本Tomcat,我选择的版本是8.5.81
,如下图所示:
选择Binary Distributions
下的Core
分类,这是Tomcat正式的二进制发布版本,一般学习和开发都用此版本。根据自己计算机系统选择下载项。
②、下载完成后,先解压到桌面备用。
③、进入IDEA,配置Tomcat。在右上侧选择Add Configuration...
,在新的界面点击左上角的+
按钮,滑到下面找到Tomcat Server
点击选择Local
,如下图所示:
[HTML_REMOVED]
④、在Server标签栏下点击Configure...
,进入新的界面,在Tomcat Home
处添加Tomcat,最后点击OK。如下图所示:
[HTML_REMOVED]
⑤、配置部署方式,选择Deployment
标签栏,在右侧点击+
按钮,选择war exploded
方式,如下图所示:
[HTML_REMOVED]
war方式
:是发布模式,先打包成war包,再发布。war exploded方式
:常在开发的时候使用这种方式,它可以支持热部署,但需要设置。跟本次案例调试无关,仅是给大家简单讲讲。
⑥、设置URL根路径后,点击Apply
,左后点击OK
。如下图所示:
[HTML_REMOVED]
至此,完成了在IDEA中配置Tomcat。
4、代码示例
下文代码示例使用的https://www.cnblogs.com/liuyangv/p/8298997.html
文章中的代码。作者给出了详细的注解,非常适合学习。
配合上面项目搭建,调试一下吧。
①、首先在 pom.xml 中添加所需依赖后重新加载 maven,如下图所示:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
②、在index.jsp
文件中键入以下代码,如下图所示:
<%@ page language="java" contentType="text/html" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<form action="FileUploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="name"><br>
文件1:<input type="file" name="f1"><br>
文件2:<input type="file" name="f2"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
③、右键main目录新建一个名为java的目录,并在该目录下创建一个class,名为FileUploadServlet
,并键入一下代码,如下图所示:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
/**
* @author powerful
*/
public class FileUploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//得到上传文件的保存目录。 将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String realPath = this.getServletContext().getRealPath("/upload");// /WEB-INF/files
System.out.println("文件存放位置:"+realPath);
//设置临时目录。 上传文件大于缓冲区则先放于临时目录中
String tempPath = "C:\\Users\\powerful\\Desktop";
System.out.println("临时文件存放位置:"+tempPath);
//判断存放上传文件的目录是否存在(不存在则创建)
File f = new File(realPath);
if(!f.exists()&&!f.isDirectory()){
System.out.println("目录或文件不存在! 创建目标目录。");
f.mkdir();
}
//判断临时目录是否存在(不存在则创建)
File f1 = new File(tempPath);
if(!f1.isDirectory()){
System.out.println("临时文件目录不存在! 创建临时文件目录");
f1.mkdir();
}
/**
* 使用Apache文件上传组件处理文件上传步骤:
*
* */
//1、设置环境:创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置上传文件的临时目录
factory.setRepository(f1);
//2、核心操作类:创建一个文件上传解析器。
ServletFileUpload upload = new ServletFileUpload(factory);
//解决上传"文件名"的中文乱码
upload.setHeaderEncoding("UTF-8");
//3、判断enctype:判断提交上来的数据是否是上传表单的数据
if(!ServletFileUpload.isMultipartContent(req)){
System.out.println("不是上传文件,终止");
//按照传统方式获取数据
return;
}
//==获取输入项==
// //限制单个上传文件大小(5M)
// upload.setFileSizeMax(1024*1024*4);
// //限制总上传文件大小(10M)
// upload.setSizeMax(1024*1024*6);
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
List<FileItem> items =upload.parseRequest(req);
for(FileItem item:items){
//如果fileitem中封装的是普通输入项的数据(输出名、值)
if(item.isFormField()){
String filedName = item.getFieldName();//普通输入项数据的名
//解决普通输入项的数据的中文乱码问题
String filedValue = item.getString("UTF-8");//普通输入项的值
System.out.println("普通字段:"+filedName+"=="+filedValue);
}else{
//如果fileitem中封装的是上传文件,得到上传的文件名称,
String fileName = item.getName();//上传文件的名
//多个文件上传输入框有空 的 异常处理
if(fileName==null||"".equals(fileName.trim())){ //去空格是否为空
continue;// 为空,跳过当次循环, 第一个没输入则跳过可以继续输入第二个
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理上传文件的文件名的路径,截取字符串只保留文件名部分。//截取留最后一个"\"之后,+1截取向右移一位("\a.txt"-->"a.txt")
fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
//拼接上传路径。存放路径+上传的文件名
String filePath = realPath+"\\"+fileName;
//构建输入输出流
InputStream in = item.getInputStream(); //获取item中的上传文件的输入流
OutputStream out = new FileOutputStream(filePath); //创建一个文件输出流
//创建一个缓冲区
byte b[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = -1;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))!=-1就表示in里面还有数据
while((len=in.read(b))!=-1){ //没数据了返回-1
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath+"\\"+filename)当中
out.write(b, 0, len);
}
//关闭流
out.close();
in.close();
//删除临时文件
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
item.delete();//删除处理文件上传时生成的临时文件
System.out.println("文件上传成功");
}
}
} catch (FileUploadException e) {
//e.printStackTrace();
throw new RuntimeException("服务器繁忙,文件上传失败");
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
}
④、运行项目,上传文件观察结果。
至此,两种上传方式均已演示完毕。大家调试下代码学习一下吧。
三、拓展文件上传项目学习
1、SpringBoot 集成上传文件,该项目多是使用的 Multipartfile 方式进行的文件上传。但其中有一块是使用了文件流方式,位于/src/main/java/com/example/springbootupload/controller/FileUploadController.java
文件内,大家可以调试一下。
https://github.com/xiaonongOne/springboot-upload
2、MyUploader-Backend 项目实现了单文件上传,多文件上传,大文件上传,断点续传,文件秒传,图片上传。完整项目建议学习。
https://github.com/gaoyuyue/MyUploader-Backend
Java 文件操作之文件读取与下载
我先抛出一个问题,读取文件与下载文件在代码审计有何区别呢?
先简单说下答案,实际上在后端大多是通过读取文件方式获得目标文件内容,这个不难理解。最后将文件流内容传给浏览器,并在 header 头中添加浏览器解析方式和文件名,比如:文件下载到本地实现方法可以使用响应头Content-disposition
来控制,也就是说下载这个动作是交给浏览器去操作的。
Content-Disposition
响应头:指示回复的内容该以何种形式展示,是以内联
的形式(即网页或者页面的一部分),还是以附件
的形式下载并保存到本地。
但从漏洞挖掘角度来看,我们使用下载或读取功能的目的是获得目标敏感文件中的数据。
一、新建 Java 基础工程
使用IDEA创建一个基础项目工程,用于下面几种文件读取/下载实现方法的调试。
①、打开 IDEA 创建一个项目,注意选择 maven webapp 模板,如下图所示:
②、点击创建即可。
③、在桌面随便创建一个 txt 文本文件,里面键入任意内容,目的是用于下面读取文件的。
到此项目创建完成,下面根据每一个实现方法编写对应实现代码。
二、文件读取/下载实现方法
下面介绍几种我学到的 Java读取/下载文件的几种实现方法。
方法一:使用java.nio.file.Files读取文本
1、简述
使用Files
类将文件的所有内容读入字节数组。Files
类还有一个方法可以读取所有行到字符串列表。Files
类是在Java 7中引入的,如果想加载所有文件内容,使用这个类是比较适合的。只有在处理小文件并且需要加载所有文件内容到内存中时才应使用此方法。
2、实现代码
①、在刚才创建的工程下的src/main/java
目录下创建一个名为ReadFiles
的Java Class。并键入以下代码。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* 编号7089
*/
public class ReadFiles {
public static void main(String[] args) throws IOException {
String fileName = "C:\\Users\\power\\Desktop\\test.txt";
//使用Java 7中的Files类处理小文件,获取完整的文件数据
readUsingFiles(fileName);
}
private static void readUsingFiles(String fileName) throws IOException {
Path path = Paths.get(fileName);
System.out.println("使用File类字节数组读取文件.........");
//将文件读取到字节数组
byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes));
System.out.println("使用File类字读取文件字符串列表.........");
@SuppressWarnings("unused")
List<String> allLines = Files.readAllLines(path, StandardCharsets.UTF_8);
System.out.println(new String(allLines.toString()));
}
}
②、启动运行项目,观察结果,如下图所示:
方法二:使用java.io.FileReader类读取文本
1、简述
可以使用FileReader
获取BufferedReader
,然后逐行读取文件。FileReader
不支持编码并使用系统默认编码,因此它不是一种java中读取文本文件的非常有效的方法。
2、实现代码
①、在刚才创建的工程下的src/main/java
目录下创建一个名为ReadFileReader
的Java Class。并键入以下代码。
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ReadFileReader {
public static void main(String[] args) throws IOException {
String fileName = "C:\\Users\\powerful\\Desktop\\test.txt";
//使用FileReader读取,没有编码支持,效率不高
readUsingFileReader(fileName);
}
private static void readUsingFileReader(String fileName) throws IOException {
File file = new File(fileName);
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String line;
System.out.println("使用FileReader读取文本文件......");
while((line = br.readLine()) != null){
//逐行读取
System.out.println(line);
}
br.close();
fr.close();
}
}
②、启动运行项目,观察结果,如下图所示:
方法三:使用java.io.BufferedReader读取文本
1、简述
如果想逐行读取文件并对它们进行处理,那么BufferedReader
是非常合适的。它适用于处理大文件,也支持编码。
BufferedReader
是同步的,因此可以安全地从多个线程完成对BufferedReader
的读取操作。BufferedReader
的默认缓冲区大小为:8KB
。
2、实现代码
①、在刚才创建的工程下的src/main/java
目录下创建一个名为ReadBufferedReader
的Java Class。并键入以下代码。
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class ReadBufferedReader {
public static void main(String[] args) throws IOException {
String fileName = "C:\\Users\\powerful\\Desktop\\test.txt";
//使用BufferedReader读取,逐行读取,并设置编码为UTF_8
readUsingBufferedReader(fileName, StandardCharsets.UTF_8);
}
private static void readUsingBufferedReader(String fileName, Charset cs) throws IOException {
File file = new File(fileName);
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis, cs);
BufferedReader br = new BufferedReader(isr);
String line;
System.out.println("使用BufferedReader读取文本文件......");
while((line = br.readLine()) != null){
//逐行读取
System.out.println(line);
}
br.close();
}
}
②、启动运行项目,观察结果,如下图所示:
方法四:使用 Scanner 读取文本
1、简述
如果要逐行读取文件或基于某些java正则表达式读取文件,则可使用Scanner
类。
Scanner
类使用分隔符模式将其输入分解为标记,分隔符模式默认匹配空格。然后可以使用各种下一种方法将得到的标记转换成不同类型的值。Scanner
类不同步,因此不是线程安全的。
2、实现代码
①、在刚才创建的工程下的src/main/java
目录下创建一个名为ReadScanner
的Java Class。并键入以下代码。
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;
public class ReadScanner {
public static void main(String[] args) throws IOException {
String fileName = "C:\\Users\\powerful\\Desktop\\test.txt";
//使用Scanner类来处理大文件,逐行读取
readUsingScanner(fileName);
}
private static void readUsingScanner(String fileName) throws IOException {
Path path = Paths.get(fileName);
Scanner scanner = new Scanner(path);
System.out.println("使用Scanner读取文本文件.....");
//逐行读取
while(scanner.hasNextLine()){
//逐行处理
String line = scanner.nextLine();
System.out.println(line);
}
scanner.close();
}
}
②、启动运行项目,观察结果,如下图所示:
方法五:使用RandomAccessFile断点续传读取文本
1、简述
随机流(RandomAccessFile)不属于IO流,支持对文件的读取和写入随机访问。
首先把随机访问的文件对象看作存储在文件系统中的一个大型 byte 数组,然后通过指向该 byte 数组的光标或索引(即:文件指针 FilePointer)在该数组任意位置读取或写入任意数据。
断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。
断点续传实现原理:
- 下载断开的时候,记录文件断点的位置position;
- 继续下载的时候,通过RandomAccessFile找到之前的position位置开始下载
2、实现代码
①、在刚才创建的工程下的src/main/java
目录下创建一个名为ReadRandomAccessFile
的Java Class。并键入以下代码。
import java.io.IOException;
import java.io.RandomAccessFile;
public class ReadRandomAccessFile {
public static void main(String[] args) throws IOException {
String fileName = "C:\\Users\\powerful\\Desktop\\test.txt";
//使用RandomAccessFile来实现断点续传读取/下载文件
readUsingRandomAccessFile(fileName);
}
private static void readUsingRandomAccessFile(String fileName) throws IOException {
RandomAccessFile file = new RandomAccessFile(fileName, "r");
String str;
while ((str = file.readLine()) != null) {
System.out.println("使用RandomAccessFile来实现断点续传读取/下载文件......");
System.out.println(str);
}
file.close();
}
}
②、启动运行项目,观察结果,如下图所示:
方式六:使用 commons-io读取文本
1、简述
Commons IO 是 Apache 软件基金会的一个项目,它提供了一组用于处理 I/O 操作的实用工具类。这个库旨在简化 Java 编程中常见的文件和流操作,使开发者能够更轻松地处理文件读写、流操作、文件过滤和目录操作等。
一些 Commons IO 提供的主要功能包括:
文件操作: 提供了一系列用于文件复制、移动、删除和重命名的方法,还包括了获取文件大小、检查文件是否存在等常见操作。
流操作: 提供了用于处理输入和输出流的实用方法,如关闭流、复制流、转换流等。
文件过滤: 提供了一些实用的文件过滤器,可以根据文件名、文件大小、最后修改时间等条件对文件进行过滤。
目录操作: 提供了一些方便的方法来处理目录,如创建目录、列出目录内容等。
文件内容操作: 提供了一些用于读取、写入和比较文件内容的方法。
下面使用 Commons-io 库一行代码文件来实现读取文件。
2、实现代码
①、首先要在pom.xml
中引入Commons-io
依赖后重载maven。如下图所示:
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
②、在刚才创建的工程下的src/main/java
目录下创建一个名为ReadCommonsIo
的Java Class。并键入以下代码。
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ReadCommonsIo {
public static void main(String[] args) throws IOException {
String fileName = "C:\\Users\\powerful\\Desktop\\test.txt";
readUsingCommonsIo(fileName);
}
private static void readUsingCommonsIo(String fileName) throws IOException {
File file = new File(fileName);
System.out.println("使用Commons-io读取文件......");
System.out.println(FileUtils.readFileToString(file, StandardCharsets.UTF_8));
}
}
③、启动运行项目,观察结果,如下图所示:
方法七:使用 Files.readString 读取文本
①、简述
Java 11添加了readString()方法来读取小文件String。官方介绍:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#readString(java.nio.file.Path,java.nio.charset.Charset)
②、实现代码
String content = Files.readString(path, StandardCharsets.US_ASCII);
三、文件读取/下载 JavaWeb 工程
我们创建一个基于SpringBoot的读取文件的JavaWeb工程,这回我们从WEB角度调试上面几种方法。
①、打开 IDEA 创建个名为 webreadfile 的 SpringBoot 项目工程,如下图所示:
②、下一步,选择 Spring web 依赖,如下图所示:
③、在pom.xml
中引入 Commons-Io 依赖后重载Maven,便于后面使用。如下图所示:
④、在src/main/java/com/example/demo
目录下创建一个名为ReadFilesController
的Java Class,并键入以下代码。
package com.example.demo;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Scanner;
/**
* 编号7089
*/
@Controller
@ResponseBody
public class ReadFilesController {
@RequestMapping("/readUsingFiles")
public String readUsingFiles(String fileName, HttpServletResponse response) throws IOException {
//使用Java 7中的Files类处理小文件,获取完整的文件数据
Path path = Paths.get(fileName);
//将文件读取到字节数组
byte[] bytes = Files.readAllBytes(path);
System.out.println("使用File类读取文件.........");
@SuppressWarnings("unused")
List<String> allLines = Files.readAllLines(path, StandardCharsets.UTF_8);
//将注释去掉,重新运行启动项目,在浏览器键入要读取的文件地址,观察下效果有什么不一样。
//response.reset();
//response.setContentType("application/octet-stream");
//response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
System.out.println(new String(bytes));
return new String(bytes);
}
@RequestMapping("/readUsingFileReader")
public void readUsingFileReader(String fileName, HttpServletResponse response) throws IOException {
//使用FileReader读取,没有编码支持,效率不高
File file = new File(fileName);
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String line;
System.out.println("使用FileReader读取文本文件......");
//将注释去掉,重新运行启动项目,在浏览器键入要读取的文件地址,观察下效果有什么不一样。
//response.reset();
//response.setContentType("application/octet-stream");
//response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
PrintWriter out = response.getWriter();
while ((line = br.readLine()) != null) {
//逐行读取
System.out.println(line);
out.print(line);
}
br.close();
fr.close();
}
@RequestMapping("/ReadBufferedReader")
public void readBufferedReader(String fileName, HttpServletResponse response) throws IOException{
File file = new File(fileName);
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String line;
//将注释去掉,重新运行启动项目,在浏览器键入要读取的文件地址,观察下效果有什么不一样。
//response.reset();
//response.setContentType("application/octet-stream");
//response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
PrintWriter out = response.getWriter();
System.out.println("使用BufferedReader读取文本文件......");
while((line = br.readLine()) != null){
//逐行读取
System.out.println(line);
out.print(line);
}
br.close();
}
@RequestMapping("/readScanner")
public void readScanner(String fileName, HttpServletResponse response) throws IOException{
Path path = Paths.get(fileName);
Scanner scanner = new Scanner(path);
System.out.println("使用Scanner读取文本文件.....");
//将注释去掉,重新运行启动项目,在浏览器键入要读取的文件地址,观察下效果有什么不一样。
//response.reset();
//response.setContentType("application/octet-stream");
//response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
PrintWriter out = response.getWriter();
//逐行读取
while(scanner.hasNextLine()){
//逐行处理
String line = scanner.nextLine();
System.out.println(line);
out.print(line);
}
scanner.close();
}
@RequestMapping("/readUsingRandomAccessFile")
public void readUsingRandomAccessFile(String fileName, HttpServletResponse response) throws IOException{
RandomAccessFile file = new RandomAccessFile(fileName, "r");
String str;
//将注释去掉,重新运行启动项目,在浏览器键入要读取的文件地址,观察下效果有什么不一样。
//response.reset();
//response.setContentType("application/octet-stream");
//response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
PrintWriter out = response.getWriter();
while ((str = file.readLine()) != null) {
System.out.println("使用RandomAccessFile来实现断点续传读取/下载文件......");
System.out.println(str);
out.print(str);
}
file.close();
}
@RequestMapping("/readUsingCommonsIo")
public String readUsingCommonsIo(String fileName,HttpServletResponse response) throws IOException{
File file = new File(fileName);
//将注释去掉,重新运行启动项目,在浏览器键入要读取的文件地址,观察下效果有什么不一样。
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
System.out.println("使用Commons-io读取文件......");
System.out.println(FileUtils.readFileToString(file, StandardCharsets.UTF_8));
return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
}
}
⑤、启动运行项目,打开浏览器,访问其中一个读取/下载文件的接口,如下图所示:
http://127.0.0.1:8080/readUsingRandomAccessFile?fileName=C:/Users/powerful/Desktop/test.txt
⑥、在代码文件中,每个接口我都留了相同的注释,我们选择一个接口将注释去除掉,如下图所示:
⑦、再次启动运行,访问去掉注释的这个接口,我们可以看到将文件下载到了本地,如下图所示:
http://127.0.0.1:8080/readUsingFiles?fileName=C:/Users/powerful/Desktop/test.txt
至此,文件读取/下载的几种方式讲完了。
一定要动手实践调试。