mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
commit
294776d04f
43
README.md
43
README.md
@ -16,18 +16,6 @@
|
||||
<a href="#投稿"><img src="https://img.shields.io/badge/support-投稿-critical.svg" alt="投稿"></a>
|
||||
</p>
|
||||
|
||||
<h2 align="center">Special Sponsors</h2>
|
||||
<p align="center">
|
||||
<!--
|
||||
<a href="https://www.aliyun.com/acts/hi618/index?userCode=hf47liqn" target="_blank">
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/阿里云外投-1600-300.png" width="390px" height="70px" alt="阿里云618 2折起!"/>
|
||||
</a>
|
||||
-->
|
||||
<a href="https://coding.net/?utm_source=JavaGuide" target="_blank">
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/006rNwoDgy1g2dw5gau7nj30eg02vwfr.jpg" alt="零成本开启敏捷研发" height="70px" width="390px"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
推荐使用 https://snailclimb.top/JavaGuide/ 在线阅读(访问速度慢的话,请使用 https://snailclimb.gitee.io/javaguide ),在线阅读内容本仓库同步一致。这种方式阅读的优势在于:有侧边栏阅读体验更好,Gitee pages 的访问速度相对来说也比较快。
|
||||
|
||||
## 目录
|
||||
@ -50,8 +38,9 @@
|
||||
- [MySQL](#mysql)
|
||||
- [Redis](#redis)
|
||||
- [系统设计](#系统设计)
|
||||
- [设计模式(工厂模式、单例模式 ... )](#设计模式)
|
||||
- [常用框架(Spring、Zookeeper ... )](#常用框架)
|
||||
- [权限认证](#权限认证)
|
||||
- [设计模式(工厂模式、单例模式 ... )](#设计模式)
|
||||
- [数据通信(消息队列、Dubbo ... )](#数据通信)
|
||||
- [网站架构](#网站架构)
|
||||
- [面试指南](#面试指南)
|
||||
@ -61,7 +50,7 @@
|
||||
- [工具](#工具)
|
||||
- [Git](#git)
|
||||
- [Docker](#Docker)
|
||||
- [资料](#资料)
|
||||
- [资源](#资源)
|
||||
- [书单](#书单)
|
||||
- [Github榜单](#Github榜单)
|
||||
- [待办](#待办)
|
||||
@ -110,6 +99,7 @@
|
||||
|
||||
* [Java 8 新特性总结](docs/java/What's%20New%20in%20JDK8/Java8Tutorial.md)
|
||||
* [Java 8 学习资源推荐](docs/java/What's%20New%20in%20JDK8/Java8教程推荐.md)
|
||||
* [Java8 forEach 指南](docs/java/What's%20New%20in%20JDK8/Java8foreach指南.md)
|
||||
|
||||
### 编程规范
|
||||
|
||||
@ -162,10 +152,6 @@
|
||||
|
||||
## 系统设计
|
||||
|
||||
### 设计模式
|
||||
|
||||
- [设计模式系列文章](docs/system-design/设计模式.md)
|
||||
|
||||
### 常用框架
|
||||
|
||||
#### Spring
|
||||
@ -175,12 +161,23 @@
|
||||
- [Spring中bean的作用域与生命周期](docs/system-design/framework/spring/SpringBean.md)
|
||||
- [SpringMVC 工作原理详解](docs/system-design/framework/spring/SpringMVC-Principle.md)
|
||||
- [Spring中都用到了那些设计模式?](docs/system-design/framework/spring/Spring-Design-Patterns.md)
|
||||
- [SpringBoot 使用指南](https://github.com/Snailclimb/springboot-guide)
|
||||
-
|
||||
|
||||
#### ZooKeeper
|
||||
|
||||
- [ZooKeeper 相关概念总结](docs/system-design/framework/ZooKeeper.md)
|
||||
- [ZooKeeper 数据模型和常见命令](docs/system-design/framework/ZooKeeper数据模型和常见命令.md)
|
||||
|
||||
### 权限认证
|
||||
|
||||
- [权限认证基础:区分Authentication,Authorization以及Cookie、Session、Token](docs/system-design/authority-certification/basis-of-authority-certification.md)
|
||||
- [适合初学者入门 Spring Security With JWT 的 Demo](https://github.com/Snailclimb/spring-security-jwt-guide)
|
||||
|
||||
### 设计模式
|
||||
|
||||
- [设计模式系列文章](docs/system-design/设计模式.md)
|
||||
|
||||
### 数据通信
|
||||
|
||||
- [数据通信(RESTful、RPC、消息队列)相关知识点总结](docs/system-design/data-communication/summary.md)
|
||||
@ -231,13 +228,17 @@
|
||||
* [Docker 入门](docs/tools/Docker.md)
|
||||
* [一文搞懂 Docker 镜像的常用操作!](docs/tools/Docker-Image.md)
|
||||
|
||||
## 资料
|
||||
## 资源
|
||||
|
||||
### 书单
|
||||
|
||||
- [Java程序员必备书单](docs/data/java-recommended-books.md)
|
||||
|
||||
### Github榜单
|
||||
### 实战项目推荐
|
||||
|
||||
- [onemall](https://github.com/YunaiV/onemall) : mall 商城,基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。
|
||||
|
||||
### Github 历史榜单
|
||||
|
||||
- [Java 项目月榜单](docs/github-trending/JavaGithubTrending.md)
|
||||
|
||||
@ -283,7 +284,7 @@ Markdown 格式参考:[Github Markdown格式](https://guides.github.com/featur
|
||||
|
||||
添加我的微信备注“Github”,回复关键字 **“加群”** 即可入群。
|
||||
|
||||

|
||||

|
||||
|
||||
### Contributor
|
||||
|
||||
|
@ -70,7 +70,7 @@ select * from user where city=xx ; // 无法命中索引
|
||||
|
||||
冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
|
||||
|
||||
MySQLS.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引
|
||||
MySQL 5.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引
|
||||
|
||||
### Mysql如何为表字段添加索引???
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
**高性能:**
|
||||
|
||||
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
|
||||
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
|
||||
|
||||

|
||||
|
||||
@ -251,6 +251,10 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。
|
||||
|
||||
在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。
|
||||
|
||||
补充内容:
|
||||
|
||||
> 1. redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。(来自[issue:关于Redis事务不是原子性问题](https://github.com/Snailclimb/JavaGuide/issues/452) )
|
||||
|
||||
### 缓存雪崩和缓存穿透问题解决方案
|
||||
|
||||
**缓存雪崩**
|
||||
|
@ -104,7 +104,7 @@ SHOW VARIABLES -- 显示系统变量信息
|
||||
-- 查看所有表
|
||||
SHOW TABLES[ LIKE 'pattern']
|
||||
SHOW TABLES FROM 库名
|
||||
-- 查看表机构
|
||||
-- 查看表结构
|
||||
SHOW CREATE TABLE 表名 (信息更详细)
|
||||
DESC 表名 / DESCRIBE 表名 / EXPLAIN 表名 / SHOW COLUMNS FROM 表名 [LIKE 'PATTERN']
|
||||
SHOW TABLE STATUS [FROM db_name] [LIKE 'pattern']
|
||||
@ -363,7 +363,7 @@ set(val1, val2, val3...)
|
||||
字段不能再分,就满足第一范式。
|
||||
-- 2NF, 第二范式
|
||||
满足第一范式的前提下,不能出现部分依赖。
|
||||
消除符合主键就可以避免部分依赖。增加单列关键字。
|
||||
消除复合主键就可以避免部分依赖。增加单列关键字。
|
||||
-- 3NF, 第三范式
|
||||
满足第二范式的前提下,不能出现传递依赖。
|
||||
某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。
|
||||
@ -590,7 +590,7 @@ CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name
|
||||
```mysql
|
||||
事务是指逻辑上的一组操作,组成这组操作的各个单元,要不全成功要不全失败。
|
||||
- 支持连续SQL的集体成功或集体撤销。
|
||||
- 事务是数据库在数据晚自习方面的一个功能。
|
||||
- 事务是数据库在数据完整性方面的一个功能。
|
||||
- 需要利用 InnoDB 或 BDB 存储引擎,对自动提交的特性支持完成。
|
||||
- InnoDB被称为事务安全型引擎。
|
||||
-- 事务开启
|
||||
|
@ -108,7 +108,7 @@
|
||||
request.getRequestDispatcher("login_success.jsp").forward(request, response);
|
||||
```
|
||||
|
||||
**重定向(Redirect)** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。
|
||||
**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。
|
||||
|
||||
1. **从地址栏显示来说**:forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。
|
||||
2. **从数据共享来说**:forward:转发页面和转发到的页面可以共享request里面的数据。redirect:不能共享数据。
|
||||
|
@ -48,7 +48,7 @@
|
||||
- [Throwable类常用方法](#throwable类常用方法)
|
||||
- [异常处理总结](#异常处理总结)
|
||||
- [33 Java序列化中如果有些字段不想进行序列化,怎么办?](#33-java序列化中如果有些字段不想进行序列化怎么办)
|
||||
- [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法)
|
||||
- [34 获取用键盘输入常用的两种方法](#34-获取用键盘输入常用的两种方法)
|
||||
- [35 Java 中 IO 流分为几种?BIO,NIO,AIO 有什么区别?](#35-java-中-io-流分为几种bionioaio-有什么区别)
|
||||
- [java 中 IO 流分为几种?](#java-中-io-流分为几种)
|
||||
- [BIO,NIO,AIO 有什么区别?](#bionioaio-有什么区别)
|
||||
@ -123,7 +123,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
|
||||
|
||||
**总结:**
|
||||
|
||||
1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;
|
||||
1. Oracle JDK大概每6个月发一次主要版本,而OpenJDK版本大概每三个月发布一次。但这不是固定的,我觉得了解这个没啥用处。详情参见:https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence。
|
||||
2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
|
||||
3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
|
||||
4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
- [1. 基础](#1-基础)
|
||||
- [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法)
|
||||
- [1.2. 整形包装类值的比较](#12-整形包装类值的比较)
|
||||
- [1.2. 整型包装类值的比较](#12-整型包装类值的比较)
|
||||
- [1.3. BigDecimal](#13-bigdecimal)
|
||||
- [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处)
|
||||
- [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较)
|
||||
@ -62,12 +62,12 @@ public static boolean equals(Object a, Object b) {
|
||||
Reference:[Java中equals方法造成空指针异常的原因及解决方案](https://blog.csdn.net/tick_tock97/article/details/72824894)
|
||||
|
||||
- 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
|
||||
- 可以使用==或者!=操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null==null`将返回true。
|
||||
- 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null == null`将返回true。
|
||||
- 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
|
||||
|
||||
## 1.2. 整形包装类值的比较
|
||||
## 1.2. 整型包装类值的比较
|
||||
|
||||
所有整形包装类对象值得比较必须使用equals方法。
|
||||
所有整型包装类对象值的比较必须使用equals方法。
|
||||
|
||||
先看下面这个例子:
|
||||
|
||||
@ -150,8 +150,7 @@ Reference:《阿里巴巴Java开发手册》
|
||||
|
||||
比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.
|
||||
|
||||
**说明** :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或
|
||||
者入库检查,都由使用者来保证。
|
||||
**说明** :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
|
||||
|
||||
**正例** : 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
|
||||
|
||||
|
@ -33,7 +33,7 @@ AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效
|
||||
|
||||
### 2 AQS 原理
|
||||
|
||||
> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
|
||||
> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参考,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
|
||||
|
||||
下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。
|
||||
|
||||
|
@ -56,7 +56,7 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
|
||||
**引用类型**
|
||||
|
||||
- AtomicReference:引用类型原子类
|
||||
- AtomicStampedReference:原子更新引用类型里的字段原子类
|
||||
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
|
||||
- AtomicMarkableReference :原子更新带有标记位的引用类型
|
||||
|
||||
**对象的属性修改类型**
|
||||
@ -553,4 +553,4 @@ class User {
|
||||
|
||||
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
|
||||
|
||||

|
||||

|
||||
|
@ -129,7 +129,19 @@ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
|
||||
|
||||
通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。**
|
||||
|
||||
**每个`Thread`中都具备一个`ThreadLocalMap`,而`ThreadLocalMap`可以存储以`ThreadLocal`为key的键值对。这里解释了为什么每个线程访问同一个`ThreadLocal`,得到的确是不同的数值。另外,`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。**
|
||||
**每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。
|
||||
|
||||
```java
|
||||
public class Thread implements Runnable {
|
||||
......
|
||||
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
|
||||
ThreadLocal.ThreadLocalMap threadLocals = null;
|
||||
|
||||
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
|
||||
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
`ThreadLocalMap`是`ThreadLocal`的静态内部类。
|
||||
|
||||
|
@ -1,163 +0,0 @@
|
||||
JDK8接口规范
|
||||
===
|
||||
在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(比如泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。。
|
||||
---
|
||||
## 1.JDK8以前的接口规范
|
||||
- JDK8以前接口可以定义的变量和方法
|
||||
- 所有变量(Field)不论是否<i>显式</i> 的声明为```public static final```,它实际上都是```public static final```的。
|
||||
- 所有方法(Method)不论是否<i>显示</i> 的声明为```public abstract```,它实际上都是```public abstract```的。
|
||||
```java
|
||||
public interface AInterfaceBeforeJDK8 {
|
||||
int FIELD = 0;
|
||||
void simpleMethod();
|
||||
}
|
||||
```
|
||||
以上接口信息反编译以后可以看到字节码信息里Filed是public static final的,而方法是public abstract的,即是你没有显示的去声明它。
|
||||
```java
|
||||
{
|
||||
public static final int FIELD;
|
||||
descriptor: I
|
||||
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
|
||||
ConstantValue: int 0
|
||||
|
||||
public abstract void simpleMethod();
|
||||
descriptor: ()V
|
||||
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
|
||||
}
|
||||
```
|
||||
## 2.JDK8之后的接口规范
|
||||
- JDK8之后接口可以定义的变量和方法
|
||||
- 变量(Field)仍然必须是 ```java public static final```的
|
||||
- 方法(Method)除了可以是public abstract之外,还可以是public static或者是default(相当于仅public修饰的实例方法)的。
|
||||
从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。
|
||||
<br>下面是一个JDK8之后的接口例子
|
||||
```java
|
||||
public interface AInterfaceInJDK8 {
|
||||
int simpleFiled = 0;
|
||||
static int staticField = 1;
|
||||
|
||||
public static void main(String[] args) {
|
||||
}
|
||||
static void staticMethod(){}
|
||||
|
||||
default void defaultMethod(){}
|
||||
|
||||
void simpleMethod() throws IOException;
|
||||
|
||||
}
|
||||
```
|
||||
进行反编译(去除了一些没用信息)
|
||||
```java
|
||||
{
|
||||
public static final int simpleFiled;
|
||||
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
|
||||
|
||||
public static final int staticField;
|
||||
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
|
||||
|
||||
public static void main(java.lang.String[]);
|
||||
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
|
||||
|
||||
public static void staticMethod();
|
||||
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
|
||||
|
||||
public void defaultMethod();
|
||||
flags: (0x0001) ACC_PUBLIC
|
||||
|
||||
public abstract void simpleMethod() throws java.io.IOException;
|
||||
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
|
||||
Exceptions:
|
||||
throws java.io.IOException
|
||||
}
|
||||
```
|
||||
可以看到 default关键字修饰的方法是像实例方法一样定义的,所以我们来定义一个只有default的方法并且实现一下试一试。
|
||||
```java
|
||||
interface Default {
|
||||
default int defaultMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultMethod implements Default {
|
||||
public static void main(String[] args) {
|
||||
DefaultMethod defaultMethod = new DefaultMethod();
|
||||
System.out.println(defaultMethod.defaultMethod());
|
||||
//compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context
|
||||
//! DefaultMethod.defaultMethod();
|
||||
}
|
||||
}
|
||||
```
|
||||
可以看到default方法确实像实例方法一样,必须有实例对象才能调用,并且子类在实现接口时,可以不用实现default方法,也可以覆盖该方法。
|
||||
这有点像子类继承父类实例方法。
|
||||
<br>
|
||||
接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用**
|
||||
```java
|
||||
interface Static {
|
||||
static int staticMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
... main(String...args)
|
||||
//!compile error: Static method may be invoked on containing interface class only
|
||||
//!aInstanceOfStatic.staticMethod();
|
||||
...
|
||||
```
|
||||
另一个问题是多继承问题,大家知道Java中类是不支持多继承的,但是接口是多继承和多实现(implements后跟多个接口)的,
|
||||
那么如果一个接口继承另一个接口,两个接口都有同名的default方法会怎么样呢?答案是会像类继承一样覆写(@Override),以下代码在IDE中可以顺利编译
|
||||
```java
|
||||
interface Default {
|
||||
default int defaultMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
interface Default2 extends Default {
|
||||
@Override
|
||||
default int defaultMethod() {
|
||||
return 9527;
|
||||
}
|
||||
}
|
||||
public class DefaultMethod implements Default,Default2 {
|
||||
public static void main(String[] args) {
|
||||
DefaultMethod defaultMethod = new DefaultMethod();
|
||||
System.out.println(defaultMethod.defaultMethod());
|
||||
}
|
||||
}
|
||||
|
||||
输出 : 9527
|
||||
```
|
||||
出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。
|
||||
<br>
|
||||
那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?IDE会要求你重写这个冲突的方法,让你自己选择去执行哪个方法,因为IDE它
|
||||
还没智能到你不告诉它,它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。
|
||||
```java
|
||||
interface Default {
|
||||
default int defaultMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
|
||||
interface Default2 {
|
||||
default int defaultMethod() {
|
||||
return 9527;
|
||||
}
|
||||
}
|
||||
//如果不重写
|
||||
//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2
|
||||
public class DefaultMethod implements Default,Default2 {
|
||||
@Override
|
||||
public int defaultMethod() {
|
||||
System.out.println(Default.super.defaultMethod());
|
||||
System.out.println(Default2.super.defaultMethod());
|
||||
return 996;
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
DefaultMethod defaultMethod = new DefaultMethod();
|
||||
System.out.println(defaultMethod.defaultMethod());
|
||||
}
|
||||
}
|
||||
|
||||
运行输出 :
|
||||
4396
|
||||
9527
|
||||
996
|
||||
```
|
139
docs/java/What's New in JDK8/Java8foreach指南.md
Normal file
139
docs/java/What's New in JDK8/Java8foreach指南.md
Normal file
@ -0,0 +1,139 @@
|
||||
> 本文由 JavaGuide 翻译,原文地址:https://www.baeldung.com/foreach-java
|
||||
|
||||
## 1 概述
|
||||
|
||||
在Java 8中引入的*forEach*循环为程序员提供了一种新的,简洁而有趣的迭代集合的方式。
|
||||
|
||||
在本文中,我们将看到如何将*forEach*与集合*一起*使用,它采用何种参数以及此循环与增强的*for*循环的不同之处。
|
||||
|
||||
## 2 基础知识
|
||||
|
||||
```Java
|
||||
public interface Collection<E> extends Iterable<E>
|
||||
```
|
||||
|
||||
Collection 接口实现了 Iterable 接口,而 Iterable 接口在 Java 8开始具有一个新的 API:
|
||||
|
||||
```java
|
||||
void forEach(Consumer<? super T> action)//对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
|
||||
```
|
||||
|
||||
使用*forEach*,我们可以迭代一个集合并对每个元素执行给定的操作,就像任何其他*迭代器一样。*
|
||||
|
||||
例如,迭代和打印字符串集合*的*for循环版本:
|
||||
|
||||
```java
|
||||
for (String name : names) {
|
||||
System.out.println(name);
|
||||
}
|
||||
```
|
||||
|
||||
我们可以使用*forEach*写这个 :
|
||||
|
||||
```java
|
||||
names.forEach(name -> {
|
||||
System.out.println(name);
|
||||
});
|
||||
```
|
||||
|
||||
## 3.使用forEach方法
|
||||
|
||||
### 3.1 匿名类
|
||||
|
||||
我们使用 *forEach*迭代集合并对每个元素执行特定操作。**要执行的操作包含在实现Consumer接口的类中,并作为参数传递给forEach 。**
|
||||
|
||||
所述*消费者*接口是一个功能接口(具有单个抽象方法的接口)。它接受输入并且不返回任何结果。
|
||||
|
||||
Consumer 接口定义如下:
|
||||
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface Consumer {
|
||||
void accept(T t);
|
||||
}
|
||||
```
|
||||
任何实现,例如,只是打印字符串的消费者:
|
||||
|
||||
```java
|
||||
Consumer<String> printConsumer = new Consumer<String>() {
|
||||
public void accept(String name) {
|
||||
System.out.println(name);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
可以作为参数传递给*forEach*:
|
||||
|
||||
```java
|
||||
names.forEach(printConsumer);
|
||||
```
|
||||
|
||||
但这不是通过消费者和使用*forEach* API 创建操作的唯一方法。让我们看看我们将使用*forEach*方法的另外2种最流行的方式:
|
||||
|
||||
### 3.2 Lambda表达式
|
||||
|
||||
Java 8功能接口的主要优点是我们可以使用Lambda表达式来实例化它们,并避免使用庞大的匿名类实现。
|
||||
|
||||
由于 Consumer 接口属于函数式接口,我们可以通过以下形式在Lambda中表达它:
|
||||
|
||||
```java
|
||||
(argument) -> { body }
|
||||
name -> System.out.println(name)
|
||||
names.forEach(name -> System.out.println(name));
|
||||
```
|
||||
|
||||
### 3.3 方法参考
|
||||
|
||||
我们可以使用方法引用语法而不是普通的Lambda语法,其中已存在一个方法来对类执行操作:
|
||||
|
||||
```java
|
||||
names.forEach(System.out::println);
|
||||
```
|
||||
|
||||
## 4.forEach在集合中的使用
|
||||
|
||||
### 4.1.迭代集合
|
||||
|
||||
**任何类型Collection的可迭代 - 列表,集合,队列 等都具有使用forEach的相同语法。**
|
||||
|
||||
因此,正如我们已经看到的,迭代列表的元素:
|
||||
|
||||
```java
|
||||
List<String> names = Arrays.asList("Larry", "Steve", "James");
|
||||
|
||||
names.forEach(System.out::println);
|
||||
```
|
||||
|
||||
同样对于一组:
|
||||
|
||||
```java
|
||||
Set<String> uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));
|
||||
|
||||
uniqueNames.forEach(System.out::println);
|
||||
```
|
||||
|
||||
或者让我们说一个*队列*也是一个*集合*:
|
||||
|
||||
```java
|
||||
Queue<String> namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));
|
||||
|
||||
namesQueue.forEach(System.out::println);
|
||||
```
|
||||
|
||||
### 4.2.迭代Map - 使用Map的forEach
|
||||
|
||||
Map没有实现Iterable接口,但它**提供了自己的forEach 变体,它接受BiConsumer**。*
|
||||
|
||||
```java
|
||||
Map<Integer, String> namesMap = new HashMap<>();
|
||||
namesMap.put(1, "Larry");
|
||||
namesMap.put(2, "Steve");
|
||||
namesMap.put(3, "James");
|
||||
namesMap.forEach((key, value) -> System.out.println(key + " " + value));
|
||||
```
|
||||
|
||||
### 4.3.迭代一个Map - 通过迭代entrySet
|
||||
|
||||
```java
|
||||
namesMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));
|
||||
```
|
@ -1,235 +0,0 @@
|
||||
JDK8--Lambda表达式
|
||||
===
|
||||
## 1.什么是Lambda表达式
|
||||
**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递**
|
||||
|
||||
## 2.Lambda表达式语法
|
||||
```(参数列表) -> {执行代码块}```
|
||||
<br>参数列表可以为空```()->{}```
|
||||
<br>可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值.
|
||||
<br>在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}```
|
||||
<br>(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)
|
||||
|
||||
## 3.函数式接口
|
||||
在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```<br>
|
||||
**函数式接口指的是有且只有一个抽象(abstract)方法的接口**<br>
|
||||
当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子:
|
||||
<br>
|
||||
```java
|
||||
Thread thread = new Thread(() -> {
|
||||
System.out.println("This is JDK8's Lambda!");
|
||||
});
|
||||
```
|
||||
这段代码和函数式接口有啥关系?我们回忆一下,Thread类的构造函数里是不是有一个以Runnable接口为参数的?
|
||||
```java
|
||||
public Thread(Runnable target) {...}
|
||||
|
||||
/**
|
||||
* Runnable Interface
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Runnable {
|
||||
public abstract void run();
|
||||
}
|
||||
```
|
||||
到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于
|
||||
```java
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("Anonymous class");
|
||||
}
|
||||
});
|
||||
```
|
||||
也就是说,上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数,只不过它的返回值、参数列表都
|
||||
由编译器帮我们推断,因此可以减少很多代码量)。
|
||||
<br>Lambda也可以这样用 :
|
||||
```java
|
||||
Runnable runnable = () -> {...};
|
||||
```
|
||||
其实这和上面的用法没有什么本质上的区别。
|
||||
<br>至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范,
|
||||
目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了,
|
||||
因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()
|
||||
<br>
|
||||
除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口
|
||||
比如(当然不止这些):
|
||||
- ```java.util.concurrent.Callable```
|
||||
- ```java.util.Comparator```
|
||||
- ```java.io.FileFilter```
|
||||
<br>**关于JDK中的预定义的函数式接口**
|
||||
|
||||
- JDK在```java.util.function```下预定义了很多函数式接口
|
||||
- ```Function<T, R> {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。
|
||||
- ```Consumer<T> {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。
|
||||
- ```Predicate<T> {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。
|
||||
- ```Supplier<T> {T get();} 提供者(工厂)``` 返回一个T对象。
|
||||
- 其他的跟上面的相似,大家可以看一下function包下的具体接口。
|
||||
## 4.变量作用域
|
||||
```java
|
||||
public class VaraibleHide {
|
||||
@FunctionalInterface
|
||||
interface IInner {
|
||||
void printInt(int x);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
int x = 20;
|
||||
IInner inner = new IInner() {
|
||||
int x = 10;
|
||||
@Override
|
||||
public void printInt(int x) {
|
||||
System.out.println(x);
|
||||
}
|
||||
};
|
||||
inner.printInt(30);
|
||||
|
||||
inner = (s) -> {
|
||||
//Variable used in lambda expression should be final or effectively final
|
||||
//!int x = 10;
|
||||
//!x= 50; error
|
||||
System.out.print(x);
|
||||
};
|
||||
inner.printInt(30);
|
||||
}
|
||||
}
|
||||
输出 :
|
||||
30
|
||||
20
|
||||
```
|
||||
对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的,像这样并不是在lambda中定义或者通过lambda的参数列表()获取的变量成为自由变量,它是被lambda表达式捕获的。
|
||||
<br>lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值,
|
||||
同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该
|
||||
不难理解。
|
||||
## 5.方法引用
|
||||
**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。**
|
||||
<br>下面的例子会打印list中的每个元素:
|
||||
```java
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.add(i);
|
||||
}
|
||||
list.forEach(System.out::println);
|
||||
```
|
||||
其中```System.out::println```这个就是一个方法引用,等价于Lambda表达式 ```(para)->{System.out.println(para);}```
|
||||
<br>我们看一下List#forEach方法 ```default void forEach(Consumer<? super T> action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface Consumer<T> {
|
||||
void accept(T t);
|
||||
```
|
||||
大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。
|
||||
<br>我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用
|
||||
```java
|
||||
public class MethodReference {
|
||||
public static void main(String[] args) {
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.add(i);
|
||||
}
|
||||
list.forEach(MethodReference::myPrint);
|
||||
}
|
||||
|
||||
static void myPrint(int i) {
|
||||
System.out.print(i + ", ");
|
||||
}
|
||||
}
|
||||
|
||||
输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
```
|
||||
可以看到,我们自己定义的方法也可以当做方法引用。
|
||||
<br>到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。
|
||||
- 方法引用
|
||||
- 类名::静态方法名
|
||||
- 类名::实例方法名
|
||||
- 类名::new (构造方法引用)
|
||||
- 实例名::实例方法名
|
||||
可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种
|
||||
方法引用的使用方法。<br>
|
||||
**类名::实例方法名**<br>
|
||||
先来看一段代码
|
||||
```java
|
||||
String[] strings = new String[10];
|
||||
Arrays.sort(strings, String::compareToIgnoreCase);
|
||||
```
|
||||
**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**<br>
|
||||
我们看一下`Arrays#sort`方法`public static <T> void sort(T[] a, Comparator<? super T> c)`,
|
||||
可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下
|
||||
`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它
|
||||
的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样?
|
||||
<br>是的,确实不一样但是别忘了,String类的这个方法是个实例方法,而不是静态方法,也就是说,这个方法是需要有一个接收者的。所谓接收者就是
|
||||
instance.method(x)中的instance,
|
||||
它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的
|
||||
参数。我们再举一个自己实现的例子:
|
||||
```java
|
||||
public class MethodReference {
|
||||
static Random random = new Random(47);
|
||||
public static void main(String[] args) {
|
||||
MethodReference[] methodReferences = new MethodReference[10];
|
||||
Arrays.sort(methodReferences, MethodReference::myCompare);
|
||||
}
|
||||
int myCompare(MethodReference o) {
|
||||
return random.nextInt(2) - 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
上面的例子可以在IDE里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。
|
||||
<br>**构造器引用**<br>
|
||||
构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口
|
||||
```java
|
||||
//compile error : String is not a functional interface
|
||||
String str = String::new;
|
||||
```
|
||||
下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。
|
||||
```java
|
||||
interface IFunctional<T> {
|
||||
T func();
|
||||
}
|
||||
|
||||
public class ConstructorReference {
|
||||
|
||||
public ConstructorReference() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Supplier<ConstructorReference> supplier0 = () -> new ConstructorReference();
|
||||
Supplier<ConstructorReference> supplier1 = ConstructorReference::new;
|
||||
IFunctional<ConstructorReference> functional = () -> new ConstructorReference();
|
||||
IFunctional<ConstructorReference> functional1 = ConstructorReference::new;
|
||||
}
|
||||
}
|
||||
```
|
||||
下面是一个JDK官方的例子
|
||||
```java
|
||||
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
|
||||
DEST transferElements(
|
||||
SOURCE sourceCollection,
|
||||
Supplier<DEST> collectionFactory) {
|
||||
|
||||
DEST result = collectionFactory.get();
|
||||
for (T t : sourceCollection) {
|
||||
result.add(t);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
Set<Person> rosterSet = transferElements(
|
||||
roster, HashSet::new);
|
||||
```
|
||||
|
||||
**实例::实例方法**
|
||||
<br>
|
||||
其实开始那个例子就是一个实例::实例方法的引用
|
||||
```java
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.add(i);
|
||||
}
|
||||
list.forEach(System.out::println);
|
||||
```
|
||||
其中System.out就是一个实例,println是一个实例方法。相信不用再给大家做解释了。
|
||||
## 总结
|
||||
Lambda表达式是JDK8引入Java的函数式编程语法,使用Lambda需要直接或者间接的与函数式接口配合,在开发中使用Lambda可以减少代码量,
|
||||
但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降,并且也节省不了多少代码,
|
||||
所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断,同样对于这个特性需要斟酌使用。
|
@ -1,556 +0,0 @@
|
||||
JDK8新特性总结
|
||||
======
|
||||
总结了部分JDK8新特性,另外一些新特性可以通过Oracle的官方文档查看,毕竟是官方文档,各种新特性都会介绍,有兴趣的可以去看。<br>
|
||||
[Oracle官方文档:What's New in JDK8](https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html)
|
||||
-----
|
||||
- [Java语言特性](#JavaProgrammingLanguage)
|
||||
- [Lambda表达式是一个新的语言特性,已经在JDK8中加入。它是一个可以传递的代码块,你也可以把它们当做方法参数。
|
||||
Lambda表达式允许您更紧凑地创建单虚方法接口(称为功能接口)的实例。](#LambdaExpressions)
|
||||
|
||||
- [方法引用为已经存在的具名方法提供易于阅读的Lambda表达式](#MethodReferences)
|
||||
|
||||
- [默认方法允许将新功能添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性。](#DefaultMethods)
|
||||
|
||||
- [改进的类型推断。](#ImprovedTypeInference)
|
||||
|
||||
- [方法参数反射(通过反射获得方法参数信息)](#MethodParameterReflection)
|
||||
|
||||
- [流(stream)](#stream)
|
||||
- [新java.util.stream包中的类提供Stream API以支持对元素流的功能样式操作。流(stream)和I/O里的流不是同一个概念
|
||||
,使用stream API可以更方便的操作集合。]()
|
||||
|
||||
- [国际化]()
|
||||
- 待办
|
||||
- 待办
|
||||
___
|
||||
<!-- ---------------------------------------------------Lambda表达式-------------------------------------------------------- -->
|
||||
<!-- 标题与跳转-->
|
||||
<span id="JavaProgrammingLanguage"></span>
|
||||
<span id="LambdaExpressions"></span>
|
||||
<!-- 标题与跳转结束-->
|
||||
<!-- 正文-->
|
||||
|
||||
## Lambda表达式
|
||||
### 1.什么是Lambda表达式
|
||||
**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递**
|
||||
|
||||
### 2.Lambda表达式语法
|
||||
```(参数列表) -> {执行代码块}```
|
||||
<br>参数列表可以为空```()->{}```
|
||||
<br>可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值.
|
||||
<br>在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}```
|
||||
<br>(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)
|
||||
|
||||
### 3.函数式接口
|
||||
在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```<br>
|
||||
**函数式接口指的是有且只有一个抽象(abstract)方法的接口**<br>
|
||||
当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子:
|
||||
<br>
|
||||
```java
|
||||
Thread thread = new Thread(() -> {
|
||||
System.out.println("This is JDK8's Lambda!");
|
||||
});
|
||||
```
|
||||
这段代码和函数式接口有啥关系?我们回忆一下,Thread类的构造函数里是不是有一个以Runnable接口为参数的?
|
||||
```java
|
||||
public Thread(Runnable target) {...}
|
||||
|
||||
/**
|
||||
* Runnable Interface
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Runnable {
|
||||
public abstract void run();
|
||||
}
|
||||
```
|
||||
到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于
|
||||
```java
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("Anonymous class");
|
||||
}
|
||||
});
|
||||
```
|
||||
也就是说,上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数,只不过它的返回值、参数列表都
|
||||
由编译器帮我们推断,因此可以减少很多代码量)。
|
||||
<br>Lambda也可以这样用 :
|
||||
```java
|
||||
Runnable runnable = () -> {...};
|
||||
```
|
||||
其实这和上面的用法没有什么本质上的区别。
|
||||
<br>至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范,
|
||||
目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了,
|
||||
因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()
|
||||
<br>
|
||||
除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口
|
||||
比如(当然不止这些):
|
||||
- ```java.util.concurrent.Callable```
|
||||
- ```java.util.Comparator```
|
||||
- ```java.io.FileFilter```
|
||||
<br>**关于JDK中的预定义的函数式接口**
|
||||
|
||||
- JDK在```java.util.function```下预定义了很多函数式接口
|
||||
- ```Function<T, R> {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。
|
||||
- ```Consumer<T> {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。
|
||||
- ```Predicate<T> {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。
|
||||
- ```Supplier<T> {T get();} 提供者(工厂)``` 返回一个T对象。
|
||||
- 其他的跟上面的相似,大家可以看一下function包下的具体接口。
|
||||
### 4.变量作用域
|
||||
```java
|
||||
public class VaraibleHide {
|
||||
@FunctionalInterface
|
||||
interface IInner {
|
||||
void printInt(int x);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
int x = 20;
|
||||
IInner inner = new IInner() {
|
||||
int x = 10;
|
||||
@Override
|
||||
public void printInt(int x) {
|
||||
System.out.println(x);
|
||||
}
|
||||
};
|
||||
inner.printInt(30);
|
||||
|
||||
inner = (s) -> {
|
||||
//Variable used in lambda expression should be final or effectively final
|
||||
//!int x = 10;
|
||||
//!x= 50; error
|
||||
System.out.print(x);
|
||||
};
|
||||
inner.printInt(30);
|
||||
}
|
||||
}
|
||||
输出 :
|
||||
30
|
||||
20
|
||||
```
|
||||
对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的,像这样并不是在lambda中定义或者通过lambda
|
||||
的参数列表()获取的变量成为自由变量,它是被lambda表达式捕获的。
|
||||
<br>lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值,同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该不难理解。
|
||||
<span id="MethodReferences"></span>
|
||||
### 5.方法引用
|
||||
**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。**
|
||||
<br>下面的例子会打印list中的每个元素:
|
||||
```java
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.add(i);
|
||||
}
|
||||
list.forEach(System.out::println);
|
||||
```
|
||||
其中```System.out::println```这个就是一个方法引用,等价于Lambda表达式 ```(para)->{System.out.println(para);}```
|
||||
<br>我们看一下List#forEach方法 ```default void forEach(Consumer<? super T> action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface Consumer<T> {
|
||||
void accept(T t);
|
||||
```
|
||||
大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。
|
||||
<br>我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用
|
||||
```java
|
||||
public class MethodReference {
|
||||
public static void main(String[] args) {
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.add(i);
|
||||
}
|
||||
list.forEach(MethodReference::myPrint);
|
||||
}
|
||||
|
||||
static void myPrint(int i) {
|
||||
System.out.print(i + ", ");
|
||||
}
|
||||
}
|
||||
|
||||
输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
```
|
||||
可以看到,我们自己定义的方法也可以当做方法引用。
|
||||
<br>到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。
|
||||
- 方法引用
|
||||
- 类名::静态方法名
|
||||
- 类名::实例方法名
|
||||
- 类名::new (构造方法引用)
|
||||
- 实例名::实例方法名
|
||||
可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种
|
||||
方法引用的使用方法。<br>
|
||||
**类名::实例方法名**<br>
|
||||
先来看一段代码
|
||||
```java
|
||||
String[] strings = new String[10];
|
||||
Arrays.sort(strings, String::compareToIgnoreCase);
|
||||
```
|
||||
**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**<br>
|
||||
我们看一下`Arrays#sort`方法`public static <T> void sort(T[] a, Comparator<? super T> c)`,
|
||||
可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下
|
||||
`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它
|
||||
的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样?
|
||||
<br>是的,确实不一样但是别忘了,String类的这个方法是个实例方法,而不是静态方法,也就是说,这个方法是需要有一个接收者的。所谓接收者就是
|
||||
instance.method(x)中的instance,
|
||||
它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的
|
||||
参数。我们再举一个自己实现的例子:
|
||||
```java
|
||||
public class MethodReference {
|
||||
static Random random = new Random(47);
|
||||
public static void main(String[] args) {
|
||||
MethodReference[] methodReferences = new MethodReference[10];
|
||||
Arrays.sort(methodReferences, MethodReference::myCompare);
|
||||
}
|
||||
int myCompare(MethodReference o) {
|
||||
return random.nextInt(2) - 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
上面的例子可以在IDE里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。
|
||||
<br>**构造器引用**<br>
|
||||
构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口
|
||||
```java
|
||||
//compile error : String is not a functional interface
|
||||
String str = String::new;
|
||||
```
|
||||
下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。
|
||||
```java
|
||||
interface IFunctional<T> {
|
||||
T func();
|
||||
}
|
||||
|
||||
public class ConstructorReference {
|
||||
|
||||
public ConstructorReference() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Supplier<ConstructorReference> supplier0 = () -> new ConstructorReference();
|
||||
Supplier<ConstructorReference> supplier1 = ConstructorReference::new;
|
||||
IFunctional<ConstructorReference> functional = () -> new ConstructorReference();
|
||||
IFunctional<ConstructorReference> functional1 = ConstructorReference::new;
|
||||
}
|
||||
}
|
||||
```
|
||||
下面是一个JDK官方的例子
|
||||
```java
|
||||
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
|
||||
DEST transferElements(
|
||||
SOURCE sourceCollection,
|
||||
Supplier<DEST> collectionFactory) {
|
||||
|
||||
DEST result = collectionFactory.get();
|
||||
for (T t : sourceCollection) {
|
||||
result.add(t);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
Set<Person> rosterSet = transferElements(
|
||||
roster, HashSet::new);
|
||||
```
|
||||
|
||||
**实例::实例方法**
|
||||
<br>
|
||||
其实开始那个例子就是一个实例::实例方法的引用
|
||||
```java
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.add(i);
|
||||
}
|
||||
list.forEach(System.out::println);
|
||||
```
|
||||
其中System.out就是一个实例,println是一个实例方法。相信不用再给大家做解释了。
|
||||
### 总结
|
||||
Lambda表达式是JDK8引入Java的函数式编程语法,使用Lambda需要直接或者间接的与函数式接口配合,在开发中使用Lambda可以减少代码量,
|
||||
但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降,并且也节省不了多少代码,
|
||||
所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断,同样对于这个特性需要斟酌使用。
|
||||
|
||||
<!-- ---------------------------------------------------Lambda表达式结束---------------------------------------------------- -->
|
||||
___
|
||||
<!-- ---------------------------------------------------接口默认方法-------------------------------------------------------- -->
|
||||
<span id="DefaultMethods"></span>
|
||||
## JDK8接口规范
|
||||
### 在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(JDK8之前扩展接口会使得实现了该接口的类必须实现添加的方法,否则会报错。为了保持兼容性而做出妥协的特性还有泛型,泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。
|
||||
### 1.JDK8以前的接口规范
|
||||
- JDK8以前接口可以定义的变量和方法
|
||||
- 所有变量(Field)不论是否<i>显式</i> 的声明为```public static final```,它实际上都是```public static final```的。
|
||||
- 所有方法(Method)不论是否<i>显示</i> 的声明为```public abstract```,它实际上都是```public abstract```的。
|
||||
```java
|
||||
public interface AInterfaceBeforeJDK8 {
|
||||
int FIELD = 0;
|
||||
void simpleMethod();
|
||||
}
|
||||
```
|
||||
以上接口信息反编译以后可以看到字节码信息里Filed是public static final的,而方法是public abstract的,即是你没有显示的去声明它。
|
||||
```java
|
||||
{
|
||||
public static final int FIELD;
|
||||
descriptor: I
|
||||
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
|
||||
ConstantValue: int 0
|
||||
|
||||
public abstract void simpleMethod();
|
||||
descriptor: ()V
|
||||
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
|
||||
}
|
||||
```
|
||||
### 2.JDK8之后的接口规范
|
||||
- JDK8之后接口可以定义的变量和方法
|
||||
- 变量(Field)仍然必须是 ```java public static final```的
|
||||
- 方法(Method)除了可以是public abstract之外,还可以是public static或者是default(相当于仅public修饰的实例方法)的。
|
||||
从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。
|
||||
<br>下面是一个JDK8之后的接口例子
|
||||
```java
|
||||
public interface AInterfaceInJDK8 {
|
||||
int simpleFiled = 0;
|
||||
static int staticField = 1;
|
||||
|
||||
public static void main(String[] args) {
|
||||
}
|
||||
static void staticMethod(){}
|
||||
|
||||
default void defaultMethod(){}
|
||||
|
||||
void simpleMethod() throws IOException;
|
||||
|
||||
}
|
||||
```
|
||||
进行反编译(去除了一些没用信息)
|
||||
```java
|
||||
{
|
||||
public static final int simpleFiled;
|
||||
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
|
||||
|
||||
public static final int staticField;
|
||||
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
|
||||
|
||||
public static void main(java.lang.String[]);
|
||||
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
|
||||
|
||||
public static void staticMethod();
|
||||
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
|
||||
|
||||
public void defaultMethod();
|
||||
flags: (0x0001) ACC_PUBLIC
|
||||
|
||||
public abstract void simpleMethod() throws java.io.IOException;
|
||||
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
|
||||
Exceptions:
|
||||
throws java.io.IOException
|
||||
}
|
||||
```
|
||||
可以看到 default关键字修饰的方法是像实例方法(就是普通类中定义的普通方法)一样定义的,所以我们来定义一个只有default方法的接口并且实现一下这个接口试一
|
||||
试。
|
||||
```java
|
||||
interface Default {
|
||||
default int defaultMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultMethod implements Default {
|
||||
public static void main(String[] args) {
|
||||
DefaultMethod defaultMethod = new DefaultMethod();
|
||||
System.out.println(defaultMethod.defaultMethod());
|
||||
//compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context
|
||||
//! DefaultMethod.defaultMethod();
|
||||
}
|
||||
}
|
||||
```
|
||||
可以看到default方法确实像实例方法一样,必须有实例对象才能调用,并且子类在实现接口时,可以不用实现default方法,也可以选择覆盖该方法。
|
||||
这有点像子类继承父类实例方法。
|
||||
<br>
|
||||
接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用**
|
||||
```java
|
||||
interface Static {
|
||||
static int staticMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
... main(String...args)
|
||||
//!compile error: Static method may be invoked on containing interface class only
|
||||
//!aInstanceOfStatic.staticMethod();
|
||||
...
|
||||
```
|
||||
另一个问题是多继承问题,大家知道Java中类是不支持多继承的,但是接口是多继承和多实现(implements后跟多个接口)的,
|
||||
那么如果一个接口继承另一个接口,两个接口都有同名的default方法会怎么样呢?答案是会像类继承一样覆写(@Override),以下代码在IDE中可以顺利编译
|
||||
```java
|
||||
interface Default {
|
||||
default int defaultMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
interface Default2 extends Default {
|
||||
@Override
|
||||
default int defaultMethod() {
|
||||
return 9527;
|
||||
}
|
||||
}
|
||||
public class DefaultMethod implements Default,Default2 {
|
||||
public static void main(String[] args) {
|
||||
DefaultMethod defaultMethod = new DefaultMethod();
|
||||
System.out.println(defaultMethod.defaultMethod());
|
||||
}
|
||||
}
|
||||
|
||||
输出 : 9527
|
||||
```
|
||||
出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。
|
||||
<br>
|
||||
那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?IDE会要求你重写这个冲突的方法,让你自己选择去执行哪个方法,因为IDE它还没智能到你不告诉它,它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。
|
||||
```java
|
||||
interface Default {
|
||||
default int defaultMethod() {
|
||||
return 4396;
|
||||
}
|
||||
}
|
||||
|
||||
interface Default2 {
|
||||
default int defaultMethod() {
|
||||
return 9527;
|
||||
}
|
||||
}
|
||||
//如果不重写
|
||||
//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2
|
||||
public class DefaultMethod implements Default,Default2 {
|
||||
@Override
|
||||
public int defaultMethod() {
|
||||
System.out.println(Default.super.defaultMethod());
|
||||
System.out.println(Default2.super.defaultMethod());
|
||||
return 996;
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
DefaultMethod defaultMethod = new DefaultMethod();
|
||||
System.out.println(defaultMethod.defaultMethod());
|
||||
}
|
||||
}
|
||||
|
||||
运行输出 :
|
||||
4396
|
||||
9527
|
||||
996
|
||||
```
|
||||
|
||||
<!-- ---------------------------------------------------接口默认方法结束---------------------------------------------------- -->
|
||||
___
|
||||
<!-- --------------------------------------------------- 改进的类型推断 ---------------------------------------------------- -->
|
||||
<span id="ImprovedTypeInference"></span>
|
||||
## 改进的类型推断
|
||||
### 1.什么是类型推断
|
||||
类型推断就像它的字面意思一样,编译器根据<b><i>你显示声明的已知的信息</i></b> 推断出你没有显示声明的类型,这就是类型推断。
|
||||
看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的:
|
||||
```java
|
||||
Map<String, Object> map = new Map<String, Object>();
|
||||
```
|
||||
而我们平常写的都是这样的:
|
||||
```java
|
||||
Map<String, Object> map = new Map<>();
|
||||
```
|
||||
这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题
|
||||
(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。
|
||||
(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽)
|
||||
<br>
|
||||
在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译
|
||||
错误
|
||||
```java
|
||||
List<String> stringList = new ArrayList<>();
|
||||
stringList.add("A");
|
||||
//error : addAll(java.util.Collection<? extends java.lang.String>)in List cannot be applied to (java.util.List<java.lang.Object>)
|
||||
stringList.addAll(Arrays.asList());
|
||||
```
|
||||
但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码
|
||||
会更少。加强的类型推断还有一个就是用于Lambda表达式了。
|
||||
<br>
|
||||
大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息,
|
||||
相信随着Java的进化,这些便利的功能会越来越强大。
|
||||
|
||||
<!-- --------------------------------------------------- 改进的类型推断结束------------------------------------------------- -->
|
||||
____
|
||||
<!-- --------------------------------------------------- 反射获得方法参数信息------------------------------------------------- -->
|
||||
<span id="MethodParameterReflection"></span>
|
||||
## 通过反射获得方法的参数信息
|
||||
JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里
|
||||
也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外
|
||||
的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如
|
||||
LocalVariableTable,javac就不会默认生成,需要你加上 -g:vars来强制让编译器生成,同样的,方法参数信息也需要加上
|
||||
-parameters来让javac为你在.class文件中生成这些信息,否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前,先看一下javac加上该
|
||||
参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。
|
||||
```java
|
||||
public class ByteCodeParameters {
|
||||
public String simpleMethod(String canUGetMyName, Object yesICan) {
|
||||
return "9527";
|
||||
}
|
||||
}
|
||||
```
|
||||
先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:
|
||||
```java
|
||||
//只截取了部分信息
|
||||
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
|
||||
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
|
||||
flags: (0x0001) ACC_PUBLIC
|
||||
Code:
|
||||
stack=1, locals=3, args_size=3
|
||||
0: ldc #2 // String 9527
|
||||
2: areturn
|
||||
LineNumberTable:
|
||||
line 5: 0
|
||||
//这个方法的描述到这里就结束了
|
||||
```
|
||||
接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:
|
||||
```java
|
||||
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
|
||||
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
|
||||
flags: (0x0001) ACC_PUBLIC
|
||||
Code:
|
||||
stack=1, locals=3, args_size=3
|
||||
0: ldc #2 // String 9527
|
||||
2: areturn
|
||||
LineNumberTable:
|
||||
line 8: 0
|
||||
MethodParameters:
|
||||
Name Flags
|
||||
canUGetMyName
|
||||
yesICan
|
||||
```
|
||||
可以看到.class文件里多了一个MethodParameters信息,这就是参数的名字,可以看到默认是不保存的。
|
||||
<br>下面看一下在Intelj Idea里运行的这个例子,我们试一下通过反射获取方法名 :
|
||||
```java
|
||||
public class ByteCodeParameters {
|
||||
public String simpleMethod(String canUGetMyName, Object yesICan) {
|
||||
return "9527";
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws NoSuchMethodException {
|
||||
Class<?> clazz = ByteCodeParameters.class;
|
||||
Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class);
|
||||
Parameter[] parameters = simple.getParameters();
|
||||
for (Parameter p : parameters) {
|
||||
System.out.println(p.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
输出 :
|
||||
arg0
|
||||
arg1
|
||||
```
|
||||
???说好的方法名呢????别急,哈哈。前面说了,默认是不生成参数名信息的,因此我们需要做一些配置,我们找到IDEA的settings里的Java Compiler选项,在
|
||||
Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自
|
||||
己编译一个.class文件放在IDEA的out下,然后再来运行 :
|
||||
```java
|
||||
输出 :
|
||||
canUGetMyName
|
||||
yesICan
|
||||
```
|
||||
这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]
|
||||
(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)
|
||||
<br>
|
||||
## 总结与补充
|
||||
在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework
|
||||
里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。
|
||||
<!-- --------------------------------------------------- 反射获得方法参数信息结束------------------------------------------------- -->
|
||||
____
|
||||
<!-- --------------------------------------------------- JDK8流库------------------------------------------------------------- -->
|
||||
<span id="stream"></span>
|
||||
|
||||
<!-- --------------------------------------------------- JDK8流库结束------------------------------------------------- -->
|
||||
___
|
@ -1,75 +0,0 @@
|
||||
Stream API 旨在让编码更高效率、干净、简洁。
|
||||
|
||||
### 从迭代器到Stream操作
|
||||
|
||||
当使用 `Stream` 时,我们一般会通过三个阶段建立一个流水线:
|
||||
|
||||
1. 创建一个 `Stream`;
|
||||
2. 进行一个或多个中间操作;
|
||||
3. 使用终止操作产生一个结果,`Stream` 就不会再被使用了。
|
||||
|
||||
**案例1:统计 List 中的单词长度大于6的个数**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 案例1:统计 List 中的单词长度大于6的个数
|
||||
*/
|
||||
ArrayList<String> wordsList = new ArrayList<String>();
|
||||
wordsList.add("Charles");
|
||||
wordsList.add("Vincent");
|
||||
wordsList.add("William");
|
||||
wordsList.add("Joseph");
|
||||
wordsList.add("Henry");
|
||||
wordsList.add("Bill");
|
||||
wordsList.add("Joan");
|
||||
wordsList.add("Linda");
|
||||
int count = 0;
|
||||
```
|
||||
Java8之前我们通常用迭代方法来完成上面的需求:
|
||||
|
||||
```java
|
||||
//迭代(Java8之前的常用方法)
|
||||
//迭代不好的地方:1. 代码多;2 很难被并行运算。
|
||||
for (String word : wordsList) {
|
||||
if (word.length() > 6) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
System.out.println(count);//3
|
||||
```
|
||||
Java8之前我们使用 `Stream` 一行代码就能解决了,而且可以瞬间转换为并行执行的效果:
|
||||
|
||||
```java
|
||||
//Stream
|
||||
//将stream()改为parallelStream()就可以瞬间将代码编程并行执行的效果
|
||||
long count2=wordsList.stream()
|
||||
.filter(w->w.length()>6)
|
||||
.count();
|
||||
long count3=wordsList.parallelStream()
|
||||
.filter(w->w.length()>6)
|
||||
.count();
|
||||
System.out.println(count2);
|
||||
System.out.println(count3);
|
||||
```
|
||||
|
||||
### `distinct()`
|
||||
|
||||
去除 List 中重复的 String
|
||||
|
||||
```java
|
||||
List<String> list = list.stream()
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
```
|
||||
|
||||
### `map`
|
||||
|
||||
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
|
||||
|
||||
```java
|
||||
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
|
||||
// 获取 List 中每个元素对应的平方数并去重
|
||||
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
|
||||
System.out.println(squaresList.toString());//[9, 4, 49, 25]
|
||||
```
|
||||
|
@ -1,30 +0,0 @@
|
||||
## 改进的类型推断
|
||||
### 1.什么是类型推断
|
||||
类型推断就像它的字面意思一样,编译器根据<b><i>你显示声明的已知的信息</i></b> 推断出你没有显示声明的类型,这就是类型推断。
|
||||
看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的:
|
||||
```java
|
||||
Map<String, Object> map = new Map<String, Object>();
|
||||
```
|
||||
而我们平常写的都是这样的:
|
||||
```java
|
||||
Map<String, Object> map = new Map<>();
|
||||
```
|
||||
这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题
|
||||
(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。
|
||||
(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽)
|
||||
<br>
|
||||
在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译
|
||||
错误
|
||||
```java
|
||||
List<String> stringList = new ArrayList<>();
|
||||
stringList.add("A");
|
||||
//error : addAll(java.util.Collection<? extends java.lang.String>)in List cannot be applied to (java.util.List<java.lang.Object>)
|
||||
stringList.addAll(Arrays.asList());
|
||||
```
|
||||
但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码
|
||||
会更少。加强的类型推断还有一个就是用于Lambda表达式了。
|
||||
<br>
|
||||
大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息,
|
||||
相信随着Java的进化,这些便利的功能会越来越强大。
|
||||
|
||||
<!-- --------------------------------------------------- 改进的类型推断结束------------------------------------------------- -->
|
@ -1,79 +0,0 @@
|
||||
## 通过反射获得方法的参数信息
|
||||
JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里
|
||||
也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外
|
||||
的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如
|
||||
LocalVariableTable,javac就不会默认生成,需要你加上 -g:vars来强制让编译器生成,同样的,方法参数信息也需要加上
|
||||
-parameters来让javac为你在.class文件中生成这些信息,否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前,先看一下javac加上该
|
||||
参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。
|
||||
```java
|
||||
public class ByteCodeParameters {
|
||||
public String simpleMethod(String canUGetMyName, Object yesICan) {
|
||||
return "9527";
|
||||
}
|
||||
}
|
||||
```
|
||||
先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:
|
||||
```java
|
||||
//只截取了部分信息
|
||||
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
|
||||
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
|
||||
flags: (0x0001) ACC_PUBLIC
|
||||
Code:
|
||||
stack=1, locals=3, args_size=3
|
||||
0: ldc #2 // String 9527
|
||||
2: areturn
|
||||
LineNumberTable:
|
||||
line 5: 0
|
||||
//这个方法的描述到这里就结束了
|
||||
```
|
||||
接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:
|
||||
```java
|
||||
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
|
||||
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
|
||||
flags: (0x0001) ACC_PUBLIC
|
||||
Code:
|
||||
stack=1, locals=3, args_size=3
|
||||
0: ldc #2 // String 9527
|
||||
2: areturn
|
||||
LineNumberTable:
|
||||
line 8: 0
|
||||
MethodParameters:
|
||||
Name Flags
|
||||
canUGetMyName
|
||||
yesICan
|
||||
```
|
||||
可以看到.class文件里多了一个MethodParameters信息,这就是参数的名字,可以看到默认是不保存的。
|
||||
<br>下面看一下在Intelj Idea里运行的这个例子,我们试一下通过反射获取方法名 :
|
||||
```java
|
||||
public class ByteCodeParameters {
|
||||
public String simpleMethod(String canUGetMyName, Object yesICan) {
|
||||
return "9527";
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws NoSuchMethodException {
|
||||
Class<?> clazz = ByteCodeParameters.class;
|
||||
Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class);
|
||||
Parameter[] parameters = simple.getParameters();
|
||||
for (Parameter p : parameters) {
|
||||
System.out.println(p.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
输出 :
|
||||
arg0
|
||||
arg1
|
||||
```
|
||||
???说好的方法名呢????别急,哈哈。前面说了,默认是不生成参数名信息的,因此我们需要做一些配置,我们找到IDEA的settings里的Java Compiler选项,在
|
||||
Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自
|
||||
己编译一个.class文件放在IDEA的out下,然后再来运行 :
|
||||
```java
|
||||
输出 :
|
||||
canUGetMyName
|
||||
yesICan
|
||||
```
|
||||
这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]
|
||||
(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)
|
||||
<br>
|
||||
## 总结与补充
|
||||
在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework
|
||||
里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。
|
@ -56,7 +56,7 @@ static int hash(int h) {
|
||||
|
||||
所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
|
||||
|
||||

|
||||

|
||||
|
||||
### JDK1.8之后
|
||||
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
|
||||
@ -170,7 +170,9 @@ static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
|
||||
```
|
||||
## HashMap源码分析
|
||||
### 构造方法
|
||||

|
||||
|
||||
HashMap 中有四个构造方法,它们分别如下:
|
||||
|
||||
```java
|
||||
// 默认构造函数。
|
||||
public HashMap() {
|
||||
@ -237,9 +239,7 @@ HashMap只提供了put用于添加元素,putVal方法只是给put方法调用
|
||||
- ①如果定位到的数组位置没有元素 就直接插入。
|
||||
- ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用`e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
```java
|
||||
public V put(K key, V value) {
|
||||
|
@ -175,7 +175,7 @@ JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1
|
||||
|
||||
#### 2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
|
||||
|
||||
整个永久代有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 java.lang.OutOfMemoryError。你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
|
||||
整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 java.lang.OutOfMemoryError。你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
|
||||
|
||||
当然这只是其中一个原因,还有很多底层的原因,这里就不提了。
|
||||
|
||||
@ -183,7 +183,7 @@ JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1
|
||||
|
||||
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
|
||||
|
||||
既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
|
||||
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
|
||||
|
||||
**JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。**
|
||||
|
||||
@ -294,7 +294,7 @@ System.out.println(str2==str3);//false
|
||||
**String 类型的常量池比较特殊。它的主要使用方法有两种:**
|
||||
|
||||
- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
|
||||
- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
|
||||
- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,JDK1.7之前(不包含1.7)的处理方式是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,JDK1.7以及之后的处理方式是在常量池中记录此字符串的引用,并返回该引用。
|
||||
|
||||
```java
|
||||
String s1 = new String("计算机");
|
||||
@ -342,7 +342,7 @@ true
|
||||
|
||||
### 4.3 8 种基本类型的包装类和常量池
|
||||
|
||||
- **Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;这 5 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。**
|
||||
- **Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;这 5 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。** 为啥把缓存设置为[-128,127]区间?([参见issue/461](https://github.com/Snailclimb/JavaGuide/issues/461))性能和资源之间的权衡。
|
||||
- **两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。**
|
||||
|
||||
```java
|
||||
@ -421,6 +421,7 @@ i4=i5+i6 true
|
||||
- <http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/>
|
||||
- <https://dzone.com/articles/jvm-permgen-%E2%80%93-where-art-thou>
|
||||
- <https://stackoverflow.com/questions/9095748/method-area-and-permgen>
|
||||
- 深入解析String#intern<https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html>
|
||||
|
||||
## 公众号
|
||||
|
||||
|
@ -54,7 +54,7 @@
|
||||
|
||||
<font color="#999999">3,路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后再进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。
|
||||
|
||||
<font color="#999999">4,互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由器组成边缘部分,其作用是提供连通性和交换。
|
||||
<font color="#999999">4,互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由器组成核心部分,其作用是提供连通性和交换。
|
||||
|
||||
<font color="#999999">5,计算机通信是计算机中进程(即运行着的程序)之间的通信。计算机网络采用的通信方式是客户-服务器方式(C/S方式)和对等连接方式(P2P方式)。
|
||||
|
||||
|
@ -290,7 +290,7 @@ for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没
|
||||
a=3;b=3;
|
||||
val=`expr $a + $b`
|
||||
#输出:Total value : 6
|
||||
echo "Total value : $val
|
||||
echo "Total value : $val"
|
||||
```
|
||||
|
||||
|
||||
@ -467,7 +467,7 @@ done
|
||||
是的!变形金刚 是一个好电影
|
||||
```
|
||||
|
||||
**无线循环:**
|
||||
**无限循环:**
|
||||
|
||||
```shell
|
||||
while true
|
||||
|
@ -0,0 +1,154 @@
|
||||
## 1. 认证 (Authentication) 和授权 (Authorization)的区别是什么?
|
||||
|
||||
这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词,很多人都会把它俩的读音搞混,所以我建议你先先去查一查这两个单词到底该怎么读,他们的具体含义是什么。
|
||||
|
||||
说简单点就是:
|
||||
|
||||
- **认证 (Authentication):** 你是谁。
|
||||
- **授权 (Authorization):** 你有权限干什么。
|
||||
|
||||
稍微正式点(啰嗦点)的说法就是:
|
||||
|
||||
- **Authentication(认证)** 是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
|
||||
- **Authorization(授权)** 发生在 **Authentication(认证)**之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。
|
||||
|
||||
这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。
|
||||
|
||||
## 2. 什么是Cookie ? Cookie的作用是什么?如何在服务端使用 Cookie ?
|
||||
|
||||
### 2.1 什么是Cookie ? Cookie的作用是什么?
|
||||
|
||||
Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
|
||||
|
||||
维基百科是这样定义 Cookie 的:Cookies是某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。简单来说: **Cookie 存放在客户端,一般用来保存用户信息**。
|
||||
|
||||
下面是 Cookie 的一些应用案例:
|
||||
|
||||
1. 我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。除此之外,Cookie 还能保存用户首选项,主题和其他设置信息。
|
||||
2. 使用Cookie 保存 session 或者 token ,向后端发送请求的时候带上 Cookie,这样后端就能取到session或者token了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。
|
||||
3. Cookie 还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为HTTP协议是没有状态的,如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在Cookie
|
||||
|
||||
### 2.2 如何能在 服务端使用 Cookie 呢?
|
||||
|
||||
这部分内容参考:https://attacomsian.com/blog/cookies-spring-boot,更多如何在Spring Boot中使用Cookie 的内容可以查看这篇文章。
|
||||
|
||||
**1)设置cookie返回给客户端**
|
||||
|
||||
```java
|
||||
@GetMapping("/change-username")
|
||||
public String setCookie(HttpServletResponse response) {
|
||||
// 创建一个 cookie
|
||||
Cookie cookie = new Cookie("username", "Jovan");
|
||||
//设置 cookie过期时间
|
||||
cookie.setMaxAge(7 * 24 * 60 * 60); // expires in 7 days
|
||||
//添加到 response 中
|
||||
response.addCookie(cookie);
|
||||
|
||||
return "Username is changed!";
|
||||
}
|
||||
```
|
||||
|
||||
**2) 使用Spring框架提供的`@CookieValue`注解获取特定的 cookie的值**
|
||||
|
||||
```java
|
||||
@GetMapping("/")
|
||||
public String readCookie(@CookieValue(value = "username", defaultValue = "Atta") String username) {
|
||||
return "Hey! My username is " + username;
|
||||
}
|
||||
```
|
||||
|
||||
**3) 读取所有的 Cookie 值**
|
||||
|
||||
```java
|
||||
@GetMapping("/all-cookies")
|
||||
public String readAllCookies(HttpServletRequest request) {
|
||||
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
return Arrays.stream(cookies)
|
||||
.map(c -> c.getName() + "=" + c.getValue()).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
return "No cookies";
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Cookie 和 Session 有什么区别?如何使用Session进行身份验证?
|
||||
|
||||
**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
|
||||
|
||||
**Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。**
|
||||
|
||||
**那么,如何使用Session进行身份验证?**
|
||||
|
||||
很多时候我们都是通过 SessionID 来实现特定的用户,SessionID 一般会选择存放在 Redis 中。举个例子:用户成功登陆系统,然后返回给客户端具有 SessionID 的 Cookie,当用户向后端发起请求的时候会把 SessionID 带上,这样后端就知道你的身份状态了。关于这种认证方式更详细的过程如下:
|
||||
|
||||

|
||||
|
||||
1. 用户向服务器发送用户名和密码用于登陆系统。
|
||||
2. 服务器验证通过后,服务器为用户创建一个 Session,并将 Session信息存储 起来。
|
||||
3. 服务器向用户返回一个 SessionID,写入用户的 Cookie。
|
||||
4. 当用户保持登录状态时,Cookie 将与每个后续请求一起被发送出去。
|
||||
5. 服务器可以将存储在 Cookie 上的 Session ID 与存储在内存中或者数据库中的 Session 信息进行比较,以验证用户的身份,返回给用户客户端响应信息的时候会附带用户当前的状态。
|
||||
|
||||
另外,Spring Session提供了一种跨多个应用程序或实例管理用户会话信息的机制。如果想详细了解可以查看下面几篇很不错的文章:
|
||||
|
||||
- [Getting Started with Spring Session](https://codeboje.de/spring-session-tutorial/)
|
||||
- [Guide to Spring Session](https://www.baeldung.com/spring-session)
|
||||
- [Sticky Sessions with Spring Session & Redis](https://medium.com/@gvnix/sticky-sessions-with-spring-session-redis-bdc6f7438cc3)
|
||||
|
||||
## 4. 什么是 Token?什么是 JWT?如何基于Token进行身份验证?
|
||||
|
||||
我们在上一个问题中探讨了使用 Session 来鉴别用户的身份,并且给出了几个 Spring Session 的案例分享。 我们知道 Session 信息需要保存一份在服务器端。这种方式会带来一些麻烦,比如需要我们保证保存 Session 信息服务器的可用性、不适合移动端(依赖Cookie)等等。
|
||||
|
||||
有没有一种不需要自己存放 Session 信息就能实现身份验证的方式呢?使用 Token 即可!JWT (JSON Web Token) 就是这种方式的实现,通过这种方式服务器端就不需要保存 Session 数据了,只用在客户端保存服务端返回给客户的 Token 就可以了,扩展性得到提升。
|
||||
|
||||
**JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。**
|
||||
|
||||
下面是 [RFC 7519](https://link.juejin.im/?target=https%3A%2F%2Ftools.ietf.org%2Fhtml%2Frfc7519) 对 JWT 做的较为正式的定义。
|
||||
|
||||
> JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——[JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)
|
||||
|
||||
JWT 由 3 部分构成:
|
||||
|
||||
1. Header :描述 JWT 的元数据。定义了生成签名的算法以及 Token 的类型。
|
||||
2. Payload(负载):用来存放实际需要传递的数据
|
||||
3. Signature(签名):服务器通过`Payload`、`Header`和一个密钥(`secret`)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
|
||||
|
||||
在基于 Token 进行身份验证的的应用程序中,服务器通过`Payload`、`Header`和一个密钥(`secret`)创建令牌(`Token`)并将 `Token` 发送给客户端,客户端将 `Token` 保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization字段中:` Authorization: Bearer Token`。
|
||||
|
||||

|
||||
|
||||
1. 用户向服务器发送用户名和密码用于登陆系统。
|
||||
2. 身份验证服务响应并返回了签名的 JWT,上面包含了用户是谁的内容。
|
||||
3. 用户以后每次向后端发请求都在Header中带上 JWT。
|
||||
4. 服务端检查 JWT 并从中获取用户相关信息。
|
||||
|
||||
|
||||
推荐阅读:
|
||||
|
||||
- [JWT (JSON Web Tokens) Are Better Than Session Cookies](https://dzone.com/articles/jwtjson-web-tokens-are-better-than-session-cookies)
|
||||
- [JSON Web Tokens (JWT) 与 Sessions](https://juejin.im/entry/577b7b56a3413100618c2938)
|
||||
- [JSON Web Token 入门教程](https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html)
|
||||
- [彻底理解Cookie,Session,Token](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485603&idx=1&sn=c8d324f44d6102e7b44554733da10bb7&chksm=cea24768f9d5ce7efe7291ddabce02b68db34073c7e7d9a7dc9a7f01c5a80cebe33ac75248df&token=844918801&lang=zh_CN#rd)
|
||||
|
||||
## 5 什么是OAuth 2.0?
|
||||
|
||||
OAuth 是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。而 OAuth 2.0是对 OAuth 1.0 的完全重新设计,OAuth 2.0更快,更容易实现,OAuth 1.0 已经被废弃。详情请见:[rfc6749](https://tools.ietf.org/html/rfc6749)。
|
||||
|
||||
实际上它就是一种授权机制,它的最终目的是为第三方应用颁发一个有时效性的令牌 token,使得第三方应用能够通过该令牌获取相关的资源。
|
||||
|
||||
OAuth 2.0 比较常用的场景就是第三方登录,当你的网站接入了第三方登录的时候一般就是使用的 OAuth 2.0 协议。
|
||||
|
||||
推荐阅读:
|
||||
|
||||
- [OAuth 2.0 的一个简单解释](http://www.ruanyifeng.com/blog/2019/04/oauth_design.html)
|
||||
- [10 分钟理解什么是 OAuth 2.0 协议](https://deepzz.com/post/what-is-oauth2-protocol.html)
|
||||
- [OAuth 2.0 的四种方式](http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html)
|
||||
- [GitHub OAuth 第三方登录示例教程](http://www.ruanyifeng.com/blog/2019/04/github-oauth.html)
|
||||
|
||||
## 参考
|
||||
|
||||
- https://medium.com/@sherryhsu/session-vs-token-based-authentication-11a6c5ac45e4
|
||||
- https://www.varonis.com/blog/what-is-oauth/
|
||||
- https://tools.ietf.org/html/rfc6749
|
@ -1,27 +1,6 @@
|
||||
<!-- TOC -->
|
||||
这篇文章主要是想通过一些问题,加深大家对于 Spring 的理解,所以不会涉及太多的代码!这篇文章整理了挺长时间,下面的很多问题我自己在使用 Spring 的过程中也并没有注意,自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章,我感觉大部分都是互相 copy,而且很多问题也不是很好,有些回答也存在问题。所以,自己花了一周的业余时间整理了一下,希望对大家有帮助。
|
||||
|
||||
- [什么是 Spring 框架?](#什么是-spring-框架)
|
||||
- [列举一些重要的Spring模块?](#列举一些重要的spring模块)
|
||||
- [谈谈自己对于 Spring IoC 和 AOP 的理解](#谈谈自己对于-spring-ioc-和-aop-的理解)
|
||||
- [Spring AOP 和 AspectJ AOP 有什么区别?](#spring-aop-和-aspectj-aop-有什么区别)
|
||||
- [Spring 中的 bean 的作用域有哪些?](#spring-中的-bean-的作用域有哪些)
|
||||
- [Spring 中的单例 bean 的线程安全问题了解吗?](#spring-中的单例-bean-的线程安全问题了解吗)
|
||||
- [Spring 中的 bean 生命周期?](#spring-中的-bean-生命周期)
|
||||
- [说说自己对于 Spring MVC 了解?](#说说自己对于-spring-mvc-了解)
|
||||
- [SpringMVC 工作原理了解吗?](#springmvc-工作原理了解吗)
|
||||
- [Spring 框架中用到了哪些设计模式?](#spring-框架中用到了哪些设计模式)
|
||||
- [@Component 和 @Bean 的区别是什么?](#component-和-bean-的区别是什么)
|
||||
- [将一个类声明为Spring的 bean 的注解有哪些?](#将一个类声明为spring的-bean-的注解有哪些)
|
||||
- [Spring 管理事务的方式有几种?](#spring-管理事务的方式有几种)
|
||||
- [Spring 事务中的隔离级别有哪几种?](#spring-事务中的隔离级别有哪几种)
|
||||
- [Spring 事务中哪几种事务传播行为?](#spring-事务中哪几种事务传播行为)
|
||||
- [参考](#参考)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
这篇文章主要是想通过一些问题,加深大家对于 Spring 的理解,所以不会涉及太多的代码!这篇文章整理了挺长时间,下面的很多问题我自己在使用 Spring 的过程中也并没有注意,自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章,我感觉大部分都是互相 copy,而且很多问题也不是很汗,有些回答也存在问题。所以,自己花了一周的业余时间整理了一下,希望对大家有帮助。
|
||||
|
||||
## 什么是 Spring 框架?
|
||||
## 1. 什么是 Spring 框架?
|
||||
|
||||
Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:<https://spring.io/>。
|
||||
|
||||
@ -36,7 +15,7 @@ Spring 官网列出的 Spring 的 6 个特征:
|
||||
- **集成** :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
|
||||
- **语言** :Kotlin,Groovy,动态语言。
|
||||
|
||||
## 列举一些重要的Spring模块?
|
||||
## 2. 列举一些重要的Spring模块?
|
||||
|
||||
下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
|
||||
|
||||
@ -51,9 +30,38 @@ Spring 官网列出的 Spring 的 6 个特征:
|
||||
- **Spring Web** : 为创建Web应用程序提供支持。
|
||||
- **Spring Test** : 提供了对 JUnit 和 TestNG 测试的支持。
|
||||
|
||||
## 谈谈自己对于 Spring IoC 和 AOP 的理解
|
||||
## 3. @RestController vs @Controller
|
||||
|
||||
### IoC
|
||||
**`Controller` 返回一个页面**
|
||||
|
||||
单独使用 `@Controller` 不加 `@ResponseBody`的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况。
|
||||
|
||||

|
||||
|
||||
**`@RestController` 返回JSON 或 XML 形式数据**
|
||||
|
||||
但`@RestController`只返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。
|
||||
|
||||

|
||||
|
||||
**`@Controller +@ResponseBody` 返回JSON 或 XML 形式数据**
|
||||
|
||||
如果你需要在Spring4之前开发 RESTful Web服务的话,你需要使用`@Controller` 并结合`@ResponseBody`注解,也就是说`@Controller` +`@ResponseBody`= `@RestController`(Spring 4 之后新加的注解)。
|
||||
|
||||
> `@ResponseBody` 注解的作用是将 `Controller` 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到HTTP 响应(Response)对象的 body 中,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。
|
||||
|
||||

|
||||
|
||||
Reference:
|
||||
|
||||
- https://dzone.com/articles/spring-framework-restcontroller-vs-controller(图片来源)
|
||||
- https://javarevisited.blogspot.com/2017/08/difference-between-restcontroller-and-controller-annotations-spring-mvc-rest.html?m=1
|
||||
|
||||
## 4. Spring IOC & AOP
|
||||
|
||||
### 4.1 谈谈自己对于 Spring IoC 和 AOP 的理解
|
||||
|
||||
#### IoC
|
||||
|
||||
IoC(Inverse of Control:控制反转)是一种**设计思想**,就是 **将原本在程序中手动创建对象的控制权,交由Spring框架来管理。** IoC 在其他语言中也有应用,并非 Spirng 特有。 **IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。**
|
||||
|
||||
@ -64,13 +72,14 @@ Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉
|
||||
推荐阅读:https://www.zhihu.com/question/23277575/answer/169698662
|
||||
|
||||
**Spring IoC的初始化过程:**
|
||||

|
||||
|
||||

|
||||
|
||||
IoC源码阅读
|
||||
|
||||
- https://javadoop.com/post/spring-ioc
|
||||
|
||||
### AOP
|
||||
#### AOP
|
||||
|
||||
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,**却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来**,便于**减少系统的重复代码**,**降低模块间的耦合度**,并**有利于未来的可拓展性和可维护性**。
|
||||
|
||||
@ -82,7 +91,7 @@ AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无
|
||||
|
||||
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
|
||||
|
||||
## Spring AOP 和 AspectJ AOP 有什么区别?
|
||||
### 4.2 Spring AOP 和 AspectJ AOP 有什么区别?
|
||||
|
||||
**Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。** Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
|
||||
|
||||
@ -90,7 +99,9 @@ AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无
|
||||
|
||||
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。
|
||||
|
||||
## Spring 中的 bean 的作用域有哪些?
|
||||
## 5. Spring bean
|
||||
|
||||
### 5.1 Spring 中的 bean 的作用域有哪些?
|
||||
|
||||
- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
|
||||
- prototype : 每次请求都会创建一个新的 bean 实例。
|
||||
@ -98,7 +109,7 @@ AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无
|
||||
- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
|
||||
- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
|
||||
|
||||
## Spring 中的单例 bean 的线程安全问题了解吗?
|
||||
### 5.2 Spring 中的单例 bean 的线程安全问题了解吗?
|
||||
|
||||
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
|
||||
|
||||
@ -108,79 +119,8 @@ AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无
|
||||
|
||||
2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
|
||||
|
||||
## Spring 中的 bean 生命周期?
|
||||
|
||||
这部分网上有很多文章都讲到了,下面的内容整理自:<https://yemengying.com/2016/07/14/spring-bean-life-cycle/> ,除了这篇文章,再推荐一篇很不错的文章 :<https://www.cnblogs.com/zrtqsk/p/3735273.html> 。
|
||||
|
||||
- Bean 容器找到配置文件中 Spring Bean 的定义。
|
||||
- Bean 容器利用 Java Reflection API 创建一个Bean的实例。
|
||||
- 如果涉及到一些属性值 利用 `set()`方法设置一些属性值。
|
||||
- 如果 Bean 实现了 `BeanNameAware` 接口,调用 `setBeanName()`方法,传入Bean的名字。
|
||||
- 如果 Bean 实现了 `BeanClassLoaderAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoader`对象的实例。
|
||||
- 如果Bean实现了 `BeanFactoryAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoade` r对象的实例。
|
||||
- 与上面的类似,如果实现了其他 `*.Aware`接口,就调用相应的方法。
|
||||
- 如果有和加载这个 Bean 的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessBeforeInitialization()` 方法
|
||||
- 如果Bean实现了`InitializingBean`接口,执行`afterPropertiesSet()`方法。
|
||||
- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
|
||||
- 如果有和加载这个 Bean的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessAfterInitialization()` 方法
|
||||
- 当要销毁 Bean 的时候,如果 Bean 实现了 `DisposableBean` 接口,执行 `destroy()` 方法。
|
||||
- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
|
||||
|
||||
图示:
|
||||
|
||||

|
||||
|
||||
与之比较类似的中文版本:
|
||||
|
||||

|
||||
|
||||
## 说说自己对于 Spring MVC 了解?
|
||||
|
||||
谈到这个问题,我们不得不提提之前 Model1 和 Model2 这两个没有 Spring MVC 的时代。
|
||||
|
||||
- **Model1 时代** : 很多学 Java 后端比较晚的朋友可能并没有接触过 Model1 模式下的 JavaWeb 应用开发。在 Model1 模式下,整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。这个模式下 JSP 即是控制层又是表现层。显而易见,这种模式存在很多问题。比如①将控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;②前端和后端相互依赖,难以进行测试并且开发效率极低;
|
||||
- **Model2 时代** :学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View,)+Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。Model:系统涉及的数据,也就是 dao 和 bean。View:展示模型中的数据,只是用来展示。Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。
|
||||
|
||||
Model2 模式下还存在很多问题,Model2的抽象和封装程度还远远不够,使用Model2进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。于是很多JavaWeb开发相关的 MVC 框架营运而生比如Struts2,但是 Struts2 比较笨重。随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。
|
||||
|
||||
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的Web层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。
|
||||
|
||||
**Spring MVC 的简单原理图如下:**
|
||||
|
||||

|
||||
|
||||
## SpringMVC 工作原理了解吗?
|
||||
|
||||
**原理如下图所示:**
|
||||

|
||||
|
||||
上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 `DispatcherServlet` 的作用是接收请求,响应结果。
|
||||
|
||||
**流程说明(重要):**
|
||||
|
||||
1. 客户端(浏览器)发送请求,直接请求到 `DispatcherServlet`。
|
||||
2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping`,解析请求对应的 `Handler`。
|
||||
3. 解析到对应的 `Handler`(也就是我们平常说的 `Controller` 控制器)后,开始由 `HandlerAdapter` 适配器处理。
|
||||
4. `HandlerAdapter` 会根据 `Handler `来调用真正的处理器开处理请求,并处理相应的业务逻辑。
|
||||
5. 处理器处理完业务后,会返回一个 `ModelAndView` 对象,`Model` 是返回的数据对象,`View` 是个逻辑上的 `View`。
|
||||
6. `ViewResolver` 会根据逻辑 `View` 查找实际的 `View`。
|
||||
7. `DispaterServlet` 把返回的 `Model` 传给 `View`(视图渲染)。
|
||||
8. 把 `View` 返回给请求者(浏览器)
|
||||
|
||||
## Spring 框架中用到了哪些设计模式?
|
||||
|
||||
关于下面一些设计模式的详细介绍,可以看笔主前段时间的原创文章[《面试官:“谈谈Spring中都用到了那些设计模式?”。》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485303&idx=1&sn=9e4626a1e3f001f9b0d84a6fa0cff04a&chksm=cea248bcf9d5c1aaf48b67cc52bac74eb29d6037848d6cf213b0e5466f2d1fda970db700ba41&token=255050878&lang=zh_CN#rd) 。
|
||||
|
||||
- **工厂设计模式** : Spring使用工厂模式通过 `BeanFactory`、`ApplicationContext` 创建 bean 对象。
|
||||
- **代理设计模式** : Spring AOP 功能的实现。
|
||||
- **单例设计模式** : Spring 中的 Bean 默认都是单例的。
|
||||
- **模板方法模式** : Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
|
||||
- **包装器设计模式** : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
|
||||
- **观察者模式:** Spring 事件驱动模型就是观察者模式很经典的一个应用。
|
||||
- **适配器模式** :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配`Controller`。
|
||||
- ......
|
||||
|
||||
## @Component 和 @Bean 的区别是什么?
|
||||
### 5.3 @Component 和 @Bean 的区别是什么?
|
||||
|
||||
1. 作用对象不同: `@Component` 注解作用于类,而`@Bean`注解作用于方法。
|
||||
2. `@Component`通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 `@ComponentScan` 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。`@Bean` 注解通常是我们在标有该注解的方法中定义产生这个 bean,`@Bean`告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
|
||||
@ -223,7 +163,7 @@ public OneService getService(status) {
|
||||
}
|
||||
```
|
||||
|
||||
## 将一个类声明为Spring的 bean 的注解有哪些?
|
||||
### 5.4 将一个类声明为Spring的 bean 的注解有哪些?
|
||||
|
||||
我们一般使用 `@Autowired` 注解自动装配 bean,要想把类标识成可用于 `@Autowired` 注解自动装配的 bean 的类,采用以下注解可实现:
|
||||
|
||||
@ -232,7 +172,83 @@ public OneService getService(status) {
|
||||
- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
|
||||
- `@Controller` : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
|
||||
|
||||
## Spring 管理事务的方式有几种?
|
||||
### 5.5 Spring 中的 bean 生命周期?
|
||||
|
||||
这部分网上有很多文章都讲到了,下面的内容整理自:<https://yemengying.com/2016/07/14/spring-bean-life-cycle/> ,除了这篇文章,再推荐一篇很不错的文章 :<https://www.cnblogs.com/zrtqsk/p/3735273.html> 。
|
||||
|
||||
- Bean 容器找到配置文件中 Spring Bean 的定义。
|
||||
- Bean 容器利用 Java Reflection API 创建一个Bean的实例。
|
||||
- 如果涉及到一些属性值 利用 `set()`方法设置一些属性值。
|
||||
- 如果 Bean 实现了 `BeanNameAware` 接口,调用 `setBeanName()`方法,传入Bean的名字。
|
||||
- 如果 Bean 实现了 `BeanClassLoaderAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoader`对象的实例。
|
||||
- 如果Bean实现了 `BeanFactoryAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoade` r对象的实例。
|
||||
- 与上面的类似,如果实现了其他 `*.Aware`接口,就调用相应的方法。
|
||||
- 如果有和加载这个 Bean 的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessBeforeInitialization()` 方法
|
||||
- 如果Bean实现了`InitializingBean`接口,执行`afterPropertiesSet()`方法。
|
||||
- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
|
||||
- 如果有和加载这个 Bean的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessAfterInitialization()` 方法
|
||||
- 当要销毁 Bean 的时候,如果 Bean 实现了 `DisposableBean` 接口,执行 `destroy()` 方法。
|
||||
- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
|
||||
|
||||
图示:
|
||||
|
||||

|
||||
|
||||
与之比较类似的中文版本:
|
||||
|
||||

|
||||
|
||||
## 6. Spring MVC
|
||||
|
||||
### 6.1 说说自己对于 Spring MVC 了解?
|
||||
|
||||
谈到这个问题,我们不得不提提之前 Model1 和 Model2 这两个没有 Spring MVC 的时代。
|
||||
|
||||
- **Model1 时代** : 很多学 Java 后端比较晚的朋友可能并没有接触过 Model1 模式下的 JavaWeb 应用开发。在 Model1 模式下,整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。这个模式下 JSP 即是控制层又是表现层。显而易见,这种模式存在很多问题。比如①将控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;②前端和后端相互依赖,难以进行测试并且开发效率极低;
|
||||
- **Model2 时代** :学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View,)+Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。Model:系统涉及的数据,也就是 dao 和 bean。View:展示模型中的数据,只是用来展示。Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。
|
||||
|
||||
Model2 模式下还存在很多问题,Model2的抽象和封装程度还远远不够,使用Model2进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。于是很多JavaWeb开发相关的 MVC 框架应运而生比如Struts2,但是 Struts2 比较笨重。随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。
|
||||
|
||||
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的Web层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。
|
||||
|
||||
**Spring MVC 的简单原理图如下:**
|
||||
|
||||

|
||||
|
||||
### 6.2 SpringMVC 工作原理了解吗?
|
||||
|
||||
**原理如下图所示:**
|
||||

|
||||
|
||||
上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 `DispatcherServlet` 的作用是接收请求,响应结果。
|
||||
|
||||
**流程说明(重要):**
|
||||
|
||||
1. 客户端(浏览器)发送请求,直接请求到 `DispatcherServlet`。
|
||||
2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping`,解析请求对应的 `Handler`。
|
||||
3. 解析到对应的 `Handler`(也就是我们平常说的 `Controller` 控制器)后,开始由 `HandlerAdapter` 适配器处理。
|
||||
4. `HandlerAdapter` 会根据 `Handler `来调用真正的处理器开处理请求,并处理相应的业务逻辑。
|
||||
5. 处理器处理完业务后,会返回一个 `ModelAndView` 对象,`Model` 是返回的数据对象,`View` 是个逻辑上的 `View`。
|
||||
6. `ViewResolver` 会根据逻辑 `View` 查找实际的 `View`。
|
||||
7. `DispaterServlet` 把返回的 `Model` 传给 `View`(视图渲染)。
|
||||
8. 把 `View` 返回给请求者(浏览器)
|
||||
|
||||
## 7. Spring 框架中用到了哪些设计模式?
|
||||
|
||||
关于下面一些设计模式的详细介绍,可以看笔主前段时间的原创文章[《面试官:“谈谈Spring中都用到了那些设计模式?”。》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485303&idx=1&sn=9e4626a1e3f001f9b0d84a6fa0cff04a&chksm=cea248bcf9d5c1aaf48b67cc52bac74eb29d6037848d6cf213b0e5466f2d1fda970db700ba41&token=255050878&lang=zh_CN#rd) 。
|
||||
|
||||
- **工厂设计模式** : Spring使用工厂模式通过 `BeanFactory`、`ApplicationContext` 创建 bean 对象。
|
||||
- **代理设计模式** : Spring AOP 功能的实现。
|
||||
- **单例设计模式** : Spring 中的 Bean 默认都是单例的。
|
||||
- **模板方法模式** : Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
|
||||
- **包装器设计模式** : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
|
||||
- **观察者模式:** Spring 事件驱动模型就是观察者模式很经典的一个应用。
|
||||
- **适配器模式** :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配`Controller`。
|
||||
- ......
|
||||
|
||||
## 8. Spring 事务
|
||||
|
||||
### 8.1 Spring 管理事务的方式有几种?
|
||||
|
||||
1. 编程式事务,在代码中硬编码。(不推荐使用)
|
||||
2. 声明式事务,在配置文件中配置(推荐使用)
|
||||
@ -242,7 +258,7 @@ public OneService getService(status) {
|
||||
1. 基于XML的声明式事务
|
||||
2. 基于注解的声明式事务
|
||||
|
||||
## Spring 事务中的隔离级别有哪几种?
|
||||
### 8.2 Spring 事务中的隔离级别有哪几种?
|
||||
|
||||
**TransactionDefinition 接口中定义了五个表示隔离级别的常量:**
|
||||
|
||||
@ -252,7 +268,7 @@ public OneService getService(status) {
|
||||
- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。**
|
||||
- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
|
||||
|
||||
## Spring 事务中哪几种事务传播行为?
|
||||
### 8.3 Spring 事务中哪几种事务传播行为?
|
||||
|
||||
**支持当前事务的情况:**
|
||||
|
||||
@ -270,17 +286,65 @@ public OneService getService(status) {
|
||||
|
||||
- **TransactionDefinition.PROPAGATION_NESTED:** 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
|
||||
|
||||
### 8.4 @Transactional(rollbackFor = Exception.class)注解了解吗?
|
||||
|
||||
我们知道:Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
|
||||
|
||||
当`@Transactional`注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
|
||||
|
||||
在`@Transactional`注解中如果不配置`rollbackFor`属性,那么事物只会在遇到`RuntimeException`的时候才会回滚,加上`rollbackFor=Exception.class`,可以让事物在遇到非运行时异常时也回滚。
|
||||
|
||||
## 9. JPA
|
||||
|
||||
### 9.1 如何使用JPA在数据库中非持久化一个字段?
|
||||
|
||||
假如我们有有下面一个类:
|
||||
|
||||
```java
|
||||
Entity(name="USER")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@Column(name = "ID")
|
||||
private Long id;
|
||||
|
||||
@Column(name="USER_NAME")
|
||||
private String userName;
|
||||
|
||||
@Column(name="PASSWORD")
|
||||
private String password;
|
||||
|
||||
private String secrect;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
如果我们想让`secrect` 这个字段不被持久化,也就是不被数据库存储怎么办?我们可以采用下面几种方法:
|
||||
|
||||
```java
|
||||
static String transient1; // not persistent because of static
|
||||
final String transient2 = “Satish”; // not persistent because of final
|
||||
transient String transient3; // not persistent because of transient
|
||||
@Transient
|
||||
String transient4; // not persistent because of @Transient
|
||||
```
|
||||
|
||||
一般使用后面两种方式比较多,我个人使用注解的方式比较多。
|
||||
|
||||
|
||||
## 参考
|
||||
|
||||
- 《Spring 技术内幕》
|
||||
- <http://www.cnblogs.com/wmyskxz/p/8820371.html>
|
||||
- <https://www.journaldev.com/2696/spring-interview-questions-and-answers>
|
||||
- <https://www.edureka.co/blog/interview-questions/spring-interview-questions/>
|
||||
- https://www.cnblogs.com/clwydjgs/p/9317849.html
|
||||
- <https://howtodoinjava.com/interview-questions/top-spring-interview-questions-with-answers/>
|
||||
- <http://www.tomaszezula.com/2014/02/09/spring-series-part-5-component-vs-bean/>
|
||||
- <https://stackoverflow.com/questions/34172888/difference-between-bean-and-autowired>
|
||||
|
||||
### 公众号
|
||||
## 公众号
|
||||
|
||||
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
|
||||
|
||||
@ -288,4 +352,4 @@ public OneService getService(status) {
|
||||
|
||||
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
|
||||
|
||||

|
||||

|
||||
|
110
docs/system-design/framework/spring/springboot-questions.md
Normal file
110
docs/system-design/framework/spring/springboot-questions.md
Normal file
@ -0,0 +1,110 @@
|
||||
|
||||
|
||||
> 本文由JavaGuide整理翻译自(做了适当删减和修改):
|
||||
>
|
||||
> - https://www.javaguides.net/2018/11/spring-boot-interview-questions-and-answers.html
|
||||
> - https://www.algrim.co/posts/101-spring-boot-interview-questions
|
||||
|
||||
### 1. 什么是 Spring Boot?
|
||||
|
||||
首先,重要的是要理解 Spring Boot 并不是一个框架,它是一种创建独立应用程序的更简单方法,只需要很少或没有配置(相比于 Spring 来说)。Spring Boot最好的特性之一是它利用现有的 Spring 项目和第三方项目来开发适合生产的应用程序。
|
||||
|
||||
### 2. 说出使用Spring Boot的主要优点
|
||||
|
||||
1. 开发基于 Spring 的应用程序很容易。
|
||||
2. Spring Boot 项目所需的开发或工程时间明显减少,通常会提高整体生产力。
|
||||
3. Spring Boot不需要编写大量样板代码、XML配置和注释。
|
||||
4. Spring引导应用程序可以很容易地与Spring生态系统集成,如Spring JDBC、Spring ORM、Spring Data、Spring Security等。
|
||||
5. Spring Boot遵循“固执己见的默认配置”,以减少开发工作(默认配置可以修改)。
|
||||
6. Spring Boot 应用程序提供嵌入式HTTP服务器,如Tomcat和Jetty,可以轻松地开发和测试web应用程序。(这点很赞!普通运行Java程序的方式就能运行基于Spring Boot web 项目,省事很多)
|
||||
7. Spring Boot提供命令行接口(CLI)工具,用于开发和测试Spring Boot应用程序,如Java或Groovy。
|
||||
8. Spring Boot提供了多种插件,可以使用内置工具(如Maven和Gradle)开发和测试Spring Boot应用程序。
|
||||
|
||||
### 3. 为什么需要Spring Boot?
|
||||
|
||||
Spring Framework旨在简化J2EE企业应用程序开发。Spring Boot Framework旨在简化Spring开发。
|
||||
|
||||

|
||||
|
||||
### 4. 什么是 Spring Boot Starters?
|
||||
|
||||
Spring Boot Starters 是一系列依赖关系的集合,因为它的存在,项目的依赖之间的关系对我们来说变的更加简单了。举个例子:在没有Spring Boot Starters之前,我们开发REST服务或Web应用程序时; 我们需要使用像Spring MVC,Tomcat和Jackson这样的库,这些依赖我们需要手动一个一个添加。但是,有了 Spring Boot Starters 我们只需要一个只需添加一个**spring-boot-starter-web**一个依赖就可以了,这个依赖包含的字依赖中包含了我们开发REST 服务需要的所有依赖。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 如何在Spring Boot应用程序中使用Jetty而不是Tomcat?
|
||||
|
||||
Spring Boot Web starter使用Tomcat作为默认的嵌入式servlet容器, 如果你想使用 Jetty 的话只需要修改pom.xml(Maven)或者build.gradle(Gradle)就可以了。
|
||||
|
||||
**Maven:**
|
||||
|
||||
```xml
|
||||
<!--从Web启动器依赖中排除Tomcat-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--添加Jetty依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jetty</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle:**
|
||||
|
||||
```groovy
|
||||
compile("org.springframework.boot:spring-boot-starter-web") {
|
||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||
}
|
||||
compile("org.springframework.boot:spring-boot-starter-jetty")
|
||||
```
|
||||
|
||||
说个题外话,从上面可以看出使用 Gradle 更加简洁明了,但是国内目前还是 Maven 使用的多一点,我个人觉得 Gradle 在很多方面都要好很多。
|
||||
|
||||
### 介绍一下@SpringBootApplication注解
|
||||
|
||||
```java
|
||||
package org.springframework.boot.autoconfigure;
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan(excludeFilters = {
|
||||
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
|
||||
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
|
||||
public @interface SpringBootApplication {
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
package org.springframework.boot;
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Configuration
|
||||
public @interface SpringBootConfiguration {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
可以看出大概可以把 `@SpringBootApplication `看作是 `@Configuration`、`@EnableAutoConfiguration`、`@ComponentScan ` 注解的集合。根据 SpringBoot官网,这三个注解的作用分别是:
|
||||
|
||||
- `@EnableAutoConfiguration`:启用 SpringBoot 的自动配置机制
|
||||
- `@ComponentScan`: 扫描被`@Component` (`@Service`,`@Controller`)注解的bean,注解默认会扫描该类所在的包下所有的类。
|
||||
- `@Configuration`:允许在上下文中注册额外的bean或导入其他配置类
|
||||
|
@ -256,3 +256,4 @@ git push origin
|
||||
- [图解Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
|
||||
- [猴子都能懂得Git入门](https://backlog.com/git-tutorial/cn/intro/intro1_1.html)
|
||||
- https://git-scm.com/book/en/v2
|
||||
- [Generating a new SSH key and adding it to the ssh-agent](https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent)
|
||||
|
209
docs/公众号历史文章汇总.md
Normal file
209
docs/公众号历史文章汇总.md
Normal file
@ -0,0 +1,209 @@
|
||||
## 热文
|
||||
|
||||
|
||||
|
||||
- [盘点阿里巴巴 15 款开发者工具](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485159&idx=1&sn=c97c087c45ad6dfc0ef61d80a9d0f702&scene=21#wechat_redirect)
|
||||
- [蚂蚁金服2019实习生面经总结(已拿口头offer)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485147&idx=1&sn=90e525a83a451d8c20298a7ef2d35ab9&scene=21#wechat_redirect)
|
||||
- [一千行 MySQL 学习笔记](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485025&idx=1&sn=1f4e19fc77af28f6795feff6ce7465b9&scene=21#wechat_redirect)
|
||||
- [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485068&idx=1&sn=c37267fe59978dbfcd6a9a54eee1c502&scene=21#wechat_redirect)
|
||||
- [搞定 JVM 垃圾回收就是这么简单](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484877&idx=1&sn=f54d41b68f0cd6cc7c0348a2fddbda9f&chksm=cea24a06f9d5c3102bfef946ba6c7cc5df9a503ccb14b9b141c54e179617e4923c260c0b0a01&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [【原创】Java学习路线以及方法推荐](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485290&idx=1&sn=569fa9724aae83bff3a353aefc5b7f1c&scene=21#wechat_redirect)
|
||||
- [技术面试复习大纲](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485505&idx=1&sn=f7d916334c078bc3fdc2933f889b5016&chksm=cea2478af9d5ce9cafcfe9a053e49e84296d8b1929f79844bba59c8c3d8b56753f34a2c2f6a9&token=1701499214&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
## Java
|
||||
|
||||
### 必看书籍
|
||||
|
||||
- [Java学习必备书籍推荐终极版!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485113&idx=1&sn=e4dd1bb22778e4e9139bf29d98a7492b&chksm=cea24972f9d5c064e5b454b84b9bc0d42f4aec007f20f79b564398e6dec7c0cdcda0e64193b5&token=1482344439&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
### 基础
|
||||
|
||||
- [关于Java基础你不得不会的34个问题](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485015&idx=1&sn=5daa243c3359b88fc88b951f9d08b273&chksm=cea2499cf9d5c08a7698559a2fc27078c6b35856bc2d3588172bf64708c115d4b35d3de80cd9&token=1913747689&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [剖析面试最常见问题之 Java 基础知识](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485173&idx=1&sn=9605f89ed0893b674d14b0c8cf4dc942&chksm=cea2493ef9d5c028a969bb89b53f48fbdd72b975319a844319e3111b15d5dbbc350d91ea5b5a&token=1667678311&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
### Java8新特性
|
||||
|
||||
- [Java 8 新特性最佳指南](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484744&idx=1&sn=9db31dca13d327678845054af75efb74&chksm=cea24a83f9d5c3956f4feb9956b068624ab2fdd6c4a75fe52d5df5dca356a016577301399548&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [看完这篇文章,别说自己不会用Lambda表达式了!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485425&idx=1&sn=3cc01bc7c42549b6b6aa62b3656e02d1&chksm=cea2483af9d5c12cd10174dac4465a631b14a6d6a09495b018a98e01c698e86368d26b3be03d&token=1667678311&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
### JVM
|
||||
|
||||
- [Java内存区域](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485068&idx=1&sn=c37267fe59978dbfcd6a9a54eee1c502&scene=21#wechat_redirect)
|
||||
- [垃圾回收](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484877&idx=1&sn=f54d41b68f0cd6cc7c0348a2fddbda9f&chksm=cea24a06f9d5c3102bfef946ba6c7cc5df9a503ccb14b9b141c54e179617e4923c260c0b0a01&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [谈Java类文件结构](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485250&idx=2&sn=33793bcce3f2ff31b83cf2f9c32df153&scene=21#wechat_redirect)
|
||||
- [类加载过程](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485264&idx=2&sn=8c97f7e7d7ad36bc50e713572dbd1529&scene=21#wechat_redirect)
|
||||
|
||||
### 并发编程
|
||||
|
||||
- [并发编程面试必备:JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484834&idx=1&sn=7d3835091af8125c13fc6db765f4c5bd&chksm=cea24a69f9d5c37ff88a8328214cb48b06afb9dc82e46cd924d2595f109ea28922212f9e653c&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484832&idx=1&sn=f902febd050eac59d67fc0804d7e1ad5&chksm=cea24a6bf9d5c37d6b505fe1d43e4fb709729149f1f77344b4a0f5956cab5020a2e102f2adf2&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [BATJ都爱问的多线程面试题](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484831&idx=1&sn=e22d2832a436dbb94233272429d4c4c4&chksm=cea24a54f9d5c3420e96aa94e3d893f4cf825852fff0b4a7e4e241cc229f4666f3dc4d53955e&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [通俗易懂,JDK 并发容器总结](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484797&idx=1&sn=e28462eec497e38053d9fb9ba17ff022&chksm=cea24ab6f9d5c3a05b5ad36111e93d824ce964442366bc8add1cd77cb432057e4995592c8024&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
### 代码质量
|
||||
|
||||
- [八点建议助您写出优雅的Java代码](https://mp.weixin.qq.com/s/o3BGTdAa8VufcIKU0ScBqA)
|
||||
- [十分钟搞懂Java效率工具Lombok使用与原理](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485385&idx=2&sn=a7c3fb4485ffd8c019e5541e9b1580cd&chksm=cea24802f9d5c1144eee0da52cfc0cc5e8ee3590990de3bb642df4d4b2a8cd07f12dd54947b9&token=1667678311&lang=zh_CN#rd)
|
||||
- [如何写出让同事无法维护的代码?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485413&idx=2&sn=70336d94018ad93d67cfacb4aeceb01b&chksm=cea2482ef9d5c1382d8a009e2ecd680c3b6ac3c7c02810af8901970e69c431273113ca7e4447&token=1667678311&lang=zh_CN#rd)
|
||||
- [Code Review最佳实践](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485494&idx=1&sn=c7f160fd1bb13a20b887493003acbbf9&chksm=cea247fdf9d5ceebf3b98bd7c524d0ecc672d4bcb10a70fae90390abd862538aa3283c93375b&token=1701499214&lang=zh_CN&scene=21#wechat_redirect)
|
||||
- [后端开发必备的 RestFul API 知识](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485510&idx=1&sn=e9273322ae638c8465a606737109ab97&chksm=cea2478df9d5ce9b58b9ff1f1e2ecca99e961b911adcec3d5a579b41e01151160cfb2891d91b&token=1701499214&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
## 网络
|
||||
|
||||
- [搞定计算机网络面试,看这篇就够了(补充版)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484889&idx=1&sn=5f9e6f5c29f9514701c246573d15d9fa&chksm=cea24a12f9d5c3041efd5cf864eb69b76aea6ef9c000a72b16d54794aab97d4fb53515a77147&token=1082669959&lang=zh_CN#rd)
|
||||
|
||||
## 系统设计
|
||||
|
||||
### Spring
|
||||
|
||||
- [Spring常见问题总结(补充版)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485576&idx=1&sn=f993349f12650a68904e1d99d2465131&chksm=cea24743f9d5ce55ffe543a0feaf2c566382024b625b59283482da6ab0cbcf2a3c9dc5b64a53&token=2133161636&lang=zh_CN#rd)
|
||||
- [面试官:“谈谈Spring中都用到了那些设计模式?”。](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485303&idx=1&sn=9e4626a1e3f001f9b0d84a6fa0cff04a&chksm=cea248bcf9d5c1aaf48b67cc52bac74eb29d6037848d6cf213b0e5466f2d1fda970db700ba41&token=1667678311&lang=zh_CN#rd)
|
||||
- [可能是最漂亮的Spring事务管理详解](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484943&idx=1&sn=46b9082af4ec223137df7d1c8303ca24&chksm=cea249c4f9d5c0d2b8212a17252cbfb74e5fbe5488b76d829827421c53332326d1ec360f5d63&token=1082669959&lang=zh_CN#rd)
|
||||
- [SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484846&idx=1&sn=490014ea65669c1a1e73e25d7b9fa569&chksm=cea24a65f9d5c373d31d6cdd61297db21de63462c1c03c34b7025a0d0b93f1182b2ad7e33cab&token=1082669959&lang=zh_CN#rd)
|
||||
- [Spring编程式和声明式事务实例讲解](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484942&idx=1&sn=b0d9e6f1af243bbf7f34ede5a6d2277d&chksm=cea249c5f9d5c0d3da9206b753bb7734d47d8d8b43edcc02bdf6f139e34e1db512cf5ed32217&token=1082669959&lang=zh_CN#rd)
|
||||
- [一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484865&idx=2&sn=178c6e64e6c12172e77efdd669eb86a7&chksm=cea24a0af9d5c31c389ae7817613a336f00c330021f73c90afe383c8caf6ea07a9e1f949c68d&token=1082669959&lang=zh_CN#rd)
|
||||
|
||||
### SpringBoot
|
||||
|
||||
- [超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484809&idx=1&sn=a789eba40404e6501d51b24345b28906&chksm=cea24a42f9d5c3544babde7f33790fc54f02ebc2f589ce9fa116bbb9c7b0c0cfb1bc314d17de&token=1082669959&lang=zh_CN#rd)
|
||||
- [基于 SpringBoot2.0+优雅整合 SpringBoot+Mybatis](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484805&idx=2&sn=0e148af5baebae53b3fd365cff689046&chksm=cea24a4ef9d5c35862efc9c67b0619f7e8ade4b75e1001189ededccd8fd35ca5cd19fda074b9&token=1082669959&lang=zh_CN#rd)
|
||||
- [新手也能实现,基于SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484803&idx=1&sn=9179890db39721a59bb815b7f985ec2d&chksm=cea24a48f9d5c35e73d3df4b29d340e1a4c76d43b220c0f9535e77b43a74ff049ee7b89a4a38&token=1082669959&lang=zh_CN#rd)
|
||||
- [SpringBoot 整合 阿里云OSS 存储服务,快来免费搭建一个自己的图床](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484801&idx=1&sn=22d97f9c963d45820559d7226874e33f&chksm=cea24a4af9d5c35cac3177921801287b1ad983eabb6e18cf30302fc235b7f699401510ea2a59&token=1082669959&lang=zh_CN#rd)
|
||||
- [Spring Boot 实现热部署的一种简单方式](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485586&idx=2&sn=01788bf8c64de91d085a01c1f4f5159d&chksm=cea24759f9d5ce4fe914aa43e517f16b7f4066de3096be09d01500596ca63ad9f1eef4b8fffa&token=2133161636&lang=zh_CN#rd)
|
||||
- [SpringBoot 处理异常的几种常见姿势](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485568&idx=2&sn=c5ba880fd0c5d82e39531fa42cb036ac&chksm=cea2474bf9d5ce5dcbc6a5f6580198fdce4bc92ef577579183a729cb5d1430e4994720d59b34&token=2133161636&lang=zh_CN#rd)
|
||||
- [5分钟搞懂如何在Spring Boot中Schedule Tasks](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485563&idx=1&sn=7419341f04036a10b141b74624a3f8c9&chksm=cea247b0f9d5cea6440759e6d49b4e77d06f4c99470243a10c1463834e873ca90266413fbc92&token=2133161636&lang=zh_CN#rd)
|
||||
|
||||
### MyBatis
|
||||
|
||||
- [面试官:“谈谈MyBatis中都用到了那些设计模式?”。](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485369&idx=1&sn=a493d646e126cd1c19ce9f1fc9c724c9&chksm=cea24872f9d5c16462d82f033699d7ad3177964100f8c8958ce9b8e0872e246f552ae6ac423f&token=1667678311&lang=zh_CN#rd)
|
||||
|
||||
## 数据库
|
||||
|
||||
### MySQL
|
||||
|
||||
- [MySQL知识点总结[修订版]](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485390&idx=1&sn=43511395093d2e0deb64f89c8af1805e&chksm=cea24805f9d5c113026292c7681238b1c65c09958588aa5c70e37249e384f5c965f87ef438ad&token=1667678311&lang=zh_CN#rd)
|
||||
- [【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484848&idx=1&sn=77a0e6e82944ec385f5df17e91ce3bf2&chksm=cea24a7bf9d5c36d4b289cccb017292f9f36da9f3c887fd2b93ecd6af021fcf30121ba09799f&token=1082669959&lang=zh_CN#rd)
|
||||
- [一条SQL语句在MySQL中如何执行的](https://mp.weixin.qq.com/s/QU4-RSqVC88xRyMA31khMg)
|
||||
- [一文带你轻松搞懂事务隔离级别(图文详解)](https://mp.weixin.qq.com/s/WhK3SrkMDTj1_o2zp64ArQ)
|
||||
- [详记一次MySQL千万级大表优化过程!](https://mp.weixin.qq.com/s/SbpM_q_-nIKJn7_TSmrW8A)
|
||||
|
||||
### Redis
|
||||
|
||||
- [史上最全Redis高可用技术解决方案大全](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484850&idx=1&sn=3238360bfa8105cf758dcf7354af2814&chksm=cea24a79f9d5c36fb2399aafa91d7fb2699b5006d8d037fe8aaf2e5577ff20ae322868b04a87&token=1082669959&lang=zh_CN#rd)
|
||||
- [redis 总结——重构版](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484858&idx=1&sn=8e222ea6115e0b69cac91af14d2caf36&chksm=cea24a71f9d5c367148dccec3d5ddecf5ecd8ea096b5c5ec32f22080e66ac3c343e99151c9e0&token=1082669959&lang=zh_CN#rd)
|
||||
|
||||
## 面试相关
|
||||
|
||||
- [面试中常见的几道智力题 来看看你会做几道?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484923&idx=1&sn=e1fd890a1a7290f996dc8d7ca4b2d599&chksm=cea24a30f9d5c326f5498929decb7b2d9e39d8806b74c823ccf46fe9fba778d5d9aa644292db&token=1082669959&lang=zh_CN#rd)
|
||||
- [面试中常见的几道智力题 来看看你会做几道(2)?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484917&idx=1&sn=8587b384b42927618067c0e3a56083a9&chksm=cea24a3ef9d5c328a5fd97441de1ccfaf42f9296e5f491b1fa4be9422431d6f26dac36a75e16&token=1082669959&lang=zh_CN#rd)
|
||||
- [[算法总结] 搞定 BAT 面试——几道常见的子符串算法题](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484869&idx=1&sn=785d0dce653aa1abdbc542007da830e8&chksm=cea24a0ef9d5c31853ae1114844041f12daf88ef753fb019980e466f32922c50d332e1d1fc2c&token=1082669959&lang=zh_CN#rd)
|
||||
- [[BAT面试必备] ——几道常见的链表算法题](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484862&idx=1&sn=015a4eb2f66978010d68c1305a46cfb5&chksm=cea24a75f9d5c363683aadf3ac434b8baf9ff5be8d2939d7875ebb3a742780b568a0926a2cc4&token=1082669959&lang=zh_CN#rd)
|
||||
- [如何判断一个元素在亿级数据中是否存在?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484796&idx=1&sn=25c190a95ac53b81063f7acda936c994&chksm=cea24ab7f9d5c3a1819605008bfc92834eddf37ca3ad24b913fab558b5d00ed5306c5d6aab55&token=1082669959&lang=zh_CN#rd)
|
||||
- [可能是一份最适合你的后端面试指南(部分内容前端同样适用)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484840&idx=1&sn=cf4611ac290ae3cbb381fe52aa76b60b&chksm=cea24a63f9d5c375215f7539ff0f3d6320f091d5f8b73e95c724ec9a31d3b5baa98c3f5a1012&token=1082669959&lang=zh_CN#rd)
|
||||
- [GitHub 上四万 Star 大佬的求职回忆](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484802&idx=1&sn=58e204718559f6cf94d3d9cd9614ebc2&chksm=cea24a49f9d5c35f8d53f79801aea21fdd3c06ff4b6c16167e8211f627eea8c5a0ce334fd240&token=1082669959&lang=zh_CN#rd)
|
||||
- [这7个问题,可能大部分Java程序员都比较关心吧!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484786&idx=1&sn=4f61c62c0213602a106ce20db7768a93&chksm=cea24ab9f9d5c3afe7b5c90f6a8782adef93e916a46685aa8c46b752fddadf88273748c7f1ab&token=1082669959&lang=zh_CN#rd)
|
||||
- [2018年BATJ面试题精选](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484774&idx=1&sn=2aaaafcecd2bc6b519a11fa0fc1f66fa&chksm=cea24aadf9d5c3bb69359b0973930387885de75b444837df4f34ac14eebbabe498605660ae48&token=1082669959&lang=zh_CN#rd)
|
||||
- [一位大佬的亲身经历总结:简历和面试的技巧](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485409&idx=1&sn=47b09ec432929306d39762b13364b04b&chksm=cea2482af9d5c13cc91e8e679f0b823e667b616866519af1523d1d8b4e550cfdaa2f57b21bf3&token=1667678311&lang=zh_CN#rd)
|
||||
- [包装严重的IT行业,作为面试官,我是如何甄别应聘者的包装程度](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485404&idx=1&sn=b0c5b9e55bb6f7e71585c1b2e769fd87&chksm=cea24817f9d5c101cfa45a5d2707445030259f030d91f48c068450c5a280457ee25fa51b3f24&token=1667678311&lang=zh_CN#rd)
|
||||
- [面试官:你是如何使用JDK来实现自己的缓存(支持高并发)?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485347&idx=1&sn=8da3919089909d39dbf0745225ca0dad&chksm=cea24868f9d5c17eaec7eba2a4a3b63ed46dff87cb08b3f5d491e96890d8f375735cfcc77562&token=1667678311&lang=zh_CN#rd)
|
||||
|
||||
### 面经
|
||||
|
||||
- [5面阿里,终获offer](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484747&idx=1&sn=bff601fd1d314f670cb44171ea1925dd&chksm=cea24a80f9d5c396619acaa9f77207019f72d43749559b5a401359915e01b51598a687c48203&token=1082669959&lang=zh_CN#rd)
|
||||
- [记一次蚂蚁金服的面试经历](https://mp.weixin.qq.com/s/LIhtyspty9Kz1qRg1b8N-w)
|
||||
- [2019年蚂蚁金服、头条、拼多多的面试总结](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485167&idx=1&sn=a35fad235a6bb2b6e31f1ff37e72bfd7&chksm=cea24924f9d5c032cbbeffc87cd366e9aa7b209897a26ad5c7f7b1b766b3c34a2c9b6870006c&token=1667678311&lang=zh_CN#rd)
|
||||
- [蚂蚁金服2019实习生面经总结(已拿口头offer)](https://mp.weixin.qq.com/s/ktq2UOvi5qI1FymWIgp8jw)
|
||||
- [2019年蚂蚁金服面经(已拿Offer)!附答案!!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485251&idx=1&sn=21e5da0d76dd71d165f19015ebeba780&chksm=cea24888f9d5c19e041a145e6da3d4fa94f63b34c71d43f10c29340c7d51a4a23971904d19b5&token=1667678311&lang=zh_CN#rd)
|
||||
|
||||
### 备战面试系列
|
||||
|
||||
- [【备战春招/秋招系列】程序员的简历就该这样写](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484830&idx=1&sn=2e868311f79f4a52a0f3be383050c810&source=41#wechat_redirect)
|
||||
- [【备战春招/秋招系列】初出茅庐的程序员该如何准备面试?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484829&idx=1&sn=29e0e08d9da0f343f14c16bb3ac92beb&chksm=cea24a56f9d5c340ecb67a186b3c8fb5ef30ff481bfbdec3249f2145b3f376fd2d6dc19fbefc&token=1082669959&lang=zh_CN#rd)
|
||||
- [【备战春招/秋招系列】Java程序员必备书单](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484826&idx=1&sn=cf015aca00e420d476c3cb456a11d994&chksm=cea24a51f9d5c34730dcdb538c99cb58d23496ebe8c51fb191dd629ab28c802f97662aef3366&token=1082669959&lang=zh_CN#rd)
|
||||
- [【备战春招/秋招系列】面试官问你“有什么问题问我吗?”,你该如何回答?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484755&idx=1&sn=4a09256062642c714f83ed03cb083846&chksm=cea24a98f9d5c38e8e040c332bf58ccac48a93190e059b9f39e4249eae579b8d32238cea88dd&token=1082669959&lang=zh_CN#rd)
|
||||
- [【备战春招/秋招系列】美团面经总结基础篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484825&idx=1&sn=a0ec65a5f2b268a6f4d22c3bb9790126&chksm=cea24a52f9d5c344922e4dcd4b9650d63ad5c4d843208e3fb7f21bcffa144a6bab0d748cdfbb&token=1082669959&lang=zh_CN#rd)
|
||||
- [【备战春招/秋招系列】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484822&idx=1&sn=efefe8a5a1ff9a54a57601edab134a04&chksm=cea24a5df9d5c34b36e8b12beb574ca31ae771de3881237c4f02be61b52e38f763d2d6137dd3&token=1082669959&lang=zh_CN#rd)
|
||||
- [【备战春招/秋招系列】美团Java面经总结终结篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484815&idx=1&sn=4dd1a280fb95c59366c73897c77049fb&chksm=cea24a44f9d5c3524b301ecf313382ea78b6ac821b4d5e9f9cf51346fc10839c1e234e7cb3ed&token=1082669959&lang=zh_CN#rd)
|
||||
|
||||
### 面试现场
|
||||
|
||||
- [【面试现场】如何实现可以获取最小值的栈?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484863&idx=2&sn=ac674bb0cf29b782aa0e6e9365defa4b&chksm=cea24a74f9d5c362b22459ceda77e635366a17175cd30bbdce14bc729e5417b137175a90331b&token=1082669959&lang=zh_CN#rd)
|
||||
- [【面试现场】为什么要分稳定排序和非稳定排序?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484856&idx=2&sn=af47a53a913b42b064a6e158d165fd2f&chksm=cea24a73f9d5c365084c65d30372f1ae35893ad3ec751da71e14ce5fda07b17ab5f0da90f663&token=1082669959&lang=zh_CN#rd)
|
||||
- [【面试现场】如何找到字符串中的最长回文子串?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484855&idx=1&sn=e75a06c56fe3b8802f18b04ef4df1c43&chksm=cea24a7cf9d5c36ab8090506442fa131a3d332343e953567c5f78dbef5aed0b6ed317b25af9f&token=1082669959&lang=zh_CN#rd)
|
||||
- [【面试现场】如何在10亿数中找出前1000大的数](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484845&idx=1&sn=6a235c6181c0f46630a9a22ef762a23b&chksm=cea24a66f9d5c3709e9618a1e418535d467053273123b81fda3897b4431a02a105703fd22644&token=1082669959&lang=zh_CN#rd)
|
||||
|
||||
## 算法
|
||||
|
||||
- [【算法技巧】位运算装逼指南](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485292&idx=1&sn=330e7f8c972bd7ed368aca7ccb434493&chksm=cea248a7f9d5c1b1f5387adbda961f0aa02c6f575fd9367937b9c15be1b241222b340f61dc5e&token=1667678311&lang=zh_CN#rd)
|
||||
|
||||
## Github 热门Java项目推荐
|
||||
|
||||
- [近几个月Github上最热门的Java项目一览](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484908&idx=1&sn=a95d37194f03b7e4aae1612e1785bf44&chksm=cea24a27f9d5c331d818fa1b2b564f9d5e9ff245a969a50e98944b909d6e45c0ebde544f982e&token=1082669959&lang=zh_CN#rd)(2018-07-20)
|
||||
- [推荐10个Java方向最热门的开源项目(8月)](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484876&idx=1&sn=3cab92ee5bda6b47bf7232724461f6a3&chksm=cea24a07f9d5c31106d0806b1bd8bae8821511b0a32879fb4f4b3bec7823db1f62e23821b07b&token=1082669959&lang=zh_CN#rd)( 2018-08-28)
|
||||
- [Github上 Star 数相加超过 7w+ 的三个面试相关的仓库推荐](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484819&idx=1&sn=6d83101ee504a6ab19ef07fb32650876&chksm=cea24a58f9d5c34ef9f3a7374c5147b1ff85e18ca4e59e21c4edf7d63bc1bd8c5c241e0c532b&token=1082669959&lang=zh_CN#rd)( 2018-11-17)
|
||||
- [11月 Github Trending 榜最热门的 10 个 Java 项目](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484805&idx=1&sn=acee5bd6c3f861c65be3a2fb8843e1f5&chksm=cea24a4ef9d5c358c263cb4605acc6b6254635c28c496a9a10aee0dc80293f457a9af2e90d1c&token=1082669959&lang=zh_CN#rd)( 2018-12-01)
|
||||
- [盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484789&idx=1&sn=2ad9fabb8fc7fae3bd3756ea05594344&chksm=cea24abef9d5c3a889b6cb8e00cb18abbb694d189c84a24fa1ed337ad4c56194cd39316dc6a5&token=1082669959&lang=zh_CN#rd)( 2018-12-24)
|
||||
- [12月GithubTrending榜Java项目总结,多了几个新面孔](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484784&idx=1&sn=3c98b2e6aa97014a8fb4bf837be40f52&chksm=cea24abbf9d5c3ad3ab779749f6a75ed9bc4321986056a65f5f04033c9cc626a382ebe597278&token=1082669959&lang=zh_CN#rd)(2019-01-02)
|
||||
- [1月份Github上收获最多star的10个项目](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484761&idx=1&sn=db6441c08f5ae8d75978384108cc852d&chksm=cea24a92f9d5c3846858a91aa7dc6b258f91407e2a22a1c7b2e46a8e5dd4d9a9ef443eed3b0e&token=1082669959&lang=zh_CN#rd)(2019-02-01)
|
||||
- [2019年2月份Github上收获最多Star的10个Java项目](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484749&idx=1&sn=a66a1e3707839454539d93499dcadfad&chksm=cea24a86f9d5c3902d07fc4614347606200536ddf9ee7464fe12bbbce5c1bd186ad9894f13e3&token=1082669959&lang=zh_CN#rd)(2019-03-05)
|
||||
- [3月Github最热门的10个Java开源项目](https://mp.weixin.qq.com/s/HYXFWeko2tGPCWhy-yrtEw)
|
||||
- [五一假期充电指南:4月Github最热门的Java项目推荐](https://mp.weixin.qq.com/s/3485Z0cbD1FvcWZMQTnRsw)
|
||||
|
||||
## 架构
|
||||
|
||||
- [8 张图读懂大型网站技术架构](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484863&idx=1&sn=8b7c8ce77f5927564d69587688114c79&chksm=cea24a74f9d5c362b7140d18bfc198e7f39572b938e597d1725e4dcf541a12b33d8db6ac1b45&token=1082669959&lang=zh_CN#rd)
|
||||
- [【面试精选】关于大型网站系统架构你不得不懂的10个问题](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484760&idx=1&sn=c41df36f538ab3d8907e23bf8f6d2cd5&chksm=cea24a93f9d5c3851f8a699fdf068f767e3571c38e1b2995297a3e5725f9ad024c6de2bcb100&token=1082669959&lang=zh_CN#rd)
|
||||
- [分布式系统的经典基础理论](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484941&idx=1&sn=e0812a9ccfde06550e24c23c4bb5ef1d&chksm=cea249c6f9d5c0d0bd16fc26c7af606c775868f1f06d81fbff2f8093d2a686c3b3befe9b971c&token=1082669959&lang=zh_CN#rd)
|
||||
- [软件开发的七条原则](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484938&idx=1&sn=597b1aec42d22caad051b3694816fb27&chksm=cea249c1f9d5c0d73bcedc02499cb44822f6b5ab9dcccd7f1a03f830aed158a1b9abec416efc&token=1082669959&lang=zh_CN#rd)
|
||||
- [关于分布式计算的一些概念](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484935&idx=2&sn=2394583a73b41cca431e392a28b0b4cb&chksm=cea249ccf9d5c0da5d1d4b8cf2dffb00ae1b6b605afe6da426112f3208898c2cca6249865146&token=1082669959&lang=zh_CN#rd)
|
||||
|
||||
## 工具
|
||||
|
||||
- [Git入门看这一篇就够了!](https://mp.weixin.qq.com/s/ylyHOuEPX4tDvOc7-SxMmw)
|
||||
- [团队开发中的 Git 实践](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485550&idx=1&sn=a0fa847b009c3c8c60c20773f0870dbf&chksm=cea247a5f9d5ceb317906f37d7dfbd44aebbe2206764cd8b50a25110c3125c7be3507ce3729e&token=2133161636&lang=zh_CN#rd)
|
||||
- [IDEA中的Git操作,看这一篇就够了!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485578&idx=1&sn=8f8ab9d597e1448053e5da380fff3e54&chksm=cea24741f9d5ce5791722dd3da12dfa3de5aa9d742e0cc78d0b72a89d75a48e6d84512265c30&token=2133161636&lang=zh_CN#rd)
|
||||
- [一文搞懂如何在Intellij IDEA中使用Debug,超级详细!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485250&idx=1&sn=9e1f270996441094104a06fc909c60f5&chksm=cea24889f9d5c19fbc6024779ca476a3ee141efa457fc86cc8c33a8f06a1c2b1e7b4922426c2&token=1667678311&lang=zh_CN#rd)
|
||||
- [十分钟搞懂Java效率工具Lombok使用与原理](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485385&idx=2&sn=a7c3fb4485ffd8c019e5541e9b1580cd&chksm=cea24802f9d5c1144eee0da52cfc0cc5e8ee3590990de3bb642df4d4b2a8cd07f12dd54947b9&token=913106598&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
## 效率
|
||||
|
||||
- [推荐几个可以提升工作效率的Chrome插件](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485315&idx=1&sn=f5e91a9386a6911acbff4d7721065563&chksm=cea24848f9d5c15ec3bc0efab93351ca7481a609e901d9c9c07816a7a9737cdcdc1b5f6497db&token=1667678311&lang=zh_CN#rd)
|
||||
|
||||
## 思维开阔
|
||||
|
||||
- [不就是个短信登录API嘛,有这么复杂吗?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485181&idx=1&sn=7a7a7ce0671e8c5456add8da098501c2&chksm=cea24936f9d5c020fa1e339a819a17e7ae6099f1c5072d9ea235cf9fca45437d96cb117f7f10&token=1667678311&lang=zh_CN#rd)
|
||||
|
||||
## 进阶
|
||||
|
||||
- [可能是把Docker的概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484921&idx=1&sn=d40518712a04b3c37d7c8fcd3e696e90&chksm=cea24a32f9d5c3243db78a227ba4e77618e679bbc856cf1974fccbd474c44847672f21658147&token=1082669959&lang=zh_CN#rd)
|
||||
- [后端必备——数据通信知识(RPC、消息队列)一站式总结](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484920&idx=1&sn=c7167df0b36522935896565973d02cc9&chksm=cea24a33f9d5c325fc663c95ebc221060ae2d5eeee254472558a99fdfc2837b31d3b9ae51a12&token=1082669959&lang=zh_CN#rd)
|
||||
- [可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484868&idx=1&sn=af1e49c5f7dc89355255a4d46bafc005&chksm=cea24a0ff9d5c3195a690d2c85f09cd8901717674f52e10b0e6fd588d69de15de76b8184307d&token=1082669959&lang=zh_CN#rd)
|
||||
- [外行人都能看懂的SpringCloud,错过了血亏!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484813&idx=1&sn=d87c01aff031f35be6b88a61f5782da5&chksm=cea24a46f9d5c35023a54e20fa1319b4cd31c33b094e2fd161bb8667ab77b8b403af62cfb3b5&token=1082669959&lang=zh_CN#rd)
|
||||
- [关于 Dubbo 的重要入门知识点总结](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484753&idx=1&sn=32d987bf9f6208e30326877b111cad61&chksm=cea24a9af9d5c38cc7eb4d9bfeaa07e72003a1bf304a0fedc3fabb2a87f01c98b5a7d1c80d53&token=1082669959&lang=zh_CN#rd)
|
||||
- [Java 工程师成神之路 | 2019正式版](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484759&idx=1&sn=5a706091c1db2b585f0a93c75bca14da&chksm=cea24a9cf9d5c38a245bdc63a0c90934b02f582c34a8dfb4ef01cd149d4829d799cb9e9bd464&token=1082669959&lang=zh_CN#rd)
|
||||
- [聊一聊开发常用小工具](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484769&idx=1&sn=0968ce79f4d31d982b9f1ce24b051432&chksm=cea24aaaf9d5c3bc5feb0ef4e814990c9e108b72a0691193b1dda29642ee2ff74da5026016f6&token=1082669959&lang=zh_CN#rd)
|
||||
- [新手也能看懂,消息队列其实很简单](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484794&idx=1&sn=61585fe69eedb3654ee2f9c9cd67e1a1&chksm=cea24ab1f9d5c3a7fd07dd49244f69fc85a5a523ee5353fc9e442dcad2ca0dd1137ed563fcbe&token=1082669959&lang=zh_CN#rd)
|
||||
|
||||
## 杂文闲记
|
||||
|
||||
- [一只准准程序员的唠叨](http://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484932&idx=1&sn=a4f3057ecd4412cb8b18e92058b29680&chksm=cea249cff9d5c0d97580310f6666a4e0c42cfd1ba13dd759850e489c9386dce8c5c35b0a1a95&token=1082669959&lang=zh_CN#rd)(2018-06-09)
|
||||
- [说几件近期的小事](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484900&idx=1&sn=745e8f6027da369ef5f8e349cb5d6657&chksm=cea24a2ff9d5c339c925322ffacdc72dd37bda63238dfb3452540085c1695681e2e79070e2a7&token=1082669959&lang=zh_CN#rd)(2018-08-02)
|
||||
- [选择技术方向都要考虑哪些因素](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484898&idx=1&sn=fd2ebf9ffd37ab5de1de09cd3272ac0a&chksm=cea24a29f9d5c33fa48f5a57de864cd9382a730578fb18d78b7f06b45504aede18b235b9bc9e&token=1082669959&lang=zh_CN#rd)(2018-08-04)
|
||||
- [结束了我短暂的秋招,说点自己的感受](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484842&idx=1&sn=4489dfab0ef2479122b71407855afc71&chksm=cea24a61f9d5c3774a8ed67c5fcc3234cb0741fbe831152986e5d1c8fb4f36a003f4fb2f247e&token=1082669959&lang=zh_CN#rd)(2018-10-22)
|
||||
- [【周日闲谈】最近想说的几件小事](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484818&idx=1&sn=e14b0a08537456eb15ba49cf5e70ff74&chksm=cea24a59f9d5c34fe0a9e0567d867b85a81d1f19b0ea8e6a3c14161e436508de9adfeb2a5e6a&token=1082669959&lang=zh_CN#rd)(2018-11-18)
|
||||
- [做公众号这一年的经历和一件“大事”](http://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484746&idx=1&sn=a519a9e3d638bff5c65008f7de167e4b&chksm=cea24a81f9d5c397ca9ac5668ba6cb18b38065e0e282a34ebc077a2dea98de3f1eb285ea5f09&token=1082669959&lang=zh_CN#rd)(2019-03-10)
|
||||
- [几经周折,公众号终于留言功能啦!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485007&idx=1&sn=096a436cd6a9251c23b8effc3cfa5076&chksm=cea24984f9d5c092d1f7740d1c3e0ea347562ba0aa597507ce275c5bdf1c8bd608328ee756ba&token=1082669959&lang=zh_CN#rd)(2019-03-15)
|
||||
- [写在毕业季的大学总结!细数一下大学干过的“傻事”。](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485358&idx=1&sn=3aaf1163fe13351e06c76b70f2bd33bd&chksm=cea24865f9d5c1735b51c707c8f5ade16af7eca304540205ab0fb1284f99034d418b9858d7db&token=1667678311&lang=zh_CN#rd) (2019-06-11)
|
||||
- [入职一个月的职场小白,谈谈自己这段时间的感受](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485540&idx=1&sn=4492eece8f7738e99350118040e14a79&chksm=cea247aff9d5ceb9e7c67f418d8a8518c550fd7dd269bf2c9bdef83309502273b4b9f1e7021f&token=1333232257&lang=zh_CN&scene=21#wechat_redirect)
|
||||
|
||||
## 其他好文推荐
|
||||
|
||||
- [谈恋爱也要懂https](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484821&idx=1&sn=e6ce4ec607a6c9b20edbd64fbeb0f6c5&chksm=cea24a5ef9d5c3480d3de284fc1038b51c2464152e25a350960efd81acd21f71d7e0eeb78c77&token=1082669959&lang=zh_CN#rd)
|
||||
- [快速入门大厂后端面试必备的 Shell 编程](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484810&idx=1&sn=0b622ae617b863ef1cc3a32c17b8b755&chksm=cea24a41f9d5c357199073c1e4692b7da7dcbd1809cf634a6cfec8c64c12250efc5274992f06&token=1082669959&lang=zh_CN#rd)
|
||||
- [为什么阿里巴巴禁止工程师直接使用日志系统(Log4j、Logback)中的 API](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484808&idx=1&sn=4774ecc18069e4ddc85f68877cff1ae3&chksm=cea24a43f9d5c35520a454d51e72c6084d38f505969835011f4dd36f76c78303af46780802c9&token=1082669959&lang=zh_CN#rd)
|
||||
- [一文搞懂 RabbitMQ 的重要概念以及安装](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484792&idx=1&sn=d34fdbb6cb21c231361038a7317ac2a4&chksm=cea24ab3f9d5c3a5c742de10cd0f7c77425b2144c86d2515d3e5d2011f4d16af12110c2de986&token=1082669959&lang=zh_CN#rd)
|
||||
- [漫话:如何给女朋友解释什么是RPC](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484795&idx=1&sn=64eb2dcac07cf6904ca79b14f408df9e&chksm=cea24ab0f9d5c3a6970c3fbbdcaa16d5230b51abed9bacd1cd2ea3192cccea4aba0ef2d1a9de&token=1082669959&lang=zh_CN#rd)
|
||||
- [Java人才市场年度盘点:转折与终局](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484779&idx=1&sn=a930b09a1783bca68e927cbe7d7a54a6&chksm=cea24aa0f9d5c3b6864ab3585a4cc5de1fa89ffc07538f2c55382776bfee0c369d7d74af7c79&token=1082669959&lang=zh_CN#rd)
|
||||
- [Github 上日获 800多 star 的阿里微服务架构分布式事务解决方案 FESCAR开源啦](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484776&idx=2&sn=bcd1cf6d72653bfff83724f1dbae0425&chksm=cea24aa3f9d5c3b5e9667f1e15f295d323efb3b2c8da6c9059f5e8b5e176b9cb1bf80cd1a78f&token=1082669959&lang=zh_CN#rd)
|
||||
- [Cloud Toolkit新版本发布,开发效率 “biu” 起来了](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484771&idx=2&sn=0eef929baacca950099ea8651f0e5cb2&chksm=cea24aa8f9d5c3bed5f359e6da981ac950dca8f31a93f1fe321d7de7908d843855604e79a4da&token=1082669959&lang=zh_CN#rd)
|
||||
- [我觉得技术人员该有的提问方式](https://mp.weixin.qq.com/s/eTEah5WOdfC3EAPbhpOKBA)
|
Loading…
x
Reference in New Issue
Block a user