Java基础-知识点04
- Object类
- wait和notify需要在什么地方使用?
- 说明 toString() 方法的作用和重写时的注意事项。
- toString() 方法在实际开发中的应用场景和作用。
- continue、break 和 return 的区别
- 1、`continue`:
- 2、`break:`
- 3、`return`:
- 反射
- 什么是反射,它的作用是什么?
- 反射的步骤及原理
- 在反射中,如何获取一个类的class对象
- 如何通过反射创建一个对象?
- 什么是反射中的访问权限检查,如何绕过?
- 反射和注解之间有什么联系?
- 反射的应用场景
- 注解
- 如何定义注解
- 自定义注解的应用场景?
- Java 中常见的内置注解有哪些?
- 注解的元注解有哪些?它们分别用于控制什么行为?
- 注解可能会带来哪些潜在的问题或者注意事项?
- JWT(Json Web Token)
- JWT的使用流程
- JWT的用途
- JWT的优缺点
- JWT和Token区别
- cookie、session、token
- Cookie
- Cookie的工作流程:
- Cookie的优缺点:
- Session
- Session的工作流程
- session的优缺点
- Token
- Cookie 、 Session、Token的区别
Object类
Object
类是 Java 中所有类的根类,它位于 java.lang 包中。这意味着所有其他类都是 Object 类的子类。下面是一些 Object 类的常用方法:
方法 | 描述 |
---|---|
equals(Object obj) | 比较两个对象是否相等。默认情况下,比较的是对象的引用,可以通过重写此方法实现自定义的相等性比较。 |
hashCode() | 返回对象的哈希码值。通常与 equals 方法一起使用,确保相等的对象具有相同的哈希码。 |
toString() | 返回对象的字符串表示。默认情况下,返回类名和对象的哈希码。可以通过重写此方法自定义输出格式。 |
getClass() | 返回对象的运行时类对象。可以用来获取对象的类信息。 |
notify() | 唤醒等待在此对象监视器上的单个线程。 |
notifyAll() | 唤醒等待在此对象监视器上的所有线程。 |
wait() | 导致当前线程等待,直到其他线程调用 notify() 或 notifyAll() 方法唤醒它。 |
wait(long timeout) | 导致当前线程等待,直到其他线程调用 notify() 或 notifyAll() 方法,或者超时时间到期。 |
wait(long timeout, int nanos) | 导致当前线程等待,直到其他线程调用 notify() 或 notifyAll() 方法,或者超时时间到期。可以指定纳秒级的超时时间。 |
finalize() | 用于对象的垃圾回收。该方法在对象被垃圾回收器回收之前被调用,子类可以重写该方法以执行清理操作。 |
wait和notify需要在什么地方使用?
wait()
和 notify()
是 Java 中用于实现多线程间协作的两个方法。wait() 方法可以使当前线程进入等待状态,同时释放对象的锁;而 notify() 方法用于唤醒一个在该对象上等待的线程。
这两个方法通常需要在以下场景下使用:
- 生产者-消费者模式:
在生产者-消费者模式中,生产者线程会向共享队列中添加数据,而消费者线程会从队列中取出数据。当队列为空时,消费者线程需要等待生产者线程向队列中添加数据,这时可以使用 wait() 方法使消费者线程进入等待状态,而当生产者线程向队列中添加了数据之后,需要使用 notify() 方法唤醒等待的消费者线程。 - 等待-通知模式:
在等待-通知模式中,一个线程会等待某个条件的成立,而另一个线程会在条件成立时通知等待的线程。例如,一个线程需要等待某个资源的释放,可以使用 wait() 方法进入等待状态,而当另一个线程释放了该资源时,需要使用 notify() 方法通知等待的线程。
注意:
wait()
和notify()
方法必须在同步代码块中使用,即在获取对象锁之后才能调用这两个方法。
wait() 方法必须在循环中使用,以避免虚假唤醒。在多线程环境下,当一个线程被唤醒时,可能会出现虚假唤醒的情况,即线程没有收到 notify() 或 notifyAll() 方法的通知,但是还是被唤醒了。为了避免这种情况,应该在循环中使用 wait() 方法,每次在循环开始之前检查条件是否满足,如果条件不满足就继续等待。
notify() 方法只会唤醒一个等待的线程,如果有多个线程在等待,需要使用 notifyAll() 方法唤醒所有等待的线程。
说明 toString() 方法的作用和重写时的注意事项。
toString()
方法是 Object
类中的一个重要方法,它的作用是返回对象的字符串表示。在默认情况下,toString() 方法返回的字符串包含对象的类名和哈希码的十六进制表示。但是,通常情况下,我们希望重写 toString() 方法来提供更有意义和可读性的对象字符串表示。
重写 toString() 方法的注意事项包括:
1、提供有意义的信息:重写 toString() 方法时,应该提供对象的关键信息,以便于调试、日志记录和可读性。
2、格式清晰明了: 返回的字符串应该具有清晰的格式。
3、避免输出敏感信息:不要在 toString() 方法中输出敏感信息,如密码、密钥或其他私密数据,以防止泄露敏感信息。
4、考虑性能影响:避免在 toString() 方法中进行大量的计算或涉及 I/O 操作,以免影响程序的性能。
5、重写 equals() 时同步修改:如果重写了 equals() 方法来定义对象的相等性比较逻辑,通常也应该相应地重写 toString() 方法,以便输出的字符串与相等性比较逻辑保持一致。
例如,假设有一个 Person 类,包含 name 和 age 属性,那么重写 toString() 方法可以如下所示:
public class Person {
private String name;
private int age;
// 构造函数、getter 和 setter 略
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
这样重写后的 toString() 方法返回的字符串将包含对象的姓名和年龄信息,更易于理解和使用。
toString() 方法在实际开发中的应用场景和作用。
toString()
方法在实际开发中有多种应用场景和作用,主要包括以下几个方面:
- 日志记录和调试信息输出:在日志记录中,使用 toString() 方法可以方便地将对象的状态信息记录下来;在调试过程中,重写了 toString() 方法的对象可以直接通过输出对象的字符串表示来查看对象的状态.
- 自定义对象的字符串表示:在需要将对象转换为字符串进行展示或输出时非常有用,比如在 GUI 界面中显示对象信息、将对象内容输出到文件或网络等场景。
- 打印和输出:当需要将对象的内容输出到控制台、日志文件或其他输出流时,可以使用 toString() 方法将对象转换为字符串输出。
- 集合类和数组的使用:在使用集合类(如 ArrayList、HashMap 等)或数组时,toString() 方法可以将集合类或数组中的元素转换为字符串表示,方便输出和查看集合或数组的内容。
- 对象的序列化和反序列化:在对象的序列化(将对象转换为字节序列以便存储或传输)和反序列化(将字节序列转换为对象)过程中,toString() 方法可以用来输出对象的字符串表示.
continue、break 和 return 的区别
continue
、break
和 return
是在编程中用于控制流程和跳出循环或方法的关键字。
1、continue
:
用于跳过当前循环中剩余的代码,直接进入下一次循环的迭代。换句话说,它会立即结束当前循环的本次迭代,然后继续执行下一次迭代。在循环中,continue 之后的代码将不会被执行,而是直接跳到下一次循环的起始点。
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue; // 当 i 等于 2 时跳过当前迭代,执行下一次迭代
}
System.out.println(i);
}
2、break:
在执行到 break 时,会立即退出循环,不再执行循环内部的后续代码,直接执行循环之后的代码。
for (int i = 0; i < 5; i++) {
if (i == 3) {
break; // 当 i 等于 3 时提前结束循环
}
System.out.println(i);
}
3、return
:
return
是用于从方法中返回结果并结束方法执行的关键字。当执行到 return 时,会立即结束当前方法的执行,并将指定的返回值(如果有)返回给调用者。return 不仅用于结束方法执行,还可以将方法的执行结果返回给调用者。如果方法声明了返回类型,那么 return 语句后面必须跟着与返回类型兼容的返回值。
public int sum(int a, int b) {
return a + b; // 返回 a 和 b 的和,并结束方法执行
}
总结
:
continue用于循环中跳过当前迭代步骤,进入下一次迭代。
break用于循环或switch语句中立即结束整个循环或switch语句的执行。
return用于函数或方法中结束执行,并返回结果给调用者。
反射
什么是反射,它的作用是什么?
Java 反射是指在运行时动态地获取类的信息、调用类的方法、操作类的属性等功能。反射使得程序可以在运行时获取类的信息并对其进行操作,而不需要在编译时知道具体的类名。
Java 反射的作用是增强程序的灵活性和扩展性。反射是框架的灵魂,在运行状态中,通过反射可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。
Java 反射主要涉及以下三个类:
a. Class 类:表示一个类或一个接口的运行时对象,可以获取类的信息和创建类的实例。
b. Constructor 类:表示类的构造方法,可以通过 Constructor 类创建类的实例。
c. Method 类:表示类的方法,可以通过 Method 类调用类的方法。
反射的步骤及原理
Java 反射的原理涉及到 Java 虚拟机(JVM)的类加载、类结构、字节码和运行时动态链接等方面。以下是 Java 反射的基本步骤及原理:
-
获取类的Class对象:在Java中,每个类都有一个对应的Class对象,反射机制就是通过获取一个类的Class对象来实现对这个类的操作。可以通过类名、对象、Class.forName()等方式获取类的Class对象。
-
加载类:当获取一个类的Class对象时,如果该类还没有被加载到内存中,Java虚拟机会先根据类的全限定名查找类文件并加载到内存中。
-
创建对象:获取了一个类的Class对象后,可以通过newInstance()方法创建该类的对象,newInstance()方法实际上调用了类的默认构造方法来创建对象。
-
获取方法、属性等信息:通过Class对象可以获取该类的所有方法、属性、构造方法等信息,这些信息都是存储在类的Class对象中的。
-
动态调用方法、修改属性等:获取到一个类的方法、属性等信息后,可以使用反射机制动态地调用方法、修改属性等。
反射机制的原理其实就是利用Java虚拟机的类加载机制,在程序运行时获取并操作类、对象、方法、属性等信息。反射机制的优点是可以动态地创建对象、调用方法、修改属性等,但也需要注意,反射机制会降低程序的性能,应该谨慎使用。
在反射中,如何获取一个类的class对象
要获取一个类的 Class 对象,可以使用 Java 反射提供的几种方式:
1、使用 .class 字面量:
对于已知类名的情况下,可以直接使用类名加上 .class 来获取 Class 对象。例如:
java Class<?> clazz = String.class;
2、使用 Class.forName() 方法:
如果类名是在运行时才确定的,可以使用 Class.forName(“ClassName”) 方法来获取 Class 对象。例如:
Class<?> clazz = Class.forName("java.lang.String");
3、使用类的对象的 getClass() 方法:
对于已经存在类的对象的情况下,可以通过调用对象的 getClass() 方法来获取该对象的 Class 对象。
String str = "Hello";
Class<?> clazz = str.getClass();
如何通过反射创建一个对象?
可以通过调用 Class 对象的 newInstance() 方法,使用构造方法来创建一个对象。
通过反射创建对象的步骤包括获取类的 Class 对象,然后利用该对象的构造函数来实例化对象。步骤如下:
- 1、获取类的 Class 对象
- 2、获取类的构造函数:使用 getConstructor() 方法或 getDeclaredConstructor() 方法获取类的构造函数对象。如果需要传入参数,则需要指定参数类型
- 3、实例化对象:使用构造函数对象的 newInstance() 方法来实例化对象。
Object instance = constructor.newInstance("example", 42);
完整的示例代码如下:
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
// 获取类的 Class 对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 获取类的构造函数
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 实例化对象
Object instance = constructor.newInstance("example", 42);
// 使用对象
MyClass myObject = (MyClass) instance;
myObject.doSomething();
}
}
什么是反射中的访问权限检查,如何绕过?
访问权限检查指的是 JVM 在执行反射操作时对类的访问权限进行的检查。具体来说,Java 反射中的方法(如获取字段、方法、构造函数等)可能会受到 Java 语言中访问修饰符(如 public、private、protected、default)的限制,而 JVM 在执行这些反射操作时会根据访问修饰符进行权限检查,确保反射操作符合类的访问控制规则。
例如,如果使用反射获取一个私有字段或调用一个私有方法,而当前类没有权限访问该字段或方法,JVM 就会抛出 IllegalAccessException 异常。
绕过反射中的访问权限检查并不被鼓励,因为这样做可能会绕过 Java 语言本身的安全性机制,导致安全风险和不稳定性。然而,有时确实需要通过反射访问类的私有成员或调用私有方法,可以通过以下方式绕过权限检查:
1、使用 setAccessible(true) 方法:可以通过反射的 setAccessible(true) 方法来绕过权限检查,使得原本私有的成员或方法可以被访问
Field field = MyClass.class.getDeclaredField("privateField");
field.setAccessible(true); // 绕过权限检查
Object value = field.get(myObject);
2、使用反射调用私有方法:同样可以使用 setAccessible(true) 方法来调用私有方法。
Method method = MyClass.class.getDeclaredMethod("privateMethod");
method.setAccessible(true); // 绕过权限检查
Object result = method.invoke(myObject);
反射和注解之间有什么联系?
可以通过反射获取注解,并根据注解信息进行相应的处理。
通过 Class、Method、Field 等反射类的方法,可以获取注解的类型和注解中定义的各种属性值。根据获取到的注解信息,动态地执行相应的逻辑。
许多框架和库(如 Spring、Hibernate 等)都大量使用了反射和注解的组合,通过注解标记配置信息,然后通过反射来读取注解信息并实现相应的功能,从而实现了灵活的配置和扩展。
注解和反射的结合可以实现运行时的动态处理,使得程序在运行时可以根据注解信息来动态地调整和处理逻辑,提高了程序的灵活性和可扩展性。
反射的应用场景
1、框架和库的设计与实现:许多流行的 Java 框架和库(如 Spring、Hibernate、JUnit 等)都广泛使用了反射。通过反射可以动态地加载和配置类、执行特定的方法、访问字段等,从而实现了框架和库的灵活性和可扩展性。
2、配置文件的解析与处理:反射可以用来解析和处理配置文件(如 XML、JSON 等格式),根据配置文件中的信息动态地加载类、执行方法、设置属性等。
3、动态代理:反射可以实现动态代理,即在运行时动态生成代理类来代理原始类的方法调用。动态代理常用于 AOP(面向切面编程)、事务管理等方面。
4、测试框架:测试框架(如 JUnit)中常用反射来动态执行测试方法,获取测试类和测试方法的注解信息,实现自动化测试和测试报告生成等功能。
5、注解处理器:反射可以用来处理注解信息,根据注解信息动态地生成代码、实现特定的功能或者进行特定的业务逻辑处理。注解处理器在许多框架和库中都有广泛应用。
注解
注解(Annotation)是 Java 5 引入的一种元数据机制,它提供了一种结构化的方式来为代码元素(如类、方法、字段等)添加元数据信息。注解在 Java 编程中广泛应用于描述代码的特性、行为、约束和配置信息,可以用来进行编译时检查、运行时处理、代码生成等操作。
如何定义注解
Java 注解使用 @interface
关键字来定义,语法如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
// 注解元素定义
public @interface MyAnnotation {
String value() default "default value"; // 默认值为 "default value"
int intValue() default 42; // 默认值为 42
Class<?> classValue() default Void.class; // 默认值为 Void 类型
String[] arrayValue() default {"value1", "value2"}; // 默认值为包含两个元素的字符串数组
}
}
在注解中,可以定义多个元素,每个元素可以有不同的类型,可以设置默认值或者不设置默认值。例如,上面的注解定义了一个名为 MyAnnotation 的注解,包含了四个注解元素。
在定义注解元素时,需要遵循以下规则:
- 注解元素的类型可以是基本类型、枚举类型、字符串、Class 类型、注解类型以及以上类型的数组。
- 注解元素的默认值使用 default 关键字指定。
- 注解元素的定义类似于接口的方法定义,不允许有参数列表和方法体。
使用注解
@MyAnnotation(value = "example", intValue = 10)
public class MyClass {
// 类体
}
在这个例子中,MyAnnotation 注解被应用在 MyClass 类上,同时指定了注解元素 value 和 intValue 的值。
自定义注解的应用场景?
1、标记特定方法或类:你可以使用自定义注解来标记特定的方法或类,以便在运行时进行识别和处理。
2、配置信息:自定义注解可以用于配置信息,例如配置文件的路径、数据库连接信息等。
3、代码生成:可以利用自定义注解生成一些代码,比如在编译时生成代码或者使用反射在运行时生成代码。
4、AOP(面向切面编程):自定义注解可以与 AOP 结合使用,实现在特定方法执行前后进行额外操作的功能。
Java 中常见的内置注解有哪些?
注解名称 | 作用 |
---|---|
@Override | 表示当前的方法定义将覆盖父类中的方法 |
@Deprecated | 表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告 |
@SuppressWarnings | 表示关闭编译器警告信息 |
@FunctionalInterface | 标记接口为函数式接口,只能包含一个抽象方法的注解。 |
@Retention | 指定注解的生命周期,在运行时保留该注解。 |
@Target | 指定注解可以应用的目标元素类型,如应用于方法。 |
@Documented | 用于告诉编译器将该注解的信息包含在生成的文档中,便于开发者查阅和理解。 |
@Inherited | 如果一个注解被 @Inherited 注解标记,那么子类会继承父类的这个注解。 |
@Repeatable | 如果一个注解被 @Repeatable 注解标记,那么可以多次使用该注解来修饰同一个目标。 |
注解的元注解有哪些?它们分别用于控制什么行为?
元注解是一种特殊的注解,用于注解其他注解。换句话说,元注解就是用来修饰注解的注解。Java 中的元注解有四种:@Retention、@Target、@Documented 和 @Inherited。这些元注解控制着注解本身的行为,例如指定注解的生命周期、指定注解可以应用的目标元素类型、指示注解是否包含在生成的文档中以及指示子类是否继承父类的注解。
1、@Retention:
用于指定注解的生命周期,控制注解在何时有效。它有三个取值:RetentionPolicy.SOURCE(在源代码中有效)、RetentionPolicy.CLASS(在编译时有效,默认值)、RetentionPolicy.RUNTIME(在运行时有效)。
2、@Target:用于指定注解可以应用的目标元素类型,例如类、方法、字段等。常见的取值包括 ElementType.TYPE(类、接口、枚举)、ElementType.FIELD(字段)、ElementType.METHOD(方法)等。
3、@Documented:用于指示注解应该包含在生成的文档中,方便开发者查阅。
4、@Inherited:用于指示子类是否继承父类的注解。如果一个注解被 @Inherited 注解标记,那么子类会继承父类的这个注解,否则子类不会继承父类的注解。
注解可能会带来哪些潜在的问题或者注意事项?
1、过度使用注解可能导致代码可读性下降,因为注解本身可能隐藏了代码的实际逻辑。
2、某些注解在运行时会影响性能,特别是那些需要进行反射处理的注解。
3、过度依赖注解可能导致代码与特定框架或工具绑定过紧,降低了代码的灵活性和可移植性。
4、过多的注解可能使代码难以维护和理解。
JWT(Json Web Token)
JWT(JSON Web Token)是一种用于在网络应用之间安全传输信息的开放标准。它通过在用户和服务器之间传递的 compact、self-contained JSON 对象来进行身份验证和信息传输。
JWT主要由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
头部(Header):包含了关于该JWT的元数据信息,例如令牌的类型(typ)、加密算法(alg)等。
载荷(Payload):包含了需要传输的信息,例如用户ID、角色等。
签名(Signature):通过使用头部中指定的加密算法和密钥对头部和载荷进行签名,用于验证令牌的完整性和真实性。
JWT的使用流程
1、用户登录时,服务器验证用户身份后,生成一个JWT并返回给客户端。
2、客户端收到JWT后,可以在后续的请求中将JWT放在请求头或者其他合适的位置发送给服务器。
3、服务器收到JWT后,验证JWT的签名和有效期,并从中提取需要的信息来进行操作,如用户身份认证、权限验证等。
JWT的用途
JWT(JSON Web Token)具有多种用途,主要包括:
1、身份认证:
JWT常用于进行用户身份认证。用户登录成功后,服务器生成一个JWT并返回给客户端,客户端在后续的请求中将JWT携带给服务器,服务器通过验证JWT的签名和有效期来确认用户的身份,从而实现无状态的身份认证机制。
2、授权和权限管理:
JWT可以包含用户的角色、权限等信息,服务器收到JWT后可以解析其中的信息来进行授权和权限管理,例如限制用户访问某些资源或执行某些操作。
3、信息交换:
JWT可以用于安全地在不同系统或服务之间传递信息。例如,在微服务架构中,可以使用JWT在不同的服务之间传递用户信息或其他必要的信息。
4、单点登录(SSO):
JWT可以作为单点登录机制的一部分,用户在一个应用登录后,可以通过JWT在多个相关的应用中实现无缝登录。
5、缓存用户信息:
服务器可以将一些常用的用户信息(例如用户ID、用户名等)存储在JWT中,并将其发送给客户端,在后续的请求中无需频繁查询数据库或进行其他操作,提高了性能和效率。
JWT的优缺点
优点:
- 无状态性:JWT是无状态的,服务器不需要在内存中保存会话信息,降低了服务器的负担,特别适用于分布式系统和微服务架构。
- 跨域支持:JWT可以在不同域之间安全地传递信息,解决了传统Cookie的跨域限制问题。
- 安全性:JWT使用签名进行验证,可以保证令牌的完整性和真实性,避免了被篡改的风险。
- 灵活性:JWT的载荷可以自定义,适用于各种场景,可以携带用户信息、权限信息等。
- 性能:由于JWT包含了所有必要的信息,减少了多次查询数据库的开销,提高了性能。
- 支持移动端:JWT可以在移动端和Web端都方便地使用,适用性广泛。
缺点:
- 令牌过期管理:JWT的令牌一旦签发,就无法撤销,需要合理设置有效期来避免安全问题。
- 传输数据量较大:由于JWT携带了信息和签名,所以相比于传统的身份认证方式(如Session),JWT的传输数据量较大。
- 无法解决并发登录问题:JWT是无状态的,无法直接解决并发登录的问题,需要配合其他机制进行处理。
- 不适合存储敏感信息:由于JWT是基于Base64编码的,虽然有签名保证完整性,但不建议将敏感信息存储在JWT中,以防止信息泄露。
- 刷新令牌机制复杂:如果要实现刷新令牌机制,需要额外的逻辑和工作,增加了复杂性。
JWT和Token区别
区别主要体现在接收的信息是否需要进入数据库查询信息。服务端验证客户端发来的token信息要进行数据的查询操作;JWT是一种特定格式的令牌(Token),JWT验证客户端发来的token信息不需要, JWT使用密钥校验不用数据库的查询。JWT是Token的一种特定格式和实现方式,用于在网络应用中进行身份认证和授权。
cookie、session、token
Cookie
Cookie是一种存储在用户计算机上的小型文本文件,由服务器发送给浏览器并存储在本地。它主要用于在客户端和服务器之间传递数据,如用户的身份标识、会话信息等。
Cookie的工作流程:
1、服务器发送Cookie:当用户通过浏览器访问某个网站时,服务器会在响应头中添加Set-Cookie字段,告诉浏览器要设置一个Cookie。
2、浏览器保存Cookie:浏览器接收到Set-Cookie字段后,会将Cookie存储在本地,通常是在用户的文件系统中的一个特定位置(如浏览器的Cookie存储目录)。
3、请求时发送Cookie:当用户再次访问该网站时,浏览器会自动将相关的Cookie信息包含在请求头中发送给服务器。
4、服务器解析Cookie:服务器收到请求后,可以解析Cookie中的信息,例如识别用户身份、保持用户登录状态等。
public String setCookie(HttpServletResponse response){
//创建cookie
Cookie cookie =new Cookie("code", CommunityUtil.generateUUID());
//设置cookie的生效范围
cookie.setPath("/community/alpha");
//设置cookie生存时间
cookie.setMaxAge(60*10); //60s*10
//发送cookie
response.addCookie(cookie);
return "set cookie";
}
Cookie的优缺点:
1、优点:
- 状态管理:Cookie可以用于实现状态管理,如保持用户登录状态、保存用户偏好设置等
- 持久性:可以设置Cookie的过期时间,使得数据可以在多次会话中保持持久性。
- 跨域支持:Cookie可以在同一域名下的不同路径之间共享信息,支持跨域访问。
2、缺点:
- 安全性:Cookie在传输过程中是明文的,存在被窃取的风险,因此不适合存储敏感信息。
- 大小限制:每个Cookie的大小通常有限制,大约在几KB到几MB之间,不能存储过大的数据。
- 性能开销:每次请求都会携带Cookie信息,增加了网络传输的数据量和服务器的处理压力。
Session
Session是一种在服务器端存储用户会话信息的机制,用于跟踪用户的会话状态。与Cookie不同,Session数据存储在服务器上而不是客户端(浏览器)上。
Session的工作流程
1、客户端请求:用户通过浏览器发送HTTP请求到服务器,请求访问某个页面或资源。
2、服务器创建Session并发送给客户端:服务器接收到请求后,如果发现该请求没有携带有效的Session ID(例如第一次访问或Session已过期),则会创建一个新的Session,并生成一个唯一的Session ID。服务器将生成的Session ID 通过响应头或者其他方式发送给客户端,通常是通过Cookie中的方式发送。
3、客户端存储Session ID:浏览器接收到Session ID后,将其存储在Cookie中(如果使用Cookie来存储Session ID),或者通过其他方式保存。
4、在后续的请求中,浏览器会自动将存储的 Session ID 发送给服务器。服务器根据 Session ID 找到对应的 Session 数据,处理请求,并向客户端发送响应。
//设置session
public String setSession(HttpSession session){
session.setAttribute("id",1);
session.setAttribute("name","sessionTest");
return "set session";
}
session的优缺点
1、优点:
- 安全性高:Session数据存储在服务器端,相对于存储在客户端的Cookie更安全,减少了被篡改或盗取的风险。
- 隐私保护:由于Session数据不暴露在客户端,可以存储一些敏感信息,如用户登录状态、购物车信息等。
- 灵活性:Session数据可以存储在服务器的内存、数据库或其他持久化存储介质中,可以根据需求灵活选择存储方式。
- 无状态性:由于Session数据存储在服务器端,不依赖于客户端,实现了无状态的HTTP协议。
2、缺点:
- 服务器资源占用:Session数据存储在服务器端,会占用服务器的资源,特别是在高并发访问时,可能会增加服务器的负担。
- 性能开销:由于每个用户会话对应一个Session ID,服务器需要维护Session ID与对应会话数据的映射关系,会增加性能开销。
- 扩展性:对于分布式系统或负载均衡的环境,需要额外考虑Session共享或Session复制的问题,增加了系统设计的复杂性。
Token
Token通常指的是身份验证或授权过程中使用的令牌,用于在客户端和服务器之间传递身份信息或权限信息。它可以是一段随机生成的字符串,也可以是包含用户信息和权限信息的加密数据。常见的Token包括JWT(JSON Web Token)和OAuth Token等。
Token的主要作用是实现无状态的身份验证和授权。
在使用Token时,通常会涉及到生成Token、验证Token、刷新Token等流程,用于实现用户身份验证、访问权限控制、单点登录等功能。例如,在Web应用中,用户登录成功后会生成一个Token并发送给客户端,客户端在后续请求中携带Token进行身份验证和权限验证。
Cookie 、 Session、Token的区别
特点 | Session | Cookie | Token |
---|---|---|---|
存储位置 | 服务器端 | 客户端(浏览器) | 客户端(通常存储在本地存储或内存中) |
数据大小限制 | 通常无明确大小限制 | 通常几KB到几MB | 可根据实际需求灵活设置 |
安全性 | 相对较高,存储在服务器端 | 相对较低,易被窃取或篡改 | 相对较高,可使用加密等方式提高安全性 |
隐私保护 | 较强,不暴露在客户端 | 较弱,存储在客户端 | 较强,不暴露在客户端 |
扩展性 | 适用于分布式系统和负载均衡环境 | 适用于跨域访问 | 适用于分布式系统和跨域应用 |
性能开销 | 较大,需要维护Session ID与数据的映射关系 | 较小 | 较小,通常是短期有效的令牌 |
生命周期 | 可设置过期时间或根据浏览器关闭自动销毁 | 可设置过期时间 | 可设置过期时间或根据实际需求灵活设置 |