diff --git a/自学/JavaWeb——后端.md b/自学/JavaWeb——后端.md index 117f5d2..8a9c687 100644 --- a/自学/JavaWeb——后端.md +++ b/自学/JavaWeb——后端.md @@ -2,11 +2,15 @@ ## Java版本解决方案 -**单个Py文件运行:Edit Configurations** +**单个Java文件运行:** + +Edit Configurations - **针对单个运行配置**:每个 Java 运行配置(如主类、测试类等)可以独立设置其运行环境(如 JRE 版本、程序参数、环境变量等)。 - **不影响全局项目**:修改某个运行配置的环境不会影响其他运行配置或项目的全局设置。 + + **如何调整全局项目的环境** - 打开 `File -> Project Structure -> Project`。 @@ -23,11 +27,13 @@ -. **三者之间的关系** + **三者之间的关系** + +- 全局项目环境 是基准,决定项目的默认 JDK 和语言级别。 +- Java Compiler 控制编译行为,可以覆盖全局的 `Project language level`。 +- Maven Runner 控制 Maven 命令的运行环境,可以覆盖全局的 `Project SDK`。 + -- **全局项目环境** 是基准,决定项目的默认 JDK 和语言级别。 -- **Java Compiler** 控制编译行为,可以覆盖全局的 `Project language level`。 -- **Maven Runner** 控制 Maven 命令的运行环境,可以覆盖全局的 `Project SDK`。 **Maven 项目**: @@ -99,7 +105,7 @@ IDEA 会自动解析 `pom.xml`,下载依赖并构建项目结构。 1.新建一个上层目录,如下,MyProject1和MyProject2的内容拷贝过去。 -```text +```java ParentProject/ ├── pom.xml <-- 父模块(聚合模块) ├── MyProject1/ <-- 子模块1 @@ -112,7 +118,7 @@ ParentProject/ 父模块 `pom.xml` 示例: -```text +```java 4.0.0 com.example @@ -130,7 +136,7 @@ ParentProject/ 3.修改子模块 `pom.xml` ,加上: -```text +```java com.example ParentProject @@ -155,7 +161,7 @@ ParentProject/ 在第二个项目的 `pom.xml` 中添加依赖坐标 -```text +```java com.example my-first-project @@ -181,7 +187,7 @@ Maven 重建 可以到mvn的中央仓库(https://mvnrepository.com/)中搜索获取依赖的坐标信息 -```text +```java @@ -207,7 +213,7 @@ Maven 重建 A依赖B,B依赖C,如果A不想将C依赖进来,可以同时排除C,被排除的资源**无需指定版本**。 -```text +```java com.itheima maven-projectB @@ -254,7 +260,7 @@ A依赖B,B依赖C,如果A不想将C依赖进来,可以同时排除C,被 1. 导入依赖junit - ```text + ```java junit junit @@ -267,7 +273,7 @@ A依赖B,B依赖C,如果A不想将C依赖进来,可以同时排除C,被 3. 创建test方法 - ```text + ```java @Test public void test1(){ System.out.println("hello1"); @@ -323,7 +329,7 @@ A依赖B,B依赖C,如果A不想将C依赖进来,可以同时排除C,被 **传统URL风格如下:** -```url +```java http://localhost:8080/user/getById?id=1 GET:查询id为1的用户 http://localhost:8080/user/saveUser POST:新增用户 http://localhost:8080/user/updateUser PUT:修改用户 @@ -334,7 +340,7 @@ http://localhost:8080/user/deleteUser?id=1 DELETE:删除id为1的用户 **基于REST风格URL如下:** -```text +```java http://localhost:8080/users/1 GET:查询id为1的用户 http://localhost:8080/users POST:新增用户 http://localhost:8080/users PUT:修改用户 @@ -345,7 +351,7 @@ http://localhost:8080/users/1 DELETE:删除id为1的用户 **REST风格后端代码:** -```text +```java @RestController @RequestMapping("/depts") //定义当前控制器的请求前缀 public class DeptController { @@ -391,6 +397,8 @@ public class DeptController { ## SpringBoot +**Servlet 容器** 是用于管理和运行 Web 应用的环境,它负责加载、实例化和管理 Servlet 组件,处理 HTTP 请求并将请求分发给对应的 Servlet。常见的 Servlet 容器包括 Tomcat、Jetty、Undertow 等。 + **SpringBoot的WEB默认内嵌了tomcat服务器,非常方便!!!** 浏览器与 Tomcat 之间通过 HTTP 协议进行通信,而 Tomcat 则充当了中间的桥梁,将请求路由到你的 Java 代码,并最终将处理结果返回给浏览器。 @@ -406,7 +414,7 @@ public class DeptController { 新建HelloController类 -```text +```java package edu.whut.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -423,7 +431,7 @@ public class HelloController { 然后启动服务器,main程序 -```text +```java package edu.whut; import org.springframework.boot.SpringApplication; @@ -449,7 +457,7 @@ public class SprintbootQuickstartApplication { - 在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。 如果是简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。 -```text +```java @RestController public class RequestController { // http://localhost:8080/simpleParam?name=Tom&age=10 @@ -471,7 +479,7 @@ public class RequestController { 在方法形参前面加上 `@RequestParam` 然后通过value属性执行请求参数名,从而完成映射。代码如下: -```text +```java @RestController public class RequestController { // http://localhost:8080/simpleParam?name=Tom&age=20 @@ -502,7 +510,7 @@ public class RequestController { 注意:这里User前面不能加`@RequestBody`是因为请求方式是 (表单)或 URL 参数;如果是JSON请求体就必须加。 -```text +```java @RequestMapping("/complexpojo") public String complexpojo(User user){ System.out.println(user); @@ -510,7 +518,7 @@ public class RequestController { } ``` -```text +```java @Data @NoArgsConstructor @AllArgsConstructor @@ -521,7 +529,7 @@ public class User { } ``` -```text +```java @Data @NoArgsConstructor @AllArgsConstructor @@ -537,7 +545,7 @@ public class Address { 数组参数:请求参数名与形参数组**名称相同**且请求参数为**多个**,定义数组类型形参即可接收参数 -```text +```java @RestController public class RequestController { //数组集合参数 @@ -658,7 +666,7 @@ public class DataController { 引入依赖: -```text +```java com.alibaba fastjson @@ -721,7 +729,7 @@ public class FastJsonDemo { 定义在一个实体类Result来包含以上信息。代码如下: -```text +```java @Data @NoArgsConstructor @AllArgsConstructor @@ -803,11 +811,11 @@ public class A { **Bean 对象**:在 Spring 中,被容器管理的对象称为 Bean。通过注解(如 `@Component`, `@Service`, `@Repository`, `@Controller`),可以将一个普通的 Java 类声明为 Bean,容器会负责它的创建、初始化以及生命周期管理。 - - ![image-20240305104036244](https://pic.bitday.top/i/2025/03/19/u6n46r-2.png) -任务:完成Controller层、Service层、Dao层的代码解耦 + + +*任务:完成Controller层、Service层、Dao层的代码解耦* 思路: 1. 删除Controller层、Service层中new对象的代码 @@ -823,7 +831,7 @@ public class A { ![image-20221204212807207](https://pic.bitday.top/i/2025/03/19/u6tjd9-2.png) -第2步:Service层及Dao层的实现类,交给IOC容器管理 +第2步:Service层及Dao层的**实现类**,交给IOC容器管理 使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理 @@ -837,12 +845,38 @@ public class A { -**如果我有多个实现类,eg:EmpServiceA、EmpServiceB、EmpServiceC,我该如何切换呢?**两种方法 +**如果我有多个实现类,eg:EmpServiceA、EmpServiceB,我该如何切换呢?**两种方法 1. 只需在需要使用的实现类上加@Component,注释掉不需要用到的类上的@Component。可以把@Component想象成装入盒子,@Autowired想象成拿出来,因此只需改变放入的物品,而不需改变拿出来的这个动作。 + + ```java + // 只启用 EmpServiceA,其他实现类的 @Component 注解被注释或移除 + @Component + public class EmpServiceA implements EmpService { + // 实现细节... + } + + // EmpServiceB 没有被 Spring 管理 + // @Component + // public class EmpServiceB implements EmpService { ... } + ``` 2. 在@Component上面加上@Primary,表明该类优先生效 + ``` + // 默认使用 EmpServiceB,其他实现类也在容器中,但未标记为 Primary + @Component + public class EmpServiceA implements EmpService { + // 实现细节... + } + + @Component + @Primary // 默认优先注入 + public class EmpServiceB implements EmpService { + // 实现细节... + } + ``` + Component衍生注解 @@ -857,12 +891,276 @@ Component衍生注解 +## SpringBoot原理 + +### 容器启动 + +在 Spring 框架中,“容器启动”指的是 ApplicationContext 初始化过程,主要包括*配置解析*、*加载 Bean 定义*、*实例化*和*初始化 Bean* 以及完成*依赖注入*。具体来说,容器启动的时机包括以下几个关键点: + +当你启动一个 Spring 应用时,无论是通过直接运行一个包含 `main` 方法的类,还是部署到一个 Servlet 容器中,Spring 的应用上下文都会被创建和初始化。这个过程包括: + +- 读取配置:加载配置文件或注解中指定的信息,确定哪些组件由 Spring 管理。 +- 注册 Bean 定义:将所有扫描到的 Bean 定义注册到容器中。 +- 实例化 Bean:根据 Bean 定义创建实例。默认情况下,所有单例 Bean在启动时被创建(除非配置为懒加载)。 +- 依赖注入:解析 Bean 之间的依赖关系,并自动注入相应的依赖。 + + + +### 配置优先级 + +在SpringBoot项目当中,常见的属性配置方式有5种, 3种配置文件,加上2种外部属性的配置(Java系统属性、命令行参数)。优先级(从低到高): + +- application.yaml(忽略) +- application.yml +- application.properties +- java系统属性(-Dxxx=xxx) +- 命令行参数(--xxx=xxx) + +在 Spring Boot 项目中,通常使用的是 **application.yml** 或 **application.properties** 文件,这些文件通常放在项目的 **src/main/resources** 目录下。 + +如果项目已经打包上线了,这个时候我们又如何来设置Java系统属性和命令行参数呢? + +```java +java -Dserver.port=9000 -jar XXXXX.jar --server.port=10010 +``` + +在这个例子中,由于命令行参数的优先级高于 Java 系统属性,最终生效的 `server.port` 是 **10010**。 + + + +### Bean 的获取和管理 + +#### 获取Bean + +**1.自动装配(@Autowired)** + +```java +@Service +public class MyService { + @Autowired + private MyRepository myRepository; // 自动注入 MyRepository Bean +} +``` + +**2.手动获取(ApplicationContext)** + +`@Autowired` 自动将 Spring 创建的 `ApplicationContext` 注入到 `applicationContext` 字段中,这样你就可以通过该对象访问容器中的所有 Bean。 + +Spring 会默认采用类名并将首字母小写作为 Bean 的名称。例如,类名为 `DeptController` 的组件默认名称就是 `deptController`。 + +```java +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringbootWebConfig2ApplicationTests { + @Autowired + private ApplicationContext applicationContext; // IoC 容器 + + @Test + public void testGetBean() { + // 根据 Bean 名称获取 + DeptController bean = (DeptController) applicationContext.getBean("deptController"); + System.out.println(bean); + } +} +``` + +默认情况下,Spring 在容器启动时会创建所有单例 Bean(饿汉模式);使用 `@Lazy` 注解则可实现延迟加载(懒汉模式) + + + +#### bean的作用域 + +| **作用域** | **说明** | +| ---------- | -------------------------------------------------- | +| singleton | 容器内同名称的bean**只有一个实例**(单例)(默认) | +| prototype | 每次使用该bean时会创建新的实例(非单例) | + +在设计单例类时,通常要求它们是**无状态的**,不仅要确保成员变量不可变,还需要确保成员方法不会对共享的、可变的状态进行不受控制的修改,从而实现整体的线程安全。 + +```java +@Service +public class CalculationService { + // 不可变的成员变量 + private final double factor = 2.0; + + // 成员方法仅依赖方法参数和不可变成员变量 + public double multiply(double value) { + return value * factor; + } +} +``` + +**更改作用域方法:** + +在bean类上加注解`@Scope("prototype")`(或其他作用域标识)即可。 + + + +#### 第三方 Bean + +- 如果要**管理的bean对象来自于第三方**(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到**@Bean**注解。 +- 如果需要定义第三方Bean时, 通常会单独定义一个**配置类** + +```java +@Configuration // 配置类 +public class CommonConfig { + // 定义第三方 Bean,并交给 IoC 容器管理 + @Bean // 返回值默认作为 Bean,Bean 名称默认为方法名 + public SAXReader reader(DeptService deptService) { + System.out.println(deptService); + return new SAXReader(); + } +} +``` + +在应用启动时,Spring 会调用配置类中标注 `@Bean` 的方法,将方法返回值注册为容器中的 Bean 对象。 + +默认情况下,该 Bean 的名称就是**该方法的名字**。本例 Bean 名称默认就是 `"reader"`。 + + + +### SpirngBoot原理 + +#### **起步依赖** + +Spring Boot 只需要引入一个起步依赖(例如 `springboot-starter-web`)就能满足项目开发需求。这是因为: + +- **Maven 依赖传递:** + 起步依赖内部已经包含了开发所需的常见依赖(如 JSON 解析、Web、WebMVC、Tomcat 等),无需开发者手动引入其它依赖。 +- **结论:** + 起步依赖的核心原理就是 Maven 的依赖传递机制。 + + + +#### **自动配置** + +Spring Boot 会自动扫描启动类**所在包及其子包中**的所有带有组件注解(如 `@Component`, `@Service`, `@Repository`, `@Controller`, `@Mapper` 等)的类并加载到IOC容器中。 + +![image-20250326164249514](https://pic.bitday.top/i/2025/03/26/r5wloq-0.png) + +自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是: + +- @SpringBootConfiguration + - 声明当前类是一个配置类 +- @**ComponentScan** + - 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包) +- @EnableAutoConfiguration + - 内部使用 `@Import` 导入一个 `ImportSelector` 实现类,该实现类在其 `selectImports()` 方法中读取依赖 jar 包中 META-INF 下的配置文件(如 `spring.factories`),获取自动配置类列表。最终,通过 `@Import` 将这些配置类加载到 Spring 的 IoC 容器中。 + + + +**自动配置类中的 Bean 加载:** + +- 自动配置类定义的所有 Bean **不一定**全部加载到容器中。 +- 通常会配合使用以 `@Conditional` 开头的条件注解,根据环境或依赖条件决定是否装配对应的 Bean,从而实现有选择的配置。 + + + +**如何让第三方bean以及配置类生效?** + +如果配置类(如 `CommonConfig`)不在 Spring Boot 启动类的扫描路径内(即不在启动类所在包或其子包下),那么就需要通过 `@Import` 手动导入该配置类。如: + +```java +com + └── example + └── SpringBootApplication.java // 启动类 +src + └── com + └── config + └── CommonConfig.java // 配置类 +``` + +借助 `@Import` 注解,我们可以将外部的普通类、配置类或实现了 `ImportSelector` 的类**显式导入**到 Spring 容器中。 + +**1.使用@Import导入普通类:** + +如果某个普通类(如 `TokenParser`)没有 `@Component` 注解标识,也可以通过 `@Import` 导入它,使其成为 Spring 管理的 Bean。 + +```java +// TokenParser 类没有 @Component 注解 +public class TokenParser { + public void parse(){ + System.out.println("TokenParser ... parse ..."); + } +} +``` + +在启动类上使用 `@Import` 导入: + +```java +@Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中 +@SpringBootApplication +public class SpringbootWebConfig2Application { + public static void main(String[] args) { + SpringApplication.run(SpringbootWebConfig2Application.class, args); + } +} +``` + +**2.使用@Import导入配置类:** + +配置类中可以定义多个 Bean,通过 `@Configuration` 和 `@Bean` 注解实现集中管理。 + +~~~java +@Configuration +public class HeaderConfig { + @Bean + public HeaderParser headerParser(){ + return new HeaderParser(); + } + @Bean + public HeaderGenerator headerGenerator(){ + return new HeaderGenerator(); + } +} +~~~ + +启动类导入配置类: + +~~~java +@Import(HeaderConfig.class) //导入配置类 +@SpringBootApplication +public class SpringbootWebConfig2Application { + public static void main(String[] args) { + SpringApplication.run(SpringbootWebConfig2Application.class, args); + } +} +~~~ + +**3.使用第三方依赖@EnableXxxx 注解** + +常见方案是第三方依赖提供一个 `@EnableXxxx` 注解,这个注解内部封装了 `@Import`,通过它可以一次性导入多个配置或 Bean。 + +~~~java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类 +public @interface EnableHeaderConfig { +} +~~~ + +在应用启动类上添加第三方依赖提供的 `@EnableHeaderConfig` 注解,即可导入相关的配置和 Bean。 + +```java +@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解 +@SpringBootApplication +public class SpringbootWebConfig2Application { + public static void main(String[] args) { + SpringApplication.run(SpringbootWebConfig2Application.class, args); + } +} +``` + +推荐第三种方式! + + + ## 常见的注解!! 1. `@RequestMapping("/jsonParam")`:可以用于**控制器级别**,也可以用于**方法级别**。 用于方法:HTTP 请求路径为 `/jsonParam` 的请求将调用该方法。 - ```text + ```java @RequestMapping("/jsonParam") public String jsonParam(@RequestBody User user){ System.out.println(user); @@ -872,7 +1170,7 @@ Component衍生注解 用于控制器: 所有方法的映射路径都会以这个前缀开始。 - ``` + ```java @RestController @RequestMapping("/depts") public class DeptController { @@ -895,7 +1193,7 @@ Component衍生注解 4. `@PathVariable` 注解用于将路径变量 `{id}` 的值绑定到方法的参数 `id` 上。当请求的路径是 "/path/123" 时,`@PathVariable` 会将路径中的 "123" 值绑定到方法的参数 `id` 上,使得方法能够获取到这个值。在这个例子中,方法的参数 `id` 的值将会是整数值 123。 - ```text + ```java public String pathParam(@PathVariable Integer id) { System.out.println(id); return "OK"; @@ -909,11 +1207,11 @@ Component衍生注解 5. `@RequestParam`,如果方法的参数名与请求参数名不同,需要在 `@RequestParam` 注解中指定请求参数的名字。 - ```text + ```java @RequestParam(defaultValue = "1" Integer page) //若page为null,可以设置page的默认值为1 ``` - ```text + ```java @RequestMapping("/example") public String exampleMethod(@RequestParam String name, @RequestParam("age") int userAge) { // 在方法内部使用获取到的参数值进行处理 @@ -951,7 +1249,7 @@ Component衍生注解 | @AllArgsConstructor | 为实体类生成除了static修饰的字段之外带有各参数的构造器方法。 | | **@Slf4j** | 可以log.info("输出日志信息"); | - ```text + ```java //equals 方法用于比较两个对象的内容是否相同 Address addr1 = new Address("SomeProvince", "SomeCity"); Address addr2 = new Address("SomeProvince", "SomeCity"); @@ -987,7 +1285,7 @@ Component衍生注解 2. 在springboot项目中,可以编写main/resources/application.properties文件,配置数据库连接信息。 -```text +```java #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url @@ -1008,7 +1306,7 @@ spring.datasource.password=1234 @Select注解:代表的就是select查询,用于书写select查询语句 -```text +```java @Mapper public interface UserMapper { //查询所有用户数据 @@ -1044,7 +1342,7 @@ Lombok是一个实用的Java类库,可以通过简单的注解来简化和消 **使用** -```text +```java import lombok.Data; @Data @@ -1065,7 +1363,7 @@ public class User { 2. 开启mybatis的日志,并指定输出到控制台 -```text +```java #指定mybatis输出日志的位置, 输出控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl ``` @@ -1080,7 +1378,7 @@ mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl **作用于单个字段** -```text +```java @Mapper public interface EmpMapper { //SQL语句中的id值不能写成固定数值,需要变为动态的数值 @@ -1102,7 +1400,7 @@ public interface EmpMapper { **作用于多个字段** -```text +```java @Mapper public interface EmpMapper { //会自动将生成的主键值,赋值给emp对象的id属性 @@ -1141,7 +1439,7 @@ useGeneratedKeys = true表示获取返回的主键值,keyProperty = "id"表示 > - 表中字段名:abc_xyz >- 类中属性名:abcXyz -```properties +```java # 在application.properties中添加: mybatis.configuration.map-underscore-to-camel-case=true ``` @@ -1161,7 +1459,7 @@ eg:通过页面原型以及需求描述我们要实现的查询: 解决方案: -```text +```java @Mapper public interface EmpMapper { @@ -1253,7 +1551,7 @@ public interface EmpMapper { -```text +```java select * from employee @@ -1437,7 +1735,7 @@ public class EmpController { > - byte[] getBytes(); //获取文件内容的字节数组 > - InputStream getInputStream(); //获取接收到的文件内容的输入流 -```text +```java @Slf4j @RestController public class UploadController { @@ -1466,7 +1764,7 @@ public class UploadController { 那么如果需要上传大文件,可以在application.properties进行如下配置: -```text +```java #配置单个文件最大上传大小 spring.servlet.multipart.max-file-size=10MB @@ -1482,7 +1780,7 @@ spring.servlet.multipart.max-request-size=100MB pom文件中添加如下依赖: -```plaintext +```java com.aliyun.oss aliyun-sdk-oss @@ -1510,7 +1808,7 @@ pom文件中添加如下依赖: 上传文件的工具类 -```text +```java package edu.whut.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; @@ -1593,7 +1891,7 @@ public class AliOSSUtils { 对象/map集合 -```text +```java user: name: zhangsan age: 18 @@ -1602,7 +1900,7 @@ user: 数组/List/Set集合 -```text +```java hobby: - java - game @@ -1615,7 +1913,7 @@ hobby: 前面获取配置项中的属性值,需要通过@Value注解,有时过于繁琐!!! -```text +```java @Component public class AliOSSUtils { @@ -1649,7 +1947,7 @@ Spring提供的简化方式套路: 4. (可选)引入依赖pom.xml -```text +```java org.springframework.boot spring-boot-configuration-processor @@ -1713,7 +2011,7 @@ Spring提供的简化方式套路: 引入依赖 -```text +```java io.jsonwebtoken jjwt @@ -1723,7 +2021,7 @@ Spring提供的简化方式套路: 生成与解析: -```text +```java public class JwtUtils { private static String signKey = "itheima"; @@ -1766,7 +2064,7 @@ public class JwtUtils { **令牌可以存储当前登录用户的信息:id、username等等,传入claims** -```text +```java Map claims = new HashMap<>(); claims.put("id",emp.getId()); claims.put("name",e.getName()); @@ -1776,7 +2074,7 @@ String jwt=JwtUtils.generateJwt(claims); **解析令牌:** -```text +```java @Autowired private HttpServletRequest request; @@ -1804,7 +2102,7 @@ private HttpServletRequest request; 1. **定义拦截器,实现HandlerInterceptor接口,并重写其所有方法** -```text +```java //自定义拦截器 @Component public class LoginCheckInterceptor implements HandlerInterceptor { @@ -1840,7 +2138,7 @@ public class LoginCheckInterceptor implements HandlerInterceptor { 2. **注册配置拦截器,实现WebMvcConfigurer接口,并重写addInterceptors方法** -```text +```java @Configuration public class WebConfig implements WebMvcConfigurer { @@ -1874,7 +2172,7 @@ addPathPatterns指定拦截路径; 主要在preHandle中写逻辑 -```text +```java @Override //目标资源方法执行前执行。 返回true:放行 返回false:不放行 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle .... "); @@ -1939,7 +2237,7 @@ addPathPatterns指定拦截路径; - 定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解**@RestControllerAdvice**,加上这个注解就代表我们定义了一个全局异常处理器。 - 在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解**@ExceptionHandler**。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。 -```text +```java @RestControllerAdvice public class GlobalExceptionHandler { @@ -1959,7 +2257,7 @@ public class GlobalExceptionHandler { ### Spring事务日志开关 -```yml +```java logging: level: org.springframework.jdbc.support.JdbcTransactionManager: debug @@ -1998,7 +2296,7 @@ logging: 在@Transactional注解的后面指定一个属性**propagation**,通过 propagation 属性来指定传播行为。可以在嵌套的子事务上加入。 -```text +```java @Transactional(propagation = Propagation.REQUIRES_NEW) ``` @@ -2025,7 +2323,7 @@ AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方 1. 导入依赖:在pom.xml中导入AOP的依赖 -```text +```java org.springframework.boot spring-boot-starter-aop @@ -2034,7 +2332,7 @@ AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方 2. 编写AOP程序:针对于特定方法根据业务需要进行编程 -```text +```java @Component @Aspect //当前类为切面类 @Slf4j @@ -2136,7 +2434,7 @@ eg:@Order(1) execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为: -```text +```java execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?) ``` @@ -2150,7 +2448,7 @@ execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) th eg: -```text +```java @Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))") ``` @@ -2160,7 +2458,7 @@ eg: - `*` :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、**任意类型的一个参数**,也可以通配包、类、方法名的一部分 -```text +```java execution(* edu.*.service.*.update*(*)) ``` @@ -2178,7 +2476,7 @@ execution(* edu.*.service.*.update*(*)) 1. **新建anno包,在这个包下**编写自定义注解 -```text +```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -2197,7 +2495,7 @@ public @interface MyLog { 2. 在业务类要做为连接点的**方法上添加**自定义注解 -```text +```java @MyLog //自定义注解(表示:当前方法属于目标方法) public void delete(Integer id) { //1. 删除部门 @@ -2209,7 +2507,7 @@ public @interface MyLog { 3. aop切面类上使用类似如下的切面表达式: -```text +```java @Before("@annotation(edu.whut.anno.MyLog)") ``` @@ -2231,257 +2529,6 @@ Object[] args = joinPoint.**getArgs()**; 可以获取调用方法时传递的 -## SpringBoot原理 - -### 容器启动 - -在 Spring 框架的上下文中,提到的“容器启动”通常指的是 Spring 应用上下文(ApplicationContext)的初始化和启动过程。这个过程涉及到多个关键步骤,其中包括配置解析、Bean 定义的加载、Bean 的实例化和初始化以及依赖注入等。具体来说,容器启动的时机包括以下几个关键点: - -#### 1. 应用启动时 - -当你启动一个 Spring 应用时,无论是通过直接运行一个包含 `main` 方法的类,还是部署到一个 Servlet 容器中,Spring 的应用上下文都会被创建和初始化。这个过程包括: - -- **读取配置**:加载配置文件或注解指定的配置信息,这些配置指定了哪些组件需要被 Spring 管理。 -- **Bean 定义的注册**:Spring 将在配置中找到的所有 Bean 定义加载到容器中。 -- **Bean 的实例化**:Spring 根据 Bean 的定义创建实例。默认情况下(非懒加载),所有的单例 Bean 在容器启动时即被创建。 -- **依赖注入**:Spring 解析 Bean 之间的依赖关系,并将相应的依赖注入到 Bean 中。 - - - -### 配置优先级 - -在SpringBoot项目当中,常见的属性配置方式有5种, 3种配置文件,加上2种外部属性的配置(Java系统属性、命令行参数)。通过以上的测试,我们也得出了优先级(从低到高): - -- application.yaml(忽略) -- application.yml -- application.properties -- java系统属性(-Dxxx=xxx) -- 命令行参数(--xxx=xxx) - -如果项目已经打包上线了,这个时候我们又如何来设置Java系统属性和命令行参数呢? - -```text -java -Dserver.port=9000 -jar XXXXX.jar --server.port=10010 -``` - - - -### Bean - -#### 获取bean - -**如何从Spring IoC容器中获取Bean** - -- **@Autowired注解**:最常见的方式是使用@Autowired注解自动装配Bean。Spring会自动在其容器中查找匹配类型的Bean并注入到被@Autowired标注的字段或方法中。 - -```text -@Service -public class MyService { - @Autowired - private MyRepository myRepository; // 自动装配MyRepository Bean -} - -``` - -- **ApplicationContext获取**:你也可以通过Spring的ApplicationContext来手动获取Bean。ApplicationContext是Spring的IoC容器,通过它你可以访问容器中的任何Bean。 - -```java -class SpringbootWebConfig2ApplicationTests { - @Autowired - private ApplicationContext applicationContext; //IOC容器对象 - //获取bean对象 - @Test - public void testGetBean(){ - //根据bean的名称获取 - DeptController bean1 = (DeptController) applicationContext.getBean("deptController"); - System.out.println(bean1); - } -} -``` - -默认是饿汉模式,通过依赖注入设置的类,会在容器**启动时自动初始化**,除非设置了@Lazy注解,懒汉模式,第一次使用bean对象时,才会创建bean对象并交给ioc容器管理。 - -#### bean的作用域 - -| **作用域** | **说明** | -| ---------- | ---------------------------------------------- | -| singleton | 容器内同名称的bean只有一个实例(单例)(默认) | -| prototype | 每次使用该bean时会创建新的实例(非单例) | - -**使用方法:** - -在bean类上加注解 - -@Scope("prototype") - - - -#### 第三方bean - -那么我们应该怎样使用并定义第三方的bean呢? - -- 如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到**@Bean**注解。 -- 如果需要定义第三方Bean时, 通常会单独定义一个**配置类** - -```text -@Configuration //配置类 (在配置类当中对第三方bean进行集中的配置管理) -public class CommonConfig { - - //声明第三方bean - @Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean - //通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名 - public SAXReader reader(DeptService deptService){ - System.out.println(deptService); - return new SAXReader(); - } -} -``` - -在**方法上**加上一个@Bean注解,Spring 容器在启动的时候,它会自动的调用这个方法,并将方法的返回值声明为Spring容器当中的Bean对象。 - - - -### SpirngBoot原理 - -#### 起步依赖 - -如果我们使用了SpringBoot,我们只需要引入一个依赖就可以了,那就是web开发的起步依赖:springboot-starter-web。 - -为什么我们只需要引入一个web开发的起步依赖? - -- 因为Maven的依赖传递。 - -> - 在SpringBoot给我们提供的这些起步依赖当中,已提供了当前程序开发所需要的所有的常见依赖(官网地址:https://docs.spring.io/spring-boot/docs/2.7.7/reference/htmlsingle/#using.build-systems.starters)。 -> -> - 比如:springboot-starter-web,这是web开发的起步依赖,在web开发的起步依赖当中,就集成了web开发中常见的依赖:json、web、webmvc、tomcat等。我们只需要引入这一个起步依赖,其他的依赖都会自动的通过Maven的依赖传递进来。 - -**结论:起步依赖的原理就是Maven的依赖传递。** - - - -#### 自动配置 - -SpringBoot的自动配置就是当Spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明。 - -**如何让第三方bean以及配置类生效?** - -@Import导入 - -- 导入形式主要有以下几种: - 1. 导入普通类 - 2. 导入配置类 - 3. 导入ImportSelector接口实现类 - - - -**导入普通类:** - -```text -@Component -public class TokenParser { - - public void parse(){ - System.out.println("TokenParser ... parse ..."); - } - -} -``` - -```text -@Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中 -@SpringBootApplication -public class SpringbootWebConfig2Application { - public static void main(String[] args) { - SpringApplication.run(SpringbootWebConfig2Application.class, args); - } -} -``` - -**导入配置类:** - -- 配置类 - -~~~java -@Configuration -public class HeaderConfig { - @Bean - public HeaderParser headerParser(){ - return new HeaderParser(); - } - - @Bean - public HeaderGenerator headerGenerator(){ - return new HeaderGenerator(); - } -} -~~~ - -- 启动类 - -~~~java -@Import(HeaderConfig.class) //导入配置类 -@SpringBootApplication -public class SpringbootWebConfig2Application { - public static void main(String[] args) { - SpringApplication.run(SpringbootWebConfig2Application.class, args); - } -} -~~~ - - - -怎么让**第三方依赖**自己指定bean对象和配置类? - -- 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以@EnableXxxx开头的注解,**注解中封装的就是@Import注解** - - 使用第三方依赖提供的 @EnableXxxxx注解 - -- 第三方依赖中提供的注解 - -~~~java -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类 -public @interface EnableHeaderConfig { -} -~~~ - -- 在使用时只需在启动类上加上@EnableXxxxx注解即可 - -```text -@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解 -@SpringBootApplication -public class SpringbootWebConfig2Application { - public static void main(String[] args) { - SpringApplication.run(SpringbootWebConfig2Application.class, args); - } -} -``` - - - - - -**Springboot自动配置** - -自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是: - -- @SpringBootConfiguration - - 声明当前类是一个配置类 -- @ComponentScan - - 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包) -- @EnableAutoConfiguration - - 封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类) - - 在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-INF/spring.factories、META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。 - - 当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。 - -**那么所有自动配置类的中声明的bean都会加载到Spring的IOC容器中吗?** - -其实并不会,因为这些配置类中在声明bean时,通常都会添加@Conditional开头的注解,这个注解就是进行条件装配。而Spring会根据Conditional注解有选择性的进行bean的创建。 - - - ## WEB开发总体图 ![image-20240326111230747](https://pic.bitday.top/i/2025/03/19/u6mzqw-2.png) diff --git a/自学/Java笔记本.md b/自学/Java笔记本.md index bcad734..0eae7a6 100644 --- a/自学/Java笔记本.md +++ b/自学/Java笔记本.md @@ -81,13 +81,13 @@ IDEA快捷键: 2. 在 `System.out.println()` 方法中,"ln" 代表 "line",表示换行。因此,`println` 实际上是 "print line" 的缩写。这个方法会在输出文本后自动换行. - ```text + ```java System.out.println("nihao "+1.3331); #Java 会自动将数值转换为字符串 ``` 当直接打印一个没有重写 `toString()` 方法的对象时,Java 默认会调用 `Object` 类的 `toString()` 方法,其输出格式通常为: - ```text + ```java java.lang.Object@15db9742 ``` @@ -95,7 +95,7 @@ IDEA快捷键: 当打印重写`toString()` 方法的对象时: - ```text + ```java class Person { private String name; private int age; @@ -120,7 +120,7 @@ IDEA快捷键: ``` - ```text + ```java Person{name='Alice', age=30} ``` @@ -128,7 +128,7 @@ IDEA快捷键: 3. 一维数组创建: - ```text + ```java // 方式1:先声明,再指定长度(默认值为0、null等) int[] arr1 = new int[10]; // 创建一个长度为10的int数组 @@ -139,14 +139,13 @@ IDEA快捷键: // 方式3:结合new关键字和初始化列表创建数组(常用于明确指定类型时) int[] arr3 = new int[]{1, 2, 3, 4, 5}; // 与方式2效果相同 - - - ``` + + 4. 字符串创建 - ```text + ```java String str = "Hello, World!"; //(1)直接赋值 String str = new String("Hello, World!"); //使用 new 关键字 @@ -155,7 +154,71 @@ IDEA快捷键: String str = new String(charArray); //通过字符数组创建 ``` - + + + +#### Java传参方式 + +基本数据类型(Primitives) + +- 传递方式:按值传递 + 每次传递的是变量的值的副本,对该值的修改不会影响原变量。例如:`int`、`double`、`boolean` 等类型。 + +引用类型(对象) + +- 传递方式:对象引用的副本传递 + 传递的是对象引用的一个副本,指向同一块内存区域。因此,方法内部通过该引用修改对象的状态,会影响到原对象。如数组、集合、String、以及其他所有对象类型。 + + + +**浅拷贝** + +拷贝对象本身,但内部成员(例如集合中的元素)只是复制引用,新旧对象的内部成员指向同一份内存。如果内部元素是不可变的(如 Integer、String 等),这种拷贝通常足够。如果元素是可变对象,修改其中一个对象**可能会影响**另一个。 + +```java +List list = new ArrayList<>(); +list.add(1); +list.add(2); +list.add(3); + +// 浅拷贝:新列表中的元素引用和原列表中的是同一份 +List shallowCopy = new ArrayList<>(list); +``` + +可变对象,浅拷贝修改对象会出错! + +```java +List list = new ArrayList<>(); +list.add(new Box(1)); +list.add(new Box(2)); +list.add(new Box(3)); + +List shallowCopy = new ArrayList<>(list); +shallowCopy.get(0).value = 10; // 修改 shallowCopy 中第一个 Box 的 value + +System.out.println(list); // 输出: [10, 2, 3],因为同一 Box 对象被修改 +System.out.println(shallowCopy); // 输出: [10, 2, 3] +``` + + + +**深拷贝** + +不仅复制对象本身,还递归地复制其所有内部成员,从而生成一个完全独立的副本。即使内部元素是可变的,修改新对象也不会影响原始对象。 + +```java +// 深拷贝 List 的例子 +List originalList = new ArrayList<>(); +originalList.add(new MyObject(10)); +originalList.add(new MyObject(20)); + +List deepCopy = new ArrayList<>(); +for (MyObject obj : originalList) { + deepCopy.add(new MyObject(obj)); // 每个元素都创建一个新的对象 +} +``` + + #### 日期 @@ -196,7 +259,7 @@ LocalDateTime.now(),获取当前时间 -```text +```java // 文件:com/example/PrivateExample.java package com.example; @@ -215,7 +278,7 @@ public class PrivateExample { 修饰符不仅可以用来修饰成员变量和方法,也可以用来**修饰类**。顶级类只能使用 `public` 或默认(即不写任何修饰符,称为包访问权限)。内部类可以使用所有访问修饰符(`public`、`protected`、`private` 和默认),这使得你可以更灵活地控制嵌套类的访问范围。 -```text +```java public class OuterClass { // 内部类使用private,只能在OuterClass内部访问 private class InnerPrivateClass { @@ -258,7 +321,7 @@ public class OuterClass { 用途:适用于内部类与外部类关系密切,需要频繁访问外部类成员的情况。 -```text +```java public class OuterClass { class InnerClass implements Runnable { // static int count = 0; // 编译错误 @@ -294,7 +357,7 @@ public class OuterClass { 用途:适用于只在方法或代码块中使用的类,有助于将实现细节隐藏在方法内部。 -```text +```java public class OuterClass { public void startThread() { class LocalInnerClass implements Runnable { @@ -327,7 +390,7 @@ public class OuterClass { 用途:适合当内部类工作不依赖外部类实例时使用,常用于实现与外部类关系不那么密切的帮助类。 -```text +```java public class OuterClass { // 外部类的静态成员 private static int staticVar = 10; @@ -371,7 +434,7 @@ public class OuterClass { 用途:适用于创建一次性使用的实例,通常用于接口或抽象类的实现。 -```text +```java //eg1 public class OuterClass { public static void main(String[] args) { @@ -413,7 +476,7 @@ public class GUIApp { 1. 创建`ActionListener`实例 -```text +```java new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("Button was clicked!"); @@ -432,7 +495,7 @@ new ActionListener() { 2. 将匿名内部类添加为事件监听器 -```text +```java button.addActionListener(...); ``` @@ -446,7 +509,7 @@ Lambda表达式特别适用于**只有单一抽象方法**的接口(也即** **`@FunctionalInterface` 注解**:这是一个可选的注解,用于表示接口是一个函数式接口。虽然不是强制的,但它可以帮助编译器识别意图,并检查接口是否确实只有一个抽象方法。 -```text +```java public class LambdaExample { // 定义函数式接口,doSomething 有两个参数 @FunctionalInterface @@ -479,7 +542,7 @@ public class LambdaExample { 可选的大括号:如果主体只有一个语句,可以不使用大括号。 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,使用大括号需显示retrun;如果函数是void则不需要返回值。 -```text +```java // 定义一个函数式接口 interface Calculator { int add(int a, int b); @@ -507,7 +570,7 @@ public class LambdaReturnExample { `list.forEach`这个方法接受一个**函数式接口**作为参数。它只有一个抽象方法 `accept(T t)`因此,可以使用 lambda 表达式来**实现**。 -```text +```java import java.util.Arrays; import java.util.List; @@ -536,7 +599,7 @@ public class Main { ​ 如果返回正数,则 `a` 应该排在 `b` 的后面。 -```text +```java import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -564,7 +627,7 @@ public class Main { **1.静态初始化块(Static Initialization Block)** -```text +```java public class MyClass { static int num1, num2; @@ -590,7 +653,7 @@ public class MyClass { 输出: -```text +```java 静态代码块1执行 静态代码块2执行 main方法执行 @@ -603,7 +666,7 @@ main方法执行 **2.在声明时直接初始化** -```text +```java public class MyClass { // 直接在声明时初始化静态成员变量 public static int staticVariable = 42; @@ -614,7 +677,7 @@ public class MyClass { 静态成员变量的访问不需要创建 `MyClass` 的实例,可以直接通过类名访问: -```text +```java int value = MyClass.staticVariable; MyClass obj = new MyClass(); System.out.println("obj.num1 = " + obj.staticVariable); #通过示例访问也可以 @@ -638,7 +701,7 @@ System.out.println("obj.num1 = " + obj.staticVariable); #通过示例访问也 - 非静态成员变量。 - 非静态方法(必须通过对象实例访问)。 -```text +```java public class MyClass { private static int staticVar = 10; private int instanceVar = 20; @@ -664,7 +727,7 @@ public class MyClass { 调用静态方法: -```text +```java MyClass.staticMethod(); // 通过类名直接调用静态方法 ``` @@ -680,7 +743,7 @@ MyClass.staticMethod(); // 通过类名直接调用静态方法 因为父类的成员变量和方法都是默认的访问修饰符,可以继承给子类,而子类也定义了同名的xxx,发生了**变量隐藏**(shadowing)。 - ```text + ```java class Parent { int num = 10; void display() { @@ -711,7 +774,7 @@ MyClass.staticMethod(); // 通过类名直接调用静态方法 输出: - ```text + ```java Child class num: 20 Parent class num: 10 Parent class method @@ -721,7 +784,7 @@ MyClass.staticMethod(); // 通过类名直接调用静态方法 可以使用 `super` 关键字调用父类的构造方法。这通常在子类的构造方法中使用,用于显式地调用父类的构造方法。 -```text +```java class Parent { Parent() { System.out.println("Parent class constructor"); @@ -745,7 +808,7 @@ public class Main { 输出: -```text +```java Parent class constructor Child class constructor ``` @@ -771,7 +834,7 @@ final 关键字,意思是最终的、不可修改的,最见不得变化 , 3. **数据类型**:变量的数据类型,如int、String、class等。 4. **变量名**:变量的名称。 -```text +```java public static final int MAX_COUNT = 100; #定义常量 protected static volatile int counter; #定义成员变量 ``` @@ -790,7 +853,7 @@ protected static volatile int counter; #定义成员变量 **继承** -```text +```java [修饰符] class 子类名 extends 父类名{ 类体部分 } @@ -820,7 +883,7 @@ Java继承了父类**非私有**的成员变量和成员方法,但是请注意 2. **向上转型(Upcasting)**:动态多态;子类对象可以赋值给父类引用,这样做可以隐藏对象的真实类型,只能调用**父类中声明的方法**。 - ```text + ```java class Animal { public void makeSound() { System.out.println("Animal makes sound"); @@ -856,7 +919,7 @@ Java继承了父类**非私有**的成员变量和成员方法,但是请注意 - **重载**发生在同一个类中,与继承无关; - **重写**发生在子类中,依赖继承关系,实现运行时多态。 -```text +```java class Calculator { int add(int a, int b) { return a + b; @@ -879,7 +942,7 @@ class Calculator { **必须实现抽象方法** 如果一个子类继承了抽象类,通常必须实现抽象类中的所有抽象方法,否则该子类也必须声明为抽象类。例如: -```text +```java abstract class Animal { // 抽象方法,没有方法体 public abstract void makeSound(); @@ -908,7 +971,7 @@ class Dog extends Animal { 1. **定义一个新的子类** 创建一个子类继承抽象类并实现所有抽象方法,然后使用子类实例化对象: - ```text + ```java Animal animal = new Dog(); animal.makeSound(); // 输出:Dog barks ``` @@ -916,7 +979,7 @@ class Dog extends Animal { 2. **使用匿名内部类** 使用匿名内部类实现抽象类相当于临时创建了一个**未命名的子类**,并且立即实例化了这个子类的对象。 - ```text + ```java Animal animal = new Animal() { @Override public void makeSound() { @@ -956,7 +1019,7 @@ class Dog extends Animal { - 类可以实现多个接口(多继承)。 - 类只能继承一个抽象类(单继承)。 -```text +```java // 定义接口 interface Flyable { void fly(); @@ -1009,7 +1072,7 @@ public class Main { - `void clear()`:移除集合中的所有元素。 - `boolean isEmpty()`:如果集合为空,则返回 `true`。 -```text +```java import java.util.ArrayList; import java.util.Collection; @@ -1051,7 +1114,7 @@ public class CollectionExample { 2. `next()`:返回迭代器的下一个元素,并将迭代器移动到下一个位置。 3. `remove()`:从迭代器当前位置删除元素。该方法是可选的,不是所有的迭代器都支持。 -```text +```java import java.util.ArrayList; import java.util.Iterator; @@ -1093,7 +1156,7 @@ public class Main { ![image-20240227152019078](https://pic.bitday.top/i/2025/03/19/u6utmg-2.png) -```text +```java // 使用 entrySet() 方法获取 Map 中所有键值对的集合,并使用增强型 for 循环遍历键值对 System.out.println("Entries in the map:"); for (Map.Entry entry : map.entrySet()) { @@ -1109,7 +1172,7 @@ public class Main { 默认是小根堆,输出1,2,5,8 -```text +```java import java.util.PriorityQueue; public class Main { @@ -1140,7 +1203,7 @@ public class Main { ### JAVA异常处理 -```text +```java public class ExceptionExample { // 方法声明中添加 throws 关键字,指定可能抛出的异常类型 public static void main(String[] args) throws SomeException, AnotherException { @@ -1181,7 +1244,7 @@ Arrays.toString() 作用:方便地输出数组。 这个方法是是用来将数组转换成String类型输出的,入参可以是long,float,double,int,boolean,byte,object 型的数组。 -```text +```java import java.util.Arrays; public class Main { @@ -1211,7 +1274,7 @@ public class Main { **类路径**是JVM在运行时用来查找类文件和资源文件的一组目录或JAR包。在许多项目(例如Maven或Gradle项目)中,`src/main/resources`目录下的内容在编译时会被复制到输出目录(如`target/classes`),`src/main/java` 下编译后的 class 文件也会放到这里。 -```text +```java MyProject/ ├── src/ │ └── main/ @@ -1226,7 +1289,7 @@ MyProject/ ``` -```text +```java // 获取 resources 根目录下的 emp.xml 文件路径 String empFile = this.getClass().getClassLoader().getResource("emp.xml").getFile(); @@ -1261,7 +1324,7 @@ String ttImgPath = resourceUrl != null ? resourceUrl.getFile() : null; **1.获取类的字节码(Class对象)**:有三种方法 -```text +```java public class Test1Class{ public static void main(String[] args){ Class c1 = Student.class; @@ -1282,7 +1345,7 @@ public class Test1Class{ **2.获取类的构造器** -```text +```java public class Cat{ private String name; private int age; @@ -1301,7 +1364,7 @@ public class Cat{ - 获取构造器列表 -```text +```java public class TestConstructor { @Test @@ -1386,7 +1449,7 @@ public class Test2Constructor(){ 示例:`Cat` 类与测试类 -```text +```java public class Cat { private String name; public int age; @@ -1406,7 +1469,7 @@ public class Cat { } ``` -```text +```java import org.junit.Test; import java.lang.reflect.Field; @@ -1478,7 +1541,7 @@ public class FieldReflectionTest { ![image-20240307172717512](https://pic.bitday.top/i/2025/03/19/u75u7l-2.png) -```text +```java @Test public void testListUser(){ Listlist=userMapper.list(); @@ -1494,7 +1557,7 @@ public class FieldReflectionTest { 原理可能是: -```text +```java //自定义注解 @Retention(RetentionPolicy.RUNTIME) //指定注解在运行时可用,这样才能通过反射获取到该注解。 @Target(ElementType.METHOD) //指定注解可用于方法上。 @@ -1539,7 +1602,7 @@ public class AnnotationTest4 { 定义: -```text +```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1564,7 +1627,7 @@ public @interface MyAnnotation { **简化使用**:当注解只有一个元素需要设置时,且该元素的名字是`value`,在使用该注解时可以不用显式地指定元素名 -```text +```java @MyAnnotation(5) // 等同于 @MyAnnotation(value = 5) public void someMethod() { // 方法实现 @@ -1576,7 +1639,7 @@ public void someMethod() { 如果要同时设置`description`,则不能省略元素名: -```text +```java @MyAnnotation(value = 5, description = "Specific description") public void anotherMethod() { // 方法实现 @@ -1588,7 +1651,7 @@ public void anotherMethod() { **获得注解上的value:**反射 -```text +```java public class MyClass { @MyAnnotation(value = "specific value") @@ -1601,7 +1664,7 @@ public class MyClass { -```text +```java import java.lang.reflect.Method; public class AnnotationReader { diff --git a/自学/力扣Hot 100题.md b/自学/力扣Hot 100题.md index ca4e56c..9c5cc72 100644 --- a/自学/力扣Hot 100题.md +++ b/自学/力扣Hot 100题.md @@ -15,7 +15,7 @@ - 支持多种类型的数组(如 `int[]`、`char[]`、`Object[]` 等)。 -- ```text +- ```java int[] arr1 = {1, 2, 3}; int[] arr2 = {1, 2, 3}; boolean isEqual = Arrays.equals(arr1, arr2); // true @@ -24,7 +24,7 @@ `Collections` 类本身没有直接提供类似 `Arrays.equals` 的方法来比较两个集合的内容是否相等。不过,Java 中的集合类(如 `List`、`Set`、`Map`)已经实现了 `equals` 方法 -- ```text +- ```java List list1 = Arrays.asList(1, 2, 3); List list2 = Arrays.asList(1, 2, 3); List list3 = Arrays.asList(3, 2, 1); @@ -36,25 +36,20 @@ **逻辑比较** -```text +```java boolean flag = false; - if (!flag) { //! 是 Java 中的逻辑非运算符,只能用于对布尔值取反。 System.out.println("flag 是 false"); } - if (flag == false) { //更常用! System.out.println("flag 是 false"); } - //java中没有 if(not flag) 这种写法! ``` - - ### 常用数据结构 #### `String` @@ -71,7 +66,7 @@ if (flag == false) { //更常用! 需要String先转为char [] 数组,排序好之后再转为String类型!! -```text +```java char[] charArray = str.toCharArray(); Arrays.sort(charArray); String sortedStr = new String(charArray); @@ -92,12 +87,51 @@ String sortedStr = new String(charArray); -#### **`HashMap`** +#### `StringBuffer` + +`StringBuffer` 是 Java 中用于操作可变字符串的类 + +**append** + +```java +public class StringBufferExample { + public static void main(String[] args) { + // 创建初始字符串 "Hello" + StringBuffer sb = new StringBuffer("Hello"); + System.out.println("Initial: " + sb.toString()); // 输出 "Hello" + + // 1. append:在末尾追加 " World" + sb.append(" World"); + System.out.println("After append: " + sb.toString()); // 输出 "Hello World" + + // 2. insert:在索引 5 位置("Hello"后)插入 ", Java" + sb.insert(5, ", Java"); + System.out.println("After insert: " + sb.toString()); // 输出 "Hello, Java World" + + // 3. delete:删除从索引 5 到索引 11(不包含)的子字符串(即删除刚才插入的 ", Java") + sb.delete(5, 11); + //sb.delete(5, sb.length()); 删除到末尾 + System.out.println("After delete: " + sb.toString()); // 输出 "Hello World" + + // 4. deleteCharAt:删除索引 5 处的字符(删除空格) + sb.deleteCharAt(5); + System.out.println("After deleteCharAt: " + sb.toString()); // 输出 "HelloWorld" + + // 5. reverse:反转整个字符串 + sb.reverse(); + System.out.println("After reverse: " + sb.toString()); // 输出 "dlroWolleH" + } +} +``` + + + +#### `HashMap` - 基于哈希表实现,查找、插入和删除的平均时间复杂度为 O(1)。 - 不保证元素的顺序。 -```text +```java import java.util.HashMap; import java.util.Map; @@ -137,7 +171,7 @@ public class HashMapExample { 记录二维数组中某元素是否被访问过,推荐使用: -```text +```java int m = grid.length; int n = grid[0].length; boolean[][] visited = new boolean[m][n]; @@ -150,13 +184,13 @@ visited[i][j] = true; -#### **`HashSet`** +#### `HashSet` - 基于哈希表实现,查找、插入和删除的平均时间复杂度为 O(1)。 - 不保证元素的顺序!!因此不太用iterator迭代,而是用contains判断是否有xx元素。 -```text +```java import java.util.HashSet; import java.util.Set; @@ -221,7 +255,7 @@ visited[i][j] = true; 6. **`clear()`**: - 清空队列。 -```text +```java import java.util.PriorityQueue; import java.util.Comparator; @@ -281,7 +315,7 @@ public class PriorityQueueExample { - 访问元素的时间复杂度为 O(1),在末尾插入和删除的时间复杂度为 O(1)。 - 在指定位置插入和删除O(n) `add(int index, E element)` `remove(int index)` -```text +```java import java.util.ArrayList; import java.util.List; @@ -324,7 +358,7 @@ public class ArrayListExample { **如果事先不知道嵌套列表的大小如何遍历呢?** -```text +```java import java.util.ArrayList; import java.util.List; @@ -359,7 +393,7 @@ for (int i = 0; i < list.size(); i++) { - **快速访问**:通过索引访问元素的时间复杂度为 O(1)。 - **连续内存**:数组的元素在内存中是连续存储的。 -```text +```java public class ArrayExample { public static void main(String[] args) { // 创建数组 @@ -429,7 +463,7 @@ public class ArrayExample { #### `二维数组` -```text +```java int rows = 3; int cols = 3; int[][] array = new int[rows][cols]; @@ -467,7 +501,7 @@ public void setZeroes(int[][] matrix) { 队尾插入,队头取! -```text +```java import java.util.Queue; import java.util.LinkedList; @@ -508,7 +542,7 @@ public class QueueExample { **栈** -```text +```java Deque stack = new ArrayDeque<>(); stack.push(1); // 入栈 Integer top1=stack.peek() @@ -541,7 +575,7 @@ Integer top = stack.pop(); // 出栈 *删除或查看元素*:调用 `remove()` 或 `poll()` 时,则是调用 `removeFirst()` 或 `pollFirst()`,即在队头移除元素;同理,`element()` 或 `peek()` 则是查看队头元素。 -```text +```java import java.util.Deque; import java.util.LinkedList; @@ -591,7 +625,7 @@ public class DequeExample { 2. `next()`:返回迭代器的下一个元素,并将迭代器移动到下一个位置。 3. `remove()`:从迭代器当前位置删除元素。该方法是可选的,不是所有的迭代器都支持。 -```text +```java import java.util.ArrayList; import java.util.Iterator; @@ -625,7 +659,7 @@ public class Main { #### **数组排序** -```text +```java import java.util.Arrays; public class ArraySortExample { @@ -641,7 +675,7 @@ public class ArraySortExample { #### 集合排序 -```text +```java import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -678,7 +712,7 @@ public class ListSortExample { - 如果返回零,说明 `o1` 等于 `o2`。 - 如果返回正数,说明 `o1` 排在 `o2`后面。 -```text +```java import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -725,7 +759,7 @@ public class ComparatorSortExample { **自定义比较器排序二维数组** 用Lambda表达式实现`Comparator接口` -```text +```java import java.util.Arrays; public class IntervalSort { @@ -771,38 +805,33 @@ public class IntervalSort { #### 双指针 -1. 问题分析: +题型: - - 确定问题是否涉及数组或链表的遍历。 - - 判断是否需要通过两个指针的协作来缩小搜索范围或比较元素。 +- 同向双指针:两个指针从同一侧开始移动,通常用于**滑动窗口**或链表问题。 +- 对向双指针:两个指针从两端向中间移动,通常用于有序数组或回文问题。重点是考虑**移动哪个指针**可能优化结果!!! +- 快慢指针:两个指针以不同速度移动,通常用于链表中的环检测或中点查找。 -2. 选择双指针类型: +适用场景: - - **同向双指针**:两个指针从同一侧开始移动,通常用于**滑动窗口**或链表问题。 - - **对向双指针**:两个指针从两端向中间移动,通常用于有序数组或回文问题。重点是考虑**移动哪个指针**可能优化结果!!! - - **快慢指针**:两个指针以不同速度移动,通常用于链表中的环检测或中点查找。 +**有序数组的两数之和**: -3. 适用场景 +- 在对向双指针的帮助下,可以在 O(n) 时间内找到两个数,使它们的和等于目标值。 - **有序数组的两数之和**: +**滑动窗口**: - - 在对向双指针的帮助下,可以在 O(n) 时间内找到两个数,使它们的和等于目标值。 +- 用于解决**子数组或子字符串**问题,如同向双指针可以在 O(n) 时间内找到满足条件的最短或最长子数组。 - **滑动窗口**: +**链表中的环检测**: - - 用于解决**子数组或子字符串**问题,如同向双指针可以在 O(n) 时间内找到满足条件的最短或最长子数组。 +- 快慢指针可以用于检测链表中是否存在环,并找到环的起点。 - **链表中的环检测**: +**回文问题**: - - 快慢指针可以用于检测链表中是否存在环,并找到环的起点。 +- 对向双指针可以用于判断字符串或数组是否是回文。 - **回文问题**: +**合并有序数组或链表**: - - 对向双指针可以用于判断字符串或数组是否是回文。 - - **合并有序数组或链表**: - - - 双指针可以用于合并两个有序数组或链表,时间复杂度为 O(n)。 +- 双指针可以用于合并两个有序数组或链表,时间复杂度为 O(n)。 @@ -881,7 +910,7 @@ public void inOrderTraversalIterative(TreeNode root, List list) { *迭代法前序* -``` +```java public void preOrderTraversalIterative(TreeNode root, List list) { if (root == null) return; @@ -904,3 +933,97 @@ public void preOrderTraversalIterative(TreeNode root, List list) { } ``` +层序遍历BFS + +```java +public List> levelOrder(TreeNode root) { + List> result = new ArrayList<>(); + if (root == null) return result; + + Queue queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + int levelSize = queue.size(); + List level = new ArrayList<>(); + + for (int i = 0; i < levelSize; i++) { + TreeNode node = queue.poll(); + level.add(node.val); + + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } + result.add(level); + } + + return result; + } +``` + + + +#### 回溯法 + +回溯算法用于 **搜索一个问题的所有的解** ,即爆搜(暴力解法),通过深度优先遍历的思想实现。核心思想是: + +**1.逐步构建解答:** + +回溯算法通过逐步构造候选解,当构造的部分解满足条件时继续扩展;如果发现当前解不符合要求,则“回溯”到上一步,尝试其他可能性。 + +**2.剪枝(Pruning):** + +在构造候选解的过程中,算法会判断当前部分解是否有可能扩展成最终的有效解。如果判断出无论如何扩展都不可能得到正确解,就立即停止继续扩展该分支,从而节省计算资源。 + +**3.递归调用** + +回溯通常通过递归来实现。递归函数在**每一层都尝试不同的选择**,并在尝试失败或达到终点时返回上一层重新尝试其他选择。 + +**例:以数组 `[1, 2, 3]` 的全排列为例。** + +先写以 1 开头的全排列,它们是:`[1, 2, 3], [1, 3, 2]`,即 `1` + `[2, 3]` 的全排列(注意:递归结构体现在这里); +再写以 2 开头的全排列,它们是:`[2, 1, 3]`, `[2, 3, 1]`,即 `2` + `[1, 3]` 的全排列; +最后写以 3 开头的全排列,它们是:`[3, 1, 2]`, `[3, 2, 1]`,即 `3` + `[1, 2]` 的全排列。 + +![image-20250326095409631](https://pic.bitday.top/i/2025/03/26/fs3bz0-0.png) + +```java +public class Permute { + public List> permute(int[] nums) { + List> res = new ArrayList<>(); + // 用来标记数组中数字是否被使用 + boolean[] used = new boolean[nums.length]; + List path = new ArrayList<>(); + backtrack(nums, used, path, res); + return res; + } + + private void backtrack(int[] nums, boolean[] used, List path, List> res) { + // 当path中元素个数等于nums数组的长度时,说明已构造出一个排列 + if (path.size() == nums.length) { + res.add(new ArrayList<>(path)); + return; + } + // 遍历数组中的每个数字 + for (int i = 0; i < nums.length; i++) { + // 如果该数字已经在当前排列中使用过,则跳过 + if (used[i]) { + continue; + } + // 选择数字nums[i] + used[i] = true; + path.add(nums[i]); + // 递归构造剩余的排列 + backtrack(nums, used, path, res); + // 回溯:撤销选择,尝试其他数字 + path.remove(path.size() - 1); + used[i] = false; + } + } +} +``` + diff --git a/自学/苍穹外卖.md b/自学/苍穹外卖.md index 1b554b2..2d6ac0e 100644 --- a/自学/苍穹外卖.md +++ b/自学/苍穹外卖.md @@ -31,7 +31,25 @@ | 10 | orders | 订单表 | | 11 | order_detail | 订单明细表 | -###  @TableName("user")public class User {    @TableId    private Long id;    private String name;    private Integer age;    @TableField("isMarried")    private Boolean isMarried;    @TableField("`order`")    private String order;}Java +```java +@TableName("user") +public class User { + @TableId + private Long id; + + private String name; + + private Integer age; + + @TableField("isMarried") + private Boolean isMarried; + + @TableField("`order`") + private String order; +} +``` + + **1.nginx 反向代理的好处:** @@ -47,7 +65,7 @@ 因为一般后台服务地址不会暴露,所以使用浏览器不能直接访问,可以把nginx作为请求访问的入口,请求到达nginx后转发到具体的服务中,从而保证后端服务的安全。 -```text +```java server{ listen 80; server_name localhost; @@ -62,7 +80,7 @@ server{ **2.负载均衡配置**(有两个后端服务器) -```text +```java upstream webservers{ server 192.168.100.128:8080; server 192.168.100.129:8080; @@ -93,7 +111,7 @@ server{ 在pom.xml中添加依赖 -```xml +```java com.github.xiaoymin knife4j-spring-boot-starter @@ -156,7 +174,7 @@ protected void addResourceHandlers(ResourceHandlerRegistry registry) { EmployeeLoginDTO.java -```text +```java @Data @ApiModel(description = "员工登录时传递的数据模型") public class EmployeeLoginDTO implements Serializable { @@ -188,7 +206,7 @@ spring security中提供了一个加密类BCryptPasswordEncoder。 - 添加依赖 -```text +```java org.springframework.boot spring-boot-starter-security @@ -197,7 +215,7 @@ spring security中提供了一个加密类BCryptPasswordEncoder。 - 添加配置 -```text +```java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @@ -218,7 +236,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - 使用 -```text +```java @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @@ -256,7 +274,7 @@ ThreadLocal为**每个线程**提供**单独**一份存储空间,具有线程 **每次请求代表一个线程**!!!注:请求可以先经过拦截器,再经过controller=>service=>mapper,都是在一个线程里。 -```text +```java public class BaseContext { public static ThreadLocal threadLocal = new ThreadLocal<>(); @@ -321,7 +339,7 @@ public class BaseContext { JacksonObjectMapper()文件: -```text +```java public class JacksonObjectMapper extends ObjectMapper { public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; @@ -373,7 +391,7 @@ public void startOrStop(Integer status, Long id) { 还有一种:把源对象source的属性值赋给目标**对象**target中与源**对象**source的中有着同属性名的属性 -```text +```java BeanUtils.copyProperties(source,target); ``` @@ -397,7 +415,7 @@ BeanUtils.copyProperties(source,target); 在 EmployeeMapper.xml 中编写SQL: -```sql +```java update employee @@ -454,7 +472,7 @@ BeanUtils.copyProperties(source,target); **1). 导入Spring Data Redis的maven坐标(已完成)** -```xml +```java org.springframework.boot spring-boot-starter-data-redis @@ -465,7 +483,7 @@ BeanUtils.copyProperties(source,target); 在application-dev.yml中添加 -```yaml +```java sky: redis: host: localhost @@ -482,7 +500,7 @@ database:指定使用Redis的哪个数据库,Redis服务启动后默认有16 在application.yml中添加读取application-dev.yml中的相关Redis配置 -```yaml +```java spring: profiles: active: dev @@ -564,7 +582,7 @@ public class SpringDataRedisTest { **哈希测试** -```text +```java /** * 操作哈希类型的数据 */ @@ -723,7 +741,7 @@ public class SpringDataRedisTest { -```text +```java public static String doGet(String url,Map paramMap){ // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); @@ -798,7 +816,7 @@ public static String doGet(String url,Map paramMap){ - 每个分类下的菜品保存一份缓存数据 - 数据库中菜品数据有**变更时清理缓存数据** -```text +```java @Autowired private RedisTemplate redisTemplate; /** @@ -845,7 +863,7 @@ public static String doGet(String url,Map paramMap){ 清理缓冲方法: -```text +```java private void cleanCache(String pattern){ Set keys = redisTemplate.keys(pattern); redisTemplate.delete(keys); @@ -858,7 +876,7 @@ public static String doGet(String url,Map paramMap){ Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。 -```text +```java org.springframework.boot spring-boot-starter-cache 2.7.3 @@ -892,7 +910,7 @@ Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要 -```text +```java @PostMapping @CachePut(value = "userCache", key = "#user.id")//key的生成:userCache::1 public User save(@RequestBody User user){ @@ -915,7 +933,7 @@ Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要 所以,@Cacheable(cacheNames = "userCache",key="#id")中的#id表示的是函数形参中的id,而不能是返回值中的user.id -```text +```java @GetMapping @Cacheable(cacheNames = "userCache",key="#id") public User getById(Long id){ @@ -928,7 +946,7 @@ Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要 ​ 作用: 清理指定缓存 -```text +```java @DeleteMapping @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据 public void deleteById(Long id){ @@ -990,13 +1008,13 @@ https://pay.weixin.qq.com/static/product/product_index.shtml 输入代码: -```text +```java cpolar.exe authtoken ZmIwMmQzZDYtZDE2ZS00ZGVjLWE2MTUtOGQ0YTdhOWI2M2Q1 ``` 3)获取临时域名 -```text +```java cpolar.exe http 8080 ``` @@ -1058,7 +1076,7 @@ cron表达式在线生成器:https://cron.qqe2.com/ -```text +```java @Component @Slf4j public class OrderTask {