Java面经

面经一

公司地点:珠海

公司业务:消防安全预警平台 + 物联网

面试次数:仅一轮

JD薪资:8K-13K

面试岗位:Java后端开发(去到突然变成面全栈。。。)

  1. String、StringBuilder 和 StringBuffer 的区别

​ String 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。

​ StringBuffer/StringBuilder 表示的字符串对象可以直接进行修改,buffer是线程安全的,builder的效率更高

  1. MySQL数据库语句的查询顺序
1
from -> on -> join -> where -> group by -> having -> select distinct -> order by -> limit
  1. vue.js的生命周期

(没有前端基础,参考一下chatGPT的)

    1. beforeCreate:Vue实例刚被创建,数据观测(data observer)和事件配置都未开始。此时,模板和组件并未渲染,data和methods都不能访问。
    2. created:Vue实例已经完成了数据观测(data observer),属性和方法的运算,事件回调也已经被设置。但是模板和组件还未渲染。此时,可以访问data和methods,但不能保证能访问到$el。
    3. beforeMount:在挂载之前调用。此时,Vue实例的$el和data都已初始化,但是模板和组件还未渲染。在这个阶段,可以修改$el中的DOM结构。
    4. mounted:Vue实例已经完成了模板和组件的渲染,并将其挂载到DOM上。此时,可以访问到$el中的DOM结构。这个阶段也是常用的异步数据获取的阶段。
    5. beforeUpdate:在数据更新之前调用。此时,Vue实例的数据已更新,但是DOM还未更新。在这个阶段,可以对更新前的数据进行一些处理。
    6. updated:在数据更新后调用。Vue实例的数据和DOM都已更新。在这个阶段,可以对更新后的数据和DOM进行操作。
    7. beforeDestroy:在实例销毁之前调用。在这个阶段,Vue实例仍然完全可用,可以访问所有数据和方法。
    8. destroyed:在实例销毁之后调用。在这个阶段,Vue实例及其所有的指令和事件监听器都已经被销毁,无法再访问其中的数据和方法。
  1. 如果一个网页页面上要加载大量图片,要怎么处理

    1. 压缩图片大小
    2. 延迟加载(懒加载)
    3. 使用CSS Sprite,将小图片组合成大图片
    4. CDN加速
    5. 缓存
  2. MySQL的索引是基于什么实现的

​ B+树

  1. 哪些情况会出现索引不命中

    1. 没有使用索引列
    2. 使用索引错误
    3. 索引列数据分布不均
    4. 数据库表过度规范化
    5. 查询条件不适合索引
    6. 数据库表数据量过大
  2. 索引有哪些类型

    1. 普通索引:允许被索引的数据列包含重复的值
    2. 唯一索引:可以保证数据记录的唯一性
    3. 主键索引:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字primary key来创建
    4. 联合索引:索引可以覆盖多个数据列
    5. 全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术
  3. 聚簇索引和非聚簇索引的区别

​ 跟数据绑定在一起的就是聚簇索引

  1. 对MySQL的集群有了解吗

  2. MySQL主从复制是怎么实现的,主从复制之间的延迟有计算过吗

    1. 监控工具
    2. sql语句:在主库和从库执行以下代码,相减就是延迟时间
1
select UNIX_TIMESTAMP();
  1. 有用过数据库连接池吗

​ 连接池通常包含以下组件:

  • 连接池管理器:用于管理连接池中的数据库连接,包括创建连接、回收连接、销毁连接等操作。
  • 连接池配置信息:包括数据库的连接URL、用户名、密码等信息,以及连接池的配置参数,如最大连接数、最小连接数、连接超时时间等。
  • 数据库连接:连接池中的数据库连接,应用程序可以从连接池中获取连接,并在使用完成后将连接归还给连接池。

​ MySQL数据库连接池的常见实现包括:

  • C3P0:C3P0是一个开源的JDBC连接池,支持连接池自动回收、断线重连、连接泄漏检测等功能。
  • DBCP:DBCP是Apache软件基金会的一个开源JDBC连接池,支持连接池自动回收、连接泄漏检测等功能。
  • Druid:Druid是阿里巴巴开源的一个JDBC连接池,支持连接池自动回收、断线重连、连接泄漏检测、SQL防注入等功能,还提供了一些监控和性能分析功能
  1. 了解MySQL8吗,相比5版本新增什么功能

​ (参考chatGPT)

    1. Window Functions:窗口函数是一种在一组行上执行计算的函数,它可以将查询结果分区并按指定的排序规则排序,从而使得在一组行上进行聚合和排序更加方便。
    2. Common Table Expressions (CTE):通用表达式是一种创建临时表并在查询中引用的方法,它可以提高查询的可读性和复用性。
    3. JSON支持:MySQL 8增加了对JSON数据类型的支持,并且提供了许多内置函数来处理JSON数据,包括JSON_EXTRACT、JSON_OBJECT、JSON_ARRAY等。
    4. Invisible Indexes:隐形索引是一种在不影响现有查询计划的情况下隐藏索引的方法,它可以减少索引对查询性能的影响,同时也提高了数据库的维护性。
    5. CTE优化:MySQL 8对CTE的执行进行了优化,包括在CTE上执行查询优化、避免在CTE中进行排序和分组、将CTE中的过滤器下推到查询中等。
    6. Group Replication:MySQL 8引入了一种新的集群解决方案,称为组复制,它是一种基于Paxos协议的多主复制技术,可以提供高可用性和数据一致性。
    7. Atomic DDL statements:MySQL 8支持原子DDL语句,可以在单个事务中执行DDL语句,从而保证DDL语句的原子性和一致性。
    8. Unicode 9.0支持:MySQL 8支持Unicode 9.0字符集,可以存储更多的字符和符号。
    9. 增强的安全性:MySQL 8引入了一些新的安全功能,包括密码策略、默认启用SSL连接、新的加密算法等。
  1. Nginx的负载均衡方法,怎么部署

​ Nginx提供了多种负载均衡方法,包括:

    1. 轮询(Round Robin):默认情况下,Nginx会使用轮询算法将请求依次分配给不同的后端服务器。
    2. IP哈希(IP Hash):Nginx可以根据客户端IP地址将请求路由到特定的服务器上。这种方式通常用于需要保持会话的应用程序。
    3. 最少连接(Least Connections):Nginx可以将请求发送到连接数最少的服务器上。这种方式通常用于处理连接数较多的场景。
    4. URL哈希(URL Hash):Nginx可以根据请求URL将请求路由到特定的服务器上。这种方式通常用于静态文件服务。

​ Nginx的负载均衡配置通常包括以下几个步骤:

    1. 在Nginx配置文件中定义后端服务器列表。
    2. 配置负载均衡方法,可以选择默认的轮询算法或其他算法。
    3. 配置代理服务器和反向代理服务器,以将请求转发到后端服务器。
  1. 你一般用redis缓存什么数据

  2. 谈一谈Http的状态码

​ 3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向

  • 301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
  • 302 Found」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。

​ 301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。

  • 304 Not Modified」不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,也就是告诉客户端可以继续使用缓存资源,用于缓存控制。

​ 4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。

  • 400 Bad Request」表示客户端请求的报文有错误,但只是个笼统的错误。
  • 403 Forbidden」表示服务器禁止访问资源,并不是客户端的请求出错。
  • 404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。

​ 5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。

  • 500 Internal Server Error」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
  • 501 Not Implemented」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
  • 502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
  • 503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思
  1. 如何使用mybatis导入一个集合并循环操作

    1. 在Mapper.xml文件中编写SQL语句,查询需要导入的数据,并使用resultMap将结果映射到实体类中。
    2. 在DAO层中定义方法,调用MyBatis的selectList方法执行查询操作,并返回查询结果。
    3. 在Service层中获取DAO层返回的查询结果,使用循环语句遍历集合中的每个元素。
    4. 在循环语句中,调用DAO层定义的方法进行数据的新增、修改或删除操作。
  2. 如何在Java层获取数据库的自增id

    1. 使用JDBC的 getGeneratedKeys方法
    2. 使用Mybatis的 useGeneratedKeys 和 keyProperty属性获取
  3. 数据库存在的意义是什么,为什么不用文件存储

​ 数据库的优点:

  • 数据库可以对数据进行结构化存储和管理,保证数据的一致性和可靠性,同时方便对数据进行查询、修改和删除
  • 实现了用户多并发访问,保证了数据的并发安全性,避免了数据的冲突和损坏
  • 数据库提供了数据备份和恢复的功能,保证了数据的安全性

​ 相比于文件存储,数据库的优势在于数据管理的可靠性和高效性方面。同时数据库的优点是文件存储自身无法实现的,所以使用数据库是更加合适的选择。

  1. 算法题:
    根据以下例子,写一个方法(可伪代码)
    • 输入a,输出a
    • 输入aa,输出a2
    • 输入aaabbbccc,输出a3b3c3

​ 手撕思路:双指针遍历

  1. MySQL手撕

image-20230225223315647

image-20230225223859847

面经二

  1. Java的三大特性

  2. String 、StringBuilder 和 StringBuffer 的区别

  3. forward 和 redirect 的区别

  4. 值传递和引用传递的区别

  5. 谈谈你对IOC的理解

  6. 创建线程有几种方式

  7. 谈谈对多线程的锁的理解

  8. 什么是XSS攻击

  9. 什么是SQL注入攻击

  10. 什么是CSRF攻击

  11. final、finally、finalize 之间的区别

  12. 集合类的基本接口有哪些

  13. 数据库集群怎么搭建

  14. 简单说一下二分查找的流程

  15. 你还会什么排序算法(满脑子都是冒泡o(╥﹏╥)o)

  16. 二分查找和冒泡排序有什么区别

  17. 什么是ORM

  18. 问项目–学成在线

    • 说一下你印象最深的模块(媒资)

    • 断点续传怎么实现

    • 视频转码怎么实现

  19. 对SaaS平台的了解

  20. 反问:

    • 贵公司招聘这个岗位是处理什么业务的
    • 如果我有幸加入贵公司,鉴于今天的面试情况,您觉得我的能力还有哪些方面可以提升呢

面经三

  1. Spring MVC是一个Web框架,用于构建Web应用程序。它提供了一组基于注解的API和配置选项,可以帮助开发者构建MVC架构的Web应用程序。
  2. Spring Boot是一个快速构建Web应用程序的框架。它是一个基于Spring框架的快速开发框架,可以自动配置Spring和第三方库,并提供了大量的起步依赖项,使得开发者可以快速搭建一个基于Spring的Web应用程序,而无需手动配置太多的参数。

因此,Spring MVC和Spring Boot的主要区别在于:

  1. 功能不同:Spring MVC主要用于构建Web应用程序,而Spring Boot则是一个快速构建Web应用程序的框架。
  2. 配置方式不同:Spring MVC需要手动配置参数和依赖项,而Spring Boot则自动配置Spring和第三方库,并提供了大量的起步依赖项。
  3. 开发效率不同:使用Spring Boot可以提高开发效率,因为它可以自动配置和集成第三方库,而Spring MVC需要手动配置很多参数和依赖项。

综上所述,如果你需要构建一个Web应用程序,可以选择使用Spring MVC,如果你需要快速构建Web应用程序并且对配置要求较低,可以选择使用Spring Boot。

PostMapping

接收键值对形式的请求,一般使用@RequestParams

接收JSON形式的请求,一般使用@RequestBody

MultipartFile:用于接收单个文件上传的参数

List:用于接收多个文件上传的参数

使用MultipartFile接收文件参数时,需要在前端表单中添加enctype=”multipart/form-data”属性,并且需要使用POST请求方式提交表单

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
// 获取文件名
String fileName = file.getOriginalFilename();
// 获取文件后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));
// 生成新的文件名
String newFileName = UUID.randomUUID().toString() + suffixName;
// 设置文件存储路径
String filePath = "D:/uploads/";
File dest = new File(filePath + newFileName);
try {
// 将上传的文件保存到指定目录下
file.transferTo(dest);
// 返回上传成功信息
return "upload success";
} catch (IOException e) {
e.printStackTrace();
}
// 返回上传失败信息
return "upload failed";
}
  1. 在接口中添加这个方法来实现添加用户的功能:
1
2
3
public interface UserMapper {
void addUser(User user);
}
  1. 在Mapper配置文件中定义该接口方法对应的SQL语句
1
2
3
<insert id="addUser" parameterType="com.example.demo.model.User">
insert into user(name, age, email) values(#{name}, #{age}, #{email})
</insert>
  1. 在代码中调用该接口方法即可完成用户添加操作
1
2
3
4
5
6
@Autowired
private UserMapper userMapper;

public void addUser(User user) {
userMapper.addUser(user);
}

Mybatis 实现一个接口,接口的参数形式有以下几种:

  1. 使用JavaBean类型作为参数

    • 定义JavaBean类User

    • public class User {
          private Integer id;
          private String name;
          private Integer age;
          private String email;
          // getters and setters
      }
      
      1
      2
      3
      4
      5
      6
      7

      - 定义UserMapper接口

      - ```java
      public interface UserMapper {
      void addUser(User user);
      }
    • 在Mapper配置文件中定义SQL语句

    • ```java

      insert into user(name, age, email) values(#{name}, #{age}, #{email})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      - 在代码中调用该接口方法

      - ```java
      @Autowired
      private UserMapper userMapper;

      public void addUser(User user) {
      userMapper.addUser(user);
      }

  2. 使用@Param注解指定参数名称

    • 定义UserMapper接口

    • public interface UserMapper {
          void addUser(@Param("name") String name, @Param("age") Integer age, @Param("email") String email);
      }
      
      1
      2
      3
      4
      5
      6
      7

      - 在Mapper配置文件中定义SQL语句

      - ```java
      <insert id="addUser" parameterType="java.util.Map">
      insert into user(name, age, email) values(#{name}, #{age}, #{email})
      </insert>
    • 在代码中调用该接口方法

    • @Autowired
      private UserMapper userMapper;
      
      public void addUser(String name, Integer age, String email) {
          userMapper.addUser(name, age, email);
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      3. 使用Map类型作为参数

      - 定义UserMapper接口

      - ```java
      public interface UserMapper {
      void addUser(Map<String, Object> params);
      }
    • 在Mapper配置文件中定义SQL语句

    • ```java

      insert into user(name, age, email) values(#{name}, #{age}, #{email})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      - 在代码中调用该接口方法

      - ```java
      @Autowired
      private UserMapper userMapper;

      public void addUser(String name, Integer age, String email) {
      Map<String, Object> params = new HashMap<>();
      params.put("name", name);
      params.put("age", age);
      params.put("email", email);
      userMapper.addUser(params);
      }

SELECT name FROM user ORDER BY num ASC;

SELECT name FROM user WHERE num >= (SELECT num FROM user ORDER BY num ASC LIMIT 19, 1) LIMIT 10;

该语句中的子查询 SELECT num FROM user ORDER BY num ASC LIMIT 19, 1 表示从 user 表中按照 num 字段升序排序后,跳过前19行记录,然后取第20行的 num 值作为起始条件。接着使用主查询 SELECT name FROM user WHERE num >= ... LIMIT 10,根据起始条件从第20行开始向下查找,取10条记录,并返回这些记录中的 name 字段

第一行的偏移量为0,所以limit的起始值为0

SELECT *
FROM user
LEFT JOIN info
ON user.num = info.num;

在已经搭建好的商品系统中添加秒杀功能,需要考虑以下几个方面:

  1. 数据库设计

为了支持秒杀功能,需要在数据库中添加一个秒杀商品表,该表至少应该包括以下字段:

  • 商品 ID
  • 秒杀开始时间
  • 秒杀结束时间
  • 秒杀价格
  • 库存数量

同时,在商品表中,需要增加一个字段,用来标识商品是否支持秒杀。

  1. 秒杀接口设计

为了实现秒杀功能,需要设计一个秒杀接口,该接口需要满足以下要求:

  • 接口需要验证用户是否已经登录。
  • 接口需要判断商品是否已经开始秒杀,如果没有开始秒杀,需要返回秒杀未开始的错误信息。
  • 接口需要判断商品是否已经结束秒杀,如果已经结束秒杀,需要返回秒杀已结束的错误信息。
  • 接口需要判断商品是否有库存,如果库存不足,需要返回库存不足的错误信息。
  • 接口需要使用事务保证秒杀的原子性,同时需要防止超卖现象的发生。
  1. 秒杀系统优化

为了保证秒杀系统的高并发性能,需要进行系统优化,包括但不限于以下方面:

  • 使用缓存技术,减轻数据库压力。
  • 使用分布式技术,将请求分散到多台服务器上处理,提高系统吞吐量。
  • 使用消息队列技术,异步处理秒杀请求,提高系统的并发能力和稳定性。
  • 使用负载均衡技术,将请求均衡分配到多台服务器上,提高系统的可用性和性能。

秒杀系统需要加锁来保证数据的一致性和并发安全。这里考虑使用分布式锁

在秒杀系统中,可以使用分布式锁来解决并发问题。例如,可以使用 Redis 的分布式锁来保证同一时刻只有一个用户可以执行秒杀操作。分布式锁的实现方式较为灵活,可以根据具体需求选择适合的实现方式。

将a.sh脚本的归属更改为B用户,可以使用 chown 命令:

sudo chown B a.sh

  1. 继承 Thread 类,重写 run 方法,并创建 Thread 实例后调用 start 方法启动线程

    • ```java
      public class MyThread extends Thread {
      @Override
      public void run() {
      // 线程要执行的代码
      }
      }

      MyThread thread = new MyThread();
      thread.start();

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      2. 实现 Runnable 接口,重写 run 方法,并通过 Thread 类的构造方法将 Runnable 实例传入创建 Thread 实例后调用 start 方法启动线程

      - ```java
      public class MyRunnable implements Runnable {
      @Override
      public void run() {
      // 线程要执行的代码
      }
      }

      MyRunnable runnable = new MyRunnable();
      Thread thread = new Thread(runnable);
      thread.start();
  2. 使用 Callable 和 Future 接口创建带有返回值的线程,Callable 接口中的 call 方法返回线程执行的结果,可以通过 Future 接口获取

    • ```java
      public class MyCallable implements Callable {
      @Override
      public Integer call() throws Exception {
      // 线程要执行的代码
      return 1;
      }
      }

      MyCallable callable = new MyCallable();
      ExecutorService executorService = Executors.newSingleThreadExecutor();
      Future future = executorService.submit(callable);

      1
      2
      3
      4
      5
      6
      7
      8

      4. 使用线程池创建线程,避免频繁创建和销毁线程带来的性能损失和资源浪费

      - ```java
      ExecutorService executorService = Executors.newFixedThreadPool(10);
      executorService.execute(() -> {
      // 线程要执行的代码
      });

多线程的操作逻辑一般写在run()方法中,这个方法需要被子线程重写实现

  1. 实现Runnable接口的方式

    1
    2
    3
    4
    5
    6
    public class MyRunnable implements Runnable {
    @Override
    public void run() {
    // 操作逻辑
    }
    }

    在创建线程时,将MyRunnable对象作为参数传入Thread构造方法中

    1
    2
    3
    MyRunnable myRunnable = new MyRunnable();
    Thread thread = new Thread(myRunnable);
    thread.start();
  2. 继承Thread类的方式

    1
    2
    3
    4
    5
    6
    public class MyThread extends Thread {
    @Override
    public void run() {
    // 操作逻辑
    }
    }

    然后直接创建MyThread对象并启动线程

    1
    2
    MyThread myThread = new MyThread();
    myThread.start();

可以使用Apache POI库来读写Excel文件,使用JDBC来连接数据库并将数据导入数据库中

  1. 读取Excel文件并获取数据

    使用POI库读取Excel文件中的数据,具体操作可参考官方文档或相关教程。读取完成后,可以将数据存储在一个Java对象中,例如List<MyData>

  2. 连接数据库

    使用JDBC来连接数据库。具体操作可参考JDBC相关教程,一般需要指定数据库的URL、用户名和密码等信息。

  3. 将数据插入数据库中

    将读取到的Excel数据插入数据库中,可以使用JDBC提供的PreparedStatement对象

    1
    2
    3
    4
    5
    6
    7
    Connection conn = DriverManager.getConnection(url, username, password);
    PreparedStatement ps = conn.prepareStatement("INSERT INTO my_table (column1, column2) VALUES (?, ?)");
    for (MyData data : dataList) {
    ps.setString(1, data.getColumn1());
    ps.setString(2, data.getColumn2());
    ps.executeUpdate();
    }

完整的代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. 读取Excel文件
List<MyData> dataList = new ArrayList<>();
Workbook workbook = WorkbookFactory.create(new File("path/to/excel/file"));
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
MyData data = new MyData();
data.setColumn1(row.getCell(0).getStringCellValue());
data.setColumn2(row.getCell(1).getStringCellValue());
dataList.add(data);
}

// 2. 连接数据库
String url = "jdbc:mysql://localhost/mydatabase";
String username = "root";
String password = "mypassword";
Connection conn = DriverManager.getConnection(url, username, password);

// 3. 将数据插入数据库中
PreparedStatement ps = conn.prepareStatement("INSERT INTO my_table (column1, column2) VALUES (?, ?)");
for (MyData data : dataList) {
ps.setString(1, data.getColumn1());
ps.setString(2, data.getColumn2());
ps.executeUpdate();
}

// 关闭资源
ps.close();
conn.close();
workbook.close();
  1. 读取一个本地文本文件,可以使用Java中的File和Scanner类来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ReadTextFile {
public static void main(String[] args) {
// 指定要读取的文件路径
String filePath = "C:\\example.txt";

// 创建一个File对象
File file = new File(filePath);

try {
// 创建一个Scanner对象读取文件
Scanner scanner = new Scanner(file);

// 逐行读取文件内容
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}

// 关闭Scanner对象
scanner.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
  1. 使用字符流可以使用Java中的FileReader类来读取本地文本文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ReadTextFile {
public static void main(String[] args) {
File file = new File("test.txt"); // 指定本地文件路径
try {
FileReader reader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(reader);
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

使用Java的文件输入流(FileInputStream)和字符输入流(InputStreamReader)来读取本地文件,然后通过标准输出流(System.out)将读取到的内容输出到控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.*;

public class FileReaderExample {
public static void main(String[] args) {
// 指定要读取的文件路径
String filePath = "C:\\example.txt";
File file = new File(filePath);
BufferedReader reader = null;
try {
// 创建文件输入流
FileInputStream fis = new FileInputStream(file);
// 创建字符输入流
InputStreamReader isr = new InputStreamReader(fis);
// 创建缓冲字符输入流
reader = new BufferedReader(isr);
String line;
// 逐行读取文件内容并输出到控制台
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

常见的拦截器类型包括:

  1. 前置拦截器(Pre-Handler Interceptor):在请求到达Controller层之前,对请求进行拦截和处理,例如权限认证、参数校验等。
  2. 后置拦截器(Post-Handler Interceptor):在Controller层处理完请求并返回响应后,对响应进行拦截和处理,例如日志记录、异常处理等。
  3. 环绕拦截器(Around-Handler Interceptor):在请求到达Controller层之前和处理完请求并返回响应之后,对请求和响应进行拦截和处理,可以实现前置和后置拦截器的功能。
  4. 异常拦截器(Exception Interceptor):在Controller层抛出异常时,对异常进行拦截和处理,例如统一异常处理、异常日志记录等。

在 Spring Boot 中,可以通过实现 WebMvcConfigurer 接口并重写 addInterceptors 方法来配置拦截器。具体步骤如下:

  1. 创建一个拦截器类,实现 HandlerInterceptor 接口,重写需要的方法。
  2. 创建一个配置类,实现 WebMvcConfigurer 接口,重写 addInterceptors 方法。
  3. addInterceptors 方法中,调用 registry.addInterceptor 方法,将拦截器注册到拦截器链中,并设置拦截器的拦截路径

示例代码:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") // 设置拦截路径
.excludePathPatterns("/login"); // 设置排除拦截的路径
}
}