diff --git a/archives/2024/04/page/2/index.html b/archives/2024/04/page/2/index.html index d08a52d8b..b0952a709 100644 --- a/archives/2024/04/page/2/index.html +++ b/archives/2024/04/page/2/index.html @@ -269,8 +269,8 @@

Potter's 个人博客

-
@@ -289,8 +289,8 @@

Potter's 个人博客

-
diff --git a/archives/2024/04/page/4/index.html b/archives/2024/04/page/4/index.html index 54dbf3f8a..ea6a2114b 100644 --- a/archives/2024/04/page/4/index.html +++ b/archives/2024/04/page/4/index.html @@ -209,8 +209,8 @@

Potter's 个人博客

-
@@ -229,8 +229,8 @@

Potter's 个人博客

-
diff --git a/archives/2024/05/index.html b/archives/2024/05/index.html index bf8fbd998..1f2271b5f 100644 --- a/archives/2024/05/index.html +++ b/archives/2024/05/index.html @@ -177,6 +177,26 @@

Potter's 个人博客

2024 +
+
+ + + +
+ +
+ +
+
+
@@ -337,26 +357,6 @@

Potter's 个人博客

-
-
- - - -
- -
- -
-
-
diff --git a/archives/2024/index.html b/archives/2024/index.html index 376c81932..2d23af781 100644 --- a/archives/2024/index.html +++ b/archives/2024/index.html @@ -177,6 +177,26 @@

Potter's 个人博客

2024 + +
@@ -337,26 +357,6 @@

Potter's 个人博客

- -
diff --git a/archives/2024/page/3/index.html b/archives/2024/page/3/index.html index ed44fb02b..3326fc8d5 100644 --- a/archives/2024/page/3/index.html +++ b/archives/2024/page/3/index.html @@ -309,8 +309,8 @@

Potter's 个人博客

-
@@ -329,8 +329,8 @@

Potter's 个人博客

-
diff --git a/archives/2024/page/5/index.html b/archives/2024/page/5/index.html index 566796cde..9935217a4 100644 --- a/archives/2024/page/5/index.html +++ b/archives/2024/page/5/index.html @@ -249,8 +249,8 @@

Potter's 个人博客

-
@@ -269,8 +269,8 @@

Potter's 个人博客

-
diff --git a/archives/index.html b/archives/index.html index 8be4777e0..e0c067caa 100644 --- a/archives/index.html +++ b/archives/index.html @@ -177,6 +177,26 @@

Potter's 个人博客

2024 + +
@@ -337,26 +357,6 @@

Potter's 个人博客

- -
diff --git a/archives/page/3/index.html b/archives/page/3/index.html index 54d61e3ff..3a5ca9444 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -309,8 +309,8 @@

Potter's 个人博客

-
@@ -329,8 +329,8 @@

Potter's 个人博客

-
diff --git a/archives/page/5/index.html b/archives/page/5/index.html index 2526d64e6..cb0eaf7d1 100644 --- a/archives/page/5/index.html +++ b/archives/page/5/index.html @@ -249,8 +249,8 @@

Potter's 个人博客

-
@@ -269,8 +269,8 @@

Potter's 个人博客

-
diff --git a/css/main.css b/css/main.css index bc393d93a..5dfcae756 100644 --- a/css/main.css +++ b/css/main.css @@ -1168,7 +1168,7 @@ pre .javascript .function { } .links-of-author a::before, .links-of-author span.exturl::before { - background: #05ff40; + background: #5dffeb; border-radius: 50%; content: ' '; display: inline-block; diff --git a/index.html b/index.html index d30961084..f4e450f3b 100644 --- a/index.html +++ b/index.html @@ -165,7 +165,7 @@

Potter's 个人博客

- +
- - - + @PostMapping("/register") + public String registerUser(@RequestBody User user) { + userService.registerUser(user); + return "User registered successfully"; + } +} +//接口类 +public interface UserService extends IService<User> { + void registerUser(User user); +} +//实现类 +@Service +public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { - - - -
- + @Autowired + private UserMapper userMapper; + @Autowired + private PasswordEncoder passwordEncoder; - - -
-

- - -

+ @Override + public void registerUser(User user) { + String encodedPassword = passwordEncoder.encode(user.getPassword()); + // 将用户名和加密后的密码存储到数据库中 + User passwdUser = new User(); + passwdUser.setUsername(user.getUsername()); + passwdUser.setPassword(encodedPassword); + userMapper.insert(passwdUser); + } +} -
-
+ @Autowired + private JwtUtils jwtUtils; - - - -
+ @Autowired + private UserDetailsService userDetailsService; - -

JVM基础

JVM的主要组成部分及其作用

    -
  1. 类加载器(ClassLoader):负责加载Java类文件,将其转化为JVM能够识别的Class对象。
  2. -
  3. 运行时数据区(Runtime Data Area):JVM在执行Java程序时会使用到的数据区,包括方法区、堆、虚拟机栈、本地方法栈和程序计数器。
  4. -
  5. 执行引擎(Execution Engine):负责执行字节码,包括解释器、即时编译器(JIT)和垃圾回收器等。
  6. -
  7. 本地接口(Native Interface):JNI(Java Native Interface)允许Java代码与其他编程语言(如C/C++)编写的代码进行交互。
  8. -
-

JVM运行时数据区

    -
  1. 方法区(Method Area):存储已被虚拟机加载的类信息、常量、静态变量和即时编译后的代码等数据。
  2. -
  3. 堆(Heap):存放对象实例和数组,是GC主要管理的区域。
  4. -
  5. 虚拟机栈(Java Virtual Machine Stack):存放每个线程的栈帧(方法调用过程中的局部变量表、操作数栈、方法返回地址等)。
  6. -
  7. 本地方法栈(Native Method Stack):为本地方法服务,与虚拟机栈类似。
  8. -
  9. 程序计数器(Program Counter Register):当前线程执行的字节码的行号指示器。
  10. -
-

深拷贝和浅拷贝

    -
  • 浅拷贝:复制对象时只复制对象的引用,不复制对象本身。
  • -
  • 深拷贝:复制对象时,除了复制对象的引用,还复制对象本身及其包含的所有对象。
  • -
-

堆和栈的区别

    -
  • :用于存储对象实例,线程共享,生命周期较长,由GC管理。
  • -
  • :用于存储局部变量、方法调用等,线程私有,生命周期较短,方法执行完自动释放。
  • -
-

队列和栈是什么?有什么区别?

    -
  • 队列(Queue):FIFO(先进先出)数据结构,元素从尾部插入,从头部删除。
  • -
  • 栈(Stack):LIFO(后进先出)数据结构,元素从顶部插入和删除。
  • -
-

HotSpot虚拟机对象探秘

对象的创建

    -
  • 类加载检查:检查类是否已加载,未加载则加载类。
  • -
  • 分配内存:在堆中为对象分配内存,使用指针碰撞或空闲列表。
  • -
  • 初始化:对分配的内存进行零值初始化。
  • -
  • 设置对象头:初始化对象的元数据信息。
  • -
  • 执行构造方法:调用方法进行初始化。
  • -
-

为对象分配内存

    -
  • 指针碰撞:适用于内存规整的堆,通过移动指针分配内存。
  • -
  • 空闲列表:适用于内存不规整的堆,通过维护空闲列表分配内存。
  • -
-

处理并发安全问题

    -
  • CAS:通过乐观锁的方式解决并发问题。
  • -
  • TLAB:每个线程分配一个本地缓冲区(Thread Local Allocation Buffer)进行内存分配。
  • -
-

对象的访问定位

    -
  • 句柄访问:对象引用存储的是句柄地址,句柄中包含对象实例数据和类型数据的指针。
  • -
  • 直接指针:对象引用存储的是对象实例的直接地址。
  • -
-

内存溢出异常

Java会存在内存泄漏吗?请简单描述

Java中也会存在内存泄漏,例如,长生命周期对象持有短生命周期对象的引用,导致短生命周期对象无法被GC回收。

-

垃圾收集器

简述Java垃圾回收机制

Java的垃圾回收机制通过GC自动回收不再使用的对象,主要算法包括标记-清除、复制和标记-整理等。

-

GC是什么?为什么要GC

GC(Garbage Collection)是垃圾回收的缩写,自动管理内存,防止内存泄漏,提高程序稳定性和性能。

-

垃圾回收的优点和原理。并考虑2种回收机制

    -
  • 优点:自动内存管理,减少内存泄漏,提高开发效率。
  • -
  • 标记-清除:标记所有存活对象,清除未标记的对象。
  • -
  • 复制:将存活对象复制到新空间,清除旧空间。
  • -
-

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

垃圾回收器通过跟踪对象的引用链,回收不可达的对象。垃圾回收器不能马上回收内存,可通过System.gc()Runtime.getRuntime().gc()主动通知进行垃圾回收。

-

Java中都有哪些引用类型?

    -
  • 强引用:不会被GC回收。
  • -
  • 软引用:内存不足时会被回收。
  • -
  • 弱引用:在GC时会被回收。
  • -
  • 虚引用:无法通过引用访问对象,仅用于监控对象的回收。
  • -
-

怎么判断对象是否可以被回收?

通过引用计数或可达性分析算法判断对象是否可达,不可达的对象可以被回收。

-

在Java中,对象什么时候可以被垃圾回收?

当对象没有任何强引用时,可以被垃圾回收。

-

JVM中的永久代中会发生垃圾回收吗?

在JDK 8之前,永久代中会发生垃圾回收,但频率较低。JDK 8以后,永久代被移除,替代为元空间(Metaspace)。

-

说一下JVM有哪些垃圾回收算法?

    -
  • 标记-清除算法:标记存活对象,清除未标记对象。
  • -
  • 复制算法:将存活对象复制到新空间,清除旧空间。
  • -
  • 标记-整理算法:标记存活对象,将存活对象移动到一端,清除剩余空间。
  • -
  • 分代收集算法:将堆分为新生代和老年代,分别使用不同的垃圾回收算法。
  • -
-

说一下JVM有哪些垃圾回收器?

    -
  • Serial收集器:单线程收集,适用于单CPU环境。
  • -
  • Parallel收集器:多线程收集,适用于多CPU环境。
  • -
  • CMS收集器:低停顿时间的并发收集器,适用于低停顿需求的应用。
  • -
  • G1收集器:面向服务端应用,适用于大内存、多CPU环境。
  • -
-

详细介绍一下CMS垃圾回收器?

    -
  • 初始标记:标记GC Roots可达的对象,短暂停顿。
  • -
  • 并发标记:标记所有可达对象,不停顿。
  • -
  • 重新标记:修正并发标记期间的变动,短暂停顿。
  • -
  • 并发清除:清除不可达对象,不停顿。
  • -
-

新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?

    -
  • 新生代垃圾回收器:Serial、ParNew、Parallel Scavenge。特点是频繁回收、停顿时间短。
  • -
  • 老年代垃圾回收器:Serial Old、CMS、Parallel Old。特点是回收频率低、停顿时间长。
  • -
-

简述分代垃圾回收器是怎么工作的?

分代垃圾回收器将堆划分为新生代和老年代。新生代对象生命周期短,频繁回收;老年代对象生命周期长,较少回收。回收时,新生代使用复制算法,老年代使用标记-整理或CMS。

-

内存分配策略

简述java内存分配与回收策略以及Minor GC和Major GC

    -
  • 内存分配:对象优先在Eden区分配,大对象直接进入老年代,长期存活对象进入老年代。
  • -
  • Minor GC:针对新生代的垃圾回收,频繁但耗时短。
  • -
  • Major GC:针对老年代的垃圾回收,频率低但耗时长。
  • -
-

对象优先在Eden区分配

大多数新创建的对象在Eden区分配,当Eden区满时触发Minor GC,将存活对象移到Survivor区。

-

大对象直接进入老年代

避免在新生代频繁复制大对象,减少内存复制开销。

-

长期存活对象将进入老年代

对象在Survivor区经历多次GC后,晋升到老年代。

-

虚拟机类加载机制

简述java类加载机制

Java类加载机制包括加载、链接和初始化三个步骤。加载阶段通过类加载器读取类文件,将其转化为二进制数据;链接阶段验证、准备和解析类;初始化阶段执行类的静态初始化器和静态变量初始化。

-

描述一下JVM加载Class文件的原理机制

JVM通过类加载器加载Class文件,加载过程包括查找和加载Class文件,将其转化为Class对象。加载器遵循双亲委派模型,保证类加载的安全性和一致性。

-

什么是类加载器,类加载器有哪些?

类加载器是负责加载类文件的组件,主要有以下几种:

-
    -
  • 启动类加载器(Bootstrap ClassLoader):加载JDK核心类库。
  • -
  • 扩展类加载器(Extension ClassLoader):加载扩展类库。
  • -
  • 应用程序类加载器(Application ClassLoader):加载应用程序类。
  • -
  • 自定义类加载器:用户定义的类加载器。
  • -
-

说一下类装载的执行过程?

类装载的执行过程包括:

-
    -
  1. 加载:通过类加载器读取类文件。
  2. -
  3. 链接:包括验证、准备和解析三个阶段。
  4. -
  5. 初始化:执行类的静态初始化器和静态变量初始化。
  6. -
-

什么是双亲委派模型?

双亲委派模型是指类加载时,类加载器先委托父类加载器加载,父类加载器无法加载时,子类加载器再尝试加载。这保证了类的加载安全和统一。

-

JVM调优

说一下JVM调优的工具?

常用的JVM调优工具包括:

-
    -
  • jstat:监控JVM内存和垃圾回收情况。
  • -
  • jmap:生成堆转储快照,分析内存使用情况。
  • -
  • jstack:生成线程快照,分析线程状态。
  • -
  • VisualVM:图形化界面监控JVM性能。
  • -
-

常用的JVM调优的参数都有哪些?

    -
  • -Xms:设置初始堆大小。
  • -
  • -Xmx:设置最大堆大小。
  • -
  • -Xmn:设置新生代大小。
  • -
  • -XX:PermSize:设置初始永久代大小(JDK 8前)。
  • -
  • -XX:MaxPermSize:设置最大永久代大小(JDK 8前)。
  • -
  • -XX:MetaspaceSize:设置初始元空间大小(JDK 8后)。
  • -
  • -XX:MaxMetaspaceSize:设置最大元空间大小(JDK 8后)。
  • -
  • -XX:+UseG1GC:使用G1垃圾回收器。
  • -
+ @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() //禁用CSRF保护(对于无状态的REST API来说,CSRF保护通常是多余的)。 + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //配置会话管理策略为无状态(STATELESS) + .and() + .authorizeRequests() + .antMatchers("/auth/login", "/auth/register").permitAll() // 允许匿名访问登录和注册路径 + .anyRequest().authenticated() + .and() + //注册JWT过滤器,确保在每次请求时验证JWT令牌 + .addFilterBefore(new JwtRequestFilter(jwtUtils, userDetailsService), UsernamePasswordAuthenticationFilter.class); - -
+ } - - - -
-
-
-
- - - + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); + } + + // 配置用户认证服务和密码编码器 + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } - - - -
- + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } +} - +

创建JWT工具类

创建一个JWT工具类,用于生成和验证JWT令牌:

+
@Component
+@Slf4j
+public class JwtUtils {
+    @Value("${jwt.secret}")
+    private String secret;
+    @Value("${jwt.expiration}")
+    private Long expiration;
 
-    
-      
-

- - -

- -
+ // 验证JWT令牌 + public boolean validateToken(String token, String username) { + Claims claims = extractClaims(token); + if (claims == null) { + return false; + } + String tokenUsername = claims.getSubject(); + boolean isTokenExpired = claims.getExpiration().before(new Date()); + boolean isValid = username.equals(tokenUsername) && !isTokenExpired; + if (!isValid) { + log.warn("Token validation failed. Username: {}, Token Username: {}, Is Token Expired: {}", username, tokenUsername, isTokenExpired); + } + return isValid; + } - - - -
+}
- -

ZooKeeper简介

什么是ZooKeeper?

ZooKeeper是Apache开源的一个分布式协调服务,用于管理分布式应用中的配置、同步、命名等信息,提供高效可靠的分布式数据一致性服务。

-

ZooKeeper的主要特性有哪些?

    -
  • 一致性:所有服务器保存相同的数据副本,保证数据一致性。
  • -
  • 可靠性:只要超过半数的服务器可用,ZooKeeper就能提供服务。
  • -
  • 原子性:所有操作都是原子性的,成功执行或完全失败。
  • -
  • 顺序性:所有更新按照严格的顺序执行,保证数据的顺序性。
  • -
  • 高性能:适合读多写少的场景。
  • -
-

ZooKeeper的核心概念

什么是ZNode?

ZNode是ZooKeeper数据模型中的数据节点,每个ZNode可以存储数据和子节点。ZNode路径类似于文件系统,分为持久节点和临时节点。

-

什么是会话?

会话是客户端与ZooKeeper服务器之间的连接,每个会话有一个唯一的会话ID。会话超时时间可以配置,当客户端在超时时间内没有心跳,服务器会关闭会话并清除临时节点。

-

什么是版本号?

每个ZNode有三个版本号:数据版本(dataVersion)、子节点版本(childrenVersion)、ACL版本(aclVersion)。版本号用于实现乐观锁机制,保证数据的一致性和原子性。

-

什么是Watcher?

Watcher是ZooKeeper的事件监听机制,客户端可以对ZNode设置Watcher,当ZNode发生变化时,服务器会通知客户端。Watcher是一次性触发的,需要重新注册。

-

ZooKeeper的架构

ZooKeeper的架构是怎样的?

ZooKeeper采用主从架构,集群由一个Leader和多个Follower组成。Leader负责处理所有的写请求,Follower负责处理读请求和转发写请求。通过Paxos算法保证数据一致性。

-

ZooKeeper的选举机制是怎样的?

ZooKeeper的选举机制基于Fast Paxos算法。当Leader挂掉时,集群通过选举机制选出新的Leader。选举过程包括投票、票数统计和Leader确认,选举成功后,集群恢复正常工作。

-

什么是ZAB协议?

ZAB(ZooKeeper Atomic Broadcast)协议是ZooKeeper实现数据一致性的重要协议。ZAB协议包含崩溃恢复和消息广播两个阶段,通过广播保证数据的顺序一致性,通过崩溃恢复保证系统的可靠性。

-

ZooKeeper的应用场景

ZooKeeper常用的应用场景有哪些?

    -
  • 配置管理:集中管理分布式系统的配置信息,动态更新配置。
  • -
  • 服务发现:注册和发现分布式系统中的服务,简化服务调用。
  • -
  • 分布式锁:实现分布式系统中的锁机制,保证数据一致性。
  • -
  • 领导者选举:选举分布式系统中的Leader,保证高可用性。
  • -
  • 队列管理:实现分布式消息队列,保证消息的顺序性和一致性。
  • -
-

ZooKeeper的操作

如何启动ZooKeeper集群?

ZooKeeper集群由多个服务器节点组成,启动前需要配置zoo.cfg文件,包括服务器列表和相关参数。启动命令如下:

-
zkServer.sh start
+

创建用户认证服务

创建一个用户认证服务,用于加载用户信息:

+
@Service
+public class JwtUserDetailsService implements UserDetailsService {
+    @Autowired
+    UserService userService;
 
-

如何连接ZooKeeper?

可以通过ZooKeeper客户端工具(如zkCli.sh)或程序化接口(如Java API)连接ZooKeeper服务器。连接命令如下:

-
zkCli.sh -server 127.0.0.1:2181
+ // 根据用户名加载用户 + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper(); + LambdaQueryWrapper<User> queryWrapper = lambdaQueryWrapper + .eq(User::getUsername, username); -

如何创建ZNode?

可以通过create命令创建ZNode,命令格式如下:

-
create /path data
-

例如,创建一个名为/myNode的ZNode:

-
create /myNode myData
+ User user = userService.getOne(queryWrapper); + if (user == null) { + throw new UsernameNotFoundException("User not found"); + } + return new JwtUserDetails(user); // user是你自定义的UserDetails实现类 + } +}
-

如何读取ZNode数据?

可以通过get命令读取ZNode的数据,命令格式如下:

-
get /path
-

例如,读取/myNode的ZNode数据:

-
get /myNode
+

创建认证控制器

创建一个控制器来处理用户登录和生成JWT令牌:

+
@RestController
+@RequestMapping("/auth")
+@Slf4j
+public class LoginController {
 
-

如何更新ZNode数据?

可以通过set命令更新ZNode的数据,命令格式如下:

-
set /path data
-

例如,更新/myNode的ZNode数据:

-
set /myNode newData
+ @Autowired + private AuthenticationManager authenticationManager; -

如何删除ZNode?

可以通过delete命令删除ZNode,命令格式如下:

-
delete /path
-

例如,删除/myNode的ZNode:

-
delete /myNode
+ @Autowired + private JwtUtils jwtUtils; -

ZooKeeper的高级特性

什么是事务(multi)操作?

事务操作允许在一次请求中执行多个ZooKeeper操作,保证所有操作的原子性。通过multi命令可以执行事务操作,示例如下:

-
multi
-create /node1 data1
-set /node2 data2
-delete /node3
-commit
+ @Autowired + private JwtUserDetailsService jwtUserDetailsService; -

什么是ACL?

ACL(Access Control List)用于控制ZNode的访问权限,包括创建、读取、写入、删除等操作。可以通过setAcl命令设置ACL,例如:

-
setAcl /path world:anyone:r
+ // 处理用户登录请求 + @PostMapping("/login") + public String createToken(@RequestBody User authRequest) throws Exception { + try { + // 进行用户认证 + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) + ); + } catch (AuthenticationException e) { + throw new Exception("Invalid username or password"); + } + // 生成JWT令牌 + final UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(authRequest.getUsername()); + return jwtUtils.generateToken(userDetails.getUsername()); + } +}
-

如何实现分布式锁?

可以通过创建临时顺序节点实现分布式锁。锁的实现步骤包括创建节点、判断节点顺序、删除节点。例如:

-
String lockPath = "/lock";
-String myNode = zk.create(lockPath + "/seq-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
-List<String> nodes = zk.getChildren(lockPath, false);
-Collections.sort(nodes);
-if (myNode.equals(nodes.get(0))) {
-    // 获取锁
-} else {
-    // 等待锁
-}
+

创建JWT过滤器

创建一个JWT过滤器,用于在每次请求时验证JWT令牌:

+
public class JwtRequestFilter extends OncePerRequestFilter {
 
-

ZooKeeper的常见问题

ZooKeeper的性能如何优化?

    -
  • 合理配置服务器:增加服务器数量,提高集群的可用性和负载能力。
  • -
  • 优化数据模型:减少ZNode的数量和大小,避免频繁的读写操作。
  • -
  • 使用批量操作:合并多次操作,减少网络通信次数,提高性能。
  • -
  • 调整会话超时:根据业务需求调整会话超时时间,减少不必要的会话重连。
  • + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + private JwtUtils jwtUtil; + + public JwtRequestFilter(JwtUtils jwtUtil,UserDetailsService userDetailsService){ + this.userDetailsService = userDetailsService; + this.jwtUtil = jwtUtil; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + final String authorizationHeader = request.getHeader("Authorization"); + + String username = null; + String jwt = null; + + // 从请求头中获取JWT令牌 + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + jwt = authorizationHeader.substring(7); + username = jwtUtil.extractClaims(jwt).getSubject(); + } + + // 验证令牌并设置认证信息 + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); + if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken + .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + } + chain.doFilter(request, response); + } +}
+ +

执行流程

注册阶段

    +
  1. 用户发送注册请求:通过客户端(如浏览器或Postman)发送一个json格式的user对象的POST请求到/regester接口。
  2. +
  3. 调用UserService的注册方法:将需要注册的user对象传入registerUser方法中,将密码使用PasswordEncoder加密后和用户名存到数据库。
  4. +
+

认证阶段

    +
  1. 用户登录请求:通过客户端(如浏览器或Postman)发送一个json格式的user对象的POST请求到/login接口
  2. +
  3. 创建认证请求对象:使用 UsernamePasswordAuthenticationToken 创建一个包含用户名和密码的认证请求对象。
  4. +
  5. 调用 AuthenticationManagerauthenticate 方法AuthenticationManager 调用内部配置的 AuthenticationProvider 进行认证。
  6. +
  7. DaoAuthenticationProvider 进行用户认证
  8. +
+
    +
  • 使用 UserDetailsService 加载用户信息。
  • +
  • 使用 PasswordEncoder 验证密码。
-

如何解决ZooKeeper的“羊群效应”?

“羊群效应”是指大量客户端同时收到Watcher通知,导致瞬时流量激增。解决方法包括:

+
    +
  1. 返回认证结果
  2. +
    -
  • 分散Watcher:减少Watcher的数量和频率,避免集中通知。
  • -
  • 批量处理:合并多个通知,减少网络流量和处理压力。
  • -
  • 异步处理:使用异步方式处理Watcher通知,提高处理效率。
  • +
  • 如果认证成功,返回一个填充了用户详细信息和权限的 Authentication 对象。
  • +
  • 如果认证失败,抛出 AuthenticationException
-

如何处理网络分区问题?

网络分区会导致ZooKeeper集群中的部分服务器无法通信,影响数据一致性。处理方法包括:

+

生成JWT令牌阶段

    +
  1. 调用 loadUserByUsername 方法
  2. +
    -
  • 设置合理的超时:通过合理设置会话超时和连接超时,减少网络分区的影响。
  • -
  • 监控网络状况:通过监控工具实时监控网络状况,及时发现和解决网络问题。
  • -
  • 数据备份:定期备份数据,防止网络分区导致的数据丢失。
  • +
  • jwtUserDetailsService 是一个实现了 UserDetailsService 接口的服务类。
  • +
  • loadUserByUsername 方法用于根据用户名加载用户详细信息。
-

如何处理ZooKeeper服务器宕机问题?

服务器宕机会影响ZooKeeper集群的可用性和数据一致性。处理方法包括:

+
    +
  1. 查询用户信息:在 loadUserByUsername 方法中,通常会查询数据库或其他存储系统,以获取用户信息。

    +
  2. +
  3. 返回 UserDetails 对象:查询到用户信息后,创建并返回一个实现了 UserDetails 接口的对象,通常是一个自定义的用户详细信息类(例如 JwtUserDetails

    +
  4. +
  5. 调用 generateToken 方法

    +
  6. +
    -
  • 自动重启:配置ZooKeeper服务器自动重启机制,减少宕机时间。
  • -
  • 数据恢复:通过备份数据和快照恢复机制,快速恢复数据。
  • -
  • 高可用部署:通过增加服务器数量和合理的负载均衡,提高集群的高可用性。
  • +
  • jwtUtils 是一个用于生成和验证 JWT 令牌的工具类。
  • +
  • generateToken 方法用于生成一个包含用户名和其他信息的 JWT 令牌。
  • +
+
    +
  1. 创建 JWT 令牌
  2. +
+
    +
  • generateToken 方法中,使用 JWT 库(例如 jjwt)创建一个 JWT 令牌。
  • +
  • 令牌中通常包含用户名、签发时间、过期时间等信息,并使用指定的密钥进行签名。
  • +
+
    +
  1. 返回 JWT 令牌:生成的 JWT 令牌以字符串形式返回。
  2. +
+

受保护的控制器类

以testController为例,来看受保护的请求,如何进行JWT验证的执行流程

+
    +
  1. 请求到达 DispatcherServlet:所有请求首先到达 Spring 的 DispatcherServlet
  2. +
  3. 进入过滤器链:请求通过 Spring Security 配置的过滤器链。
  4. +
  5. 通过每个过滤器
  6. +
+
    +
  • SecurityContextPersistenceFilter:从存储中加载 SecurityContext 并将其存储在 SecurityContextHolder 中,以便在请求处理过程中使用。
  • +
  • JwtRequestFilter(自定义过滤器):从请求头中提取 JWT 令牌,并验证其有效性。如果令牌有效,将认证信息存储到 SecurityContextHolder 中。
  • +
  • ExceptionTranslationFilter:处理认证和授权过程中抛出的异常,将其转换为适当的 HTTP 响应。
  • +
  • FilterSecurityInterceptor:进行访问控制决策,检查当前用户是否有权限访问请求的资源。
  • +
+
    +
  1. 处理器映射DispatcherServlet 使用 HandlerMapping 查找与请求路径对应的处理器方法。
  2. +
  3. 调用处理器方法DispatcherServlet 调用找到的处理器方法。
  4. +
  5. 返回响应:处理器方法执行并返回响应,DispatcherServlet 将响应返回给客户端。
  6. +
+

涉及类介绍

UsernamePasswordAuthenticationToken

作用:

+
    +
  1. 存储用户认证信息
      +
    • UsernamePasswordAuthenticationToken 对象包含了用户的认证信息,例如用户名(principal)和密码(credentials)。
    • +
    +
  2. +
  3. 传递认证请求
      +
    • 在用户登录时,UsernamePasswordAuthenticationToken 对象通常用于创建认证请求对象,并传递给 AuthenticationManager 进行认证。
    • +
    +
  4. +
  5. 表示认证状态
      +
    • UsernamePasswordAuthenticationToken 对象还可以表示认证状态,通过 setAuthenticated 方法设置为已认证或未认证。
    • +
    +
  6. +
+

构造函数:

+
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
+    super((Collection)null); // 调用父类的构造函数,传入 null 表示没有权限信息
+    this.principal = principal; // 设置用户主体,就是用户名
+    this.credentials = credentials; // 设置用户凭据,就是密码
+    this.setAuthenticated(false);// 设置认证状态为未认证
+}
+ +

SecurityContextPersistenceFilter

作用

+
    +
  • 从存储中加载 SecurityContext 并将其存储在 SecurityContextHolder 中,以便在请求处理过程中使用。
  • +
  • 在请求结束时,将 SecurityContext 的状态保存到存储中。
  • +
+
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException {
+    HttpServletRequest request = (HttpServletRequest) req;
+    HttpServletResponse response = (HttpServletResponse) res;
+    SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
+    try {
+        SecurityContextHolder.setContext(contextBeforeChainExecution);
+        chain.doFilter(request, response);
+    } finally {
+        SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
+        SecurityContextHolder.clearContext();
+        repo.saveContext(contextAfterChainExecution, request, response);
+    }
+}
+ +

ExceptionTranslationFilter

作用

+
    +
  • 处理认证和授权过程中抛出的异常,将其转换为适当的 HTTP 响应。
+
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException {
+    HttpServletRequest request = (HttpServletRequest) req;
+    HttpServletResponse response = (HttpServletResponse) res;
+    try {
+        chain.doFilter(request, response);
+    } catch (AuthenticationException ex) {
+        sendStartAuthentication(request, response, chain, ex);
+    } catch (AccessDeniedException ex) {
+        handleAccessDeniedException(request, response, chain, ex);
+    }
+}
+ +

FilterSecurityInterceptor

作用

+
    +
  • 进行访问控制决策,检查当前用户是否有权限访问请求的资源。
  • +
+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+        throws IOException, ServletException {
+    FilterInvocation fi = new FilterInvocation(request, response, chain);
+    InterceptorStatusToken token = super.beforeInvocation(fi);
+    try {
+        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+    } finally {
+        super.finallyInvocation(token);
+        super.afterInvocation(token, null);
+    }
+}
+ @@ -621,7 +704,7 @@

- +

- +

+ + + + + + +
+ + -

注解

注解原理

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

-

SpringMVC常用注解

控制器相关注解

    -
  • **@Controller**:标识一个类为SpringMVC控制器。

    -
    @Controller
    -public class MyController {
    -    // ...
    -}
    -
  • -
  • **@RestController**:标识一个类为RESTful控制器,相当于同时使用了@Controller@ResponseBody

    -
    @RestController
    -public class MyRestController {
    -    // ...
    -}
  • + +
    +

    + + +

    + + +
    + + + + +
    + + +

    JVM基础

    JVM的主要组成部分及其作用

      +
    1. 类加载器(ClassLoader):负责加载Java类文件,将其转化为JVM能够识别的Class对象。
    2. +
    3. 运行时数据区(Runtime Data Area):JVM在执行Java程序时会使用到的数据区,包括方法区、堆、虚拟机栈、本地方法栈和程序计数器。
    4. +
    5. 执行引擎(Execution Engine):负责执行字节码,包括解释器、即时编译器(JIT)和垃圾回收器等。
    6. +
    7. 本地接口(Native Interface):JNI(Java Native Interface)允许Java代码与其他编程语言(如C/C++)编写的代码进行交互。
    8. +
    +

    JVM运行时数据区

      +
    1. 方法区(Method Area):存储已被虚拟机加载的类信息、常量、静态变量和即时编译后的代码等数据。
    2. +
    3. 堆(Heap):存放对象实例和数组,是GC主要管理的区域。
    4. +
    5. 虚拟机栈(Java Virtual Machine Stack):存放每个线程的栈帧(方法调用过程中的局部变量表、操作数栈、方法返回地址等)。
    6. +
    7. 本地方法栈(Native Method Stack):为本地方法服务,与虚拟机栈类似。
    8. +
    9. 程序计数器(Program Counter Register):当前线程执行的字节码的行号指示器。
    10. +
    +

    深拷贝和浅拷贝

      +
    • 浅拷贝:复制对象时只复制对象的引用,不复制对象本身。
    • +
    • 深拷贝:复制对象时,除了复制对象的引用,还复制对象本身及其包含的所有对象。
    -

    请求映射注解

      -
    • **@RequestMapping**:映射HTTP请求到处理方法或类上。可以用在类和方法级别。

      -
      @Controller
      -@RequestMapping("/home")
      -public class HomeController {
      -    @RequestMapping("/welcome")
      -    public String welcome() {
      -        return "welcome";
      -    }
      -}
      -
    • -
    • **@GetMapping**:专门用于处理HTTP GET请求。

      -
      @GetMapping("/items")
      -public String getItems() {
      -    // ...
      -}
      -
    • -
    • **@PostMapping**:专门用于处理HTTP POST请求。

      -
      @PostMapping("/items")
      -public String addItem() {
      -    // ...
      -}
      -
    • -
    • **@PutMapping**:专门用于处理HTTP PUT请求。

      -
      @PutMapping("/items/{id}")
      -public String updateItem(@PathVariable("id") Long id) {
      -    // ...
      -}
      -
    • -
    • **@DeleteMapping**:专门用于处理HTTP DELETE请求。

      -
      @DeleteMapping("/items/{id}")
      -public String deleteItem(@PathVariable("id") Long id) {
      -    // ...
      -}
    • +

      堆和栈的区别

        +
      • :用于存储对象实例,线程共享,生命周期较长,由GC管理。
      • +
      • :用于存储局部变量、方法调用等,线程私有,生命周期较短,方法执行完自动释放。
      -

      参数绑定注解

        -
      • **@PathVariable**:用于绑定URL中的路径变量到方法参数。

        -
        @GetMapping("/items/{id}")
        -public String getItem(@PathVariable("id") Long id) {
        -    // ...
        -}
        -
      • -
      • **@RequestParam**:用于绑定HTTP请求参数到方法参数。

        -
        @GetMapping("/search")
        -public String search(@RequestParam("q") String query) {
        -    // ...
        -}
        -
      • -
      • **@RequestBody**:用于将HTTP请求体中的内容绑定到方法参数。

        -
        java复制代码@PostMapping("/items")
        -public String createItem(@RequestBody Item item) {
        -    // ...
        -}
      • +

        队列和栈是什么?有什么区别?

          +
        • 队列(Queue):FIFO(先进先出)数据结构,元素从尾部插入,从头部删除。
        • +
        • 栈(Stack):LIFO(后进先出)数据结构,元素从顶部插入和删除。
        -

        返回值注解

          -
        • @ResponseBody

          -

          :用于将方法返回值直接写入HTTP响应体,常用于AJAX请求。

          -
          @GetMapping("/data")
          -@ResponseBody
          -public Data getData() {
          -    return new Data();
          -}
        • +

          HotSpot虚拟机对象探秘

          对象的创建

            +
          • 类加载检查:检查类是否已加载,未加载则加载类。
          • +
          • 分配内存:在堆中为对象分配内存,使用指针碰撞或空闲列表。
          • +
          • 初始化:对分配的内存进行零值初始化。
          • +
          • 设置对象头:初始化对象的元数据信息。
          • +
          • 执行构造方法:调用方法进行初始化。
          -

          表单处理和验证注解

            -
          • **@ModelAttribute**:用于绑定请求参数到命令对象,并将该对象添加到模型中。

            -
            @PostMapping("/register")
            -public String register(@ModelAttribute User user) {
            -    // ...
            -}
            -
          • -
          • **@Valid**:用于启用JSR-303/JSR-380校验。

            -
            @PostMapping("/register")
            -public String register(@Valid @ModelAttribute User user, BindingResult result) {
            -    if (result.hasErrors()) {
            -        return "registerForm";
            -    }
            -    // ...
            -}
          • +

            为对象分配内存

              +
            • 指针碰撞:适用于内存规整的堆,通过移动指针分配内存。
            • +
            • 空闲列表:适用于内存不规整的堆,通过维护空闲列表分配内存。
            -

            SpringMVC常见问题

            转发和重定向的区别

            转发(Forward)

              -
            • 定义:在服务器内部,服务器将请求转发到另一个资源(如JSP、Servlet)进行处理,浏览器地址栏不发生变化。

              -
            • -
            • 应用场景:常用于服务器内部跳转,不需要客户端知道URL变化的情况。

              -
            • -
            • 实现:使用RequestDispatcher的forward方法。

              -
              request.getRequestDispatcher("/targetPage").forward(request, response);
            • +

              处理并发安全问题

                +
              • CAS:通过乐观锁的方式解决并发问题。
              • +
              • TLAB:每个线程分配一个本地缓冲区(Thread Local Allocation Buffer)进行内存分配。
              -

              重定向(Redirect)

                -
              • 定义:服务器向客户端发送一个状态码(通常是302),指示客户端去访问另一个URL,浏览器地址栏会更新为新的URL。

                -
              • -
              • 应用场景:常用于需要客户端知道URL变化的情况,如登录后跳转到主页。

                -
              • -
              • 实现:使用HttpServletResponse的sendRedirect方法。

                -
                response.sendRedirect("/targetPage");
              • +

                对象的访问定位

                  +
                • 句柄访问:对象引用存储的是句柄地址,句柄中包含对象实例数据和类型数据的指针。
                • +
                • 直接指针:对象引用存储的是对象实例的直接地址。
                -

                SpringMVC和AJAX如何互相调用

                  -
                1. 前端AJAX请求

                  -
                  $.ajax({
                  -    type: "POST",
                  -    url: "/yourController/yourMethod",
                  -    data: JSON.stringify(yourData),
                  -    contentType: "application/json",
                  -    success: function(response) {
                  -        console.log(response);
                  -    }
                  -});
                  -
                2. -
                3. 后端SpringMVC处理AJAX请求

                  -
                  @RestController
                  -@RequestMapping("/yourController")
                  -public class YourController {
                  -    @PostMapping("/yourMethod")
                  -    public ResponseEntity<String> handleAjaxRequest(@RequestBody YourDataType yourData) {
                  -        // 处理数据
                  -        return ResponseEntity.ok("Success");
                  -    }
                  -}
                4. -
                -

                中文乱码如何解决

                  -
                1. 配置SpringMVC:在配置类或XML中添加字符编码过滤器。

                  -
                  @Configuration
                  -public class WebConfig implements WebMvcConfigurer {
                  -    @Bean
                  -    public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
                  -        CharacterEncodingFilter filter = new CharacterEncodingFilter();
                  -        filter.setEncoding("UTF-8");
                  -        filter.setForceEncoding(true);
                  -        FilterRegistrationBean<CharacterEncodingFilter> registrationBean = new FilterRegistrationBean<>(filter);
                  -        registrationBean.addUrlPatterns("/*");
                  -        return registrationBean;
                  -    }
                  -}
                  -
                2. -
                3. 设置请求和响应的编码

                  -
                  @GetMapping("/example")
                  -public ResponseEntity<String> example(HttpServletRequest request, HttpServletResponse response) {
                  -    request.setCharacterEncoding("UTF-8");
                  -    response.setCharacterEncoding("UTF-8");
                  -    return ResponseEntity.ok("中文内容");
                  -}
                4. -
                -

                如何进行异常处理

                  -
                1. **使用@ExceptionHandler**:

                  -
                  @ControllerAdvice
                  -public class GlobalExceptionHandler {
                  -    @ExceptionHandler(Exception.class)
                  -    public ResponseEntity<String> handleException(Exception e) {
                  -        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error: " + e.getMessage());
                  -    }
                  -}
                  -
                2. -
                3. 自定义异常和处理器

                  -
                  @ResponseStatus(HttpStatus.NOT_FOUND)
                  -public class ResourceNotFoundException extends RuntimeException {
                  -    public ResourceNotFoundException(String message) {
                  -        super(message);
                  -    }
                  -}
                  - -
                  @ControllerAdvice
                  -public class GlobalExceptionHandler {
                  -    @ExceptionHandler(ResourceNotFoundException.class)
                  -    public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException e) {
                  -        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
                  -    }
                  -}
                4. -
                -

                如何实现拦截器

                  -
                1. 创建拦截器

                  -
                  public class MyInterceptor implements HandlerInterceptor {
                  -    @Override
                  -    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                  -        // 前置处理
                  -        return true; // 返回true继续处理,返回false中止处理
                  -    }
                  -
                  -    @Override
                  -    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                  -        // 后置处理
                  -    }
                  -
                  -    @Override
                  -    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                  -        // 完成后处理
                  -    }
                  -}
                  -
                2. -
                3. 注册拦截器

                  -
                  @Configuration
                  -public class WebConfig implements WebMvcConfigurer {
                  -    @Override
                  -    public void addInterceptors(InterceptorRegistry registry) {
                  -        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
                  -    }
                  -}
                4. -
                -

                SpringMVC前后台传递对象有哪些,区别是什么

                  -
                1. Model

                  -
                    -
                  • 用法:用于将数据添加到模型中,供视图渲染时使用。

                    -
                  • -
                  • 常见方法:

                    -
                    @Controller
                    -public class MyController {
                    -    @RequestMapping("/example")
                    -    public String example(Model model) {
                    -        model.addAttribute("attributeName", attributeValue);
                    -        return "viewName";
                    -    }
                    -}
                    -
                  • -
                  • 特点:主要用于在控制器和视图之间传递数据,适用于返回视图页面的场景。

                    -
                  • +

                    内存溢出异常

                    Java会存在内存泄漏吗?请简单描述

                    Java中也会存在内存泄漏,例如,长生命周期对象持有短生命周期对象的引用,导致短生命周期对象无法被GC回收。

                    +

                    垃圾收集器

                    简述Java垃圾回收机制

                    Java的垃圾回收机制通过GC自动回收不再使用的对象,主要算法包括标记-清除、复制和标记-整理等。

                    +

                    GC是什么?为什么要GC

                    GC(Garbage Collection)是垃圾回收的缩写,自动管理内存,防止内存泄漏,提高程序稳定性和性能。

                    +

                    垃圾回收的优点和原理。并考虑2种回收机制

                      +
                    • 优点:自动内存管理,减少内存泄漏,提高开发效率。
                    • +
                    • 标记-清除:标记所有存活对象,清除未标记的对象。
                    • +
                    • 复制:将存活对象复制到新空间,清除旧空间。
                    - -
                  • ModelAndView

                    -
                      -
                    • 用法:既可以传递模型数据,又可以指定视图名称。

                      -
                    • -
                    • 常见方法:

                      -
                      @Controller
                      -public class MyController {
                      -    @RequestMapping("/example")
                      -    public ModelAndView example() {
                      -        ModelAndView mav = new ModelAndView("viewName");
                      -        mav.addObject("attributeName", attributeValue);
                      -        return mav;
                      -    }
                      -}
                      -
                    • -
                    • 特点:封装了模型数据和视图信息,适用于需要灵活指定视图和数据的情况。

                      -
                    • +

                      垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

                      垃圾回收器通过跟踪对象的引用链,回收不可达的对象。垃圾回收器不能马上回收内存,可通过System.gc()Runtime.getRuntime().gc()主动通知进行垃圾回收。

                      +

                      Java中都有哪些引用类型?

                        +
                      • 强引用:不会被GC回收。
                      • +
                      • 软引用:内存不足时会被回收。
                      • +
                      • 弱引用:在GC时会被回收。
                      • +
                      • 虚引用:无法通过引用访问对象,仅用于监控对象的回收。
                      - -
                    • @ModelAttribute

                      -
                        -
                      • 用法:可以用于方法参数,表示从请求参数中绑定对象;也可以用于方法上,表示在每个请求处理前都会调用,用于预处理数据。

                        -
                      • -
                      • 常见方法:

                        -
                        @Controller
                        -public class MyController {
                        -    @RequestMapping("/example")
                        -    public String example(@ModelAttribute("attributeName") MyModel model) {
                        -        // 使用model对象
                        -        return "viewName";
                        -    }
                        -
                        -    @ModelAttribute
                        -    public void addAttributes(Model model) {
                        -        model.addAttribute("commonAttribute", commonValue);
                        -    }
                        -}
                        -
                      • -
                      • 特点:用于自动绑定请求参数到对象,并可以在每次请求前设置预处理数据。

                        -
                      • +

                        怎么判断对象是否可以被回收?

                        通过引用计数或可达性分析算法判断对象是否可达,不可达的对象可以被回收。

                        +

                        在Java中,对象什么时候可以被垃圾回收?

                        当对象没有任何强引用时,可以被垃圾回收。

                        +

                        JVM中的永久代中会发生垃圾回收吗?

                        在JDK 8之前,永久代中会发生垃圾回收,但频率较低。JDK 8以后,永久代被移除,替代为元空间(Metaspace)。

                        +

                        说一下JVM有哪些垃圾回收算法?

                          +
                        • 标记-清除算法:标记存活对象,清除未标记对象。
                        • +
                        • 复制算法:将存活对象复制到新空间,清除旧空间。
                        • +
                        • 标记-整理算法:标记存活对象,将存活对象移动到一端,清除剩余空间。
                        • +
                        • 分代收集算法:将堆分为新生代和老年代,分别使用不同的垃圾回收算法。
                        - -
                      • @RequestBody和**@ResponseBody**:

                        -
                          -
                        • 用法@RequestBody用于将请求体中的数据绑定到方法参数上,常用于处理JSON或XML格式的数据;@ResponseBody用于将方法返回值作为响应体返回,常用于返回JSON或XML数据。

                          -
                        • -
                        • 常见方法:

                          -
                          @Controller
                          -public class MyController {
                          -    @RequestMapping(value = "/example", method = RequestMethod.POST)
                          -    public @ResponseBody Response example(@RequestBody Request request) {
                          -        // 处理request对象并返回response对象
                          -        return new Response();
                          -    }
                          -}
                          -
                        • -
                        • 特点:主要用于RESTful风格的接口中,便于处理和返回JSON或XML数据。

                          -
                        • +

                          说一下JVM有哪些垃圾回收器?

                            +
                          • Serial收集器:单线程收集,适用于单CPU环境。
                          • +
                          • Parallel收集器:多线程收集,适用于多CPU环境。
                          • +
                          • CMS收集器:低停顿时间的并发收集器,适用于低停顿需求的应用。
                          • +
                          • G1收集器:面向服务端应用,适用于大内存、多CPU环境。
                          - -
                        • Session和Request作用域对象

                          +

                          详细介绍一下CMS垃圾回收器?

                            +
                          • 初始标记:标记GC Roots可达的对象,短暂停顿。
                          • +
                          • 并发标记:标记所有可达对象,不停顿。
                          • +
                          • 重新标记:修正并发标记期间的变动,短暂停顿。
                          • +
                          • 并发清除:清除不可达对象,不停顿。
                          • +
                          +

                          新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?

                            +
                          • 新生代垃圾回收器:Serial、ParNew、Parallel Scavenge。特点是频繁回收、停顿时间短。
                          • +
                          • 老年代垃圾回收器:Serial Old、CMS、Parallel Old。特点是回收频率低、停顿时间长。
                          • +
                          +

                          简述分代垃圾回收器是怎么工作的?

                          分代垃圾回收器将堆划分为新生代和老年代。新生代对象生命周期短,频繁回收;老年代对象生命周期长,较少回收。回收时,新生代使用复制算法,老年代使用标记-整理或CMS。

                          +

                          内存分配策略

                          简述java内存分配与回收策略以及Minor GC和Major GC

                            +
                          • 内存分配:对象优先在Eden区分配,大对象直接进入老年代,长期存活对象进入老年代。
                          • +
                          • Minor GC:针对新生代的垃圾回收,频繁但耗时短。
                          • +
                          • Major GC:针对老年代的垃圾回收,频率低但耗时长。
                          • +
                          +

                          对象优先在Eden区分配

                          大多数新创建的对象在Eden区分配,当Eden区满时触发Minor GC,将存活对象移到Survivor区。

                          +

                          大对象直接进入老年代

                          避免在新生代频繁复制大对象,减少内存复制开销。

                          +

                          长期存活对象将进入老年代

                          对象在Survivor区经历多次GC后,晋升到老年代。

                          +

                          虚拟机类加载机制

                          简述java类加载机制

                          Java类加载机制包括加载、链接和初始化三个步骤。加载阶段通过类加载器读取类文件,将其转化为二进制数据;链接阶段验证、准备和解析类;初始化阶段执行类的静态初始化器和静态变量初始化。

                          +

                          描述一下JVM加载Class文件的原理机制

                          JVM通过类加载器加载Class文件,加载过程包括查找和加载Class文件,将其转化为Class对象。加载器遵循双亲委派模型,保证类加载的安全性和一致性。

                          +

                          什么是类加载器,类加载器有哪些?

                          类加载器是负责加载类文件的组件,主要有以下几种:

                            -
                          • 用法:可以通过@SessionAttributes注解声明某些模型属性存储到会话中,或者直接操作HttpSession对象;通过HttpServletRequest对象直接操作请求作用域数据。

                            -
                          • -
                          • 常见方法:

                            -
                            @Controller
                            -@SessionAttributes("sessionAttributeName")
                            -public class MyController {
                            -    @RequestMapping("/example")
                            -    public String example(HttpSession session) {
                            -        session.setAttribute("sessionAttribute", value);
                            -        return "viewName";
                            -    }
                            -}
                            -
                          • -
                          • 特点:适用于需要跨请求共享数据的场景,比如用户登录信息。

                            -
                          • +
                          • 启动类加载器(Bootstrap ClassLoader):加载JDK核心类库。
                          • +
                          • 扩展类加载器(Extension ClassLoader):加载扩展类库。
                          • +
                          • 应用程序类加载器(Application ClassLoader):加载应用程序类。
                          • +
                          • 自定义类加载器:用户定义的类加载器。
                          -
                        • +

                          说一下类装载的执行过程?

                          类装载的执行过程包括:

                          +
                            +
                          1. 加载:通过类加载器读取类文件。
                          2. +
                          3. 链接:包括验证、准备和解析三个阶段。
                          4. +
                          5. 初始化:执行类的静态初始化器和静态变量初始化。
                          -

                          每种对象传递方式都有其特定的使用场景和特点,Model和ModelAndView适用于返回视图的传统Web应用,@RequestBody和@ResponseBody则更适合RESTful API的开发。

                          +

                          什么是双亲委派模型?

                          双亲委派模型是指类加载时,类加载器先委托父类加载器加载,父类加载器无法加载时,子类加载器再尝试加载。这保证了类的加载安全和统一。

                          +

                          JVM调优

                          说一下JVM调优的工具?

                          常用的JVM调优工具包括:

                          +
                            +
                          • jstat:监控JVM内存和垃圾回收情况。
                          • +
                          • jmap:生成堆转储快照,分析内存使用情况。
                          • +
                          • jstack:生成线程快照,分析线程状态。
                          • +
                          • VisualVM:图形化界面监控JVM性能。
                          • +
                          +

                          常用的JVM调优的参数都有哪些?

                            +
                          • -Xms:设置初始堆大小。
                          • +
                          • -Xmx:设置最大堆大小。
                          • +
                          • -Xmn:设置新生代大小。
                          • +
                          • -XX:PermSize:设置初始永久代大小(JDK 8前)。
                          • +
                          • -XX:MaxPermSize:设置最大永久代大小(JDK 8前)。
                          • +
                          • -XX:MetaspaceSize:设置初始元空间大小(JDK 8后)。
                          • +
                          • -XX:MaxMetaspaceSize:设置最大元空间大小(JDK 8后)。
                          • +
                          • -XX:+UseG1GC:使用G1垃圾回收器。
                          • +
    @@ -1072,7 +1010,7 @@

    - +

    - +

+ + + -

Spring Cloud的高级特性

什么是分布式配置中心?

分布式配置中心用于集中管理和动态更新分布式系统中的配置。Spring Cloud Config就是一个典型的分布式配置中心,实现了集中化配置管理和动态配置刷新。

-

什么是服务熔断?

服务熔断是指在服务调用失败或延迟过高时,自动中断后续请求,保护系统避免因连锁故障导致的整体崩溃。Hystrix通过断路器模式实现服务熔断。

-

什么是服务降级?

服务降级是指在服务不可用时,提供备用的响应或处理逻辑,保证系统的高可用性。Hystrix通过Fallback机制实现服务降级。

-

什么是服务限流?

服务限流是指限制服务的调用频率,防止系统因过载而崩溃。常用的限流策略包括令牌桶、漏桶算法等,Spring Cloud Gateway支持通过自定义过滤器实现服务限流。

-

什么是分布式追踪?

分布式追踪用于跟踪微服务系统中跨服务的请求链路,帮助开发人员分析系统的性能瓶颈和故障点。Spring Cloud Sleuth和Zipkin是常用的分布式追踪解决方案。

-

Spring Cloud的测试

如何测试Spring Cloud服务?

可以使用Spring Boot的测试框架,如JUnit、MockMvc、RestTemplate等,结合Spring Cloud提供的模拟服务注册中心(如Eureka Server)的功能,进行集成测试和功能测试。

-

如何进行契约测试?

契约测试用于验证服务提供方和服务调用方之间的契约,确保服务接口的正确性。Spring Cloud Contract提供了契约测试的支持,通过定义契约文件生成测试用例,验证服务的输入输出。

-

如何进行性能测试?

性能测试用于验证系统在高负载下的表现,常用的工具有JMeter、Gatling等。可以通过模拟高并发请求,检测系统的响应时间、吞吐量和资源占用情况,识别性能瓶颈并优化。

-

Spring Cloud的部署

如何打包Spring Cloud应用?

Spring Cloud应用可以打包成可执行的JAR文件,通过Maven或Gradle插件将应用及其依赖打包在一起。例如,使用Maven:

-
<build>
-    <plugins>
-        <plugin>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-maven-plugin</artifactId>
-        </plugin>
-    </plugins>
-</build>
-

然后执行mvn package命令生成JAR文件。

-

如何部署Spring Cloud应用到Docker?

可以通过Dockerfile创建Docker镜像并运行Spring Cloud应用。示例如下:

-
FROM openjdk:11-jre
-COPY target/myapp.jar /app.jar
-ENTRYPOINT ["java", "-jar", "/app.jar"]
-

然后使用Docker命令生成镜像并运行容器:

-
docker build -t myapp .
-docker run -p 8080:8080 myapp
+ + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

SpringMVC介绍

MVC是什么

MVC(Model-View-Controller,模型-视图-控制器)是一种软件架构模式,主要用于构建用户界面应用程序。它将应用程序分为三个核心部分:模型(Model)、视图(View)和控制器(Controller),从而实现关注点分离,使开发和维护更加简便和高效。

+

模型(Model)

模型是应用程序的核心部分,负责处理应用程序的业务逻辑和数据管理。它直接与数据库交互,执行CRUD操作(创建、读取、更新、删除)。模型中的数据通常是纯粹的业务逻辑,没有任何关于用户界面的知识。

+

视图(View)

视图是用户界面部分,负责数据的展示。视图从模型获取数据并将其呈现给用户。视图是静态的,不包含业务逻辑,它只负责显示数据。视图的主要任务是渲染模型的数据到适当的格式,如HTML、JSON、XML等。

+

控制器(Controller)

控制器充当模型和视图之间的中介,处理用户输入并更新模型和视图。它接收用户输入,通过调用模型的方法来处理数据,并选择合适的视图来显示处理后的结果。控制器包含应用程序的流控制逻辑,是用户与应用程序交互的主要渠道。

+

MVC的工作流程

    +
  1. 用户在视图中执行操作(例如点击按钮或链接)。
  2. +
  3. 控制器捕捉到用户的输入,调用相应的模型方法来处理业务逻辑。
  4. +
  5. 模型处理业务逻辑,更新数据或从数据库中获取数据。
  6. +
  7. 控制器接收到模型返回的数据,选择合适的视图来呈现这些数据。
  8. +
  9. 视图将数据渲染并显示给用户。
  10. +
+

MVC设计模式优点

    +
  • 关注点分离:每个组件有明确的职责,使代码更易于理解和维护。
  • +
  • 可测试性:模型、视图和控制器可以独立测试,提高代码质量。
  • +
  • 可复用性:视图和模型可以独立变化,提高组件的重用性。
  • +
  • 并行开发:开发人员可以独立开发模型、视图和控制器,减少开发时间。
  • +
+

什么是SpringMVC

SpringMVC是Spring Framework的一部分,是一种基于Java的MVC(Model-View-Controller)框架,主要用于构建Web应用程序。它提供了一套强大的工具和功能,使开发者能够轻松创建和管理Web应用程序的各个组件。

+

SpringMVC的优点

    +
  1. 与Spring生态系统的无缝集成:SpringMVC与Spring框架的其他模块(如Spring Security、Spring Data等)紧密集成,提供一致的编程模型。
  2. +
  3. 注解驱动:大量使用注解(如@Controller@RequestMapping@GetMapping等)简化配置和开发。
  4. +
  5. 灵活的视图解析:支持多种视图技术,开发者可以根据需求选择合适的视图引擎。
  6. +
  7. 强大的数据绑定和验证功能:提供数据绑定、表单处理和验证功能,简化用户输入处理。
  8. +
  9. 易于测试:SpringMVC的各个组件可以独立测试,提高了代码的可维护性和可测试性。
  10. +
+

核心组件

SpringMVC的核心组件有哪些?

    +
  1. DispatcherServlet:SpringMVC的核心组件,充当前端控制器。它接收所有的HTTP请求,并将它们分派到适当的处理器(Controller)。
  2. +
  3. Controller:控制器处理HTTP请求,执行业务逻辑,并返回一个视图名称或视图对象。控制器类通常使用@Controller注解来标识。
  4. +
  5. Model:模型包含应用程序的数据和业务逻辑。SpringMVC通过ModelModelAndView对象将数据传递给视图。
  6. +
  7. View:视图负责将模型数据渲染成用户界面。SpringMVC支持多种视图技术,如JSP、Thymeleaf、FreeMarker等。
  8. +
  9. ViewResolver:视图解析器负责将控制器返回的视图名称解析为实际的视图对象。常见的视图解析器有InternalResourceViewResolverThymeleafViewResolver等。
  10. +
  11. HandlerMapping:处理器映射器将HTTP请求映射到相应的处理器方法。常用的映射器有RequestMappingHandlerMapping,它通过@RequestMapping注解进行映射。
  12. +
+

SpringMVC的工作原理

    +
  1. 请求接收:用户通过浏览器发送一个HTTP请求到服务器。
  2. +
  3. DispatcherServlet分派:DispatcherServlet拦截请求,并根据URL映射查找相应的控制器。
  4. +
  5. 处理请求:控制器处理请求,执行相应的业务逻辑,并将数据添加到模型中。
  6. +
  7. 选择视图:控制器返回一个视图名称,DispatcherServlet通过ViewResolver解析该视图名称,找到对应的视图。
  8. +
  9. 渲染视图:视图渲染模型数据,生成最终的HTML响应,并返回给客户端。
  10. +
+

示例代码

下面是一个简单的SpringMVC应用程序示例:

+

配置类(Java Config)

import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@EnableWebMvc
+public class WebConfig implements WebMvcConfigurer {
+    @Override
+    public void addViewControllers(ViewControllerRegistry registry) {
+        registry.addViewController("/").setViewName("home");
+    }
+}
+ +

控制器类

import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("/hello")
+public class HelloController {
+
+    @GetMapping
+    public String sayHello(Model model) {
+        model.addAttribute("message", "Hello, SpringMVC!");
+        return "hello";
+    }
+}
+ +

视图(JSP)

<!-- /WEB-INF/views/hello.jsp -->
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Hello</title>
+</head>
+<body>
+    <h1>${message}</h1>
+</body>
+</html>
-

如何部署Spring Cloud应用到Kubernetes?

可以通过Kubernetes的Pod、Deployment和Service等资源,将Spring Cloud应用部署到Kubernetes集群中。创建Kubernetes部署文件:

-
apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: myapp
-spec:
-  replicas: 3
-  selector:
-    matchLabels:
-      app: myapp
-  template:
-    metadata:
-      labels:
-        app: myapp
-    spec:
-      containers:
-      - name: myapp
-        image: myapp:latest
-        ports:
-        - containerPort: 8080
----
-apiVersion: v1
-kind: Service
-metadata:
-  name: myapp
-spec:
-  selector:
-    app: myapp
-  ports:
-  - protocol: TCP
-    port: 80
-    targetPort: 8080
-  type: LoadBalancer
-

然后使用kubectl命令部署到Kubernetes集群:

-
kubectl apply -f deployment.yml
+

注解

注解原理

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

+

SpringMVC常用注解

控制器相关注解

    +
  • **@Controller**:标识一个类为SpringMVC控制器。

    +
    @Controller
    +public class MyController {
    +    // ...
    +}
    +
  • +
  • **@RestController**:标识一个类为RESTful控制器,相当于同时使用了@Controller@ResponseBody

    +
    @RestController
    +public class MyRestController {
    +    // ...
    +}
  • +
+

请求映射注解

    +
  • **@RequestMapping**:映射HTTP请求到处理方法或类上。可以用在类和方法级别。

    +
    @Controller
    +@RequestMapping("/home")
    +public class HomeController {
    +    @RequestMapping("/welcome")
    +    public String welcome() {
    +        return "welcome";
    +    }
    +}
    +
  • +
  • **@GetMapping**:专门用于处理HTTP GET请求。

    +
    @GetMapping("/items")
    +public String getItems() {
    +    // ...
    +}
    +
  • +
  • **@PostMapping**:专门用于处理HTTP POST请求。

    +
    @PostMapping("/items")
    +public String addItem() {
    +    // ...
    +}
    +
  • +
  • **@PutMapping**:专门用于处理HTTP PUT请求。

    +
    @PutMapping("/items/{id}")
    +public String updateItem(@PathVariable("id") Long id) {
    +    // ...
    +}
    +
  • +
  • **@DeleteMapping**:专门用于处理HTTP DELETE请求。

    +
    @DeleteMapping("/items/{id}")
    +public String deleteItem(@PathVariable("id") Long id) {
    +    // ...
    +}
  • +
+

参数绑定注解

    +
  • **@PathVariable**:用于绑定URL中的路径变量到方法参数。

    +
    @GetMapping("/items/{id}")
    +public String getItem(@PathVariable("id") Long id) {
    +    // ...
    +}
    +
  • +
  • **@RequestParam**:用于绑定HTTP请求参数到方法参数。

    +
    @GetMapping("/search")
    +public String search(@RequestParam("q") String query) {
    +    // ...
    +}
    +
  • +
  • **@RequestBody**:用于将HTTP请求体中的内容绑定到方法参数。

    +
    java复制代码@PostMapping("/items")
    +public String createItem(@RequestBody Item item) {
    +    // ...
    +}
  • +
+

返回值注解

    +
  • @ResponseBody

    +

    :用于将方法返回值直接写入HTTP响应体,常用于AJAX请求。

    +
    @GetMapping("/data")
    +@ResponseBody
    +public Data getData() {
    +    return new Data();
    +}
  • +
+

表单处理和验证注解

    +
  • **@ModelAttribute**:用于绑定请求参数到命令对象,并将该对象添加到模型中。

    +
    @PostMapping("/register")
    +public String register(@ModelAttribute User user) {
    +    // ...
    +}
    +
  • +
  • **@Valid**:用于启用JSR-303/JSR-380校验。

    +
    @PostMapping("/register")
    +public String register(@Valid @ModelAttribute User user, BindingResult result) {
    +    if (result.hasErrors()) {
    +        return "registerForm";
    +    }
    +    // ...
    +}
  • +
+

SpringMVC常见问题

转发和重定向的区别

转发(Forward)

    +
  • 定义:在服务器内部,服务器将请求转发到另一个资源(如JSP、Servlet)进行处理,浏览器地址栏不发生变化。

    +
  • +
  • 应用场景:常用于服务器内部跳转,不需要客户端知道URL变化的情况。

    +
  • +
  • 实现:使用RequestDispatcher的forward方法。

    +
    request.getRequestDispatcher("/targetPage").forward(request, response);
  • +
+

重定向(Redirect)

    +
  • 定义:服务器向客户端发送一个状态码(通常是302),指示客户端去访问另一个URL,浏览器地址栏会更新为新的URL。

    +
  • +
  • 应用场景:常用于需要客户端知道URL变化的情况,如登录后跳转到主页。

    +
  • +
  • 实现:使用HttpServletResponse的sendRedirect方法。

    +
    response.sendRedirect("/targetPage");
  • +
+

SpringMVC和AJAX如何互相调用

    +
  1. 前端AJAX请求

    +
    $.ajax({
    +    type: "POST",
    +    url: "/yourController/yourMethod",
    +    data: JSON.stringify(yourData),
    +    contentType: "application/json",
    +    success: function(response) {
    +        console.log(response);
    +    }
    +});
    +
  2. +
  3. 后端SpringMVC处理AJAX请求

    +
    @RestController
    +@RequestMapping("/yourController")
    +public class YourController {
    +    @PostMapping("/yourMethod")
    +    public ResponseEntity<String> handleAjaxRequest(@RequestBody YourDataType yourData) {
    +        // 处理数据
    +        return ResponseEntity.ok("Success");
    +    }
    +}
  4. +
+

中文乱码如何解决

    +
  1. 配置SpringMVC:在配置类或XML中添加字符编码过滤器。

    +
    @Configuration
    +public class WebConfig implements WebMvcConfigurer {
    +    @Bean
    +    public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
    +        CharacterEncodingFilter filter = new CharacterEncodingFilter();
    +        filter.setEncoding("UTF-8");
    +        filter.setForceEncoding(true);
    +        FilterRegistrationBean<CharacterEncodingFilter> registrationBean = new FilterRegistrationBean<>(filter);
    +        registrationBean.addUrlPatterns("/*");
    +        return registrationBean;
    +    }
    +}
    +
  2. +
  3. 设置请求和响应的编码

    +
    @GetMapping("/example")
    +public ResponseEntity<String> example(HttpServletRequest request, HttpServletResponse response) {
    +    request.setCharacterEncoding("UTF-8");
    +    response.setCharacterEncoding("UTF-8");
    +    return ResponseEntity.ok("中文内容");
    +}
  4. +
+

如何进行异常处理

    +
  1. **使用@ExceptionHandler**:

    +
    @ControllerAdvice
    +public class GlobalExceptionHandler {
    +    @ExceptionHandler(Exception.class)
    +    public ResponseEntity<String> handleException(Exception e) {
    +        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error: " + e.getMessage());
    +    }
    +}
    +
  2. +
  3. 自定义异常和处理器

    +
    @ResponseStatus(HttpStatus.NOT_FOUND)
    +public class ResourceNotFoundException extends RuntimeException {
    +    public ResourceNotFoundException(String message) {
    +        super(message);
    +    }
    +}
    + +
    @ControllerAdvice
    +public class GlobalExceptionHandler {
    +    @ExceptionHandler(ResourceNotFoundException.class)
    +    public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException e) {
    +        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
    +    }
    +}
  4. +
+

如何实现拦截器

    +
  1. 创建拦截器

    +
    public class MyInterceptor implements HandlerInterceptor {
    +    @Override
    +    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    +        // 前置处理
    +        return true; // 返回true继续处理,返回false中止处理
    +    }
    +
    +    @Override
    +    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    +        // 后置处理
    +    }
    +
    +    @Override
    +    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    +        // 完成后处理
    +    }
    +}
    +
  2. +
  3. 注册拦截器

    +
    @Configuration
    +public class WebConfig implements WebMvcConfigurer {
    +    @Override
    +    public void addInterceptors(InterceptorRegistry registry) {
    +        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    +    }
    +}
  4. +
+

SpringMVC前后台传递对象有哪些,区别是什么

    +
  1. Model

    +
      +
    • 用法:用于将数据添加到模型中,供视图渲染时使用。

      +
    • +
    • 常见方法:

      +
      @Controller
      +public class MyController {
      +    @RequestMapping("/example")
      +    public String example(Model model) {
      +        model.addAttribute("attributeName", attributeValue);
      +        return "viewName";
      +    }
      +}
      +
    • +
    • 特点:主要用于在控制器和视图之间传递数据,适用于返回视图页面的场景。

      +
    • +
    +
  2. +
  3. ModelAndView

    +
      +
    • 用法:既可以传递模型数据,又可以指定视图名称。

      +
    • +
    • 常见方法:

      +
      @Controller
      +public class MyController {
      +    @RequestMapping("/example")
      +    public ModelAndView example() {
      +        ModelAndView mav = new ModelAndView("viewName");
      +        mav.addObject("attributeName", attributeValue);
      +        return mav;
      +    }
      +}
      +
    • +
    • 特点:封装了模型数据和视图信息,适用于需要灵活指定视图和数据的情况。

      +
    • +
    +
  4. +
  5. @ModelAttribute

    +
      +
    • 用法:可以用于方法参数,表示从请求参数中绑定对象;也可以用于方法上,表示在每个请求处理前都会调用,用于预处理数据。

      +
    • +
    • 常见方法:

      +
      @Controller
      +public class MyController {
      +    @RequestMapping("/example")
      +    public String example(@ModelAttribute("attributeName") MyModel model) {
      +        // 使用model对象
      +        return "viewName";
      +    }
      +
      +    @ModelAttribute
      +    public void addAttributes(Model model) {
      +        model.addAttribute("commonAttribute", commonValue);
      +    }
      +}
      +
    • +
    • 特点:用于自动绑定请求参数到对象,并可以在每次请求前设置预处理数据。

      +
    • +
    +
  6. +
  7. @RequestBody和**@ResponseBody**:

    +
      +
    • 用法@RequestBody用于将请求体中的数据绑定到方法参数上,常用于处理JSON或XML格式的数据;@ResponseBody用于将方法返回值作为响应体返回,常用于返回JSON或XML数据。

      +
    • +
    • 常见方法:

      +
      @Controller
      +public class MyController {
      +    @RequestMapping(value = "/example", method = RequestMethod.POST)
      +    public @ResponseBody Response example(@RequestBody Request request) {
      +        // 处理request对象并返回response对象
      +        return new Response();
      +    }
      +}
      +
    • +
    • 特点:主要用于RESTful风格的接口中,便于处理和返回JSON或XML数据。

      +
    • +
    +
  8. +
  9. Session和Request作用域对象

    +
      +
    • 用法:可以通过@SessionAttributes注解声明某些模型属性存储到会话中,或者直接操作HttpSession对象;通过HttpServletRequest对象直接操作请求作用域数据。

      +
    • +
    • 常见方法:

      +
      @Controller
      +@SessionAttributes("sessionAttributeName")
      +public class MyController {
      +    @RequestMapping("/example")
      +    public String example(HttpSession session) {
      +        session.setAttribute("sessionAttribute", value);
      +        return "viewName";
      +    }
      +}
      +
    • +
    • 特点:适用于需要跨请求共享数据的场景,比如用户登录信息。

      +
    • +
    +
  10. +
+

每种对象传递方式都有其特定的使用场景和特点,Model和ModelAndView适用于返回视图的传统Web应用,@RequestBody和@ResponseBody则更适合RESTful API的开发。

+
@@ -1407,7 +1611,7 @@

- +

- +

- - - +

Spring Boot简介

什么是Spring Boot?

Spring Boot是由Pivotal团队提供的一个基于Spring框架的开源项目,旨在简化Spring应用的开发。它通过约定优于配置的方式,减少了开发人员需要编写的配置代码,提供了一套快速创建独立运行、生产级Spring应用的解决方案。

+

Spring Boot的主要特性有哪些?

    +
  • 自动配置:根据项目中的依赖自动配置Spring应用。
  • +
  • 独立运行:可以打包成可执行的JAR或WAR文件,直接运行。
  • +
  • 内嵌服务器:默认提供内嵌的Tomcat、Jetty和Undertow服务器。
  • +
  • 简化的Maven配置:通过提供的“starter”依赖,简化Maven配置。
  • +
  • 生产级监控和管理:内置健康检查、监控和管理功能。
  • +
  • 无代码生成:没有代码生成,不需要配置文件生成,直接使用Java代码配置。
  • +
+

Spring Boot与Spring的区别?

Spring Boot是对Spring框架的一种增强,旨在简化Spring应用的配置和部署。Spring Boot通过自动配置和内嵌服务器,使得Spring应用的开发更快捷、更简单。而Spring是一个全面的框架,提供了各种各样的组件和配置选项。

+

Spring Boot的核心概念

什么是Spring Boot Starter?

Spring Boot Starter是一种方便的依赖包集合,提供了一组常用的依赖和自动配置。开发者只需添加相应的Starter依赖,即可快速引入所需的功能。例如,spring-boot-starter-web包含了开发Web应用所需的所有依赖。

+

什么是Spring Boot的自动配置?

Spring Boot的自动配置是一种机制,根据项目的依赖和配置,自动配置Spring应用上下文中的bean。通过@EnableAutoConfiguration@SpringBootApplication注解启用,Spring Boot会根据项目中的类路径和bean定义,自动配置所需的bean。

+

什么是Spring Boot的约定优于配置?

Spring Boot采用约定优于配置的原则,提供合理的默认配置,减少开发人员的配置负担。如果默认配置不满足需求,开发人员可以通过配置文件或代码进行自定义配置。

+

Spring Boot的主要注解有哪些?

    +
  • @SpringBootApplication:用于标注主类,集成了@Configuration@EnableAutoConfiguration@ComponentScan
  • +
  • @Configuration:用于定义配置类,等同于XML配置文件。
  • +
  • @EnableAutoConfiguration:启用自动配置。
  • +
  • @ComponentScan:自动扫描并注册组件。
  • +
  • @RestController:用于创建RESTful Web服务的控制器。
  • +
  • @RequestMapping:用于映射HTTP请求到处理方法。
  • +
+

Spring Boot的配置

Spring Boot的配置文件有哪些?

Spring Boot主要使用application.propertiesapplication.yml来配置应用。配置文件位于src/main/resources目录下。

+

如何在Spring Boot中读取配置文件?

可以通过@Value注解或@ConfigurationProperties注解读取配置文件中的值。例如:

+
@Value("${my.property}")
+private String myProperty;
+

或者使用@ConfigurationProperties

+
@ConfigurationProperties(prefix = "my")
+public class MyProperties {
+    private String property;
+    // getters and setters
+}
- - - -
- +

如何配置多环境支持?

通过在application.properties文件中配置不同环境的配置文件,例如:

+
    +
  • application-dev.properties:开发环境配置。
  • +
  • application-prod.properties:生产环境配置。
    然后在启动时通过--spring.profiles.active=devapplication.yml中的spring.profiles.active=dev指定激活的配置文件。
  • +
+

Spring Boot的高级特性

什么是Spring Boot Actuator?

Spring Boot Actuator提供了一组用于监控和管理Spring Boot应用的端点,包括健康检查、审计、度量和环境信息等。可以通过/actuator访问这些端点,并通过配置文件进行自定义和安全设置。

+

什么是Spring Boot DevTools?

Spring Boot DevTools是一个开发工具包,旨在提高开发效率。它提供了自动重启、实时加载属性文件、调试等功能。当类路径上的文件发生变化时,DevTools会自动重新启动应用。

+

如何实现Spring Boot的安全管理?

Spring Boot可以集成Spring Security实现安全管理。只需添加spring-boot-starter-security依赖,并通过配置类或配置文件进行安全配置。例如:

+
@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+            .authorizeRequests()
+            .antMatchers("/public/**").permitAll()
+            .anyRequest().authenticated()
+            .and()
+            .formLogin();
+    }
+}
- +

什么是Spring Boot的异步处理?

Spring Boot支持通过@EnableAsync注解和@Async注解实现异步方法调用。例如:

+
@EnableAsync
+@SpringBootApplication
+public class Application {
+    // ...
+}
 
-    
-      
-

- - -

+public class MyService { + @Async + public void asyncMethod() { + // 异步执行的代码 + } +}
- -
+public void publishEvent() { + eventPublisher.publishEvent(new MyEvent(this)); +} - - - -
+

Spring Boot的数据访问

Spring Boot如何集成JPA?

Spring Boot通过spring-boot-starter-data-jpa集成JPA。只需添加依赖并配置数据源,Spring Boot会自动配置EntityManagerFactory和事务管理。可以通过@Entity注解定义实体类,通过JpaRepository接口定义数据访问层。

+

如何在Spring Boot中使用MyBatis?

Spring Boot可以通过spring-boot-starter-data-mybatis集成MyBatis。配置数据源并编写Mapper接口和XML映射文件,然后通过@MapperScan注解自动扫描Mapper接口。

+

什么是Spring Data REST?

Spring Data REST是Spring提供的一个项目,旨在通过简单配置将Spring Data仓库暴露为RESTful服务。可以通过@RepositoryRestResource注解自动生成RESTful API。例如:

+
@RepositoryRestResource
+public interface UserRepository extends JpaRepository<User, Long> {
+    // 自动生成的RESTful API
+}
- -

MyBatis简介

什么是ORM框架

ORM(Object-Relational Mapping)框架是用于在面向对象编程语言中,将对象映射到数据库表的一种技术。ORM框架通过自动生成SQL语句和自动映射数据库记录与编程语言中的对象,实现面向对象与关系数据库之间的转换。

-

MyBatis是什么

MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJOs(Plain Old Java Objects)到数据库中的记录。

-

MyBatis和其他ORM框架的区别

    -
  • 灵活性:MyBatis允许直接编写SQL语句,因此在灵活性上优于Hibernate等全自动化ORM框架。
  • -
  • 学习曲线:MyBatis的学习曲线较缓,相对简单,适合需要精确控制SQL的开发者。
  • -
  • 性能:由于可以手写SQL,MyBatis在处理复杂查询和优化方面具有优势。
  • -
  • 对象-关系映射:MyBatis对对象-关系映射提供了简单的XML配置或注解配置,而像Hibernate提供更强大的映射机制。
  • -
-

MyBatis的由来

MyBatis最初是Apache旗下的一个项目,名为iBatis。iBatis的名字来源于“internet”和“abatis”的结合,象征着网络时代的持久层解决方案。2010年,iBatis项目迁移到Google Code并更名为MyBatis,最终MyBatis成为一个独立的开源项目。

-

MyBatis的优缺点

优点

-
    -
  • 简单易学,配置灵活。
  • -
  • SQL编写灵活,开发人员可以精确控制SQL执行。
  • -
  • 轻量级框架,便于与其他框架集成。
  • -
  • 支持动态SQL。
  • -
-

缺点

-
    -
  • 需要手写SQL,工作量较大。
  • -
  • 对于复杂对象的映射支持不如Hibernate。
  • -
  • 维护SQL映射文件时可能较为繁琐。
  • -
-

MyBatis的使用场景

    -
  • 项目中需要精确控制SQL执行的情况。
  • -
  • 需要频繁调整和优化SQL的应用。
  • -
  • 轻量级应用或简单项目。
  • -
  • 不需要复杂关系映射的场景。
  • -
-

MyBatis原理

MyBatis工作原理

MyBatis的工作原理是通过配置文件和注解,将Java对象与SQL语句进行映射。配置文件中定义了数据源和事务管理等配置信息,映射文件则定义了SQL语句和对象的映射关系。在执行查询或更新操作时,MyBatis将输入参数与SQL语句绑定,并执行SQL,最后将结果集映射到Java对象。

-

MyBatis编程步骤

    -
  1. 创建MyBatis配置文件(mybatis-config.xml)。
  2. -
  3. 创建数据库映射文件(Mapper.xml),定义SQL语句。
  4. -
  5. 创建Java实体类,映射数据库表。
  6. -
  7. 创建Mapper接口,定义映射方法。
  8. -
  9. 通过SqlSessionFactory获取SqlSession,并执行数据库操作。
  10. -
-

MyBatis的架构

MyBatis的架构包括以下几个核心组件:

-
    -
  • Configuration:全局配置类,包含所有MyBatis的配置信息。
  • -
  • SqlSessionFactory:SqlSession工厂类,用于创建SqlSession实例。
  • -
  • SqlSession:用于执行SQL语句的接口。
  • -
  • Mapper:映射器接口,用于定义数据库操作方法。
  • -
  • Executor:执行器,负责SQL的执行和结果集的映射。
  • -
-

什么是MyBatis预编译

MyBatis预编译是指在执行SQL语句之前,先对SQL进行编译处理,将SQL中的参数占位符替换为对应的参数值。预编译后的SQL可以重复使用,减少了编译时间,提高了执行效率。

-

MyBatis的执行器有哪些

MyBatis提供了三种执行器:

-
    -
  • SimpleExecutor:每次执行都会创建一个新的预处理语句对象。
  • -
  • ReuseExecutor:执行SQL时会复用预处理语句对象。
  • -
  • BatchExecutor:用于批量执行SQL语句。
  • -
-

MyBatis的懒加载

MyBatis的懒加载是一种延迟加载技术,当关联对象被真正访问时才加载它。通过在配置文件中设置lazyLoadingEnabled属性为true来启用懒加载。

-

映射器

#{}和${}的区别

    -
  • #{}:使用预编译机制,可以防止SQL注入,将参数替换为占位符。
  • -
  • ${}:直接拼接SQL字符串,不进行预编译,容易导致SQL注入。
  • -
-

如何实现模糊查询

使用LIKE关键字进行模糊查询。例如:

-
<select id="findByName" resultType="User">
-  SELECT * FROM users WHERE name LIKE #{name}
-</select>
-

在传递参数时需要在Java代码中加上%

-
mapper.findByName("%name%");
+

什么是Spring Boot的事务管理?

Spring Boot通过@EnableTransactionManagement注解启用事务管理,并通过@Transactional注解管理事务边界。例如:

+
@EnableTransactionManagement
+@SpringBootApplication
+public class Application {
+    // ...
+}
 
-

在mapper中如何传递多个参数

可以使用@Param注解或者传递一个Map。例如:

-
public List<User> findByAgeAndName(@Param("age") int age, @Param("name") String name);
-

-
public List<User> findByAgeAndName(Map<String, Object> params);
+@Service +public class MyService { + @Transactional + public void transactionalMethod() { + // 事务性操作 + } +}
-

MyBatis如何执行批量操作

通过使用<foreach>标签来实现批量操作。例如批量插入:

-
<insert id="batchInsert">
-  INSERT INTO users (name, age) VALUES
-  <foreach collection="list" item="user" separator=",">
-    (#{user.name}, #{user.age})
-  </foreach>
-</insert>
+

Spring Boot的测试

如何使用Spring Boot进行单元测试?

Spring Boot提供了spring-boot-starter-test,集成了JUnit、Spring Test、AssertJ和Mockito等常用测试框架。可以通过@SpringBootTest注解进行集成测试。例如:

+
@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MyServiceTests {
+    @Autowired
+    private MyService myService;
 
-

如何获取生成的主键

使用useGeneratedKeyskeyProperty属性。例如:

-
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
-  INSERT INTO users (name, age) VALUES (#{name}, #{age})
-</insert>
+ @Test + public void testServiceMethod() { + assertNotNull(myService); + } +}
-

什么是别名

别名是为Java类型设置的简短名称,方便在配置文件中使用。可以在配置文件中设置:

-
<typeAliases>
-  <typeAlias alias="User" type="com.example.User"/>
-</typeAliases>
+

如何进行Mock测试?

可以通过@MockBean注解创建Mock对象,并注入到Spring应用上下文中。例如:

+
@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MyControllerTests {
+    @MockBean
+    private MyService myService;
 
-

Mapper 编写有哪几种方式?

    -
  1. XML方式:通过XML配置文件定义SQL语句。
  2. -
  3. 注解方式:在Mapper接口中使用注解定义SQL语句。
  4. -
  5. 混合方式:同时使用XML和注解定义SQL语句。
  6. -
-

什么是MyBatis的接口绑定?

接口绑定是指通过Mapper接口绑定SQL映射文件,实现面向接口编程。MyBatis会自动将接口方法与映射文件中的SQL语句进行关联。

-

Xml映射文件中有哪些标签

常用的标签包括:

-
    -
  • <select>:查询语句。
  • -
  • <insert>:插入语句。
  • -
  • <update>:更新语句。
  • -
  • <delete>:删除语句。
  • -
  • <resultMap>:结果集映射。
  • -
  • <parameterMap>:参数映射。
  • -
  • <sql>:可重用的SQL片段。
  • -
  • <include>:包含SQL片段。
  • -
  • <foreach>:循环处理。
  • -
  • <if>:条件判断。
  • -
  • <choose>、<when>、<otherwise>:条件选择。
  • -
-

高级查询

MyBatis实现一对一,一对多有几种方式,怎么操作的?

    -
  1. 一对一

    -
      -
    • 使用association标签映射。
    • -
    • 通过嵌套查询或嵌套结果映射。
    • -
    -
  2. -
  3. 一对多

    -
      -
    • 使用collection标签映射。
    • -
    • 通过嵌套查询或嵌套结果映射。
    • -
    -
  4. -
-

示例:

-
<resultMap id="orderMap" type="Order">
-  <id property="id" column="id"/>
-  <result property="orderNumber" column="order_number"/>
-  <association property="user" column="user_id" javaType="User" select="selectUser"/>
-  <collection property="items" column="order_id" javaType="ArrayList" ofType="Item" select="selectItems"/>
-</resultMap>
+ @Autowired + private MockMvc mockMvc; -

Mybatis是否可以映射Enum枚举类?

可以,通过自定义TypeHandler来实现枚举类型的映射。例如:

-
@MappedTypes(MyEnum.class)
-public class MyEnumTypeHandler extends BaseTypeHandler<MyEnum> {
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, MyEnum parameter, JdbcType jdbcType) throws SQLException {
-        ps.setString(i, parameter.name());
+    @Test
+    public void testControllerMethod() throws Exception {
+        given(myService.someMethod()).willReturn("result");
+        mockMvc.perform(get("/someEndpoint"))
+               .andExpect(status().isOk())
+               .andExpect(content().string("result"));
     }
+}
- @Override - public MyEnum getNullableResult(ResultSet rs, +

如何进行数据层的测试?

可以通过@DataJpaTest注解进行JPA相关的测试,自动配置嵌入式数据库和Spring Data JPA。例如:

+
@RunWith(SpringRunner.class)
+@DataJpaTest
+public class UserRepositoryTests {
+    @Autowired
+    private TestEntityManager entityManager;
 
- String columnName) throws SQLException {
-        return MyEnum.valueOf(rs.getString(columnName));
+    @Autowired
+    private UserRepository userRepository;
+
+    @Test
+    public void testFindByUsername() {
+        User user = new User();
+        user.setUsername("testuser");
+        entityManager.persist(user);
+        User foundUser = userRepository.findByUsername("testuser");
+        assertEquals(user.getUsername(), foundUser.getUsername());
     }
+}
+

Spring Boot的部署

如何打包Spring Boot应用?

Spring Boot

+

可以打包成可执行的JAR或WAR文件。通过Maven或Gradle插件,可以将应用打包并包含所有依赖。例如,使用Maven:

+
<build>
+    <plugins>
+        <plugin>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-maven-plugin</artifactId>
+        </plugin>
+    </plugins>
+</build>
+

然后执行mvn package命令生成JAR文件。

+

如何在外部服务器上部署Spring Boot应用?

可以将打包生成的WAR文件部署到外部服务器(如Tomcat)。在pom.xml中将打包方式改为war,并继承SpringBootServletInitializer

+
@SpringBootApplication
+public class Application extends SpringBootServletInitializer {
     @Override
-    public MyEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return MyEnum.valueOf(rs.getString(columnIndex));
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
+        return builder.sources(Application.class);
     }
+}
- @Override - public MyEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { - return MyEnum.valueOf(cs.getString(columnIndex)); - } -}
+

Spring Boot如何支持Docker?

可以通过Dockerfile创建Docker镜像并运行Spring Boot应用。示例如下:

+
FROM openjdk:11-jre
+COPY target/myapp.jar /app.jar
+ENTRYPOINT ["java", "-jar", "/app.jar"]
+

然后使用Docker命令生成镜像并运行容器:

+
docker build -t myapp .
+docker run -p 8080:8080 myapp
-

动态SQL

Mybatis动态sql是做什么的?

MyBatis动态SQL用于根据条件动态生成不同的SQL语句,从而避免手写复杂的SQL拼接逻辑,提高SQL的灵活性和可读性。

-

都有哪些动态sql?

常见的动态SQL标签包括:

-
    -
  • <if>:条件判断。
  • -
  • <choose>、<when>、<otherwise>:条件选择。
  • -
  • <trim>、<where>、<set>:处理SQL片段。
  • -
  • <foreach>:循环处理。
  • -
  • <bind>:绑定参数。
  • -
-

动态sql的执行原理

动态SQL的执行原理是通过OGNL(Object-Graph Navigation Language)解析并根据上下文生成实际的SQL语句。MyBatis在解析映射文件时,会将动态SQL标签中的逻辑和条件编译成对应的代码片段,执行时根据传入参数的值动态生成最终的SQL。

-

插件模块

Mybatis是如何进行分页的?

MyBatis通过分页插件实现分页查询。分页插件拦截SQL执行,根据分页参数修改SQL语句,实现数据的分页查询。

-

分页插件的原理是什么?

分页插件利用MyBatis的拦截器机制,在SQL执行前后拦截SQL语句,并根据分页参数(如limitoffset)修改SQL,实现分页查询。

-

缓存

一级缓存

一级缓存是SqlSession级别的缓存,默认开启。相同的SqlSession在执行相同查询时会从缓存中获取数据,而不是再次访问数据库。

-

二级缓存

二级缓存是Mapper级别的缓存,多个SqlSession共享。需要在配置文件中显式开启,二级缓存可以减少数据库访问,提高性能。

-

MyBatis集成Spring

MyBatis可以与Spring框架无缝集成,通过Spring容器管理MyBatis的SqlSessionFactory和Mapper接口,实现事务管理和依赖注入。

-

MyBatis的日志管理

MyBatis支持多种日志框架(如Log4j、SLF4J等),可以配置日志记录SQL语句和执行时间,便于调试和优化。

-

MyBatis的异常处理

MyBatis会将所有的数据库操作异常转换为PersistenceException,可以通过自定义异常处理器对异常进行处理和日志记录。

@@ -1978,7 +2195,7 @@

- +

- +

+ + + - @Autowired - private AuthenticationManager authenticationManager; + + + +
+ - @Autowired - private JwtUtils jwtUtils; + - @Autowired - private JwtUserDetailsService jwtUserDetailsService; + +
+

+ + +

- // 处理用户登录请求 - @PostMapping("/login") - public String createToken(@RequestBody User authRequest) throws Exception { - try { - // 进行用户认证 - Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) - ); - } catch (AuthenticationException e) { - throw new Exception("Invalid username or password"); - } - // 生成JWT令牌 - final UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(authRequest.getUsername()); - return jwtUtils.generateToken(userDetails.getUsername()); - } -}
+ +
- public JwtRequestFilter(JwtUtils jwtUtil,UserDetailsService userDetailsService){ - this.userDetailsService = userDetailsService; - this.jwtUtil = jwtUtil; - } + + + +
- @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws ServletException, IOException { + +

MyBatis简介

什么是ORM框架

ORM(Object-Relational Mapping)框架是用于在面向对象编程语言中,将对象映射到数据库表的一种技术。ORM框架通过自动生成SQL语句和自动映射数据库记录与编程语言中的对象,实现面向对象与关系数据库之间的转换。

+

MyBatis是什么

MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJOs(Plain Old Java Objects)到数据库中的记录。

+

MyBatis和其他ORM框架的区别

    +
  • 灵活性:MyBatis允许直接编写SQL语句,因此在灵活性上优于Hibernate等全自动化ORM框架。
  • +
  • 学习曲线:MyBatis的学习曲线较缓,相对简单,适合需要精确控制SQL的开发者。
  • +
  • 性能:由于可以手写SQL,MyBatis在处理复杂查询和优化方面具有优势。
  • +
  • 对象-关系映射:MyBatis对对象-关系映射提供了简单的XML配置或注解配置,而像Hibernate提供更强大的映射机制。
  • +
+

MyBatis的由来

MyBatis最初是Apache旗下的一个项目,名为iBatis。iBatis的名字来源于“internet”和“abatis”的结合,象征着网络时代的持久层解决方案。2010年,iBatis项目迁移到Google Code并更名为MyBatis,最终MyBatis成为一个独立的开源项目。

+

MyBatis的优缺点

优点

+
    +
  • 简单易学,配置灵活。
  • +
  • SQL编写灵活,开发人员可以精确控制SQL执行。
  • +
  • 轻量级框架,便于与其他框架集成。
  • +
  • 支持动态SQL。
  • +
+

缺点

+
    +
  • 需要手写SQL,工作量较大。
  • +
  • 对于复杂对象的映射支持不如Hibernate。
  • +
  • 维护SQL映射文件时可能较为繁琐。
  • +
+

MyBatis的使用场景

    +
  • 项目中需要精确控制SQL执行的情况。
  • +
  • 需要频繁调整和优化SQL的应用。
  • +
  • 轻量级应用或简单项目。
  • +
  • 不需要复杂关系映射的场景。
  • +
+

MyBatis原理

MyBatis工作原理

MyBatis的工作原理是通过配置文件和注解,将Java对象与SQL语句进行映射。配置文件中定义了数据源和事务管理等配置信息,映射文件则定义了SQL语句和对象的映射关系。在执行查询或更新操作时,MyBatis将输入参数与SQL语句绑定,并执行SQL,最后将结果集映射到Java对象。

+

MyBatis编程步骤

    +
  1. 创建MyBatis配置文件(mybatis-config.xml)。
  2. +
  3. 创建数据库映射文件(Mapper.xml),定义SQL语句。
  4. +
  5. 创建Java实体类,映射数据库表。
  6. +
  7. 创建Mapper接口,定义映射方法。
  8. +
  9. 通过SqlSessionFactory获取SqlSession,并执行数据库操作。
  10. +
+

MyBatis的架构

MyBatis的架构包括以下几个核心组件:

+
    +
  • Configuration:全局配置类,包含所有MyBatis的配置信息。
  • +
  • SqlSessionFactory:SqlSession工厂类,用于创建SqlSession实例。
  • +
  • SqlSession:用于执行SQL语句的接口。
  • +
  • Mapper:映射器接口,用于定义数据库操作方法。
  • +
  • Executor:执行器,负责SQL的执行和结果集的映射。
  • +
+

什么是MyBatis预编译

MyBatis预编译是指在执行SQL语句之前,先对SQL进行编译处理,将SQL中的参数占位符替换为对应的参数值。预编译后的SQL可以重复使用,减少了编译时间,提高了执行效率。

+

MyBatis的执行器有哪些

MyBatis提供了三种执行器:

+
    +
  • SimpleExecutor:每次执行都会创建一个新的预处理语句对象。
  • +
  • ReuseExecutor:执行SQL时会复用预处理语句对象。
  • +
  • BatchExecutor:用于批量执行SQL语句。
  • +
+

MyBatis的懒加载

MyBatis的懒加载是一种延迟加载技术,当关联对象被真正访问时才加载它。通过在配置文件中设置lazyLoadingEnabled属性为true来启用懒加载。

+

映射器

#{}和${}的区别

    +
  • #{}:使用预编译机制,可以防止SQL注入,将参数替换为占位符。
  • +
  • ${}:直接拼接SQL字符串,不进行预编译,容易导致SQL注入。
  • +
+

如何实现模糊查询

使用LIKE关键字进行模糊查询。例如:

+
<select id="findByName" resultType="User">
+  SELECT * FROM users WHERE name LIKE #{name}
+</select>
+

在传递参数时需要在Java代码中加上%

+
mapper.findByName("%name%");
- final String authorizationHeader = request.getHeader("Authorization"); +

在mapper中如何传递多个参数

可以使用@Param注解或者传递一个Map。例如:

+
public List<User> findByAgeAndName(@Param("age") int age, @Param("name") String name);
+

+
public List<User> findByAgeAndName(Map<String, Object> params);
- String username = null; - String jwt = null; +

MyBatis如何执行批量操作

通过使用<foreach>标签来实现批量操作。例如批量插入:

+
<insert id="batchInsert">
+  INSERT INTO users (name, age) VALUES
+  <foreach collection="list" item="user" separator=",">
+    (#{user.name}, #{user.age})
+  </foreach>
+</insert>
- // 从请求头中获取JWT令牌 - if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { - jwt = authorizationHeader.substring(7); - username = jwtUtil.extractClaims(jwt).getSubject(); - } +

如何获取生成的主键

使用useGeneratedKeyskeyProperty属性。例如:

+
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
+  INSERT INTO users (name, age) VALUES (#{name}, #{age})
+</insert>
- // 验证令牌并设置认证信息 - if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); - if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities()); - usernamePasswordAuthenticationToken - .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); - } - } - chain.doFilter(request, response); - } -}
+

什么是别名

别名是为Java类型设置的简短名称,方便在配置文件中使用。可以在配置文件中设置:

+
<typeAliases>
+  <typeAlias alias="User" type="com.example.User"/>
+</typeAliases>
-

执行流程

    -
  1. 用户登录请求:通过客户端(如浏览器或Postman)发送一个包含用户名和密码的POST请求到/login接口
  2. -
  3. 创建UsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationToken 是 Spring Security 中 Authentication 接口的一个实现类。它主要用于存储用户的认证信息(用户名和密码),并在认证过程中传递这些信息。
  4. +

    Mapper 编写有哪几种方式?

      +
    1. XML方式:通过XML配置文件定义SQL语句。
    2. +
    3. 注解方式:在Mapper接口中使用注解定义SQL语句。
    4. +
    5. 混合方式:同时使用XML和注解定义SQL语句。
    -

    涉及类介绍

    UsernamePasswordAuthenticationToken

    -

    UsernamePasswordAuthenticationToken是Spring Security中Authentication 接口的一个实现类。它主要用于存储用户的认证信息(用户名和密码),并在认证过程中传递这些信息。

    -
    -

    作用:

    -
      -
    1. 存储用户认证信息
        -
      • UsernamePasswordAuthenticationToken 对象包含了用户的认证信息,例如用户名(principal)和密码(credentials)。
      • +

        什么是MyBatis的接口绑定?

        接口绑定是指通过Mapper接口绑定SQL映射文件,实现面向接口编程。MyBatis会自动将接口方法与映射文件中的SQL语句进行关联。

        +

        Xml映射文件中有哪些标签

        常用的标签包括:

        +
          +
        • <select>:查询语句。
        • +
        • <insert>:插入语句。
        • +
        • <update>:更新语句。
        • +
        • <delete>:删除语句。
        • +
        • <resultMap>:结果集映射。
        • +
        • <parameterMap>:参数映射。
        • +
        • <sql>:可重用的SQL片段。
        • +
        • <include>:包含SQL片段。
        • +
        • <foreach>:循环处理。
        • +
        • <if>:条件判断。
        • +
        • <choose>、<when>、<otherwise>:条件选择。
        - -
      • 传递认证请求
          -
        • 在用户登录时,UsernamePasswordAuthenticationToken 对象通常用于创建认证请求对象,并传递给 AuthenticationManager 进行认证。
        • +

          高级查询

          MyBatis实现一对一,一对多有几种方式,怎么操作的?

            +
          1. 一对一

            +
              +
            • 使用association标签映射。
            • +
            • 通过嵌套查询或嵌套结果映射。
          2. -
          3. 表示认证状态
              -
            • UsernamePasswordAuthenticationToken 对象还可以表示认证状态,通过 setAuthenticated 方法设置为已认证或未认证。
            • +
            • 一对多

              +
                +
              • 使用collection标签映射。
              • +
              • 通过嵌套查询或嵌套结果映射。
          -

          构造函数:

          -
          public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
          -    super((Collection)null); // 调用父类的构造函数,传入 null 表示没有权限信息
          -    this.principal = principal; // 设置用户主体,就是用户名
          -    this.credentials = credentials; // 设置用户凭据,就是密码
          -    this.setAuthenticated(false);// 设置认证状态为未认证
          -}
          +

          示例:

          +
          <resultMap id="orderMap" type="Order">
          +  <id property="id" column="id"/>
          +  <result property="orderNumber" column="order_number"/>
          +  <association property="user" column="user_id" javaType="User" select="selectUser"/>
          +  <collection property="items" column="order_id" javaType="ArrayList" ofType="Item" select="selectItems"/>
          +</resultMap>
          + +

          Mybatis是否可以映射Enum枚举类?

          可以,通过自定义TypeHandler来实现枚举类型的映射。例如:

          +
          @MappedTypes(MyEnum.class)
          +public class MyEnumTypeHandler extends BaseTypeHandler<MyEnum> {
          +    @Override
          +    public void setNonNullParameter(PreparedStatement ps, int i, MyEnum parameter, JdbcType jdbcType) throws SQLException {
          +        ps.setString(i, parameter.name());
          +    }
          +
          +    @Override
          +    public MyEnum getNullableResult(ResultSet rs,
           
          + String columnName) throws SQLException {
          +        return MyEnum.valueOf(rs.getString(columnName));
          +    }
          +
          +    @Override
          +    public MyEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
          +        return MyEnum.valueOf(rs.getString(columnIndex));
          +    }
          +
          +    @Override
          +    public MyEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
          +        return MyEnum.valueOf(cs.getString(columnIndex));
          +    }
          +}
          + +

          动态SQL

          Mybatis动态sql是做什么的?

          MyBatis动态SQL用于根据条件动态生成不同的SQL语句,从而避免手写复杂的SQL拼接逻辑,提高SQL的灵活性和可读性。

          +

          都有哪些动态sql?

          常见的动态SQL标签包括:

          +
            +
          • <if>:条件判断。
          • +
          • <choose>、<when>、<otherwise>:条件选择。
          • +
          • <trim>、<where>、<set>:处理SQL片段。
          • +
          • <foreach>:循环处理。
          • +
          • <bind>:绑定参数。
          • +
          +

          动态sql的执行原理

          动态SQL的执行原理是通过OGNL(Object-Graph Navigation Language)解析并根据上下文生成实际的SQL语句。MyBatis在解析映射文件时,会将动态SQL标签中的逻辑和条件编译成对应的代码片段,执行时根据传入参数的值动态生成最终的SQL。

          +

          插件模块

          Mybatis是如何进行分页的?

          MyBatis通过分页插件实现分页查询。分页插件拦截SQL执行,根据分页参数修改SQL语句,实现数据的分页查询。

          +

          分页插件的原理是什么?

          分页插件利用MyBatis的拦截器机制,在SQL执行前后拦截SQL语句,并根据分页参数(如limitoffset)修改SQL,实现分页查询。

          +

          缓存

          一级缓存

          一级缓存是SqlSession级别的缓存,默认开启。相同的SqlSession在执行相同查询时会从缓存中获取数据,而不是再次访问数据库。

          +

          二级缓存

          二级缓存是Mapper级别的缓存,多个SqlSession共享。需要在配置文件中显式开启,二级缓存可以减少数据库访问,提高性能。

          +

          MyBatis集成Spring

          MyBatis可以与Spring框架无缝集成,通过Spring容器管理MyBatis的SqlSessionFactory和Mapper接口,实现事务管理和依赖注入。

          +

          MyBatis的日志管理

          MyBatis支持多种日志框架(如Log4j、SLF4J等),可以配置日志记录SQL语句和执行时间,便于调试和优化。

          +

          MyBatis的异常处理

          MyBatis会将所有的数据库操作异常转换为PersistenceException,可以通过自定义异常处理器对异常进行处理和日志记录。

diff --git a/page/3/index.html b/page/3/index.html index 509461ace..7ce442f1c 100644 --- a/page/3/index.html +++ b/page/3/index.html @@ -716,6 +716,235 @@

+
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

Webhook基础

+

Webhook是一种通过HTTP协议将实时数据传输到指定URL的机制。与传统API不同,它不需要客户端主动轮询服务器以获取更新,而是由服务器主动向客户端推送数据。这种设计使得Webhook非常适合用于实时通知、事件驱动的应用和集成不同系统之间的数据。

+
+

WebHook与传统API的区别

    +
  • 触发方式:传统API是由客户端主动发起请求来获取数据或执行操作,而Webhook是由服务端主动向客户端推送数据。
  • +
  • 实时性:Webhook可以实现实时通知,当事件发生时立即推送数据给客户端,而传统API则需要客户端定期轮询或手动发起请求来获取数据,可能存在一定的延迟。
  • +
  • 节省资源:使用Webhook可以减少无效请求和服务器负载,因为只有在事件发生时才会触发数据传输,而传统API可能会频繁发送不必要的请求。
  • +
  • 适用场景:Webhook适用于需要实时数据更新和事件驱动的场景,例如实时通知、即时聊天等,而传统API更适用于按需获取数据或执行操作的场景。
  • +
+ + +

Webhook的工作原理

    +
  1. 注册Webhook:在目标服务上,客户端需要注册一个Webhook,通常是通过提供一个URL来告知目标服务在发生特定事件时向该URL发送数据。
  2. +
  3. 事件触发:当目标服务上发生了注册的事件,比如数据更新、状态更改等,服务会触发Webhook,即向注册的URL发送HTTP请求。
  4. +
  5. HTTP请求:服务向注册的URL发送HTTP请求,通常是一个POST请求。这个请求会包含一些元数据,比如事件类型、发生时间等,以及相关的数据,比如更新的数据内容。这些数据通常以JSON格式或其他常见的数据格式进行传输。
  6. +
  7. 客户端接收请求:客户端(Webhook的接收端)接收到这个HTTP请求后,需要解析其中的数据。这可以通过读取请求体中的内容来完成。客户端应该验证请求的来源和完整性,以确保安全性。
  8. +
  9. 处理数据:一旦客户端解析了请求中的数据,它可以根据需要进行进一步的处理。这可能涉及将数据存储到数据库中、更新应用程序的状态、发送通知给用户等等。
  10. +
  11. 响应:处理完数据后,客户端需要向目标服务发送一个响应,通常是一个HTTP状态码。这个响应可以告知目标服务数据是否已经成功接收和处理。通常情况下,目标服务不会对这个响应做出太多的处理,但一些服务可能会根据响应来判断是否继续尝试发送数据。
  12. +
  13. 错误处理:在整个过程中,客户端需要注意处理可能出现的错误,比如网络连接问题、数据格式错误等。一些常见的错误处理方式包括记录错误日志、发送警报通知等。
  14. +
+

Java实现WebHook

WebHookClient

import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+
+/**
+ * Author: Zhi Liu
+ * Date: 2024/2/7 14:09
+ * Contact: liuzhi0531@gmail.com
+ * Desc: webhook客户端测试类
+ */
+public class WebhookClient {
+    public static void main(String[] args) throws Exception {
+        int port = 8000; // 定义服务器端口号
+        HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); // 创建HTTP服务器实例
+        server.createContext("/webhook", new WebhookHandler()); // 设置请求处理程序
+        server.start(); // 启动服务器
+        System.out.println("Webhook 服务器已启动,监听端口 " + port);
+    }
+
+    static class WebhookHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange exchange) throws IOException {
+            if ("POST".equals(exchange.getRequestMethod())) {
+                InputStream requestBody = exchange.getRequestBody(); // 获取请求体
+                // 读取请求体中的数据
+                StringBuilder requestData = new StringBuilder();
+                int byteRead;
+                while ((byteRead = requestBody.read()) != -1) {
+                    requestData.append((char) byteRead);
+                }
+                System.out.println("收到Webhook请求:\n" + requestData.toString());
+
+                // 响应webhook服务端
+                String response = "Webhook 请求已收到";
+                exchange.sendResponseHeaders(200, response.getBytes().length);
+                OutputStream responseBody = exchange.getResponseBody();
+                responseBody.write(response.getBytes());
+                responseBody.close();
+            } else {
+                // 如果不是POST请求,返回405 Method Not Allowed
+                exchange.sendResponseHeaders(405, -1);
+            }
+        }
+    }
+}
+ +

WebHookServer

import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Author: Zhi Liu
+ * Date: 2024/2/7 14:15
+ * Contact: liuzhi0531@gmail.com
+ * Desc:
+ */
+public class WebhookServer {
+    public static void main(String[] args) {
+        String url = "http://localhost:8000/webhook";
+        String postData = "{hello,world}";
+
+        try {
+            sendPostRequest(url, postData);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void sendPostRequest(String url, String postData) throws Exception {
+        URL urlObj = new URL(url);
+        HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
+
+        // 设置请求方法为POST
+        connection.setRequestMethod("POST");
+
+        // 设置请求头部信息
+        connection.setRequestProperty("Content-Type", "application/json");
+
+        // 允许向服务器输出内容
+        connection.setDoOutput(true);
+
+        // 写入请求数据
+        try (OutputStream outputStream = connection.getOutputStream()) {
+            byte[] postDataBytes = postData.getBytes("UTF-8");
+            outputStream.write(postDataBytes);
+            outputStream.flush();
+        }
+
+        // 获取客户端的响应状态码
+        int responseCode = connection.getResponseCode();
+        System.out.println("Response Code: " + responseCode);
+
+        // 读取响应内容
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+            StringBuilder response = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                response.append(line);
+            }
+            System.out.println("Response Content: " + response.toString());
+        }
+        // 关闭连接
+        connection.disconnect();
+    }
+}
+ + + +
    +
  1. 阅读相关文档

    +
      +
    • 查阅Webhook服务提供商的文档,了解如何创建和管理Webhook。
    • +
    • 阅读关于HTTP请求和响应的文档,以便了解如何处理Webhook请求和发送响应。
    • +
    +
  2. +
  3. 编写代码

    +
      +
    • 编写一个简单的Webhook接收端,用于接收来自外部服务的HTTP请求。
    • +
    • 编写处理Webhook请求的代码,例如解析请求、处理数据、执行逻辑等。
    • +
    +
  4. +
  5. 测试和调试

    +
      +
    • 使用工具(例如Postman)测试Webhook端点,确保它能够正确处理来自外部服务的请求。
    • +
    • 调试代码,解决可能出现的问题和错误。
    • +
    +
  6. +
  7. 进阶学习

    +
      +
    • 学习如何实现安全性和可靠性,例如使用HTTPS、认证、重试机制等。
    • +
    • 探索如何处理各种类型的Webhook事件,例如GitHub的push事件、Stripe的支付通知等。
    • +
    +
  8. +
  9. 实践项目

    +
      +
    • 开发一个实际的项目,其中包括使用Webhook与其他服务进行集成。
    • +
    • 参与开源项目,贡献Webhook相关的代码或解决问题。
    • +
    +
  10. +
  11. 不断更新知识

    +
      +
    • 持续关注Webhook领域的新发展和最佳实践。
    • +
    • 参加相关的线上或线下培训课程,与其他开发者交流经验。
    • +
    +
  12. +
+ + +
+ + + + +
+
+
+
+ + + + + + +
@@ -1085,235 +1314,6 @@

- - - - - -
-

- - -

- - -
- - - - -
- - -

Webhook基础

-

Webhook是一种通过HTTP协议将实时数据传输到指定URL的机制。与传统API不同,它不需要客户端主动轮询服务器以获取更新,而是由服务器主动向客户端推送数据。这种设计使得Webhook非常适合用于实时通知、事件驱动的应用和集成不同系统之间的数据。

-
-

WebHook与传统API的区别

    -
  • 触发方式:传统API是由客户端主动发起请求来获取数据或执行操作,而Webhook是由服务端主动向客户端推送数据。
  • -
  • 实时性:Webhook可以实现实时通知,当事件发生时立即推送数据给客户端,而传统API则需要客户端定期轮询或手动发起请求来获取数据,可能存在一定的延迟。
  • -
  • 节省资源:使用Webhook可以减少无效请求和服务器负载,因为只有在事件发生时才会触发数据传输,而传统API可能会频繁发送不必要的请求。
  • -
  • 适用场景:Webhook适用于需要实时数据更新和事件驱动的场景,例如实时通知、即时聊天等,而传统API更适用于按需获取数据或执行操作的场景。
  • -
- - -

Webhook的工作原理

    -
  1. 注册Webhook:在目标服务上,客户端需要注册一个Webhook,通常是通过提供一个URL来告知目标服务在发生特定事件时向该URL发送数据。
  2. -
  3. 事件触发:当目标服务上发生了注册的事件,比如数据更新、状态更改等,服务会触发Webhook,即向注册的URL发送HTTP请求。
  4. -
  5. HTTP请求:服务向注册的URL发送HTTP请求,通常是一个POST请求。这个请求会包含一些元数据,比如事件类型、发生时间等,以及相关的数据,比如更新的数据内容。这些数据通常以JSON格式或其他常见的数据格式进行传输。
  6. -
  7. 客户端接收请求:客户端(Webhook的接收端)接收到这个HTTP请求后,需要解析其中的数据。这可以通过读取请求体中的内容来完成。客户端应该验证请求的来源和完整性,以确保安全性。
  8. -
  9. 处理数据:一旦客户端解析了请求中的数据,它可以根据需要进行进一步的处理。这可能涉及将数据存储到数据库中、更新应用程序的状态、发送通知给用户等等。
  10. -
  11. 响应:处理完数据后,客户端需要向目标服务发送一个响应,通常是一个HTTP状态码。这个响应可以告知目标服务数据是否已经成功接收和处理。通常情况下,目标服务不会对这个响应做出太多的处理,但一些服务可能会根据响应来判断是否继续尝试发送数据。
  12. -
  13. 错误处理:在整个过程中,客户端需要注意处理可能出现的错误,比如网络连接问题、数据格式错误等。一些常见的错误处理方式包括记录错误日志、发送警报通知等。
  14. -
-

Java实现WebHook

WebHookClient

import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import com.sun.net.httpserver.HttpServer;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-
-/**
- * Author: Zhi Liu
- * Date: 2024/2/7 14:09
- * Contact: liuzhi0531@gmail.com
- * Desc: webhook客户端测试类
- */
-public class WebhookClient {
-    public static void main(String[] args) throws Exception {
-        int port = 8000; // 定义服务器端口号
-        HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); // 创建HTTP服务器实例
-        server.createContext("/webhook", new WebhookHandler()); // 设置请求处理程序
-        server.start(); // 启动服务器
-        System.out.println("Webhook 服务器已启动,监听端口 " + port);
-    }
-
-    static class WebhookHandler implements HttpHandler {
-        @Override
-        public void handle(HttpExchange exchange) throws IOException {
-            if ("POST".equals(exchange.getRequestMethod())) {
-                InputStream requestBody = exchange.getRequestBody(); // 获取请求体
-                // 读取请求体中的数据
-                StringBuilder requestData = new StringBuilder();
-                int byteRead;
-                while ((byteRead = requestBody.read()) != -1) {
-                    requestData.append((char) byteRead);
-                }
-                System.out.println("收到Webhook请求:\n" + requestData.toString());
-
-                // 响应webhook服务端
-                String response = "Webhook 请求已收到";
-                exchange.sendResponseHeaders(200, response.getBytes().length);
-                OutputStream responseBody = exchange.getResponseBody();
-                responseBody.write(response.getBytes());
-                responseBody.close();
-            } else {
-                // 如果不是POST请求,返回405 Method Not Allowed
-                exchange.sendResponseHeaders(405, -1);
-            }
-        }
-    }
-}
- -

WebHookServer

import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-
-/**
- * Author: Zhi Liu
- * Date: 2024/2/7 14:15
- * Contact: liuzhi0531@gmail.com
- * Desc:
- */
-public class WebhookServer {
-    public static void main(String[] args) {
-        String url = "http://localhost:8000/webhook";
-        String postData = "{hello,world}";
-
-        try {
-            sendPostRequest(url, postData);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    public static void sendPostRequest(String url, String postData) throws Exception {
-        URL urlObj = new URL(url);
-        HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
-
-        // 设置请求方法为POST
-        connection.setRequestMethod("POST");
-
-        // 设置请求头部信息
-        connection.setRequestProperty("Content-Type", "application/json");
-
-        // 允许向服务器输出内容
-        connection.setDoOutput(true);
-
-        // 写入请求数据
-        try (OutputStream outputStream = connection.getOutputStream()) {
-            byte[] postDataBytes = postData.getBytes("UTF-8");
-            outputStream.write(postDataBytes);
-            outputStream.flush();
-        }
-
-        // 获取客户端的响应状态码
-        int responseCode = connection.getResponseCode();
-        System.out.println("Response Code: " + responseCode);
-
-        // 读取响应内容
-        try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
-            StringBuilder response = new StringBuilder();
-            String line;
-            while ((line = reader.readLine()) != null) {
-                response.append(line);
-            }
-            System.out.println("Response Content: " + response.toString());
-        }
-        // 关闭连接
-        connection.disconnect();
-    }
-}
- - - -
    -
  1. 阅读相关文档

    -
      -
    • 查阅Webhook服务提供商的文档,了解如何创建和管理Webhook。
    • -
    • 阅读关于HTTP请求和响应的文档,以便了解如何处理Webhook请求和发送响应。
    • -
    -
  2. -
  3. 编写代码

    -
      -
    • 编写一个简单的Webhook接收端,用于接收来自外部服务的HTTP请求。
    • -
    • 编写处理Webhook请求的代码,例如解析请求、处理数据、执行逻辑等。
    • -
    -
  4. -
  5. 测试和调试

    -
      -
    • 使用工具(例如Postman)测试Webhook端点,确保它能够正确处理来自外部服务的请求。
    • -
    • 调试代码,解决可能出现的问题和错误。
    • -
    -
  6. -
  7. 进阶学习

    -
      -
    • 学习如何实现安全性和可靠性,例如使用HTTPS、认证、重试机制等。
    • -
    • 探索如何处理各种类型的Webhook事件,例如GitHub的push事件、Stripe的支付通知等。
    • -
    -
  8. -
  9. 实践项目

    -
      -
    • 开发一个实际的项目,其中包括使用Webhook与其他服务进行集成。
    • -
    • 参与开源项目,贡献Webhook相关的代码或解决问题。
    • -
    -
  10. -
  11. 不断更新知识

    -
      -
    • 持续关注Webhook领域的新发展和最佳实践。
    • -
    • 参加相关的线上或线下培训课程,与其他开发者交流经验。
    • -
    -
  12. -
- - -
- - - - -
-
-
-

- - - - - - -
diff --git a/page/5/index.html b/page/5/index.html index 19724a68c..5d9a444bf 100644 --- a/page/5/index.html +++ b/page/5/index.html @@ -549,6 +549,63 @@

+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

Maven

Gradle

Ant

+ +
+ + + + +
+
+
+

+ + + + + + + - - - - - - -
diff --git a/post/Api.html b/post/Api.html index 7387091e4..f8a27a7e7 100644 --- a/post/Api.html +++ b/post/Api.html @@ -550,8 +550,8 @@

有状态和无状态请求的区别
-
diff --git a/post/Build tools.html b/post/Build tools.html index 401ce3de3..6ef2c12a8 100644 --- a/post/Build tools.html +++ b/post/Build tools.html @@ -226,12 +226,12 @@

Maven

<
-
-
diff --git a/post/Canal.html b/post/Canal.html index 8a30a18f7..3faf6531f 100644 --- a/post/Canal.html +++ b/post/Canal.html @@ -328,12 +328,12 @@

-
-
diff --git "a/post/FTP\345\222\214SFTP.html" "b/post/FTP\345\222\214SFTP.html" index e5c948ceb..c82d5eae8 100644 --- "a/post/FTP\345\222\214SFTP.html" +++ "b/post/FTP\345\222\214SFTP.html" @@ -280,8 +280,8 @@

-

认证流程原理

JWT (JSON Web Token)

JWT是一种用于在各方之间作为JSON对象安全传输信息的紧凑、URL安全的令牌。它由三部分组成:

+

认证流程原理

跨域认证的问题

互联网服务离不开用户认证。一般流程是下面这样。

+
+

1、用户向服务器发送用户名和密码。

+

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

+

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

+

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

+

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

+
+

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

+

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

+

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

+

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

+

JWT (JSON Web Token)

JWT是一种用于在各方之间作为JSON对象安全传输信息的紧凑、URL安全的令牌。它由三部分组成:

  • Header:包含令牌类型(通常为JWT)和签名算法(例如HMAC SHA256)。
  • Payload:包含声明(claims),即传输的数据。常见的声明包括:用户ID、用户名、过期时间等。
  • Signature:用于验证令牌的真实性。它是由Header和Payload通过签名算法和密钥生成的。
+
Header.Payload.Signature
+ +

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

+
+
{
+  "alg": "HS256",
+  "typ": "JWT"
+}
+
+

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

+

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

+

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

+
+
    +
  • iss (issuer):签发人
  • +
  • exp (expiration time):过期时间
  • +
  • sub (subject):主题
  • +
  • aud (audience):受众
  • +
  • nbf (Not Before):生效时间
  • +
  • iat (Issued At):签发时间
  • +
  • jti (JWT ID):编号
  • +
+
+

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

+
+
{
+  "sub": "1234567890",
+  "name": "John Doe",
+  "admin": true
+}
+
+

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

+

这个 JSON 对象也要使用 Base64URL 算法转成字符串。

+

Signature

Signature 部分是对前两部分的签名,防止数据篡改。

+

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

+
+
HMACSHA256(
+  base64UrlEncode(header) + "." +
+  base64UrlEncode(payload),
+  secret)
+
+

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

+

JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

+

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

+
+
Authorization: Bearer <token>
+
+

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

认证流程

  1. 用户登录:用户提交用户名和密码。
  2. 认证服务器验证:服务器验证用户信息,成功后生成JWT令牌。
  3. @@ -237,6 +297,53 @@

    <version>0.9.1</version> </dependency> +

    创建用户注册类

    前期准备,创建一个用户注册类来进行用户注册,后续采用jwt登录涉及加密处理,前期注册的时候采用加密存进数据库。

    +
    //控制器类
    +@RestController
    +@RequestMapping("/auth")
    +public class UserController {
    +    @Autowired
    +    private UserService userService;
    +
    +    @PostMapping("/register")
    +    public String registerUser(@RequestBody User user) {
    +        userService.registerUser(user);
    +        return "User registered successfully";
    +    }
    +}
    +//接口类
    +public interface UserService extends IService<User> {
    +    void registerUser(User user);
    +}
    +//实现类
    +@Service
    +public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    +
    +    @Autowired
    +    private UserMapper userMapper;
    +    @Autowired
    +    private PasswordEncoder passwordEncoder;
    +
    +
    +    @Override
    +    public void registerUser(User user) {
    +        String encodedPassword = passwordEncoder.encode(user.getPassword());
    +        // 将用户名和加密后的密码存储到数据库中
    +        User passwdUser = new User();
    +        passwdUser.setUsername(user.getUsername());
    +        passwdUser.setPassword(encodedPassword);
    +        userMapper.insert(passwdUser);
    +    }
    +}
    +
    +//实体类
    +@Data
    +@TableName(value = "user")
    +public class User  {
    +    private String username;
    +    private String password;
    +}
    +

    创建Security配置类

    创建一个配置类来配置Spring Security:

    @Configuration
     @EnableWebSecurity
    @@ -440,14 +547,74 @@ 

    } }

    -

    执行流程

      -
    1. 用户登录请求:通过客户端(如浏览器或Postman)发送一个包含用户名和密码的POST请求到/login接口
    2. -
    3. 创建UsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationToken 是 Spring Security 中 Authentication 接口的一个实现类。它主要用于存储用户的认证信息(用户名和密码),并在认证过程中传递这些信息。
    4. +

      执行流程

      注册阶段

        +
      1. 用户发送注册请求:通过客户端(如浏览器或Postman)发送一个json格式的user对象的POST请求到/regester接口。
      2. +
      3. 调用UserService的注册方法:将需要注册的user对象传入registerUser方法中,将密码使用PasswordEncoder加密后和用户名存到数据库。
      -

      涉及类介绍

      UsernamePasswordAuthenticationToken

      -

      UsernamePasswordAuthenticationToken是Spring Security中Authentication 接口的一个实现类。它主要用于存储用户的认证信息(用户名和密码),并在认证过程中传递这些信息。

      -
      -

      作用:

      +

      认证阶段

        +
      1. 用户登录请求:通过客户端(如浏览器或Postman)发送一个json格式的user对象的POST请求到/login接口
      2. +
      3. 创建认证请求对象:使用 UsernamePasswordAuthenticationToken 创建一个包含用户名和密码的认证请求对象。
      4. +
      5. 调用 AuthenticationManagerauthenticate 方法AuthenticationManager 调用内部配置的 AuthenticationProvider 进行认证。
      6. +
      7. DaoAuthenticationProvider 进行用户认证
      8. +
      +
        +
      • 使用 UserDetailsService 加载用户信息。
      • +
      • 使用 PasswordEncoder 验证密码。
      • +
      +
        +
      1. 返回认证结果
      2. +
      +
        +
      • 如果认证成功,返回一个填充了用户详细信息和权限的 Authentication 对象。
      • +
      • 如果认证失败,抛出 AuthenticationException
      • +
      +

      生成JWT令牌阶段

        +
      1. 调用 loadUserByUsername 方法
      2. +
      +
        +
      • jwtUserDetailsService 是一个实现了 UserDetailsService 接口的服务类。
      • +
      • loadUserByUsername 方法用于根据用户名加载用户详细信息。
      • +
      +
        +
      1. 查询用户信息:在 loadUserByUsername 方法中,通常会查询数据库或其他存储系统,以获取用户信息。

        +
      2. +
      3. 返回 UserDetails 对象:查询到用户信息后,创建并返回一个实现了 UserDetails 接口的对象,通常是一个自定义的用户详细信息类(例如 JwtUserDetails

        +
      4. +
      5. 调用 generateToken 方法

        +
      6. +
      +
        +
      • jwtUtils 是一个用于生成和验证 JWT 令牌的工具类。
      • +
      • generateToken 方法用于生成一个包含用户名和其他信息的 JWT 令牌。
      • +
      +
        +
      1. 创建 JWT 令牌
      2. +
      +
        +
      • generateToken 方法中,使用 JWT 库(例如 jjwt)创建一个 JWT 令牌。
      • +
      • 令牌中通常包含用户名、签发时间、过期时间等信息,并使用指定的密钥进行签名。
      • +
      +
        +
      1. 返回 JWT 令牌:生成的 JWT 令牌以字符串形式返回。
      2. +
      +

      受保护的控制器类

      以testController为例,来看受保护的请求,如何进行JWT验证的执行流程

      +
        +
      1. 请求到达 DispatcherServlet:所有请求首先到达 Spring 的 DispatcherServlet
      2. +
      3. 进入过滤器链:请求通过 Spring Security 配置的过滤器链。
      4. +
      5. 通过每个过滤器
      6. +
      +
        +
      • SecurityContextPersistenceFilter:从存储中加载 SecurityContext 并将其存储在 SecurityContextHolder 中,以便在请求处理过程中使用。
      • +
      • JwtRequestFilter(自定义过滤器):从请求头中提取 JWT 令牌,并验证其有效性。如果令牌有效,将认证信息存储到 SecurityContextHolder 中。
      • +
      • ExceptionTranslationFilter:处理认证和授权过程中抛出的异常,将其转换为适当的 HTTP 响应。
      • +
      • FilterSecurityInterceptor:进行访问控制决策,检查当前用户是否有权限访问请求的资源。
      • +
      +
        +
      1. 处理器映射DispatcherServlet 使用 HandlerMapping 查找与请求路径对应的处理器方法。
      2. +
      3. 调用处理器方法DispatcherServlet 调用找到的处理器方法。
      4. +
      5. 返回响应:处理器方法执行并返回响应,DispatcherServlet 将响应返回给客户端。
      6. +
      +

      涉及类介绍

      UsernamePasswordAuthenticationToken

      作用:

      1. 存储用户认证信息
        • UsernamePasswordAuthenticationToken 对象包含了用户的认证信息,例如用户名(principal)和密码(credentials)。
        • @@ -470,6 +637,59 @@

          this.setAuthenticated(false);// 设置认证状态为未认证 } +

          SecurityContextPersistenceFilter

          作用

          +
            +
          • 从存储中加载 SecurityContext 并将其存储在 SecurityContextHolder 中,以便在请求处理过程中使用。
          • +
          • 在请求结束时,将 SecurityContext 的状态保存到存储中。
          • +
          +
          public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
          +        throws IOException, ServletException {
          +    HttpServletRequest request = (HttpServletRequest) req;
          +    HttpServletResponse response = (HttpServletResponse) res;
          +    SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
          +    try {
          +        SecurityContextHolder.setContext(contextBeforeChainExecution);
          +        chain.doFilter(request, response);
          +    } finally {
          +        SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
          +        SecurityContextHolder.clearContext();
          +        repo.saveContext(contextAfterChainExecution, request, response);
          +    }
          +}
          + +

          ExceptionTranslationFilter

          作用

          +
            +
          • 处理认证和授权过程中抛出的异常,将其转换为适当的 HTTP 响应。
          • +
          +
          public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
          +        throws IOException, ServletException {
          +    HttpServletRequest request = (HttpServletRequest) req;
          +    HttpServletResponse response = (HttpServletResponse) res;
          +    try {
          +        chain.doFilter(request, response);
          +    } catch (AuthenticationException ex) {
          +        sendStartAuthentication(request, response, chain, ex);
          +    } catch (AccessDeniedException ex) {
          +        handleAccessDeniedException(request, response, chain, ex);
          +    }
          +}
          + +

          FilterSecurityInterceptor

          作用

          +
            +
          • 进行访问控制决策,检查当前用户是否有权限访问请求的资源。
          • +
          +
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          +        throws IOException, ServletException {
          +    FilterInvocation fi = new FilterInvocation(request, response, chain);
          +    InterceptorStatusToken token = super.beforeInvocation(fi);
          +    try {
          +        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
          +    } finally {
          +        super.finallyInvocation(token);
          +        super.afterInvocation(token, null);
          +    }
          +}
          +
@@ -488,13 +708,10 @@

-
-
-
+

@@ -553,7 +770,7 @@

-
+
diff --git "a/post/Mybatis\347\220\206\350\256\272.html" "b/post/Mybatis\347\220\206\350\256\272.html" index e5f1aa503..0b1713bf5 100644 --- "a/post/Mybatis\347\220\206\350\256\272.html" +++ "b/post/Mybatis\347\220\206\350\256\272.html" @@ -397,8 +397,8 @@

-

软件安装

-
diff --git "a/post/java\345\237\272\347\241\200\344\272\214-\351\235\242\345\220\221\345\257\271\350\261\241.html" "b/post/java\345\237\272\347\241\200\344\272\214-\351\235\242\345\220\221\345\257\271\350\261\241.html" index 4249e4481..46897b022 100644 --- "a/post/java\345\237\272\347\241\200\344\272\214-\351\235\242\345\220\221\345\257\271\350\261\241.html" +++ "b/post/java\345\237\272\347\241\200\344\272\214-\351\235\242\345\220\221\345\257\271\350\261\241.html" @@ -318,8 +318,8 @@

java基础-常识
-
diff --git "a/post/java\346\263\250\350\247\243.html" "b/post/java\346\263\250\350\247\243.html" index 916db9a39..41777a481 100644 --- "a/post/java\346\263\250\350\247\243.html" +++ "b/post/java\346\263\250\350\247\243.html" @@ -295,7 +295,10 @@

-
+
+
diff --git a/post/springboot.html b/post/springboot.html index aa35abb0f..7a8860fd8 100644 --- a/post/springboot.html +++ b/post/springboot.html @@ -540,12 +540,12 @@

-
-
diff --git a/post/webhook.html b/post/webhook.html index f1b36041a..ec7121db2 100644 --- a/post/webhook.html +++ b/post/webhook.html @@ -400,12 +400,12 @@

-
-
diff --git "a/post/\346\226\260\345\255\246\344\271\240\350\267\257\347\272\277.html" "b/post/\346\226\260\345\255\246\344\271\240\350\267\257\347\272\277.html" index 281bbee2d..c0e658ab2 100644 --- "a/post/\346\226\260\345\255\246\344\271\240\350\267\257\347\272\277.html" +++ "b/post/\346\226\260\345\255\246\344\271\240\350\267\257\347\272\277.html" @@ -227,8 +227,8 @@

-

@@ -210,8 +210,8 @@

java

-
@@ -230,8 +230,8 @@

java

-
@@ -250,8 +250,8 @@

java
-
@@ -270,8 +270,8 @@

java
-
@@ -290,8 +290,8 @@

java
-
@@ -310,8 +310,8 @@

java
-
@@ -323,15 +323,15 @@

java
-