-
SSL/TLS(弱加密)抓包解密办法
-
大家好,首先祝大家新年快乐,身体健康,代码没bug,投产顺利。本次整理带来的有关于mybatis,java,spring,springboot,linux,shell,Python,HarmonyOS,算法等多种类技术干货,希望可以帮到大家。 1.MyBatis 探秘之#{} 与 ${} 参传差异解码(数据库连接池筑牢数据交互根基)【转】 https://bbs.huaweicloud.com/forum/thread-0296171007849327168-1-1.html 2.scala中正则表达式的使用详解【转】 https://bbs.huaweicloud.com/forum/thread-02119171007725887139-1-1.html 3.SpringBoot实现websocket服务端及客户端的详细过程【转】 https://bbs.huaweicloud.com/forum/thread-0217171007411153148-1-1.html 4.spring 参数校验Validation示例详解【转】 https://bbs.huaweicloud.com/forum/thread-02109171007351771185-1-1.html 5.java集成kafka实例代码【转】 https://bbs.huaweicloud.com/forum/thread-02112171007196458168-1-1.html 6.SpringBoot中Get请求和POST请求接收参数示例详解【转】 https://bbs.huaweicloud.com/forum/thread-02112171007106397167-1-1.html 7.Java中StopWatch工具类的用法详解【转】 https://bbs.huaweicloud.com/forum/thread-0263171006880471176-1-1.html 8.Linux下shell基本命令之grep用法及示例小结【转】 https://bbs.huaweicloud.com/forum/thread-02119170989547937131-1-1.html 9.Python使用PIL库拼接图片的详细教程【转】 https://bbs.huaweicloud.com/forum/thread-02119170989479126130-1-1.html 10.bash shell的条件语句详解【转】 https://bbs.huaweicloud.com/forum/thread-0217170989387999141-1-1.html 11.pandas数据缺失的两种处理办法【转】 https://bbs.huaweicloud.com/forum/thread-02112170989326183158-1-1.html 12.Python使用PyQt5实现中英文切换功能【转】 https://bbs.huaweicloud.com/forum/thread-0263170989204908173-1-1.html 13.python螺旋数字矩阵的实现示例【转】 https://bbs.huaweicloud.com/forum/thread-0241170989116650156-1-1.html 14.使用Python实现文件查重功能【转】 https://bbs.huaweicloud.com/forum/thread-02109170989032191180-1-1.html 15.Linux内核验证套件(LKVS) https://bbs.huaweicloud.com/forum/thread-0217170613884758126-1-1.html 16.三大排序算法:插入排序、希尔排序、选择排序 https://bbs.huaweicloud.com/forum/thread-0263170613042983158-1-1.html 17.【Linux】多用户协作-转载 https://bbs.huaweicloud.com/forum/thread-02109170612919963145-1-1.html 18.SSH可以连接但sftp确无法链接,有可能是防火墙的问题吗-转载 https://bbs.huaweicloud.com/forum/thread-0217170612856550125-1-1.html 19.【Linux】线程同步与互斥 (生产者消费者模型-转载 https://bbs.huaweicloud.com/forum/thread-02112170612824528138-1-1.html 20.【HarmonyOS】公司鸿蒙项目收工总结之《组件》 https://bbs.huaweicloud.com/forum/thread-0263170475389496136-1-1.html 21.【HarmonyOS】高仿华为阅读翻页 https://bbs.huaweicloud.com/forum/thread-02109170473860592128-1-1.html 22.【HarmonyOS】仿iOS线性渐变实现 https://bbs.huaweicloud.com/forum/thread-0241170473759150128-1-1.html 23.【HarmonyOS】利用emitter封装工具类 https://bbs.huaweicloud.com/forum/thread-0276170473613513148-1-1.html 24.【HarmonyOS】多Toast显示工具类 https://bbs.huaweicloud.com/forum/thread-0217170472563245114-1-1.html 25.【HarmonyOS】头像裁剪圆形遮罩效果实现demo https://bbs.huaweicloud.com/forum/thread-0276170472467118147-1-1.html
-
stopWatch 是org.springframework.util 包下的一个工具类,使用它可直观的输出代码执行耗时,以及执行时间百分比,下面就跟随小编一起来看看它的具体用法吧stopWatch 是org.springframework.util 包下的一个工具类,使用它可直观的输出代码执行耗时,以及执行时间百分比。在未使用这个工具类之前,如果我们需要统计某段代码的耗时,我们会这样写:public static void main(String[] args) throws InterruptedException { test0(); } public static void test0() throws InterruptedException { long start = System.currentTimeMills(); // do something Thread.sleep(100); long end = System.currentTimeMills(); long start2 = System.currentTimeMills(); // do somethind long end2 = System.currentTimeMills(); System.out.println("某某1执行耗时:" + (end -start)); System.out.println("某某2执行耗时:" + (end2 -start2)); }如果改用stopWatch 实现的一个示例public class StopWatchDemo { public static void main(String[] args) throws InterruptedException { test1(); } public static void test1() throws InterruptedException { StopWatch sw = new StopWatch("test"); sw.start("task1"); // do something Thread.sleep(100); sw.stop(); sw.start("task2"); // do someting Thread.sleep(200); sw.stop(); System.out.println("sw.prettyPrint()-------"); System.out.println(sw.prettyPrint()); } }运行结果如下:sw.prettyPrint()------StopWatch 'test': running time (millis) = 310-----------------------------------------ms % Task name-----------------------------------------00110 035% task100200 065% task2通过start 与stop 方法分别记录开始时间与结束时间,其中在记录结束时间的时候,会维护一个链表类型的taskList 属性,从而时该类可记录多个任务,最后的输出页仅仅是对之前的记录信息做了一个统一的归纳输出。
-
本文详细介绍了SpringBoot的核心特性,包括约定优于配置、自动配置机制、日志框架的使用,以及源码分析部分,如自动配置的加载过程、SpringBoot启动流程、数据源和Mybatis的自动配置。此外,还涉及到了SpringBoot的缓存管理和数据访问解析,包括JSR107标准和Spring缓存注解的使用。 一、SpringBoot简介 1.1 原有Spring优缺点分析 1.1.1 Spring的优点分析 Spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品。无需开发重量级的Enterprise JavaBean(EJB),Spring为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java对象(Plain Old Java Object,POJO)实现了EJB的功能。 1.1.2 Spring的缺点分析 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring用XML配置,而且是很多XML配置。Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring 3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。 所有这些配置都代表了开发时的损耗。因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但与此同时它要求的回报也不少。 除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。 1.2 SpringBoot的概述 1.2.1 SpringBoot解决上述Spring的缺点 SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。 1.2.2 SpringBoot的特点 为基于Spring的开发提供更快的入门体验 开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等 SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式 1.2.3 SpringBoot的核心功能 起步依赖 起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。 简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。 自动配置 Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。 简单项目结构 SpringBoot项目简单结构如下: ├── demo1 │ ├── pom.xml # 依赖管理 │ ├── src │ │ ├── main │ │ │ ├── java # 核心代码 │ │ │ │ └── online │ │ │ │ └── yuluo │ │ │ │ └── demo1 │ │ │ │ ├── Demo1Application.java │ │ │ │ ├── controller │ │ │ │ ├── dao │ │ │ │ └── service │ │ │ └── resources #项目资源文件 │ │ │ ├── application.properties #项目配置文件 可用application.yml代替 │ │ │ ├── static #静态资源文件 │ │ │ └── templates #模板文件,可存放邮件、SMS等模板 │ │ └── test #测试 │ │ └── java │ │ └── online │ │ └── yuluo │ │ └── demo1 │ │ └── Demo1ApplicationTests.java │ └── target #打包目标生成文件 ———————————————— 原文链接:https://blog.csdn.net/weixin_44935456/article/details/108475035
-
一、简介 1.1、什么是SpringBoot 我们知道,从 2002 年开始,Spring 一直在飞速的发展,如今已经成为了在Java EE(Java Enterprise Edition)开发中真正意义上的标准,但是随着技术的发展,Java EE使用 Spring 逐渐变得笨重起来,大量的 XML 文件存在于项目之中。繁琐的配置,整合第三方框架的配置问题,导致了开发和部署效率的降低。 2012 年 10 月,Mike Youngstrom 在 Spring jira 中创建了一个功能请求,要求在 Spring 框架中支持无容器 Web 应用程序体系结构。他谈到了在主容器引导 spring 容器内配置 Web 容器服务。这是 jira 请求的摘录: 我认为 Spring 的 Web 应用体系结构可以大大简化,如果它提供了从上到下利用 Spring 组件和配置模型的工具和参考体系结构。在简单的 main()方法引导的 Spring 容器内嵌入和统一这些常用Web 容器服务的配置。 这一要求促使了 2013 年初开始的 Spring Boot 项目的研发,到今天,Spring Boot 的版本已经到了 2.0.3 RELEASE。Spring Boot 并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。 它集成了大量常用的第三方库配置,Spring Boot应用中这些第三方库几乎可以是零配置的开箱即用(out-of-the-box),大部分的 Spring Boot 应用都只需要非常少量的配置代码(基于 Java 的配置),开发者能够更加专注于业务逻辑。 1.2.、特性 快速创建独立 Spring 应用 SSM:导包、写配置、启动运行 直接嵌入Tomcat、Jetty or Undertow(无需部署 war 包)【Servlet容器】 linux java tomcat mysql: war 放到 tomcat 的 webapps下 jar: java环境; java -jar 重点:提供可选的starter,简化应用整合 场景启动器(starter):web、json、邮件、oss(对象存储)、异步、定时任务、缓存... 导包一堆,控制好版本。 为每一种场景准备了一个依赖; web-starter。mybatis-starter 重点:按需自动配置 Spring 以及 第三方库 如果这些场景我要使用(生效)。这个场景的所有配置都会自动配置好。 约定大于配置:每个场景都有很多默认配置。 自定义:配置文件中修改几项就可以 提供生产级特性:如 监控指标、健康检查、外部化配置等 监控指标、健康检查(k8s)、外部化配置 无代码生成、无xml 1.3、四大核心 自动配置、起步依赖、Actuator、命令行界面 1.4、特点 简化开发,简化配置,简化整合,简化部署,简化监控,简化运维。 二、快速入门 2.1、开发流程 2.1.1、创建项目 maven项目 <!-- 所有springboot项目都必须继承自 spring-boot-starter-parent --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.5</version> </parent> 2.1.2、导入场景 场景启动器 <dependencies> <!-- web开发的场景启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> 2.1.3、主程序 @SpringBootApplication //这是一个SpringBoot应用 public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } } 2.1.4、业务 @RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "Hello,Spring Boot 3!"; } } 2.1.5、测试 默认启动访问:localhost:8080 三、Spring Initializr 创建向导 一键创建好整个项目结构 3.1、依赖管理机制 3.1.1、为什么导入starter-web所有相关依赖都导入进来? 开发什么场景,导入什么场景启动器。 maven依赖传递原则。A-B-C: A就拥有B和C 导入 场景启动器。 场景启动器 自动把这个场景的所有核心依赖全部导入进来 3.1.2、为什么版本号不用写? 每个boot项目都有一个父项目spring-boot-starter-parent parent的父项目是spring-boot-dependencies 父项目 版本仲裁中心,把所有常见的jar的依赖版本都声明好了。 比如:mysql-connector-j 3.1.3、自定义版本号 利用maven的就近原则 直接在当前项目properties标签中声明父项目用的版本属性的key 直接在导入依赖的时候声明版本 3.1.4、第三方的jar包 boot父项目没有管理的需要自行声明好 <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency> 四、自动配置机制 4.1、 初步理解 自动配置的Tomcat、SpringMVC等 导入场景,容器中就会自动配置好这个场景的核心组件。 以前:DispatcherServlet、ViewResolver、CharacterEncodingFilter…… 现在:自动配置好的这些组件 验证:容器中有了什么组件,就具有什么功能 public static void main(String[] args) { //java10: 局部变量类型的自动推断 var ioc = SpringApplication.run(MainApplication.class, args); //1、获取容器中所有组件的名字 String[] names = ioc.getBeanDefinitionNames(); //2、挨个遍历: // dispatcherServlet、beanNameViewResolver、characterEncodingFilter、multipartResolver // SpringBoot把以前配置的核心组件现在都给我们自动配置好了。 for (String name : names) { System.out.println(name); } } 默认的包扫描规则 @SpringBootApplication 标注的类就是主程序类 SpringBoot只会扫描主程序所在的包及其下面的子包,自动的component-scan功能 自定义扫描路径 @SpringBootApplication(scanBasePackages = "com.atguigu") @ComponentScan("com.atguigu") 直接指定扫描的路径 配置默认值 配置文件的所有配置项是和某个类的对象值进行一一绑定的。 绑定了配置文件中每一项值的类: 属性类。 比如: ServerProperties绑定了所有Tomcat服务器有关的配置 ....参照官方文档:或者参照 绑定的 属性类。 MultipartProperties绑定了所有文件上传相关的配置 按需加载自动配置 导入场景spring-boot-starter-web 场景启动器除了会导入相关功能依赖,导入一个spring-boot-starter,是所有starter的starter,基础核心starter spring-boot-starter导入了一个包 spring-boot-autoconfigure。包里面都是各种场景的AutoConfiguration自动配置类 虽然全场景的自动配置都在 spring-boot-autoconfigure这个包,但是不是全都开启的。 导入哪个场景就开启哪个自动配置 总结: 导入场景启动器、触发 spring-boot-autoconfigure这个包的自动配置生效、容器中就会具有相关场景的功能 4.2、 完整流程 自动配置流程细节梳理: 1、导入starter-web:导入了web开发场景 1、场景启动器导入了相关场景的所有依赖:starter-json、starter-tomcat、springmvc 2、每个场景启动器都引入了一个spring-boot-starter,核心场景启动器。 3、核心场景启动器引入了spring-boot-autoconfigure包。 4、spring-boot-autoconfigure里面囊括了所有场景的所有配置。 5、只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。 6、SpringBoot默认却扫描不到 spring-boot-autoconfigure下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包。 2、主程序:@SpringBootApplication 1、@SpringBootApplication由三个注解组成@SpringBootConfiguration、@EnableAutoConfiguratio、@ComponentScan 2、SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure包中官方写好的配置类 3、@EnableAutoConfiguration:SpringBoot 开启自动配置的核心。 1. 是由@Import(AutoConfigurationImportSelector.class)提供功能:批量给容器中导入组件。 2. SpringBoot启动会默认加载 142个配置类。 3. 这142个配置类来自于spring-boot-autoconfigure下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件指定的 项目启动的时候利用 @Import 批量导入组件机制把 autoconfigure 包下的142 xxxxAutoConfiguration类导入进来(自动配置类) 虽然导入了142个自动配置类 4、按需生效: 并不是这142个自动配置类都能生效 每一个自动配置类,都有条件注解@ConditionalOnxxx,只有条件成立,才能生效 3、xxxxAutoConfiguration自动配置类 1、给容器中使用@Bean 放一堆组件。 2、每个自动配置类都可能有这个注解@EnableConfigurationProperties(ServerProperties.class),用来把配置文件中配的指定前缀的属性值封装到 xxxProperties属性类中 3、以Tomcat为例:把服务器的所有配置都是以server开头的。配置都封装到了属性类中。 4、给容器中放的所有组件的一些核心参数,都来自于xxxProperties。xxxProperties都是和配置文件绑定。 只需要改配置文件的值,核心组件的底层参数都能修改 ———————————————— 原文链接:https://blog.csdn.net/nine06/article/details/136487114
-
Spring Boot是Spring生态系统中的重要组成部分,它极大地简化了Spring应用的开发和配置。本文将详细介绍Spring Boot的核心概念、关键特性及其在实际开发中的应用,帮助读者全面掌握Spring Boot的使用。 1. Spring Boot简介 1.1 什么是Spring Boot? Spring Boot是由Pivotal团队开发的基于Spring框架的项目,旨在简化新Spring应用的初始搭建及开发过程。通过提供一系列默认配置和自动化功能,Spring Boot可以大幅减少配置文件的数量和复杂度,使开发者能够专注于业务逻辑的实现。 1.2 Spring Boot的历史背景 Spring Boot最早于2014年发布,其设计初衷是为了应对复杂的企业级应用开发中频繁出现的配置冗余和重复代码问题。通过Spring Boot,开发者可以更快地启动一个新项目,并迅速进入实际开发阶段。 1.3 Spring Boot的核心特点 自动配置:Spring Boot自动配置机制能根据类路径中的依赖和环境,自动配置Spring应用程序。 独立运行:Spring Boot应用可以打包成JAR文件并独立运行,不依赖外部的应用服务器。 生产就绪:内置的监控、健康检查及外部配置功能,使应用能够在生产环境中平稳运行。 简化的依赖管理:通过Spring Boot Starter简化依赖管理和版本控制。 2. Spring Boot的核心概念 2.1 自动配置 自动配置是Spring Boot的核心特性之一。它通过@EnableAutoConfiguration注解实现,根据类路径中的依赖自动配置合适的Spring组件。 2.1.1 自动配置原理 Spring Boot的自动配置通过扫描META-INF/spring.factories文件,加载其中定义的自动配置类。每个自动配置类都会根据一定的条件(如类路径中是否存在特定的类或Bean)来决定是否生效。 2.1.2 自定义配置 虽然自动配置为开发者提供了极大的便利,但有时需要自定义配置以满足特定需求。可以通过以下几种方式进行自定义配置: 配置属性:在application.properties或application.yml文件中配置属性。 配置类:创建配置类并使用@Configuration注解。 排除自动配置:通过@SpringBootApplication(exclude = ...)注解排除特定的自动配置类。 2.2 Spring Boot Starter Spring Boot Starter是Spring Boot提供的依赖管理机制,通过预定义的一组依赖,简化项目中的依赖管理。例如,spring-boot-starter-web包含了开发Web应用所需的所有基本依赖。 2.3 Spring Boot CLI Spring Boot CLI(命令行界面)是一个用于快速创建、运行和测试Spring Boot应用的工具。通过Spring Boot CLI,开发者可以使用Groovy脚本快速搭建Spring Boot应用。 3. Spring Boot的主要功能模块 3.1 Web开发 Spring Boot通过spring-boot-starter-web提供了简便的Web开发支持。这个Starter包括Spring MVC、Jackson和Tomcat(默认嵌入式容器)。 3.1.1 Spring MVC Spring MVC是Spring框架的核心Web模块,支持创建基于注解的Web应用。通过Spring Boot,开发者可以轻松配置和使用Spring MVC。 示例: @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello, Spring Boot!"; } } 3.1.2 嵌入式服务器 Spring Boot默认使用Tomcat作为嵌入式服务器,但也支持Jetty和Undertow。嵌入式服务器使应用可以打包成JAR文件,并通过简单的命令运行: java -jar myapp.jar 3.2 数据访问 Spring Boot提供了一整套便捷的数据访问解决方案,包括Spring Data JPA、JDBC和Redis等。 3.2.1 Spring Data JPA Spring Data JPA通过spring-boot-starter-data-jpa简化了JPA的使用。只需简单配置即可连接数据库并进行CRUD操作。 示例: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // getters and setters } public interface UserRepository extends JpaRepository<User, Long> { } 3.2.2 数据库配置 在application.properties中配置数据库连接信息: spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=root spring.datasource.password=secret spring.jpa.hibernate.ddl-auto=update 3.3 安全管理 Spring Boot通过spring-boot-starter-security提供了Spring Security的默认配置,使应用能够轻松实现认证和授权功能。 3.3.1 基本安全配置 默认情况下,Spring Security会保护所有的HTTP端点,需要用户进行身份验证。可以通过自定义配置类来调整安全设置: @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); } } 3.4 测试支持 Spring Boot提供了强大的测试支持,包括单元测试和集成测试工具。 3.4.1 单元测试 使用@SpringBootTest注解,可以方便地加载Spring应用上下文进行测试: @RunWith(SpringRunner.class) @SpringBootTest public class MyApplicationTests { @Autowired private MockMvc mockMvc; @Test public void testHelloEndpoint() throws Exception { mockMvc.perform(get("/hello")) .andExpect(status().isOk()) .andExpect(content().string("Hello, Spring Boot!")); } } 3.4.2 集成测试 Spring Boot集成测试可以测试应用的整个运行环境,包括数据库连接和Web服务器: @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class MyApplicationTests { @LocalServerPort private int port; @Test public void testHomePage() throws Exception { URL url = new URL("http://localhost:" + port + "/"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); assertEquals(200, connection.getResponseCode()); } } 4. Spring Boot实战案例 4.1 创建一个简单的RESTful API 4.1.1 项目结构 src └── main ├── java │ └── com.example.demo │ ├── DemoApplication.java │ ├── controller │ │ └── UserController.java │ ├── model │ │ └── User.java │ └── repository │ └── UserRepository.java └── resources └── application.properties 4.1.2 代码实现 DemoApplication.java @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } User.java @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; // getters and setters } UserRepository.java public interface UserRepository extends JpaRepository<User, Long> { } UserController.java @RestController @RequestMapping("/users") public class UserController { @Autowired private UserRepository userRepository; @GetMapping public List<User> getAllUsers() { return userRepository.findAll(); } @PostMapping public User createUser(@RequestBody User user) { return userRepository.save(user); } @GetMapping("/{id}") public User getUserById(@PathVariable Long id) { return userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("User not found")); } @PutMapping("/{id}") public User updateUser(@PathVariable Long id, @RequestBody User userDetails) { User user = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("User not found")); user.setName(userDetails.getName()); user.setEmail(userDetails.getEmail()); return userRepository.save(user); } @DeleteMapping("/{id}") public ResponseEntity<?> deleteUser(@PathVariable Long id) { User user = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("User not found")); userRepository.delete(user); return ResponseEntity.ok().build(); } } 4.1.3 配置文件 application.properties spring.datasource.url=jdbc:mysql://localhost:3306/demo spring.datasource.username=root spring.datasource.password=secret spring.jpa.hibernate.ddl-auto=update 5. 总结 Spring Boot通过提供自动配置、简化依赖管理和独立运行等特性,大大提高了开发效率,使得构建和部署Spring应用变得更加简单。本文介绍了Spring Boot的核心概念和主要功能模块,并通过一个简单的RESTful API示例展示了Spring Boot的实际应用。掌握Spring Boot的使用,不仅可以提升开发效率,还能更好地应对复杂的企业级应用开发需求。 Spring Boot的生态系统仍在不断发展和完善,未来的版本将引入更多新特性和改进。通过不断学习和实践,开发者可以充分利用Spring Boot的优势,构建高质量的Java应用程序。 ———————————————— 原文链接:https://blog.csdn.net/Easonmax/article/details/139358071
-
本文介绍了Spring Boot的发展历程、核心概念及其与Spring的关系。Spring Boot简化了Spring应用程序的开发,通过自动配置和starter依赖,解决了Spring配置复杂的问题。文章详细阐述了Spring的历史,Spring Boot的诞生背景,以及它如何解决Spring的配置难题,成为微服务时代的热门框架。此外,还探讨了Spring Boot与Spring MVC、微服务和Spring Cloud的关系,以及使用Spring Boot的八大理由。 Spring Boot是一个全新的框架,是用来简化Spring应用的初始搭建及开发过程的,可以使用特定的方式来进行配置,可使得开发人员不在需要定义样板化的配置,所以,Spring boot能够大大简化开发模式,学习spring boot可以将你想集成的常用框架,都有对应的组件支持。 Springboot如何系统学习? 1、理论联系实践 在很多时候,我们接触到一个新的技术的时候,最开始肯定是被这些技术涉及到的术语、词汇所困扰,不明白这些技术术语词汇的定义、概念、含义,没有这些做根基,就很难做到掌握和学习这个技术,并达到融汇贯通的程度。所以学习SpringBoot,首先就要从宏观的层面上,去了解这个技术它的背景知识、运用场景、发展渊源,演进历史等。 2、多访问官方网站了解官方定义和解读 建议访问spring官网,获取最权威的介绍和定义。 3、全面系统的从基础知识入手,包括但不仅限于如下知识点: 框架原理介绍 框架环境搭建 快速入门 创建Bean的方式及实现原理 Bean种类 Bean生命周期 Bean的作用域 Bean的注值方式 SpEL 整合Junit测试 Web项目集成Spring 注解装配Bean AOP思想、原理解剖 传统方式实现AOP开发 AspectJ介绍及实现AOP开发 使用Spring Boot有什么好处? 其实就是简单、快速、方便!平时如果我们需要搭建一个Spring Web项目的时候需要怎么做呢? 1)配置web.xml,加载Spring和Spring mvc 2)配置数据库连接、配置Spring事务 3)配置加载配置文件的读取,开启注解 4)配置日志文件 配置完成之后部署Tomcat调试 现在非常流行微服务,如果我这个项目仅仅只是需要发送一个邮件,如果我的项目仅仅是生产一个积分;我都需要这样折腾一遍! 但是如果使用Spring Boot呢? 很简单,我仅仅只需要非常少的几个配置就可以迅速方便的搭建起来一套Web项目或者是构建一个微服务! SpringBoot所具备的特征 (1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs; (2)内嵌Tomcat或Jetty等Servlet容器; (3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置; (4)尽可能自动配置Spring容器; (5)提供准备好的特性,如指标、健康检查和外部化配置; (6)绝对没有代码生成,不需要XML配置。 Spring Boot官方提供了很多Starter组件,涉及Web、模板引擎、SQL、NoSQL、缓存、验证、日志、测试、内嵌容器,还提供了事务、消息、安全、监控、大数据等支持。前面模块会在本书中一一介绍,后面这些模块本书不会涉及,如需自行请参看Spring Boot官方文档。 每个模块会有多种技术实现选型支持,来实现各种复杂的业务需求: Web:Spring Web、Spring WebFlux等 模板引擎:Thymeleaf、FreeMarker、Groovy、Mustache等 SQL:MySQL、H2等 NoSQL:Redis、MongoDB、Cassandra、Elasticsearch等 验证框架:Hibernate Validator、Spring Validator等 日志框架:Log4j2、Logback等 测试:JUnit、Spring Boot Test、AssertJ、Mockito等 内嵌容器:Tomcat、Jetty、Undertow等 另外,Spring WebFlux框架目前支持Servlet 3.1以上的Servlet容器和Netty,各种模块组成了Spring Boot 2.x的工作常用技术栈,如图1-1所示。 动力节点的 SpringBoot入门教程由浅入深,手把手带你学习Spring Boot,体验Spring Boot的极速开发过程,内容丰富,涵盖了SpringBoot开发的方方面面,并且同步更新到Spring 2.x版本。 ———————————————— 原文链接:https://blog.csdn.net/Javanewspaper/article/details/121422135
-
Spring Framework 是跨平台 Java/Spring 应用程序开发框架,也是 J2EE(Java 2 Platform, Enterprise Edition) 轻量级框架,其 Spring 平台为 Java 开发者提供了全面的基础设施支持。 Spring 许多基础组件的代码是轻量级,但其配置依旧是重量级的。它是怎么解决了呢?当然是 Spring Boot,Spring Boot 提供了新的编程模式,让开发 Spring 应用变得更加简单方便。本书将会由各个最佳实践案例驱动,涉及 Spring Boot 开发相关方面。下面先了解下 Spring Boot 框架。1.1 Spring Boot 是什么Spring Boot (Boot 顾名思义,引导的意思)框架是简化 Spring 应用从搭建到开发的过程。应用开箱即用,只要通过一个指令,包括命令行 java -jar 、SpringApplication 应用启动类 、 Spring Boot Maven 插件等,就可以启动应用了。另外,Spring Boot 强调只需要很少的配置文件,所以在开发生产级 Spring 应用中,让开发变得高效和简易。1.1.1 Spring Boot 2.x 特性那么 Spring Boot 2.x 具有哪些生产的特性呢?常用的特性如下:SpringApplication 应用类自动配置外化配置内嵌容器Starter 组件还有对日志、Web、消息、测试及扩展等支持。SpringApplicationSpringApplication 是 Spring Boot 应用启动类,在 main() 方法中调用 SpringApplication.run() 静态方法,即可运行一个 Spring Boot 应用。简单使用代码片段如下:public static void main(String[] args) { SpringApplication.run(QuickStartApplication.class, args); }AI助手Spring Boot 运行的应用是独立的一个 Jar 应用,实际上在运行时启动了应用内部内嵌容器,容器初始化 Spring 环境及其组件。也可以使用 Spring Boot 开发传统的应用,只要通过 Spring Boot Maven 插件将 Jar 应用转换成 War 应用即可。自动配置Spring Boot 在不需要任何配置情况下,就直接可以运行一个应用。实际上,Spring Boot 框架的 spring-boot-autoconfigure 依赖做了很多默认的配置项,即应用默认值。这种模式叫做 “自动配置”。Spring Boot 自动配置会根据添加的依赖,自动加载依赖相关的配置属性。例如,默认用的内嵌式容器是 Tomcat 并端口设置为 8080。外化配置Spring Boot 简化了配置,基本在 application.properties 文件配置常用的应用属性。Spring Boot 可以将配置外部化,这种模式叫做 “外化配置”。将配置从代码中分离外置,最明显的作用是只有简单地修改下外化配置文件,就可以在不同环境中,可以运行相同的应用代码。配置相关的会在第 2 章进行实践介绍。内嵌容器Spring Boot 启动应用,默认情况下是自动启动了内嵌容器 Tomcat,并且自动设置了端口为 8080。另外还提供了对 Jetty、Undertow 等容器的支持。开发者自行在添加对应的容器 Starter 组件,即可配置对应内嵌容器实例。Starter 组件Starter 组件,其开箱即用,是 Spring Boot 重要的组成部分。实际上,Starter 组件是一组可以被加载在应用中的 Maven 依赖项,只需要对应在 Maven 配置中添加依赖配置,即可开启对应依赖使用。例如,添加 spring-boot-starter-web 依赖,就可用于构建 RESTful Web 服务,其包含了 Spring MVC 和 Tomcat 内嵌容器等。其实,开发中很多功能是通过添加 Starter 组件的方式来进行实现。那么,Spring Boot 2.x 常用的 Starter 组件有哪些呢?Spring Boot 2.x Starter 组件Spring Boot 官方提供了很多 Starter 组件,涉及 Web、模板引擎、SQL 、NoSQL、缓存、验证、日志、测试、内嵌容器等,还提供了事务、消息、安全、监控、大数据等支持。原文链接:https://blog.csdn.net/Majker/article/details/84728820
-
本教程展示了如何使用Java JWT库(JJWT)轻松生成和验证JSON Web Tokens(JWT)。通过简单的代码示例,解释了JWT的创建、解码和验证过程,包括设置哈希算法、添加声明以及使用JUnit进行测试。 “我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。 Java对JWT(JSON Web令牌)的支持过去需要进行大量工作:广泛的自定义,花费数小时来解决依赖项,以及仅用于组装简单JWT的代码页。 不再! 本教程将向您展示如何使用现有的JWT库做两件事: 生成一个JWT 解码并验证JWT 您会注意到本教程很短。 那是因为那很容易。 如果您想更深入地了解,请查看JWT规范或深入阅读有关在Spring Boot应用程序中使用JWT进行令牌身份验证的更长的文章 。 什么是JWT? JSON Web令牌是JSON对象,用于在各方之间以紧凑和安全的方式发送信息。 JSON规范 (或Javascript对象表示法)定义了一种使用键值对创建纯文本对象的方法。 这是一种构造基于原始类型(数字,字符串等)的数据的紧凑方法。 您可能已经非常熟悉JSON。 就像没有括号的XML。 令牌可用于在各方之间发送任意状态。 这里的“当事人”通常是指客户端Web应用程序和服务器。 JWT具有多种用途:身份验证机制,URL安全编码,安全共享私有数据,互操作性,数据过期等。 实际上,此信息通常与两件事有关:授权和会话状态。 服务器可以使用JWT告诉客户端应用程序允许用户执行哪些操作(或允许他们访问哪些数据)。 JWT通常还用于存储Web会话的状态相关用户数据。 因为JWT是在客户端应用程序和服务器之间来回传递的,这意味着状态数据不必存储在某个地方的数据库中(随后在每个请求中都可以检索到); 因此,它可以很好地扩展。 让我们看一个JWT示例(取自jsonwebtoken.io ) JWT具有三部分:标头,正文和签名。 标头包含有关JWT编码方式的信息。 主体是代币的肉 ( 索赔所在的地方)。 签名提供了安全性。 关于令牌的编码方式以及信息在体内的存储方式,我们这里不做很多详细介绍。 如果需要,请查看前面提到的教程 。 不要忘记:加密签名不提供保密性; 它们只是检测篡改JWT的一种方式,除非JWT经过专门加密,否则它们是公开可见的。 签名只是提供了一种验证内容的安全方法。 大。 得到它了? 现在,您需要使用JJWT制作令牌! 对于本教程,我们使用现有的JWT库。 Java JWT (又名JJWT)是由Les Hazlewood (Apache Shiro的主要提交人,Apache Shiro是Stormpath的前联合创始人兼CTO,现在是Okta自己的高级架构师)创建的,JJWT是一个简化JWT创建和验证的Java库。 它完全基于JWT , JWS , JWE , JWK和JWA RFC规范,并根据Apache 2.0许可的条款开源。 该库还为规范添加了一些不错的功能,例如JWT压缩和声明执行。 用Java生成令牌 这部分超级容易。 让我们看一些代码。 克隆GitHub存储库 : git clone https://github.com/oktadeveloper/okta-java-jwt-example.git cd okta-java-jwt-example 这个示例非常基础,并且包含一个src/main/java/JWTDemo.java类文件,其中包含两个静态方法: createJWT()和decodeJWT() 。 足够巧妙的是,这两种方法创建了一个JWT并对JWT进行解码。 看下面的第一种方法。 public static String createJWT(String id, String issuer, String subject, long ttlMillis) { //The JWT signature algorithm we will be using to sign the token SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //We will sign our JWT with our ApiKey secret byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //Let's set the JWT Claims JwtBuilder builder = Jwts.builder().setId(id) .setIssuedAt(now) .setSubject(subject) .setIssuer(issuer) .signWith(signatureAlgorithm, signingKey); //if it has been specified, let's add the expiration if (ttlMillis > 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } //Builds the JWT and serializes it to a compact, URL-safe string return builder.compact(); } 总而言之, createJWT()方法执行以下操作: 设置哈希算法 获取签发日期索赔的当前日期 使用SECRET_KEY静态属性生成签名密钥 使用流畅的API添加声明并签署JWT 设置到期日期 可以根据您的需求进行定制。 例如,如果您想添加其他或自定义声明。 解码令牌 现在看一下更简单的decodeJWT()方法。 public static Claims decodeJWT(String jwt) { //This line will throw an exception if it is not a signed JWS (as expected) Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY)) .parseClaimsJws(jwt).getBody(); return claims; } 该方法再次使用静态SECRET_KEY属性生成签名密钥,并使用该方法来验证JWT是否未被篡改。 如果签名与令牌不匹配,则该方法将引发io.jsonwebtoken.SignatureException异常。 如果签名确实匹配,则该方法将索赔作为Claims对象返回。 差不多了! 运行JUnit测试 为了获得更多的荣誉,您可以在示例项目中运行JUnit测试。 有三个测试,它们演示了JJWT库的一些基本功能。 第一个测试显示了一条愉快的路,创建并成功解码了有效的JWT。 第二个测试显示了当您尝试将完全伪造的字符串解码为JWT时,JJWT库将如何失败。 最后一个测试显示了被JJWT篡改的方式将如何导致decodeJWT()方法引发SignatureException 。 您可以使用以下命令从命令行运行这些测试: ./gradlew test -i AI助手 -i将Gradle的日志级别设置为Info以便我们看到测试的简单日志输出。 了解有关在Java应用程序中使用JWT的更多信息 JJWT库使创建和验证JWT非常容易。 只需指定一个秘密密钥和一些声明,您就会拥有一个JJWT。 以后,使用相同的密钥对JJWT进行解码并验证其内容。 现在,创建和使用JJWT非常简单,为什么不使用它们呢? 不要忘记SSL! 请记住,除非对JWT进行加密,否则它们中编码的信息通常仅是Base64编码的,任何小孩和一些宠物都可以读取。 因此,除非您希望中国,俄罗斯和FBI读取所有会话数据,否则请使用SSL对其进行加密。 Baeldung 在Java和JWT上有相当不错的深入教程 。 另外,这里还有Okta博客提供的更多链接,可帮助您继续前进: Java应用程序的简单令牌认证 Spring Boot,OAuth 2.0和Okta入门 确保Spring Boot应用程序安全的10种绝佳方法 如果您的JWT被盗怎么办? JWT分析器和检查器Chrom插件 在线编码或解码JWT 如果您对此帖子有任何疑问,请在下面添加评论。 原文链接:https://blog.csdn.net/dnc8371/article/details/106701217
-
JWTtoken登录校验 session用户认证的一般流程 JWTToken认证的流程 JWT token的组成 头部(header) payload 负载 signature签名 三个部分组合形成token java中使用JWTToken JWT (Json Web Token) 是为了网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT可以校验用户的身份,传递用户的身份信息,一般用在用户登录上。 session用户认证的一般流程 用户输入账号密码, 向服务器发送请求 服务器验证账号密码是否正确, 如果正确则在该用户的session回话里保存相关的信息如id, 登录时间等 服务器向用户返回一个Cookie, 存着sessionId 用户再次请求服务器时带上sessionId.服务器根据sessionId从服务器保存的session中获取的信息. 这种方式在单服务器场景下没有太大问题 但在服务器集群下扩展性较差, session存在于不同的服务器, 则用户下次请求又必须分配到上次请求的服务器, 影响了负载均衡的能力. CSRF: 跨站请求伪造, 如果截获了用户Cookie中的sessionId, 则很容易会受到跨站请求伪造的攻击 使用JWTToken则不会遇到该问题, 因为使用JWTToken服务器就不保存信息了, 而是token里保存着编码的信息, 并对附带一个加密的签名, 服务器接收到token可直接解密进行认证. JWTToken认证的流程 用户输入账号密码, 向服务器发送请求. 服务器验证账号密码,返回一个加密的保存有用户信息的token 用户再次请求时带上token 服务器解析token, 取出token中的信息进行认证 JWT token的组成 三个部分 Header 头部 Payload 负载 Signature 签名 头部(header) { "alg": "HS256", "typ": "JWT" } alg : 加密类型 typ : JWT token的类型 alg 算法 介绍 HS256 HMAC256 HMAC with SHA-256 HS384 HMAC384 HMAC with SHA-384 HS512 HMAC512 HMAC with SHA-512 RS256 RSA256 RSASSA-PKCS1-v1_5 with SHA-256 RS384 RSA384 RSASSA-PKCS1-v1_5 with SHA-384 RS512 RSA512 RSASSA-PKCS1-v1_5 with SHA-512 ES256 ECDSA256 ECDSA with curve P-256 and SHA-256 ES384 ECDSA384 ECDSA with curve P-384 and SHA-384 ES512 ECDSA512 ECDSA with curve P-521 and SHA-512 payload 负载 负载主要是存放有效信息的地方 标准中注册的声明 建议但不强制使用 iss : jwt签发者 sub : jwt所面向的用户 aud : 接收jwt的一方 exp : jwt的过期时间, 要大于签发时间 nbf : 定义一个时间前, token都是不可用的 iat : jwt的签发时间 jti : jwt的唯一身份标识, 作为一次性token, 避免重放攻击 自定义数据 可以加入一些自己定义的数据, 用来进行用户认证 不建议存放敏感信息, 因为这部分内容不会进行加密, 而是采用base64进行编码. id : 22 name : zhangs signature签名 签名为jwt的第三个部分 jwt的签名是根据headers头部和payload负载通过头部中声明的加密方式进行加盐secret组合加密 secret存在服务器中, 服务器通过这个签名验证token的合法性, 防止伪造的token. 三个部分组合形成token HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload)+ "." + your-256-bit-secret ) 样例 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjIsImV4cCI6MTU1NzU4Mjc1NSwiaWF0IjoxNTU2OTc3OTU1fQ.icWwR1ysVb4p30XywCck6Fkzn6Oep45zG50NmRLc3BE 解析出来的headers { "alg": "HS256", "typ": "JWT" } 解析出来的payload { "id": 22, "exp": 1557582755, "iat": 1556977955 } java中使用JWTToken 导入依赖 <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.5.0</version> </dependency> 创建token Map<String, Object> map = new HashMap<String, Object>(); map.put("alg", "HS256"); map.put("typ", "JWT"); String token = JWT.create() .withHeader(map) // 添加头部 .withClaim("id", id) // 添加自定义数据 .withExpiresAt(experiesDate) // 设置过期的日期 .withIssuedAt(iatDate) // 签发时间 .sign(Algorithm.HMAC256(SECRET)); // 加密 校验token JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); DecodedJWT jwt = null; try { jwt = verifier.verify(token); } catch (Exception e) { throw new Exception("登录过期"); } return jwt.getClaims(); 在做登录的intercepter中加入验证逻辑 String token = request.getHeader("token"); // 获取请求头里的token if (token == null) { // 跳转返回未登录 request.getRequestDispatcher("/user/need_login.do").forward(request, response); logger.info("未登录"); } else { try { Map<String, Claim> map = JWTUtil.verifyToken(token); // 该方法验证失败会抛出异常 int id = map.get("id").asInt(); // 没有id也会抛出异常 request.setAttribute("id", id); // 传递参数id return true; // 验证成功放行 } catch (Exception e) { // 抛出异常进行跳转 request.getRequestDispatcher("/user/need_login.do").forward(request, response); logger.info("登录过期"); } } ———————————————— 原文链接:https://blog.csdn.net/z944733142/article/details/89646877
-
一、Token简单介绍 简单来说,token就是一个将信息加密之后的密文,而jwt也是token的实现方式之一,用于服务器端进行身份验证和授权访问控制。由于是快速入门,这里简单介绍一下jwt的生成原理 jwt由三部分组成。分别是 1.Header(标头),一般用于指明token的类型和加密算法 2.PayLoad(载荷),存储token有效时间及各种自定义信息,如用户名,id、发行者等 3.Signature(签名),是用标头提到的算法对前两部分进行加密,在签名认证时,防止止信息被修改 而Header和PayLoad最初都是json格式的键值对,格式如下 Header: { "alg": "HS256", #签名加密所用的算法 "typ": "JWT" #表名token的格式 } AI助手 PayLoad:此段json中保存了用户的部分信息 { "sub": "1234567890", #主题 "name": "John Doe", #用户名 "isVIP": true, #是否为vip用户 "exp": 1677740800 #过期时间 } AI助手 Signature:签名,首先将Header和PayLoad的json字符串进行Base64Url加密后,得到两个加密后的字符串,然后把这两个字符串由‘.’拼接起来,然后再将拼接好的字符串再用之前 Header中提到的算法与一个密钥(一般为一个字符串)进行加密后得到一个新的字符串,最后把这个新字符串和之前使用Base64Url加密后的两段字符串进行连接,最终得到token字符串,然后就可以把它发送给客户端进行存储了。 一般jwt格式token格式如下 eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiam9rZXIiLCJleHAiOjE2OTQxODE2NDUsInB3ZCI6IjEyMyJ9.vDrOQp-FkmRCQBzfaZsxzkef-iNjkAuAaqvT7Ns5Ab0 #(Header).(PayLoad).(Signature) 当然,作为快速上手jwt的文章,这里就不对jwt及token进行更加深入的讲解了,以上内容只需要做简单了解有个映像即可,总而言之: token就是一个在服务器端生成,包含了部分用户信息,最后发送给客户端进行保管的加密字符串,当客户端向服务器再次发起请求时,请求需携带token,服务器端对token进行验证,以确定用户是否有权访问。(以此来实现,登录验证,权限校验,记住密码等功能) 接下来做一个简单的代码实现 二、代码实现 导入相关依赖jar包(此jwt框架功能比较齐全且简单,适合初学者) <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.2.1</version> </dependency> jwt生成 首先需要一个字符串密钥进行加密,这个字符串可自定义,然后提前规划好你项目的jwt想要储存哪些自定义信息,例如用户名,密码,id,等等(这里为了方便演示,这里就把用户名以及密钥字符串设为常量了) package com.example.jwtdemo.Controller; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.algorithms.Algorithm; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; import java.util.HashMap; import java.util.Map; @RestController @Controller("/") public class TestController { //用户的用户名 private static final String USERNAME = "admin"; //用于签名加密的密钥,为一个字符串(需严格保密) private static final String KEY = "the_key"; @GetMapping("/jwt") public String setToken() { //获取jwt生成器 JWTCreator.Builder jwtBuilder = JWT.create(); //由于该生成器设置Header的参数为一个<String, Object>的Map, //所以我们提前准备好 Map<String, Object> headers = new HashMap<>(); headers.put("typ", "jwt"); //设置token的type为jwt headers.put("alg", "hs256"); //表明加密的算法为HS256 //开始生成token //我们将之前准备好的header设置进去 String token = jwtBuilder.withHeader(headers) //接下来为设置PayLoad,Claim中的键值对可自定义 //设置用户名 .withClaim("username", USERNAME) //是否为VIP用户 .withClaim("isVIP", true) //设置用户id .withClaim("userId", 123) //token失效时间,这里为一天后失效 .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) //设置该jwt的发行时间,一般为当前系统时间 .withIssuedAt(new Date(System.currentTimeMillis())) //token的发行者(可自定义) .withIssuer("issuer") //进行签名,选择加密算法,以一个字符串密钥为参数 .sign(Algorithm.HMAC256(KEY)); //token生成完毕,可以发送给客户端了,前端可以使用 //localStorage.setItem("your_token", token)进行存储,在 //下次请求时携带发送给服务器端进行验证 System.out.println(token); return token; } } 最终生成的jwt eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3N1ZXIiLCJleHAiOjE2OTQyNzA0OTMsImlhdCI6MTY5NDE4NDA5MywiaXNWSVAiOnRydWUsInVzZXJuYW1lIjoiYWRtaW4ifQ.JiTMn2jDaUTolPXB0TCBBOwSHG1l75W2oy2isdWhQIU AI助手 PS:以上代码中向jwt中注册的键值对不是都为必须的,需按照自己的情况进行设置 jwt验证 我们从客户端的请求中获取其携带的token进行验证,已确定其是否有权访问或进行其他的业务逻辑操作 @GetMapping("/verify") public boolean verify(HttpServletRequest request) { /*从请求头中获取token(具体要看你的token放在了请求的哪里, 这里以放在请求头举例) */ String token = request.getHeader("token"); /*判断token是否存在,若不存在,验证失败, 并进行验证失败的逻辑操作(例如跳转到登录界面, 或拒绝访问等等)*/ if (token == null) return false; /*获取jwt的验证器对象,传入的算法参数以及密钥字符串(KEY)必须 和加密时的相同*/ var require = JWT.require(Algorithm.HMAC256(KEY)).build(); DecodedJWT decode; try { /*开始进行验证,该函数会验证此token是否遭到修改, 以及是否过期,验证成功会生成一个解码对象 ,如果token遭到修改或已过期就会 抛出异常,我们用try-catch抓一下*/ decode = require.verify(token); } catch (Exception e) { //抛出异常,验证失败 return false; } //若验证成功,就可获取其携带的信息进行其他操作 //可以一次性获取所有的自定义参数,返回Map集合 Map<String, Claim> claims = decode.getClaims(); if (claims == null) return false; claims.forEach((k, v) -> System.out.println(k + " " + v.asString())); //也可以根据自定义参数的键值来获取 if (!decode.getClaim("isVIP").asBoolean()) return false; System.out.println(decode.getClaim("username").asString()); //获取发送者,没有设置则为空 System.out.println(decode.getIssuer()); //获取过期时间 System.out.println(decode.getExpiresAt()); //获取主题,没有设置则为空 System.out.println(decode.getSubject()); return true; } ———————————————— 原文链接:https://blog.csdn.net/greatming666/article/details/132767798
-
Token 令牌:原理、使用场景及操作指南 一、引言 在当今的数字化时代,信息安全和用户权限管理是软件系统开发中的关键环节。Token 令牌作为一种有效的身份验证和授权机制,被广泛应用于各种网络应用、移动应用以及分布式系统中。它提供了一种安全、灵活且高效的方式来控制用户对资源的访问,同时也保护了系统的安全性。 二、Token 令牌的概念与原理 (一)什么是 Token 令牌 Token 令牌是一种包含用户相关信息的加密字符串。它由服务器生成并颁发给客户端,作为客户端身份的一种标识。Token 通常包含用户的身份信息(如用户 ID)、权限信息(如用户角色)以及一些用于验证的签名信息。这个加密字符串可以在不同的请求中传递,让服务器能够识别客户端的身份并验证其权限。 (二)工作原理 生成阶段 当用户成功登录或者完成某种认证流程后,服务器会根据用户的信息和系统设置的规则生成一个 Token。这个生成过程通常涉及到加密算法,以确保 Token 的安全性。例如,服务器可能会使用 JSON Web Token(JWT)标准,通过对包含用户信息的 JSON 对象进行签名和加密,生成一个 JWT 令牌。 传递阶段 客户端收到 Token 后,会将其存储起来,通常存储在浏览器的本地存储(Local Storage)、会话存储(Session Storage)或者作为 HTTP 请求头的一部分。在后续向服务器发送请求时,客户端会将 Token 一同发送给服务器。 验证阶段 服务器收到带有 Token 的请求后,会对 Token 进行验证。这包括检查 Token 的签名是否正确(用于验证 Token 是否被篡改)、验证 Token 是否过期等。如果 Token 验证通过,服务器就会根据 Token 中包含的权限信息来决定是否允许客户端访问请求的资源。 三、Token 令牌的使用场景 (一)身份认证 单页应用(SPA) 在单页应用中,传统的基于会话(Session)的身份认证可能会遇到一些问题,如跨域访问困难等。Token 令牌提供了一种更好的解决方案。例如,用户登录成功后,服务器颁发一个 JWT 令牌给客户端。客户端在后续的每一个 API 请求中都将这个令牌放在请求头中发送给服务器。服务器通过验证令牌来确认用户的身份,从而允许用户访问受保护的资源。 移动应用 对于移动应用来说,Token 令牌同样适用。移动应用在用户登录后获取令牌,并将其存储在本地安全的存储区域(如设备的加密存储)。在与服务器通信时,将令牌添加到请求中,方便服务器进行身份验证。这样可以确保只有经过认证的用户才能访问移动应用后端的服务,如获取用户个人信息、进行数据更新等。 (二)授权访问 多用户角色系统 在一个具有多种用户角色(如管理员、普通用户、访客)的系统中,Token 令牌可以携带用户的角色信息。服务器根据令牌中的角色信息来决定用户可以访问哪些资源。例如,管理员角色的用户可能拥有对系统所有功能的访问权限,而普通用户只能访问部分功能。当用户发送请求时,服务器通过验证令牌中的角色信息来授权或拒绝访问相应的资源。 第三方 API 集成 当一个应用需要访问第三方 API 时,第三方服务提供商可能会要求使用 Token 令牌进行授权。应用首先需要向第三方服务申请令牌,通常是通过注册应用并获取客户端 ID 和客户端秘密,然后使用这些信息进行认证以获取令牌。在后续访问第三方 API 时,将令牌包含在请求中,以获得授权访问。 四、如何使用 Token 令牌 (一)在 Web 应用中使用 JWT(JSON Web Token) 后端生成 JWT 令牌 首先,在后端应用(如使用 Node.js + Express)中,需要安装相关的 JWT 库,如 jsonwebtoken。当用户登录成功后,使用用户信息(如用户 ID、用户名等)生成 JWT 令牌。以下是一个简单的示例代码: const jwt = require('jsonwebtoken'); const secretKey ='my_secret_key'; // 假设这是从数据库中获取的用户信息 const user = { id: 1, username: 'example_user' }; // 生成JWT令牌 const token = jwt.sign(user, secretKey); 在这个示例中,我们定义了一个密钥(secretKey),并使用 jwt.sign 方法将用户信息(user)进行签名,生成一个 JWT 令牌(token)。 前端存储和传递 JWT 令牌 前端(如使用 JavaScript)在收到后端返回的 JWT 令牌后,可以将其存储在本地存储中。例如,使用 localStorage: localStorage.setItem('token', token); 在后续向服务器发送请求时,将令牌添加到请求头中。如果使用 Axios 库进行 HTTP 请求,可以这样设置: import axios from 'axios'; const token = localStorage.getItem('token'); axios.defaults.headers.common['Authorization'] = 'Bearer'+ token; 这样,每次发送请求时,Axios 都会将包含令牌的请求头发送给服务器。 后端验证 JWT 令牌 在后端,需要对收到的 JWT 令牌进行验证。同样使用 jsonwebtoken 库,在处理受保护的路由时,添加验证逻辑。例如: const jwt = require('jsonwebtoken'); const secretKey ='my_secret_key'; // 中间件函数用于验证JWT令牌 const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (token == null) return res.sendStatus(401); jwt.verify(token, secretKey, (err, user) => { if (err) return res.sendStatus(403); req.user = user; next(); }); }; // 在受保护的路由中使用中间件 app.get('/protected', authenticateToken, (req, res) => { res.send('You have access to protected resource.'); }); 在这个示例中,我们定义了一个中间件函数(authenticateToken)来验证 JWT 令牌。首先从请求头中获取令牌,然后使用 jwt.verify 方法进行验证。如果验证通过,将用户信息(user)添加到请求对象(req)中,并调用 next 函数,允许请求继续处理;如果验证失败,根据情况返回 401(未授权)或 403(禁止访问)状态码。 (二)在移动应用中使用 Token 令牌(以 Android 为例) 获取 Token 令牌 在移动应用的登录模块,通过与后端服务器进行通信获取 Token 令牌。通常使用 HTTP 库(如 OkHttp)进行网络请求。假设后端返回的是一个 JSON 格式的响应,其中包含令牌,以下是一个简单的示例代码: import android.os.AsyncTask; import android.util.Log; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class TokenFetcher extends AsyncTask<String, Void, String> { private static final String TAG = "TokenFetcher"; private static final String BASE_URL = "https://your_backend_url/login"; private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); @Override protected String doInBackground(String... params) { String username = params[0]; String password = params[1]; OkHttpClient client = new OkHttpClient(); JSONObject json = new JSONObject(); try { json.put("username", username); json.put("password", password); } catch (JSONException e) { Log.e(TAG, "Error creating JSON object", e); } RequestBody body = RequestBody.create(json.toString(), JSON); Request request = new Request.Builder() .url(BASE_URL) .post(body) .build(); try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { JSONObject responseJson = new JSONObject(response.body().string()); return responseJson.getString("token"); } else { Log.e(TAG, "Login failed. Status code: " + response.code()); } } catch (IOException | JSONException e) { Log.e(TAG, "Error fetching token", e); } return null; } @Override protected void onPostExecute(String token) { if (token!= null) { // 将令牌存储在安全的存储区域,如Android的SharedPreferences // 这里只是示例,实际应用中需要考虑加密等安全措施 // 假设已经有一个名为TokenStorage的类用于存储令牌 TokenStorage.storeToken(token); } } } 在这个示例中,我们通过 AsyncTask 在后台线程中进行网络请求,向服务器发送用户名和密码,获取返回的令牌,并将其存储在本地(这里只是简单演示存储在一个假设的 TokenStorage 类中,实际应用中需要更好的安全存储方式)。 使用 Token 令牌进行请求 在移动应用需要访问受保护的后端资源时,将存储的 Token 令牌添加到请求中。同样使用 OkHttp 库,以下是一个示例代码: import android.util.Log; import java.io.IOException; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class TokenInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); String token = TokenStorage.getToken(); if (token!= null) { Request newRequest = originalRequest.newBuilder() .header("Authorization", "Bearer " + token) .build(); return chain.proceed(newRequest); } else { Log.w("TokenInterceptor", "No token found."); return chain.proceed(originalRequest); } } public static OkHttpClient getClient() { OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new TokenInterceptor()) .build(); return client; } } 在这里,我们定义了一个拦截器(TokenInterceptor),在每个请求中检查是否有令牌,如果有,则将其添加到请求头(“Authorization”)中,格式为 “Bearer <令牌内容>”,然后继续发送请求。通过这种方式,移动应用在访问后端资源时可以进行身份验证。 后端验证移动应用的 Token 令牌 后端验证移动应用发送的 Token 令牌的方式与 Web 应用类似。根据使用的技术栈,采用相应的验证逻辑,确保令牌的真实性和有效性,以及验证用户的权限等信息。 五、Token 令牌使用的注意事项 (一)安全性 令牌存储安全 在前端应用中,无论是 Web 应用还是移动应用,存储 Token 令牌时需要注意安全性。在 Web 应用中,避免将令牌存储在容易被跨站脚本攻击(XSS)获取的地方,如使用 Cookie 存储时要设置合适的安全属性(如 HttpOnly、Secure)。在移动应用中,要使用设备提供的安全存储机制,如 Android 的 KeyStore 或 iOS 的 Keychain。 传输安全 Token 令牌在网络传输过程中应该使用安全的协议,如 HTTPS。这样可以防止令牌被中间人拦截和窃取。 (二)有效期管理 设置合理的有效期 Token 令牌应该有合理的有效期设置。有效期过短可能会导致用户频繁登录,影响用户体验;有效期过长则增加了令牌被窃取后滥用的风险。根据应用的安全需求和用户使用场景,合理设置令牌的有效期,如几分钟到几天不等。 刷新机制 对于需要长时间使用的应用,应该建立令牌刷新机制。当令牌接近过期时,自动向服务器请求刷新令牌,以确保用户的持续访问权限。 (三)权限管理 精细的权限控制 根据应用的用户角色和功能需求,在 Token 令牌中设置精细的权限信息。确保服务器能够根据令牌准确地授权或拒绝用户对不同资源的访问。 权限更新 当用户的权限发生变化时,如用户角色升级或权限被回收,要及时更新 Token 令牌中的权限信息或者重新颁发令牌,以保证权限控制的准确性。 六、总结 Token 令牌是一种强大的身份验证和授权工具,在现代软件系统中发挥着重要的作用。通过了解其原理、使用场景以及正确的使用方法,开发者可以构建更加安全、灵活的应用程序。在使用过程中,要注意安全性、有效期管理和权限管理等重要事项,以确保 Token 令牌能够有效地保护系统和用户的权益。无论是 Web 应用还是移动应用,Token 令牌都为用户身份认证和授权访问提供了可靠的解决方案。 ———————————————— 原文链接:https://blog.csdn.net/Andrew_Chenwq/article/details/143253009
-
随着互联网的不断发展,技术的迭代也非常之快。我们的用户认证也从刚开始的用户名密码转变到基于cookie的session认证,然而到了今天,这种认证已经不能满足与我们的业务需求了(分布式,微服务)。我们采用了另外一种认证方式:基于token的认证。 一、与cookie相比较的优势: 1、支持跨域访问,将token置于请求头中,而cookie是不支持跨域访问的; 2、无状态化,服务端无需存储token,只需要验证token信息是否正确即可,而session需要在服务端存储,一般是通过cookie中的sessionID在服务端查找对应的session; 3、无需绑定到一个特殊的身份验证方案(传统的用户名密码登陆),只需要生成的token是符合我们预期设定的即可; 4、更适用于移动端(Android,iOS,小程序等等),像这种原生平台不支持cookie,比如说微信小程序,每一次请求都是一次会话,当然我们可以每次去手动为他添加cookie,详情请查看博主另一篇博客; 5、避免CSRF跨站伪造攻击,还是因为不依赖cookie; 6、非常适用于RESTful API,这样可以轻易与各种后端(java,.net,python......)相结合,去耦合 还有一些优势这里就不一一列举了。 二、基于JWT的token认证实现 JWT:JSON Web Token,其实token就是一段字符串,由三部分组成:Header,Payload,Signature。详细情况请自行百度,现在,上代码。 1、引入依赖,这里选用java-jwt,选择其他的依赖也可以 2、实现签名方法 设置15分钟过期也是出于安全考虑,防止token被窃取,不过一般选择基于token认证,传输方式我们都应该选择https,这样别人无法抓取到我们的请求信息。这个私钥是非常重要的,加密解密都需要用到它,要设置的足够复杂并且不能被盗取,我这里选用的是一串uuid,加密方式是HMAC256。 3、认证 我这里演示的还是以传统的用户名密码验证,验证通过发放token。 4、配置拦截器 实现HandleInterceptor,重写preHandle方法,该方法是在每个请求之前触发执行,从request的头里面取出token,这里我们统一了存放token的键为accessToken,验证通过,放行,验证不通过,返回认证失败信息。 5、设置拦截器 这里使用的是Spring的xml配置拦截器,放过认证接口。 6、token解码方法 7、测试 访问携带token,请求成功。 未携带token或者token错误,过期,返回认证失败信息。 8、获取token里携带的信息 我们可以将一些常用的信息放入token中,比如用户登陆信息,可以方便我们的使用 至此,一个简单的基于token认证就实现了,下次我将shiro与JWT整合到一起。 ———————————————— 原文链接:https://blog.csdn.net/KKKun_Joe/article/details/81878231
-
1.基本概念不同 ConcurrentHashMap是一个支持高并发更新与查询的哈希表。在保证安全的前提下,进行检索不需要锁定。 HashMap是基于哈希表的Map接口的实现,此实现提供所有可选的映射操作,并允许使用null值和null键。 2.底层数据结构不同 HashMap的底层数据结构主要是:数组+链表,确切的说是由链表为元素的数组。 ConcurrentHashMap的底层数据结构是:Segments数组+HashEntry数组+链表。 3.线程安全属性不同 ConcurrentHashMap是线程安全的数组,它采用分段锁保证安全性。容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这样便可以有效地提高并发效率。 而HashMap不是线程安全,没有锁机制,在多线程环境下会导致数据覆盖之类的问题,所以在多线程中使用HashMap是会抛出异常的。 4.对整个桶数组的处理方式不同 ConcurrentHashMap对整个桶数组进行了分段;而HashMap则没有对整个桶数组进行分段。 延伸阅读 HashTable是什么 HashTable也就是哈希表,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 HashTable继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。HashTable的函数都是同步的,这意味着它是线程安全的,它的key、value都不可以为null。此外,HashTable中的映射不是有序的。 HashTable的成员变量主要有以下五个: table:一个Entry[]数组类型,而Entry(在 HashMap 中有讲解过)就是一个单向链表。哈希表的”key-value键值对”都是存储在Entry数组中的 count:Hashtable的大小,它是Hashtable保存的键值对的数量 threshold:Hashtable的阈值,用于判断是否需要调整Hashtable的容量,threshold的值 = (容量 * 负载因子) loadFactor:负载因子 modCount:用来实现fail-fast机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你已经出错了。 ———————————————— 原文链接:https://blog.csdn.net/azybjbajzc/article/details/130626216
-
HashTable (1)底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化 (2)初始size为11,扩容:newsize = olesize*2+1 (3)计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length HashMap (1)底层数组+链表实现,可以存储null键和null值,线程不安全 (2)初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂 (3)扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入 (4)插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容) (5)当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀 (6)计算index方法:index = hash & (tab.length – 1) HashMap的初始值还要考虑加载因子: (7)哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。 (8)加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。 (9)空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。 HashMap和Hashtable都是用hash算法来决定其元素的存储,因此HashMap和Hashtable的hash表包含如下属性: (1)容量(capacity):hash表中桶的数量 (2)初始化容量(initial capacity):创建hash表时桶的数量,HashMap允许在构造器中指定初始化容量 (3)尺寸(size):当前hash表中记录的数量 (4)负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的散列表,依此类推。轻负载的散列表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢) 除此之外,hash表里还有一个“负载极限”,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。 HashMap和Hashtable的构造器允许指定一个负载极限,HashMap和Hashtable默认的“负载极限”为0.75,这表明当该hash表的3/4已经被填满时,hash表会发生rehashing。 “负载极限”的默认值(0.75)是时间和空间成本上的一种折中: (1)较高的“负载极限”可以降低hash表所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操(HashMap的get()与put()方法都要用到查询) (2)较低的“负载极限”会提高查询数据的性能,但会增加hash表所占用的内存开销 程序猿可以根据实际情况来调整“负载极限”值。 ConcurrentHashMap (1)底层采用分段的数组+链表实现,线程安全 (2)通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。) (3)Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术 (4)有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁 (5)扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容 Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类的。Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。 HashMap基于哈希思想,实现对数据的读写。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来存储值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞时,对象将会储存在链表的下一个节点中。HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中,可通过键对象的equals()方法来找到键值对。如果链表大小超过阈值(TREEIFY_THRESHOLD,8),链表就会被改造为树形结构。 在HashMap中,null可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该key,也可以表示该key所对应的value为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个key,应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null。 Hashtable是线程安全的,它的方法是同步的,可以直接用在多线程环境中。而HashMap则不是线程安全的,在多线程环境中,需要手动实现同步机制。 Hashtable与HashMap另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。 先看一下简单的类图: 从类图中可以看出来在存储结构中ConcurrentHashMap比HashMap多出了一个类Segment,而Segment是一个可重入锁。 ConcurrentHashMap是使用了锁分段技术来保证线程安全的。 锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。 ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。 ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。 HashMap和ConcurrentHashMap的区别总结 (1)HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。 (2)ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。 (3)ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。 原文链接:https://blog.csdn.net/g_ood_good_study/article/details/120438768
推荐直播
-
DTT年度收官盛典:华为开发者空间大咖汇,共探云端开发创新
2025/01/08 周三 16:30-18:00
Yawei 华为云开发工具和效率首席专家 Edwin 华为开发者空间产品总监
数字化转型进程持续加速,驱动着技术革新发展,华为开发者空间如何巧妙整合鸿蒙、昇腾、鲲鹏等核心资源,打破平台间的壁垒,实现跨平台协同?在科技迅猛发展的今天,开发者们如何迅速把握机遇,实现高效、创新的技术突破?DTT 年度收官盛典,将与大家共同探索华为开发者空间的创新奥秘。
去报名 -
GaussDB应用实战:手把手带你写SQL
2025/01/09 周四 16:00-18:00
Steven 华为云学堂技术讲师
本期直播将围绕数据库中常用的数据类型、数据库对象、系统函数及操作符等内容展开介绍,帮助初学者掌握SQL入门级的基础语法。同时在线手把手教你写好SQL。
去报名
热门标签