什么是反射?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任 意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法 的功能称为java语言的反射机制。
反射的优缺点是什么
优点
1. 提高代码灵活性
反射允许程序在运行时动态识别类型、调用方法、操作属性,能编写出更通用的代码。
2. 简化开发
在序列化、反序列化、测试等场景中,反射能大幅减少重复代码:
缺点
1. 性能开销较大
反射操作需要在运行时解析类的元数据(如 Class 对象、方法、字段),相比直接调用方法、访问属性,存在明显的性能损耗:
2. 破坏面向对象的封装性
反射可以绕过访问修饰符的限制,直接访问和修改类的私有成员,这违背了面向对象的封装原则:
3. 代码可读性与可维护性降低
反射的动态特性使代码变得抽象,相比直接调用的代码,可读性和调试难度显著增加:
什么是代理?静态代理和动态代理的区别是什么?jdk动态代理和cglib的区别是什么?
1.什么是代理
代理是一种设计模式,核心是通过一个代理对象来控制对目标对象的访问。代理对象可以在不修改目标对象代码的前提下,对目标方法进行增强,比如添加日志、权限校验、事务管理等操作。
2. 静态代理和动态代理的区别
在代理类创建时机上:
- 静态代理的代理类在编译期就已确定,是开发者手动编写的.java 文件,编译后生成.class 文件;
- 动态代理的代理类则是在程序运行时,由框架通过反射或字节码技术动态生成。
在代码冗余度和维护成本上:
- 静态代理需要为每个目标类编写对应的代理类,当抽象类的方法发生变化时,目标类和所有对应的代理类都要同步修改,冗余度高且维护成本大;
- 动态代理只需要编写一套增强逻辑,就可以代理多个目标对象,接口方法变更时,仅需调整增强逻辑即可,维护成本更低。
在性能表现上:
- 静态代理是直接调用目标方法,没有额外的性能开销;
- 动态代理因为涉及反射或字节码生成的过程,会有少量性能损耗,不过在大多数业务场景下,这种损耗几乎可以忽略不计。
3. JDK动态代理和 CGLIB 的区别
它们是动态代理的两种主流实现方式,核心区别在底层原理和适用场景。
- JDK 动态代理基于基于 JDK 自带的接口和反射实现,要求目标对象必须实现至少一个接口;
- CGLIB 基于字节码生成技术实现,通过继承目标类生成代理子类,不需要目标对象实现接口,但无法代理 final 类和 final 方法。
如何搭建一个 Spring Boot 项目
搭建 Spring Boot 项目主要有两种核心方式,一种是通过官方的Spring Initializr工具来快速生成项目骨架,
另一种是手动在本地 IDE 中创建普通项目并引入相关依赖。
- 搭建springboot项目的步骤:
- 首先创建一个Maven项目
- pom文件中定义父工程,spring-boot-starter-parent,要定义maven三要素
- 引用起步依赖,spring-boot-starter-web,不需要配置版本号
- 在resources下创建一个springboot的配置文件,application.yml
- 创建启动类,类上面添加@SpringBootApplication注解,添加main方法,代码实现为SpringApplication.run(类名.class,args)
@RestController和@Controller的区别
我从注解本质、返回值处理、适用场景、底层原理四个层面详细说明两者的区别。
1. 注解的本质与定义
从注解的构成维度来看,@Controller 是 Spring MVC 的核心注解之一,属于单一功能注解,仅用于标识控制器类,告知 Spring 这是一个处理请求的 Bean;
@RestController 是一个组合注解,查看其源码可以发现,它是由 @Controller 和 @ResponseBody 两个注解共同组成的,其中 @ResponseBody 的作用是覆盖了控制器方法的默认返回值处理逻辑。
2. 返回值的处理机制
从请求响应的处理维度来看,这是两者最核心的区别:
- @Controller 是传统的控制器注解,默认情况下,其方法的返回值会被解析为视图名称(比如跳转到 JSP、HTML 页面),需要配合 @ResponseBody 注解才能将返回值转换为 JSON
- 使用 @RestController 注解的控制器类,其所有方法都会默认应用 @ResponseBody 的效果,方法的返回值会被直接转换为 JSON、XML 等格式的响应体,不会被视图解析器处理,因此无法实现页面跳转。
3. 适用场景的差异
从项目开发架构的维度来看:
- @Controller 更适合前后端不分离的项目架构。
- @RestController 更适合前后端分离的项目架构。
4. 底层实现的简要说明
从 Spring 框架处理逻辑的维度来看,当请求到达控制器方法时,Spring 会根据注解的不同触发不同的处理流程:
- 对于 @Controller 标注的类,Spring 会先判断方法是否有 @ResponseBody 注解,若无则走视图解析流程,若有则走消息转换器(HttpMessageConverter)的序列化流程。
- 对于 @RestController 标注的类,Spring 会直接为所有方法启用消息转换器流程,将返回值通过 Jackson 等工具序列化为 JSON 数据后返回给客户端。
5. 补充说明
还有一个细节是,@RestController 的 @ResponseBody 作用于整个类,而 @Controller 配合的 @ResponseBody 可以灵活作用于单个方法,这意味着在 @Controller 类中,既可以有返回视图的方法,也可以有返回 JSON 的方法,而 @RestController 类中所有方法都只能返回响应体数据。
什么是IOC
IOC 叫做控制反转,是一种设计思想,旨在解耦对象之间的依赖关系,提升代码的可维护性和可扩展性。
控制反转的 “控制” 指的是控制对象的创建权和依赖关系的管理权;“反转” 指的是这部分控制权从应用程序的业务代码中反转到了框架的 IOC 容器中,打破了传统开发中对象之间的紧耦合。
例如在传统的程序开发中,比如我们要在 A 类中使用 B 类的对象,需要在 A 类中手动通过 new 创建 B 类的对象实例,这就导致 A 类和 B 类之间紧耦合 —— 如果 B 类的构造方式发生变化,A 类的代码也需要同步修改。
而在 IOC 思想下,开发者不再需要手动创建对象,而是通过配置(如 @Autowired)声明对象的依赖,由 IOC 容器统一负责对象的创建、初始化和依赖注入。这种方式让对象之间的依赖关系由容器管理,实现了依赖的解耦。
什么是 DI?有哪些方式?推荐用哪种?
什么是DI
DI 即依赖注入,是 IOC 思想的具体实现。这种方式的核心价值在于解耦:传统开发中,对象需要自行创建依赖,导致对象之间紧耦合;而依赖注入让对象只需要声明依赖,由容器统一管理依赖的创建和注入,极大降低了对象间的耦合度,提升了代码的可维护性和可扩展性。
有哪些方式
DI 的实现方式主要有三种,分别为构造器注入,setter方法注入和字段注入。
- 构造器注入是通过类的构造方法传入依赖对象;
- Setter 方法注入是通过类的 setter 方法为依赖属性赋值;
- 字段注入则是直接在类的成员字段上通过 @Autowired 等注解注入依赖。
推荐用哪种?
推荐使用构造器注入。因为构造器注入能保证对象在创建时就拥有所有必要的依赖,确保对象状态的完整性,同时也更利于单元测试,还能避免空指针异常的风险。
@Autowired 和 @Resource 的区别
1. 从来源上看
- @Autowired 是Spring 框架自身提供的注解。
- @Resource 是Java EE 的标准注解。
2. 从注入匹配的规则上看(核心)
@Autowired 的匹配优先级是先按类型,后按名称:
- 首先,Spring 会根据注入点的类型(如字段类型、方法参数类型)在容器中查找所有匹配的 Bean;
- 若找到唯一的 Bean,直接注入;
- 若找到多个同类型 Bean,会尝试按字段 / 方法名称匹配 Bean 的名称,若仍匹配失败,需手动添加 @Qualifier 注解指定 Bean 的名称(如
@Qualifier("userServiceImpl")); - 若未找到任何匹配的 Bean,且
required=true,则抛出异常。
@Resource 的匹配优先级是先按名称,后按类型:
- 首先,Spring 会根据 @Resource 的
name属性值查找 Bean,若未指定name,则使用注入字段的名称或 Setter 方法的属性名作为查找依据; - 若按名称找到匹配的 Bean,直接注入;
- 若按名称未找到,则降级为按类型查找;
- 若按类型找到唯一 Bean,注入;若找到多个,抛出
NoUniqueBeanDefinitionException;若未找到,返回 null(或抛异常)。
3. 从注入方式的支持范围上看
- @Autowired:支持构造器注入、Setter 方法注入、字段注入三种方式,是 Spring 推荐的注入注解之一,尤其配合构造器注入时,能保证对象依赖的完整性。
- @Resource:仅支持字段注入、Setter 方法注入,不支持构造器注入。这是因为 @Resource 的规范定义中未考虑构造器注入的场景,因此 Spring 对其的实现也未覆盖该方式。
mybatis的#和$的区别
两者是 MyBatis 中参数绑定的核心符号,核心差异体现在参数处理方式、安全性、适用场景三个方面:
- 参数处理方式不同:
#{}会将参数值作为 “占位符” 处理,MyBatis 会先预编译 SQL 语句,再将参数值替换到占位符位置,参数值会被自动加上引号(比如字符串参数会变成'value');而${}是直接字符串拼接,MyBatis 会把参数值原样拼接到 SQL 语句中,不会加引号,也不会预编译。 - 安全性不同:
#{}因为采用预编译和参数占位符,能有效防止 SQL 注入攻击,是安全的参数绑定方式;${}是直接拼接字符串,若参数值包含恶意 SQL 片段(如' or 1=1 --),会直接融入 SQL 语句执行,存在严重的 SQL 注入风险。 - 适用场景不同:
#{}是日常开发的首选,适用于绝大多数参数绑定场景(如 WHERE 条件、INSERT/UPDATE 的字段值等);${}仅适用于必须动态拼接 SQL 片段的场景(如动态表名、动态列名、ORDER BY 后的排序字段),使用时需自行做好参数校验,避免安全问题。
sql的书写顺序是什么?执行顺序又是什么?
1. 完整书写顺序(含所有常用子句)
贴合人类阅读习惯,完整书写顺序如下(按书写先后):
| 书写步骤 | 子句类型 | 具体内容 & 注意事项 |
|---|---|---|
| 1 | SELECT 子句 | SELECT [DISTINCT/ALL] [TOP N] 列/聚合/窗口函数 [AS 别名]- DISTINCT/ALL:默认 ALL;- 窗口函数(如 ROW_NUMBER ())只能写在 SELECT 中;- 别名仅后续 ORDER BY 可用 |
| 2 | FROM 子句 | FROM 主表 [AS 别名]- 基础数据源,必须有(除非 SELECT 1 这类无表查询) |
| 3 | JOIN 子句 | [INNER/LEFT/RIGHT/FULL] JOIN 关联表 [AS 别名] ON 关联条件- 必须紧跟 FROM;- ON 是关联条件,仅过滤关联表的行 |
| 4 | WHERE 子句 | WHERE 行级条件- 仅过滤行,不能用聚合函数 / SELECT 别名;- 先于 GROUP BY 执行 |
| 5 | GROUP BY 子句 | GROUP BY [ROLLUP/CUBE] 分组字段1, 分组字段2- ROLLUP/CUBE:分组汇总(如 MySQL 的 WITH ROLLUP);- 分组字段可写 SELECT 别名 |
| 6 | HAVING 子句 | HAVING 组级条件- 仅过滤分组,可用聚合函数 / GROUP BY 别名;- 必须紧跟 GROUP BY |
| 7 | ORDER BY 子句 | ORDER BY 字段 [ASC/DESC] [NULLS FIRST/LAST]- 唯一可用 SELECT 别名的过滤 / 排序子句;- NULLS FIRST/LAST:指定 NULL 值排序位置(MySQL 默认 NULLS LAST) |
| 8 | 分页子句 | MySQL:LIMIT [偏移量,] 行数/OFFSET 偏移 LIMIT 行SQL Server:TOP N [PERCENT]Oracle:ROWNUM <= N |
2. 完整执行顺序(数据库引擎处理流程)
数据库按 “先缩小数据范围,再加工处理” 的逻辑执行,每一步的输出作为下一步的输入,完整执行顺序如下:
| 执行步骤 | 子句 | 核心作用 |
|---|---|---|
| 1 | FROM/JOIN/ON | 确定基础数据源:先加载主表,再通过 JOIN 关联其他表,ON 过滤关联条件生成临时表 |
| 2 | WHERE | 过滤临时表中的行:仅保留符合条件的记录,不能使用聚合函数 / SELECT 别名 |
| 3 | GROUP BY | 将 WHERE 过滤后的行按指定字段分组,每组作为一个独立的计算单元 |
| 4 | HAVING | 过滤 GROUP BY 后的分组:仅保留符合聚合条件的组,可使用聚合函数 |
| 5 | SELECT | 执行列选择、计算、别名定义:仅提取需要的列,生成新的临时结果集 |
| 6 | DISTINCT | 对 SELECT 结果集去重(若有) |
| 7 | ORDER BY | 对结果集按指定字段排序:可使用 SELECT 定义的别名 |
| 8 | LIMIT/OFFSET | 限制最终结果的行数或偏移量,仅返回指定范围的数据 |
char和varchar的区别
char 和 varchar 是 MySQL 中存储字符串的核心数据类型,核心差异集中在长度特性、存储方式、空间占用、性能四个维度:
- 长度特性:char 是固定长度类型,定义时指定长度(如
char(10)),无论实际存储的字符串多长,都会占用该长度的空间;varchar 是可变长度类型,定义时指定最大长度(如varchar(10)),实际占用空间为字符串真实长度 + 1/2 字节(用于记录长度)。 - 存储方式:char 存储时,若字符串长度小于指定值,会在右侧补空格至指定长度;varchar 仅存储实际字符串,不补空格,仅额外存储长度标识。
- 空间占用:char 易浪费空间(如
char(10)存 "abc",占用 10 字节);varchar 更节省空间(如varchar(10)存 "abc",仅占 3+1=4 字节)。 - 性能:char 读写性能更高(固定长度,无需解析长度,存储连续);varchar 性能略低(需解析长度标识,存储易碎片化)。
适用场景:char 适合存储长度固定的字符串(如手机号、身份证号、状态码);varchar 适合存储长度不固定的字符串(如姓名、地址、商品描述)。
补充: 注意上边讲的是字符数,而不是字节数,比如配置了char(10),已经存储了1个英文,1个中文,还有一个火星文,mysql一般选用的是最大占用4个字节的可变编码集utf8mb4,为什么不使用传统的最大占用3个字节的可变字符集utf8?因为有些场景有可能需要存储一些火星文,火星文需要4个字节来存储,上边的例子中英文在utf8mb4编码中是占1个字节,中文一般占3个字节(某些国家的语言可能占2个字节),剩下7个长度按空格来计算,空格在utf8mb4编码中也是占1个字节,所以总共占的空间是15个字节
这里想表达的是,char底层的真正存储空间是可变的,取决于具体存储的字符类型和编码集,而varchar底层的真正存储空间计算方法和char类似,也是取决于字符类型和编码集,还是上边的案例,但是换成varchar(10),底层的存储空间是8个字节,但是varchar的底层真实存储还会再额外预留1-2字节来存储字符串实际所占字节的长度
内连接和外连接有什么区别?内连接和外连接的连接条件能放在on和where吗?
1. 内连接与外连接的区别
- 内连接(INNER JOIN):只返回两个表中都有匹配记录的数据行
- 外连接(OUTER JOIN):返回主表的全部记录,另一个表中如果没有匹配的记录则用null填充
2. ON和WHERE的详细区别
执行顺序与作用:
- ON:在连接表时使用的条件,定义两表之间的连接关系
- WHERE:在表连接后,对连接好的表进行过滤时使用的条件
- 执行顺序:先执行ON连接,再执行WHERE过滤10
不同连接类型中的区别:
-
内连接中:
- ON和WHERE效果基本一致
- 例子:
SELECT * FROM A INNER JOIN B ON A.id = B.id与SELECT * FROM A INNER JOIN B WHERE A.id = B.id结果相同
-
外连接中:
-
条件写在ON中:保证外连接特性,保留主表全部数据
-
条件写在WHERE中:可能导致外连接退化为内连接
-
关键区别:WHERE会过滤掉NULL值,从而丢失外连接的"补全"功能
-
例子:
SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id(保留所有用户)SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id WHERE orders.id IS NOT NULL(只保留有订单的用户,等同于内连接)
-
性能优化建议:
- 外连接中,被连接表的过滤条件必须放在 ON 中(既保留主表全量,又减少关联数据量);
- 主表的过滤条件放在 WHERE 中(提前过滤主表数据,减少关联基数);