1. 仅使用 @Bean 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
}

public class Test {

@Bean
public Person get() {
return new Person();
}
}

@RestController
public class TestController {

private static final Logger log = LoggerFactory.getLogger(TestController.class);

@RequestMapping("/test")
public void test() {
Test test = new Test();
log.error("Class: {}, Class#get(): {}", test, test.get());
}
}

此时在 SpringBoot 当中,被注解的方法返回的对象并没有被自动注入到 IoC 容器,因此使用注解和没使用一样,并非单例的,每次调用返回的对象都是新对象


2. @Bean 和 @Configuration 注解联合使用

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
public class Person {
}

@Configuration
public class Test {

@Bean
public Person get() {
return new Person();
}
}

@RestController
public class TestController {

private static final Logger log = LoggerFactory.getLogger(TestController.class);

@Resource
private Test test;

@RequestMapping("/test")
public void test() {
log.error("Class: {}, Class#get(): {}", test, test.get());
}
}

此时在 SpringBoot 当中,被 @Configuration 注解的类会被框架使用 CGLIB 生成一个代理类并对 @Bean 注解的方法处理,此时自动注入的类对象以及 @Bean 注解的方法返回的对象都是单例的


3. @Bean 和 @Component(或者 @Service …) 注解联合使用

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
public class Person {
}

@Component
public class Test {

@Bean
public Person get() {
return new Person();
}
}

@RestController
public class TestController {

private static final Logger log = LoggerFactory.getLogger(TestController.class);

@Resource
private Test test;

@Resource
private Person get;

@RequestMapping("/test")
public void test() {
log.error("Class: {}, Class#get(): {}", test, test.get());
log.error("Class: {}", get);
}
}

此时在 SpringBoot 当中,自动注入的类对象是单例的,自动注入 @Bean 注解的方法返回的对象是单例的,但是类对象调用get()方法返回的对象不是单例的,每次调用返回的对象都是新对象

拦截方式

  • Filter: 过滤器,与框架无关,粒度最大
    • 配置方式:
      1. 定义一个类实现 Filter 接口及其定义的所有方法,加上 @Component 注解,让容器管理,拦截所有的URL,配合 @Order 注解设置执行顺序
      2. 定义一个类实现 Filter 接口及其定义的所有方法,加上 @WebFilter 注解可以匹配指定URL,通过类名来确定执行顺序,但是要配合 @ServletComponentScan 注解,能够让容器管理
      3. 定义一个类实现 Filter 接口及其定义的所有方法,@Configuration 定义配置类,定义返回类型是 FilterRegistrationBean 的方法,该方法加上 @Bean 注解,让容器管理,能够自定义拦截URL,设置执行顺序,方式灵活
      4. 定义一个类继承 OncePerRequestFilter 并重写 doFilterInternal() 方法,通过类名来确定执行顺序,加上 @Component 注解,让容器管理
  • Interceptor: 拦截器,框架提供
    • 配置方式: 自定义类实现 HandlerInterceptor 接口,实现相关方法
      (继承抽象类 HandlerInterceptorAdapter 的方式已经弃用),定义一
      个配置类实现 WebMvcConfigurer 接口并重写 addInterceptors()
      方法注册拦截器
  • ControllerAdvice: 控制器增强,一般配合 @ExceptionHandler 注解
    进行全局异常处理,也可以配合实现 ResponseBodyAdvice 接口对
    @ResponseBody 注解返回的数据加工处理再返回(RequestBodyAdvice
    接口和 @RequestBody 注解同理,接口有一个 RequestBodyAdviceAdapter
    适配器类)
  • Aspect: 自定义切入的类或者方法,粒度最小

简单总结

\ Filter Interceptor ControllerAdvice Aspect
参数 ServletRequest request, ServletResponse response, FilterChain chain HttpServletRequest request, HttpServletResponse response, Object handler \ ProceedingJoinPoint joinPoint
总结 可以拿到Http请求,但无法获取控制器 可以拿到请求的控制器和方法,但拿不到请求方法里的参数 \ 拿得到方法的参数,但是拿不到Http请求和响应对象,可以通过RequestContextHolder获得

同时使用所有的拦截方式,拦截顺序
Filter -> Interceptor -> ControllerAdvice -> Aspect -> Controller

Spring Security帐号密码认证

相关类图

SpringSecurity类图


相关时序图

SpringSecurity时序图


流程解释

  • UsernamePasswordAuthenticationFilter类继承AbstractAuthenticationProcessingFilter类,该类调用attemptAuthentication方法,
    该方法现将用户名及密码通过UsernamePasswordAuthenticationToken.unauthenticated()方法封装成UsernamePasswordAuthenticationToken对象(即Authentication对象),
    再调用 this.getAuthenticationManager().authenticate()方法
  • AuthenticationManager接口找到ProviderManager实现类的authenticate()方法,通过getProviders()方法遍历AuthenticationProvider集合,找到
    AbstractUserDetailsAuthenticationProvider类调用authenticate()方法
  • AbstractUserDetailsAuthenticationProvider类的authenticate()方法需要调用子类DaoAuthenticationProvider的retrieveUser()方法
  • retrieveUser()方法调用UserDetailsService接口的loadUserByUsername()方法,因此UserDetailsService接口需要用户实现其方法
  • loadUserByUsername()方法调用完毕需要返回UserDetails对象,UserDetails是个接口,仍需要用户实现

Object类中的wait和notify方法

关于wait和notify方法:

  • wait和notify方法不是线程对象的方法,是Java中任何一个Java对象都有的方法
  • wait和notify方法不是通过线程对象调用,通过Java对象调用
  • wait和notify方法建立在synchronized线程同步的基础上,因为多线程同时操作一个对象,有线程安全问题

wait()方法作用:

1
2
3
4
Object obj = new Object();

// 表示让正在obj对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止,并且释放之前占有的obj对象的锁
obj.wait();

notify()方法作用:

1
2
3
4
5
6
7
Object obj = new Object();

// 表示唤醒正在obj对象上等待的线程,其只会通知,不会释放之前占有的obj对象的锁
obj.notify();

// 表示唤醒obj对象上处于等待的所有线程
obj.notifyAll();

生产者模式和消费者模式

生产者模式和消费者模式是为了专门解决某个特定需求

关于生产者和消费者模式:

  • 生产线程负责生产,消费线程负责消费
  • 生产线程和消费线程要达到均衡
  • 这是一种特殊的业务需求,需要使用wait方法和notify方法

守护线程

Java中线程分为两类:

  • 一类是:用户线程
  • 一类是:守护线程(后台线程)
    代表性的例如:垃圾回收线程

守护线程特点:

  • 一般守护线程是一个死循环
  • 所有用户线程只要结束,守护线程自动结束(即使守护线程是死循环)

主线程main方法是一个用户线程

创建守护线程:

1
2
3
4
5
6
Thread thread = new Thread(new DaemonThread());
thread.setName("守护线程");

// 在线程启动前调用setDaemon()方法
thread.setDaemon(true);
thread.start();

定时器

定时器作用:

  • 间隔特定的时间,执行一段特定的代码

定时器实现:

  • 可以使用sleep方法,设置睡眠时间,每到设定的时间段醒来,执行任务,这种方式比较原始
  • Java类库中有一个定时器:java.util.Timer,可以直接用,但是开发中也使用较少,因为很多高级框架都是支持定时任务的
  • Spring框架提供的SpringTask框架

实际实现定时器功能的还有其它框架和类库


通过Callable接口实现线程

实现线程方式:

  • 继承Thread类,重写run()方法
  • 实现Runnable接口,实现run()方法
  • 实现Callable接口,实现call()方法

Callable接口优缺点:

  • 可以获取线程的执行结果
  • 获取结果通过get()方法,但get()方法会导致当前线程阻塞

线程生命周期

stateDiagram-v2
    direction LR

    s1 : 就绪状态
    s2 : 运行状态
    s3 : 阻塞状态
    s4 : 锁池(lockpool)

    classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow

    [*] --> s1::: badBadEvent : 调用start方法
    s1 --> s2::: badBadEvent : JVM的调度
    s2 --> s1 : JVM的调度(yield方法)
    s2 --> s3::: badBadEvent : 遇到阻塞事件(sleep/join方法)
    s2 --> s4 : synchronized
    s4 --> s1 : 
    s3 --> s1 : 阻塞解除
    s2 --> [*] : run方法结束

初始是新建状态:刚新建出来的线程对象

就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利(CPU时间片就是CPU执行权),当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着线程进入运行状态

运行状态:run方法的开始执行标志着这个线程进入运行状态,当之前占着的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间片之后,会重新进入run方法接着上一次的代码继续往下执行

阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片。阻塞状态解除时,之前的CPU时间片没了,需要再次回到就绪状态,抢夺CPU时间片

锁池(lockpool)锁池不是一种状态,便于理解的话可以当成一种阻塞状态。线程进入锁池找共享对象的对象锁的时候,会释放之前占有的CPU时间片,有可能找到了,也有可能没找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片

结束是死亡状态:run方法结束


synchronized三种写法

  1. 第一种:同步代码块

    1
    2
    3
    synchronized (线程共享对象) {
    // 线程同步代码块
    }
  2. 第二种:在实例方法上使用synchronized
    表示共享的对象是调用该实例方法(不包括静态方法)的对象
    并且同步代码块是整个方法体

  3. 第三种:在静态方法上使用synchronized
    表示找类锁
    因为静态方法是不属于当前实例的,而是属于类的,因此锁的是类
    类锁永远只有一把
    就算创建了100个对象,那类锁也只有一把


开发中如何解决线程安全?

  • 尽量使用局部变量代替实例变量和静态变量
  • 如果必须使用实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了
  • 如果不能使用局部变量,也不能创建多个对象,此时考虑使用synchronized

集合继承结构图

classDiagram
    direction BT
    class Iterable {
        <<interface>>
        +Iterator()
    }
    
    class Collection {
        <<interface>>
    }
    
    class Iterator {
        <<interface>>
        +hasNext()
        +next()
        +remove()
    }
    
    class List {
        <<interface>>
    }
    
    class Set {
        <<interface>>
    }

    class ArrayList

    class LinkedList

    class Vector

    class HashSet

    class SortedSet {
      <<interface>>
    }

    class TreeSet
    
    Collection --|> Iterable : 继承
    Iterator <-- Collection : 关联
    List --|> Collection : 继承
    Set --|> Collection : 继承
    
    ArrayList ..|> List : 实现
    LinkedList ..|> List : 实现
    Vector ..|> List : 实现
    
    HashSet ..|> Set : 实现
    SortedSet --|> Set : 继承
    TreeSet ..|> SortedSet : 实现
  1. Iterable 可迭代的,可遍历的,所有集合元素都是可迭代的,可遍历的

  2. Collection 继承 Iterable,其所有集合也都是可迭代的

  3. Iterator 和 Collection 是关联关系,Iterator 是迭代器

  4. List 继承 Collection,List集合存储元素特点:

    • 有序可重复,存储的元素有下标
    • 有序实际上是说存进去是这个顺序,取出来还是这个顺序
    • 这里的顺序不是按照大小排序。有序是因为List集合都有下标
    • 下标从 0 开始,以 1 递增
  5. ArrayList 集合底层是数组这种数据结构,是非线程安全的

  6. LinkedList 集合底层采用了双向链表的数据结构

  7. Vector 集合底层是数组这种数据结构,是线程安全的

  8. Set 继承 Collection,Set 集合存储元素特点:

    • 无序不可重复,无序表示存进去是这个顺序,取出来就不一定是这个顺序了
    • Set 集合中元素没有下标,且 Set 集合中的元素不能重复
  9. HashSet 集合在创建对象的时候,底层实际上是创建了一个 HashMap 集合
    向 HashSet 集合存储元素,实际是存储到 HashMap 集合中,HashMap 集合是一个哈希表数据结构
    HashSet集合初始化容量是16,初始化容量建议是2的幂
    扩容:扩容之后是原容量的2倍

  10. SortedSet 集合存储元素的特点:

    • 由于继承 Set 集合,所以它也是无序不可重复,但是放在 SortedSet 集合中的元素可以自动排序
    • 该集合中的元素是自动按照大小顺序排序的
  11. TreeSet 集合底层实际是 TreeMap,创建TreeSet集合时,底层实际上创建了一个 TreeMap 集合,
    向 TreeSet 集合中存放数据时,是将数据存放到 TreeMap 集合中

  12. TreeMap 集合底层采用的是二叉树数据结构


classDiagram
    class Map {
        <<interface>>
    }
    
    class SortedMap {
        <<interface>>
    }
    
    class HashMap
    
    class Hashtable
    
    class TreeMap
    
    class Properties
    
    HashMap ..|> Map : 实现
    Hashtable ..|> Map : 实现
    SortedMap --|> Map : 继承
    TreeMap ..|> SortedMap : 实现
    Properties --|> Hashtable : 继承
  1. Map 集合和 Collection 集合没有关系
    • Map 集合以 key 和 value 的键值对的方式存储元素
    • key 和 value 存储的是java对象的内存地址
    • 所有Map集合的 key 特点:无序不可重复的
    • Map 集合的 key 和 Set 集合存储元素特点相同
  2. HashMap 集合底层是哈希表数据结构 / (数组+链表+红黑树),是非线程安全的
    • JDK8之后,如果哈希表中单向链表中元素超过8个,单向链表会变成红黑树数据结构
    • 当红黑树上节点数量小于6时,会重新把红黑树变成单向链表数据结构
    • 初始化容量16,默认加载因子0.75
    • 扩容:扩容之后的容量是原容量的2倍
    • 集合的key和value可以为null
  3. Hashtable 集合底层也是哈希表数据结构,是线程安全的,现使用较少,因为控制线程安全有更好的方案了
    • 初始化容量11
    • 扩容:原容量 * 2 + 1
    • 集合的key和value不能为null
  4. SortedMap 集合的 key 存储元素特点:
    • 无序不可重复的,SortedMap 集合 key 部分的元素会自动按照大小顺序排序,称为可排序的集合
  5. TreeMap 集合底层是二叉树的数据结构
  6. Properties 集合是线程安全的,因为继承 Hashtable,存储元素的时候也是采用 key 和 value 的形式存储,并且 key 和 value 只支持 String 类型,不支持其它类型,其被称为属性类

总结(所有的实现类)

  • ArrayList:底层是数组
  • LinkedList:底层是双向链表
  • Vector:底层是数组,线程安全的,效率较低,使用较少
  • HashSet:底层是 HashMap,放到 HashSet 集合中的元素等同于放到 HashMap 集合 key 部分
  • TreeSet:底层是 TreeMap,放到 TreeSet 集合中的元素等同于放到 TreeMap
  • HashMap:底层是哈希表 / (数组+链表+红黑树)
  • Hashtable:底层是哈希表,线程安全的,效率较低,使用较少
  • Properties:线程安全,key 和 value 只能存储字符串
  • TreeMap:底层是二叉树,TreeMap 集合的 key 可以自动按照大小顺序排序

  • List 集合存储元素的特点:
    • 有序可重复
  • Set(Map) 集合存储元素特点:
    • 无序不可重复
    • 集合中元素没有下标
  • SortedSet(SortedMap) 集合存储元素特点:
    • 无序不可重复
    • 集合中元素是可排序的

      Map 集合的 key,就是一个 Set 集合
      往 Set 集合中放数据,实际上放到了 Map 集合的 Key 部分


  1. TreeSet/TreeMap自平衡二叉树,遵循左小右大原则存放

  2. 遍历二叉树有三种方式:
    前序遍历:根左右
    中序遍历:左根右
    后序遍历:左右根

    前中后说的是 “根” 的位置

  3. TreeSet集合/TreeMap集合采用的是:中序遍历方式
    Interator迭代器采用的是中序遍历

  • 放到TreeSet或者TreeMap集合key部分的元素要想要做到排序,包括两种方式:
    • 第一种:放在集合中的元素实现java.lang.Comparable接口
    • 第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象

MySql安装 (Version5.7.36,基于Debian11)

1.下载、解压


2.创建mysql用户及用户组

1
2
[root@localhost]# groupadd mysql
[root@localhost]# useradd -g mysql mysql

3.创建/opt/mysql/data、/opt/mysql/log目录,修改mysql目录下所有文件归属于mysql用户及用户组

1
2
3
[root@localhost]# mkdir /opt/mysql/data
[root@localhost]# mkdir /opt/mysql/log
[root@localhost]# chown -R mysql:mysql mysql

4.准备配置文件

  • 4.1 /etc目录新建my.cnf文件,写入以下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [mysql]
    # 设置mysql客户端默认字符集
    default-character-set=utf8mb4
    socket=/var/lib/mysql/mysql.socket

    [mysqld]
    skip-name-resolve
    #设置3306端口
    port = 3306
    socket=/var/lib/mysql/mysql.socket
    # 设置mysql的安装目录
    basedir=/opt/mysql
    # 设置mysql数据库的数据的存放目录
    datadir=/opt/mysql/data
    # 允许最大连接数
    max_connections=200
    # 服务端使用的字符集默认为8比特编码的latin1字符集
    character-set-server=utf8mb4
    # 创建新表时将使用的默认存储引擎
    default-storage-engine=INNODB
    lower_case_table_names=1
    max_allowed_packet=16M
    log-error=/opt/mysql/log/error.log
  • 4.2 创建/var/lib/mysql目录,设置权限
    1
    2
    [root@localhost]# mkdir /var/lib/mysql
    [root@localhost]# chmod 755 /var/lib/mysql

5.开始安装MySql

  • 5.1 安装依赖
    1
    [root@localhost]# apt install libaio1 libncurses5 libtinfo5
  • 5.2 安装
    运行完成无报错后,默认的root密码在/opt/mysql/log/error.log文件内
    1
    2
    [root@localhost]# cd /opt/mysql/bin
    [root@localhost]# ./mysqld --initialize --user=mysql

6.利用Systemd实现开机自启动

  • 6.1 创建/usr/lib/systemd/system/mysqld.service文件
  • 6.2 /usr/lib/systemd/system/mysqld.service文件写入以下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [Unit]
    Description=MySQL
    Documentation=https://dev.mysql.com/doc/refman/5.7/en

    [Service]
    ExecStart=/usr/bin/bash /opt/mysql/bin/mysqld_safe --user=mysql
    KillMode=mixed
    TimeoutStopSec=1s
    Restart=always

    [Install]
    WantedBy=multi-user.target
  • 6.3 设置开机自启动并启动mysql
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [root@localhost]# systemctl enable mysqld.service
    Created symlink /etc/systemd/system/multi-user.target.wants/mysqld.service → /lib/systemd/system/mysqld.service. //出现这句话即设置成功
    [root@localhost]# systemctl start mysqld.service //启动mysql
    [root@localhost]# systemctl status mysqld.service //查看启动情况,Active: active (running)即成功运行
    ● mysqld.service - MySQL
    Loaded: loaded (/lib/systemd/system/mysqld.service; enabled; vendor preset: enabled)
    Active: active (running) since Wed 2021-11-03 18:43:06 CST; 1 months 2 days ago
    Docs: https://dev.mysql.com/doc/refman/5.7/en
    Main PID: 494 (bash)
    Tasks: 35 (limit: 2341)
    Memory: 232.5M
    CPU: 14min 20.836s
    CGroup: /system.slice/mysqld.service
    ├─494 /bin/bash /opt/mysql/bin/mysqld_safe --user=debian
    └─762 /opt/mysql/bin/mysqld --basedir=/opt/mysql --datadir=/opt/mysql/data

7. 总结

  • 这次安装实际上还是靠别人的总结,我主要还是按照CodeSheepB站Up主的视频和他制作的《编程环境和软件工具安装手册》pdf文件的步骤来进行的,由于他是在CentOS安装的,我在Debian11安装时还是有一些问题,我做了一些小小的改动,最后成功运行还是很激动的
  • pdf文件得去Up主公众号CodeSheep里回复”安装手册”获取

MarkDown格式记录(一)

标题

1
2
3
4
5
6
7
8
9
10
11
# 一级标题

## 二级标题

### 三级标题

#### 四级标题

##### 五级标题

###### 六级标题

段落

Markdown 段落没有特殊的格式,直接编写文字就好,段落的换行是使用两个以上空格加上回车。
也可以在段落后面使用一个空行来表示重新开始一个段落。

1.字体
Markdown 可以使用以下几种字体:

1
2
3
4
5
6
7
8
9
10
11
*斜体文本*

_斜体文本_

**粗体文本**

__粗体文本__

***粗斜体文本***

___粗斜体文本___

效果如下:

斜体文本

斜体文本

粗体文本

粗体文本

粗斜体文本

粗斜体文本


2.分隔线
你可以在一行中用三个以上的星号、减号、底线来建立一个分隔线,行内不能有其他东西。你也可以在星号或是减号中间插入空格。下面每种写法都可以建立分隔线:

1
2
3
4
5
6
7
8
9
***

* * *

*****

- - -

----------

3.删除线
如果段落上的文字要添加删除线,只需要在文字的两端加上两个波浪线 ~~ 即可,实例如下:

1
2
3
GITHUB.COM

~~GITHUB.COM~~

效果如下:
GITHUB.COM


4.下划线
下划线可以通过 HTML 的 <u> 标签来实现:

1
<u>TEXT</u>

效果如下:
TEXT


5.脚注
脚注是对文本的补充说明。
Markdown 脚注的格式如下:

1
2
3
使用 Markdown[1]可以效率的书写文档, 直接转换成 HTML[2], 你可以使用VSCode编辑器进行书写。
[^1]:Markdown是一种纯文本标记语言
[^2]:HyperText Markup Language 超文本标记语言

效果如下:
使用 Markdown[1]可以效率的书写文档, 直接转换成 HTML[2], 你可以使用VSCode编辑器进行书写。


  1. 1.Markdown是一种纯文本标记语言
  2. 2.HyperText Markup Language 超文本标记语言
0%