【面试题】Java 高级工程师面试刷题100题(一)

文章目录

Java 面向对象有哪些特征,如何应用

​ 面向对象编程是利用类和对象编程的一种思想。万物可归类,类是对于世界事物的高度抽象 ,不同的事物之间有不同的关系 ,一个类自身与外界的封装关系,一个父类和子类的继承关系, 一个类和多个类的多态关系。万物皆对象,对象是具体的世界事物,面向对象的三大特征封装,继承,多态。封装,封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;继承是父类和子类的关系,多态说的是类与类的关系。

​ 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。属性的封装:使用者只能通过事先定制好的方法来访问数据,可以方便地加入逻辑控制,限制对属性的 不合理操作;方法的封装:使用者按照既定的方式调用方法,不必关心方法的内部实现,便于使用; 便于修改,增强 代码的可维护性;

​ 继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。在本质上是特殊~一般的关系,即常说的 is-a 关系。子类继承父类,表明子类是一种特殊的父类,并且具有父类所不具有的一些属性或方法。从多种实现类中抽象出一个基类,使其具备多种实现类的共同特性 ,当实现类用 extends 关键字继承了基类(父类)后,实现类就具备了这些相同的属性。继承的类叫做子类(派生类或者超类),被继承的类叫做父类(或者基类)。比如从猫类、狗类、虎类中可以抽象出一个动物类,具有和猫、狗、虎类的共同特性(吃、跑、叫等)。Java 通过 extends 关键字来实现继承,父类中通过 private 定义的变量和方法不会被继承,不能在子类中直接操作父类通过 private 定义的变量以及方法。继承避免了对一般类和特殊类之间共同特征进行的重复描述,通过继承可以清晰地表达每一项共同特征所适应的概念范围,在一般类中定义的属性和操作适应于这个类本身以及它以下的每一层特殊类的全部对象。运用继承原则使得系统模型比较简练也比较清晰。

​ 相比于封装和继承,Java 多态是三大特性中比较难的一个,封装和继承最后归结于多态, 多态指的是类和类的关系,两个类由继承关系,存在有方法的重写,故而可以在调用时有父类引用指向子类对象。多态必备三个要素:继承,重写,父类引用指向子类对象。

HashMap 原理是什么,在 jdk1.7 和 1.8 中有什么区别

HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。我们用下面这张图来介绍

HashMap 的结构。

JAVA7 实现

大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色

的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。

  1. capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。

  2. loadFactor:负载因子,默认为 0.75。

  3. threshold:扩容的阈值,等于 capacity * loadFactor

**JAVA8 实现 **

Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。

根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决

于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

ArrayList 和 LinkedList 有什么区别

ArrayList 和 LinkedList 都实现了 List 接口,他们有以下的不同点:
ArrayList 是基于索引的数据接口,它的底层是数组。它可以以 O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList 是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是 O(n)。
相对于 ArrayList,LinkedList 的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
也可以参考 ArrayList vs. LinkedList。

  1. 因为 Array 是基于索引 (index) 的数据结构,它使用索引在数组中搜索和读取数据是很快的。 Array 获取数据的时间复杂度是 O(1), 但是要删除数据却是开销很大的,因为这需要重排数组中的所有数据。

  2. 相对于 ArrayList , LinkedList 插入是更快的。因为 LinkedList 不像 ArrayList 一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是 ArrayList 最坏的一种情况,时间复杂度是 O(n) ,而 LinkedList 中插入或删除的时间复杂度仅为 O(1) 。 ArrayList 在插入数据时还需要更新索引(除了插入数组的尾部)。

  3. 类似于插入数据,删除数据时, LinkedList 也优于 ArrayList 。

  4. LinkedList 需要更多的内存,因为 ArrayList 的每个索引的位置是实际的数据,而 LinkedList 中的每个节点中存储的是实际的数据和前后节点的位置 ( 一个 LinkedList 实例存储了两个值: Node first 和 Node last 分别表示链表的其实节点和尾节点,每个 Node 实例存储了三个值: E item,Node next,Node pre) 。

什么场景下更适宜使用 LinkedList,而不用 ArrayList

  1. 你的应用不会随机访问数据 。因为如果你需要 LinkedList 中的第 n 个元素的时候,你需要从第一个元素顺序数到第 n 个数据,然后读取数据。

  2. 你的应用更多的插入和删除元素,更少的读取数据 。因为插入和删除元素不涉及重排数据,所以它要比 ArrayList 要快。

换句话说,ArrayList 的实现用的是数组,LinkedList 是基于链表,ArrayList 适合查找,LinkedList 适合增删

以上就是关于 ArrayList 和 LinkedList 的差别。你需要一个不同步的基于索引的数据访问时,请尽量使用 ArrayList。ArrayList 很快,也很容易使用。但是要记得要给定一个合适的初始大小,尽可能的减少更改数组的大小。

高并发中的集合有哪些问题

**第一代线程安全集合类 **

Vector、Hashtable

是怎么保证线程安排的: 使用 synchronized 修饰方法*

缺点:效率低下

第二代线程非安全集合类

ArrayList、HashMap

线程不安全,但是性能好,用来替代 Vector、Hashtable

使用 ArrayList、HashMap,需要线程安全怎么办呢?

使用 Collections.synchronizedList(list); Collections.synchronizedMap(m);

底层使用 synchronized 代码块锁 虽然也是锁住了所有的代码,但是锁在方法里边,并所在方法外边性能可以理解为稍有提高吧。毕竟进方法本身就要分配资源的

第三代线程安全集合类

在大量并发情况下如何提高集合的效率和安全呢?

java.util.concurrent.*

ConcurrentHashMap:

CopyOnWriteArrayList :

CopyOnWriteArraySet: 注意 不是 CopyOnWriteHashSet*

底层大都采用 Lock 锁(1.8 的 ConcurrentHashMap 不使用 Lock 锁),保证安全的同时,性能也很高。

jdk1.8 的新特性有哪些

一、接口的默认方法

Java 8 允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做扩展方法,示例如下:

代码如下:

interface Formula { double calculate(int a);

default double sqrt(int a) { return Math.sqrt(a); } }

Formula 接口在拥有 calculate 方法之外同时还定义了 sqrt 方法,实现了 Formula 接口的子类只需要实现一个 calculate 方法,默认方法 sqrt 将在子类上可以直接使用。

代码如下:

Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } };

formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0

文中的 formula 被实现为一个匿名类的实例,该代码非常容易理解,6 行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。

译者注: 在 Java 中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在 C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做 mixin。新的 Java 8 的这个特新在编译器实现的角度上来说更加接近 Scala 的 trait。 在 C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和 Java 8 的这个在语义上有差别。

二、Lambda 表达式

首先看看在老版本的 Java 中是如何排列字符串的:

代码如下:

List names = Arrays.asList(“peterF”, “anna”, “mike”, “xenia”);

Collections.sort(names, new Comparator() { @Override public int compare(String a, String b) { return b.compareTo(a); } });

只需要给静态方法 Collections.sort 传入一个 List 对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给 sort 方法。

在 Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8 提供了更简洁的语法,lambda 表达式:

代码如下:

Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });

看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:

代码如下:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

对于函数体只有一行代码的,你可以去掉大括号{}以及 return 关键字,但是你还可以写得更短点:

代码如下:

Collections.sort(names, (a, b) -> b.compareTo(a));

Java 编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看 lambda 表达式还能作出什么更方便的东西来:

三、函数式接口

Lambda 表达式是如何在 java 的类型系统中表示的呢?每一个 lambda 表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的 lambda 表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。

我们可以将 lambda 表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

示例如下:

代码如下:

@FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert(“123”); System.out.println(converted); // 123

需要注意如果@FunctionalInterface 如果没有指定,上面的代码也是对的。

译者注 将 lambda 表达式映射到一个单方法的接口上,这种做法在 Java 8 之前就有别的语言实现,比如 Rhino JavaScript 解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个 function,Rhino 解释器会自动做一个单接口的实例到 function 的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的 addEventListener 第二个参数 EventListener。

四、方法与构造函数引用

前一节中的代码还可以通过静态方法引用来表示:

代码如下:

Converter<String, Integer> converter = Integer::valueOf; Integer converted = converter.convert(“123”); System.out.println(converted); // 123

Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:

代码如下:

converter = something::startsWith; String converted = converter.convert(“Java”); System.out.println(converted); // “J”

接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:

代码如下:

class Person { String firstName; String lastName;

Person() {}

Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }

接下来我们指定一个用来创建 Person 对象的对象工厂接口:

代码如下:

interface PersonFactory

{ P create(String firstName, String lastName); }

这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:

代码如下:

PersonFactory personFactory = Person::new; Person person = personFactory.create(“Peter”, “Parker”);

我们只需要使用 Person::new 来获取 Person 类构造函数的引用,Java 编译器会自动根据 PersonFactory.create 方法的签名来选择合适的构造函数。

五、Lambda 作用域

在 lambda 表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了 final 的外层局部变量,或者实例的字段以及静态变量。

六、访问局部变量

我们可以直接在 lambda 表达式中访问外层的局部变量:

代码如下:

final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);

stringConverter.convert(2); // 3

但是和匿名对象不同的是,这里的变量 num 可以不用声明为 final,该代码同样正确:

代码如下:

int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);

stringConverter.convert(2); // 3

不过这里的 num 必须不可被后面的代码修改(即隐性的具有 final 的语义),例如下面的就无法编译:

代码如下:

int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3;

在 lambda 表达式中试图修改 num 同样是不允许的。

七、访问对象字段与静态变量

和本地变量不同的是,lambda 内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:

代码如下:

class Lambda4 { static int outerStaticNum; int outerNum;

void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); };

Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }

八、访问接口的默认方法

还记得第一节中的 formula 例子么,接口 Formula 定义了一个默认方法 sqrt 可以直接被 formula 的实例包括匿名对象访问到,但是在 lambda 表达式中这个是不行的。 Lambda 表达式中是无法访问到默认方法的,以下代码将无法编译:

代码如下:

Formula formula = (a) -> sqrt( a * 100); Built-in Functional Interfaces

JDK 1.8 API 包含了很多内建的函数式接口,在老 Java 中常用到的比如 Comparator 或者 Runnable 接口,这些接口都增加了@FunctionalInterface 注解以便能用在 lambda 上。 Java 8 API 同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自 Google Guava 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到 lambda 上使用的。

**Predicate****接口**

Predicate 接口只有一个参数,返回 boolean 类型。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):

代码如下:

Predicate predicate = (s) -> s.length() > 0;

predicate.test(“foo”); // true predicate.negate().test(“foo”); // false

Predicate nonNull = Objects::nonNull; Predicate isNull = Objects::isNull;

Predicate isEmpty = String::isEmpty; Predicate isNotEmpty = isEmpty.negate();

Function 接口

Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):

代码如下:

Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply(“123”); // “123”

Supplier 接口 Supplier 接口返回一个任意范型的值,和 Function 接口不同的是该接口没有任何参数

代码如下:

Supplier personSupplier = Person::new; personSupplier.get(); // new Person

Consumer 接口 Consumer 接口表示执行在单个参数上的操作。

代码如下:

Consumer greeter = § -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person(“Luke”, “Skywalker”));

Comparator 接口 Comparator 是老 Java 中的经典接口, Java 8 在此之上添加了多种默认方法:

代码如下:

Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person(“John”, “Doe”); Person p2 = new Person(“Alice”, “Wonderland”);

comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0

Optional 接口

Optional 不是函数是接口,这是个用来防止 NullPointerException 异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:

Optional 被定义为一个简单的容器,其值可能是 null 或者不是 null。在 Java 8 之前一般某个函数应该返回非空对象但是偶尔却可能返回了 null,而在 Java 8 中,不推荐你返回 null 而是返回 Optional。

代码如下:

Optional optional = Optional.of(“bam”);

optional.isPresent(); // true optional.get(); // “bam” optional.orElse(“fallback”); // “bam”

optional.ifPresent((s) -> System.out.println(s.charAt(0))); // “b”

Stream 接口

java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回 Stream 本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。

首先看看 Stream 是怎么用,首先创建实例代码的用到的数据 List:

代码如下:

List stringCollection = new ArrayList<>(); stringCollection.add(“ddd2”); stringCollection.add(“aaa2”); stringCollection.add(“bbb1”); stringCollection.add(“aaa1”); stringCollection.add(“bbb3”); stringCollection.add(“ccc”); stringCollection.add(“bbb2”); stringCollection.add(“ddd1”);

Java 8 扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个 Stream。下面几节将详细解释常用的 Stream 操作:

Filter 过滤

过滤通过一个 predicate 接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他 Stream 操作(比如 forEach)。forEach 需要一个函数来对过滤后的元素依次执行。forEach 是一个最终操作,所以我们不能在 forEach 之后来执行其他 Stream 操作。

代码如下:

stringCollection .stream() .filter((s) -> s.startsWith(“a”)) .forEach(System.out::println);

// “aaa2”, “aaa1”

Sort 排序

排序是一个中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。

代码如下:

stringCollection .stream() .sorted() .filter((s) -> s.startsWith(“a”)) .forEach(System.out::println);

// “aaa1”, “aaa2”

需要注意的是,排序只创建了一个排列好后的 Stream,而不会影响原有的数据源,排序之后原数据 stringCollection 是不会被修改的:

代码如下:

System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map 映射 中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过 map 来讲对象转换成其他类型,map 返回的 Stream 类型是根据你 map 传递进去的函数的返回值决定的。

代码如下:

stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println);

// “DDD2”, “DDD1”, “CCC”, “BBB3”, “BBB2”, “AAA2”, “AAA1”

Match 匹配

Stream 提供了多种匹配操作,允许检测指定的 Predicate 是否匹配整个 Stream。所有的匹配操作都是最终操作,并返回一个 boolean 类型的值。

代码如下:

boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith(“a”));

System.out.println(anyStartsWithA); // true

boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith(“a”));

System.out.println(allStartsWithA); // false

boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith(“z”));

System.out.println(noneStartsWithZ); // true

Count 计数 计数是一个最终操作,返回 Stream 中元素的个数,返回值类型是 long。

代码如下:

long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith(“b”)) .count();

System.out.println(startsWithB); // 3

Reduce 规约

这是一个最终操作,允许通过指定的函数来讲 stream 中的多个元素规约为一个元素,规越后的结果是通过 Optional 接口表示的:

代码如下:

Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + “#” + s2);

reduced.ifPresent(System.out::println); // “aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2”

**并行****Streams**

前面提到过 Stream 有串行和并行两种,串行 Stream 上的操作是在一个线程中依次完成,而并行 Stream 则是在多个线程上同时执行。

下面的例子展示了是如何通过并行 Stream 来提升性能:

首先我们创建一个没有重复元素的大表:

代码如下:

int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }

然后我们计算一下排序这个 Stream 要耗时多久, 串行排序:

代码如下:

long t0 = System.nanoTime();

long count = values.stream().sorted().count(); System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format(“sequential sort took: %d ms”, millis));

// 串行耗时: 899 ms 并行排序:

代码如下:

long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count(); System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format(“parallel sort took: %d ms”, millis));

// 并行排序耗时: 472 ms 上面两个代码几乎是一样的,但是并行版的快了 50%之多,唯一需要做的改动就是将 stream()改为 parallelStream()。

Map

前面提到过,Map 类型不支持 stream,不过 Map 提供了一些新的有用的方法来处理一些日常任务。

代码如下:

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) { map.putIfAbsent(i, “val” + i); }

map.forEach((id, val) -> System.out.println(val)); 以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而 forEach 则接收一个 Consumer 接口来对 map 里的每一个键值对进行操作。

下面的例子展示了 map 上的其他有用的函数:

代码如下:

map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33

map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false

map.computeIfAbsent(23, num -> “val” + num); map.containsKey(23); // true

map.computeIfAbsent(3, num -> “bam”); map.get(3); // val33

接下来展示如何在 Map 里删除一个键值全都匹配的项:

代码如下:

map.remove(3, “val3”); map.get(3); // val33

map.remove(3, “val33”); map.get(3); // null

另外一个有用的方法:

代码如下:

map.getOrDefault(42, “not found”); // not found

对 Map 的元素做合并也变得很容易了:

代码如下:

map.merge(9, “val9”, (value, newValue) -> value.concat(newValue)); map.get(9); // val9

map.merge(9, “concat”, (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat

Merge 做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到 map 中。

九、Date API

Java 8 在包 java.time 下包含了一组全新的时间日期 API。新的日期 API 和开源的 Joda-Time 库差不多,但又不完全一样,下面的例子展示了这组新 API 里最重要的一些部分:

Clock 时钟

Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用 Instant 类来表示,Instant 类也可以用来创建老的 java.util.Date 对象。

代码如下:

Clock clock = Clock.systemDefaultZone(); long millis = clock.millis();

Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date

Timezones 时区

在新 API 中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法 of 来获取到。 时区定义了到 UTS 时间的时间差,在 Instant 时间点对象到本地日期对象之间转换的时候是极其重要的。

代码如下:

System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids

ZoneId zone1 = ZoneId.of(“Europe/Berlin”); ZoneId zone2 = ZoneId.of(“Brazil/East”); System.out.println(zone1.getRules()); System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]

LocalTime 本地时间

LocalTime 定义了一个没有时区信息的时间,例如 晚上 10 点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:

代码如下:

LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2)); // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239

LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。

代码如下:

LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59

DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse(“13:37”, germanFormatter); System.out.println(leetTime); // 13:37

LocalDate 本地日期

LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和 LocalTime 基本一致。下面的例子展示了如何给 Date 对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。

代码如下:

LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();

System.out.println(dayOfWeek); // FRIDAY 从字符串解析一个 LocalDate 类型和解析 LocalTime 一样简单:

代码如下:

DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse(“24.12.2014”, germanFormatter); System.out.println(xmas); // 2014-12-24

LocalDateTime 本地日期时间

LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime 还有 LocalDate 一样,都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。

代码如下:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY

Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439

只要附加上时区信息,就可以将其转换为一个时间点 Instant 对象,Instant 时间点对象可以很容易的转换为老式的 java.util.Date。

代码如下:

Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant();

Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014

格式化 LocalDateTime 和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:

代码如下:

DateTimeFormatter formatter = DateTimeFormatter .ofPattern(“MMM dd, yyyy - HH:mm”);

LocalDateTime parsed = LocalDateTime.parse(“Nov 03, 2014 - 07:13”, formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13

和 java.text.NumberFormat 不一样的是新版的 DateTimeFormatter 是不可变的,所以它是线程安全的。

十、Annotation 注解

在 Java 8 中支持多重注解了,先看个例子来理解一下是什么意思。 首先定义一个包装类 Hints 注解用来放置一组具体的 Hint 注解:

代码如下:

@interface Hints { Hint[] value(); }

@Repeatable(Hints.class) @interface Hint { String value(); }

Java 8 允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable 即可。

例 1: 使用包装类当容器来存多个注解(老方法)

代码如下:

@Hints({@Hint(“hint1”), @Hint(“hint2”)}) class Person {}

例 2:使用多重注解(新方法)

代码如下:

@Hint(“hint1”) @Hint(“hint2”) class Person {}

第二个例子里 java 编译器会隐性的帮你定义好@Hints 注解,了解这一点有助于你用反射来获取这些信息:

代码如下:

Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null

Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2

即便我们没有在 Person 类上定义@Hints 注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints 注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint 注解。 另外 Java 8 的注解还增加到两种新的 target 上了:

代码如下:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}

关于 Java 8 的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8 里还有很多很有用的东西,比如 Arrays.parallelSort, StampedLock 和 CompletableFuture 等等。

Java 中重写和重载有哪些区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态

性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为

重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方

法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

方法重载的规则:

1.方法名一致,参数列表中参数的顺序,类型,个数不同。

2.重载与方法的返回值无关,存在于父类和子类,同类中。

3.可以抛出不同的异常,可以有不同修饰符

方法重写的规则:

1.参数列表必须完全与被重写方法的一致,返回类型必须完全与被重写方法的返回类型一致。

2.构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次

声明。

3.访问权限不能比父类中被重写的方法的访问权限更低。

4.重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是

否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则

可以。

接口和抽象类有哪些区别

不同:

抽象类:

1.抽象类中可以定义构造器

2.可以有抽象方法和具体方法

3.接口中的成员全都是 public 的

4.抽象类中可以定义成员变量

5.有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法

6.抽象类中可以包含静态方法

7.一个类只能继承一个抽象类

接口:

1.接口中不能定义构造器

2.方法全部都是抽象方法

3.抽象类中的成员可以是 private、默认、protected、public

4.接口中定义的成员变量实际上都是常量

5.接口中不能有静态方法

6.一个类可以实现多个接口

相同:

1.不能够实例化

2.可以将抽象类和接口类型作为引用类型

3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要

被声明为抽象类

怎样声明一个类不会被继承,什么场景下会用

如果一个类被 final 修饰,此类不可以有子类,不能被其它类继承,如果一个中的所有方法都没有重写的需要,当前类没有子类也罢,就可以使用 final 修饰类。

Java 中==和 equals 有哪些区别

equals 和== 最大的区别是一个是方法一个是运算符。

==:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象

的地址值是否相等。

equals():用来比较方法两个对象的内容是否相等。

注意:equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变

量所指向的对象的地址。

String、StringBuffer、StringBuilder 区别及使用场景

Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它们都可以储存和操作字符串,区别

如下。

1)String 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。初学者可能会有这样的误解:

String str = “abc”;
str = “bcd”;

如上,字符串 str 明明是可以改变的呀!其实不然,str 仅仅是一个引用对象,它指向一个字符串对象“abc”。第

二行代码的含义是让 str 重新指向了一个新的字符串“bcd”对象,而“abc”对象并没有任何改变,只不过该对象已

经成为一个不可及对象罢了。

2)StringBuffer/StringBuilder 表示的字符串对象可以直接进行修改。

3)StringBuilder 是 Java5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,

因为它的所有方法都没有被 synchronized 修饰,因此它的效率理论上也比 StringBuffer 要高。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FdQ5VAsJ-1645802475730)(images/StringBuilder.png)]

Java 代理的几种实现方式

第一种:静态代理,只能静态的代理某些类或者某些方法,不推荐使用,功能比较弱,但是编码简单

第二种:动态代理,包含 Proxy 代理和 CGLIB 动态代理

Proxy 代理是 JDK 内置的动态代理

​ 特点:面向接口的,不需要导入三方依赖的动态代理,可以对多个不同的接口进行增强,通过反射读取注解时,只能读取到接口上的注解

​ 原理:面向接口,只能对实现类在实现接口中定义的方法进行增强

定义接口和实现

package com.proxy;

public interface UserService {
    public String getName(int id);

    public Integer getAge(int id);
}
package com.proxy;

public class UserServiceImpl implements UserService {
    @Override
    public String getName(int id) {
        System.out.println("------getName------");
        return "riemann";
    }

    @Override
    public Integer getAge(int id) {
        System.out.println("------getAge------");
        return 26;
    }
}
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    public Object target;

    MyInvocationHandler() {
        super();
    }

    MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getName".equals(method.getName())) {
            System.out.println("++++++before " + method.getName() + "++++++");
            Object result = method.invoke(target, args);
            System.out.println("++++++after " + method.getName() + "++++++");
            return result;
        } else {
            Object result = method.invoke(target, args);
            return result;
        }
    }
}
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main1 {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        InvocationHandler invocationHandler = new MyInvocationHandler(userService);
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),invocationHandler);
        System.out.println(userServiceProxy.getName(1));
        System.out.println(userServiceProxy.getAge(1));
    }
}

CGLIB 动态代理

​ 特点:面向父类的动态代理,需要导入第三方依赖

​ 原理:面向父类,底层通过子类继承父类并重写方法的形式实现增强

Proxy 和 CGLIB 是非常重要的代理模式,是 springAOP 底层实现的主要两种方式

CGLIB 的核心类:
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是 Callback 接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK 的 java.lang.reflect.Method 类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

net.sf.cglib.proxy.MethodInterceptor 接口是最通用的回调(callback)类型,它经常被基于代理的 AOP 用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;

第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用 java.lang.reflect.Method 对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy 对象调用。net.sf.cglib.proxy.MethodProxy 通常被首选使用,因为它更快。

package com.proxy.cglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
        System.out.println(method.getName());
        Object o1 = methodProxy.invokeSuper(o, args);
        System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
        return o1;
    }
}

package com.proxy.cglib;

import com.test3.service.UserService;
import com.test3.service.impl.UserServiceImpl;
import net.sf.cglib.proxy.Enhancer;

public class Main2 {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(cglibProxy);

        UserService o = (UserService)enhancer.create();
        o.getName(1);
        o.getAge(1);
    }
}

hashcode 和 equals 如何使用

equals()源自于 java.lang.Object,该方法用来简单验证两个对象的相等性。Object 类中定义的默认实现只检查两个对象的对象引用,以验证它们的相等性。 通过重写该方法,可以自定义验证对象相等新的规则,如果你使用 ORM 处理一些对象的话,你要确保在 hashCode()和 equals()对象中使用 getter 和 setter 而不是直接引用成员变量

hashCode()源自于 java.lang.Object ,该方法用于获取给定对象的唯一的整数(散列码)。当这个对象需要存储在哈希表这样的数据结构时,这个整数用于确定桶的位置。默认情况下,对象的 hashCode()方法返回对象所在内存地址的整数表示。hashCode()是 HashTable、HashMap 和 HashSet 使用的。默认的,Object 类的 hashCode()方法返回这个对象存储的内存地址的编号。

hash 散列算法,使得在 hash 表中查找一个记录速度变 O(1). 每个记录都有自己的 hashcode,散列算法按照 hashcode 把记录放置在合适的位置. 在查找一个记录,首先先通过 hashcode 快速定位记录的位置.然后再通过 equals 来比较是否相等。如果 hashcode 没找到,则不 equal,元素不存在于哈希表中;即使找到了,也只需执行 hashcode 相同的几个元素的 equal,如果不 equal,还是不存在哈希表中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QYjEUsg6-1645802475733)(images/HashMap1.7hashcodequals.png)]

HashMap 和 HashTable 的区别及底层实现

HashMap 和 HashTable 对比

  1. HashTable 线程同步,HashMap 非线程同步。
  2. HashTable 不允许<键,值>有空值,HashMap 允许<键,值>有空值。
  3. HashTable 使用 Enumeration,HashMap 使用 Iterator。
  4. HashTable 中 hash 数组的默认大小是 11,增加方式的 old*2+1,HashMap 中 hash 数组的默认大小是 16,增长方式是 2 的指数倍。

5.HashMap jdk1.8 之前 list + 链表 jdk1.8 之后 list + 链表,当链表长度到 8 时,转化为红黑树

6.HashMap 链表插入节点的方式 在 Java1.7 中,插入链表节点使用头插法。Java1.8 中变成了尾插法

7.Java1.8 的 hash()中,将 hash 值高位(前 16 位)参与到取模的运算中,使得计算结果的不确定性增强,降低发生哈希碰撞的概率

HashMap 扩容优化:

扩容以后,1.7 对元素进行 rehash 算法,计算原来每个元素在扩容之后的哈希表中的位置,1.8 借助 2 倍扩容机制,元素不需要进行重新计算位置

JDK 1.8 在扩容时并没有像 JDK 1.7 那样,重新计算每个元素的哈希值,而是通过高位运算**(e.hash & oldCap)**来确定元素是否需要移动,比如 key1 的信息如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0CtV961Y-1645802475734)(images/1621414916379-1621752756248.png)]

使用 e.hash & oldCap 得到的结果,高一位为 0,当结果为 0 时表示元素在扩容时位置不会发生任何变化,而 key 2 信息如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P1PdbZHL-1645802475735)(images/1621414931120-1621752756248.png)]

高一位为 1,当结果为 1 时,表示元素在扩容时位置发生了变化,新的下标位置等于原下标位置 + 原数组长度**hashmap,****不必像 1.7 一样全部重新计算位置**

为什么 hashmap 扩容的时候是两倍?

查看源代码

在存入元素时,放入元素位置有一个 (n-1)&hash 的一个算法,和 hash&(newCap-1),这里用到了一个&位运算符

当 HashMap 的容量是 16 时,它的二进制是 10000,(n-1)的二进制是 01111,与 hash 值得计算结果如下

下面就来看一下 HashMap 的容量不是 2 的 n 次幂的情况,当容量为 10 时,二进制为 01010,(n-1)的二进制是 01001,向里面添加同样的元素,结果为

可以看出,有三个不同的元素进过&运算得出了同样的结果,严重的 hash 碰撞了

只有当 n 的值是 2 的 N 次幂的时候,进行&位运算的时候,才可以只看后几位,而不需要全部进行计算

hashmap 线程安全的方式?

HashMap 不是线程安全的,往往在写程序时需要通过一些方法来回避.其实 JDK 原生的提供了 2 种方法让 HashMap 支持线程安全.

方法一:通过 Collections.synchronizedMap()返回一个新的 Map,这个新的 map 就是线程安全的. 这个要求大家习惯基于接口编程,因为返回的并不是 HashMap,而是一个 Map 的实现.

方法二:重新改写了 HashMap,具体的可以查看 java.util.concurrent.ConcurrentHashMap. 这个方法比方法一有了很大的改进.

方法一特点:

通过 Collections.synchronizedMap()来封装所有不安全的 HashMap 的方法,就连 toString, hashCode 都进行了封装. 封装的关键点有 2 处,1)使用了经典的 synchronized 来进行互斥, 2)使用了代理模式 new 了一个新的类,这个类同样实现了 Map 接口.在 Hashmap 上面,synchronized 锁住的是对象,所以第一个申请的得到锁,其他线程将进入阻塞,等待唤醒. 优点:代码实现十分简单,一看就懂.缺点:从锁的角度来看,方法一直接使用了锁住方法,基本上是锁住了尽可能大的代码块.性能会比较差.

方法二特点:

重新写了 HashMap,比较大的改变有如下几点.使用了新的锁机制,把 HashMap 进行了拆分,拆分成了多个独立的块,这样在高并发的情况下减少了锁冲突的可能,使用的是 NonfairSync. 这个特性调用 CAS 指令来确保原子性与互斥性.当如果多个线程恰好操作到同一个 segment 上面,那么只会有一个线程得到运行.

优点:需要互斥的代码段比较少,性能会比较好. ConcurrentHashMap 把整个 Map 切分成了多个块,发生锁碰撞的几率大大降低,性能会比较好. 缺点:代码繁琐

Java 异常处理方式

Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对

象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、 catch、throw、throws 和 finally。

在 Java 应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。

throw 和 throws 的区别:
(1)位置不同:
throw:方法内部
throws: 方法的签名处,方法的声明处

(2)内容不同:
throw+异常对象(检查异常,运行时异常)
throws+异常的类型(可以多个类型,用,拼接)

(3)作用不同:
throw:异常出现的源头,制造异常。
throws:在方法的声明处,告诉方法的调用者,这个方法中可能会出现我声明的这些异常。然后调用者对这个异常进行处理:
要么自己处理要么再继续向外抛出异常

1.throws 声明异常

通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下

去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。注意

非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。

​ 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误

2.throw 抛出异常

如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。 throw 关键字作用是在方法内部抛出一个 Throwable 类型的异常。任何 Java 代码都可以通过 throw 语句抛出异常。

3.trycatch 捕获异常

程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过 try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。如何选择异常类型

可以根据下图来选择是捕获异常,声明异常还是抛出异常

自定义异常在生产中如何应用

Java 虽然提供了丰富的异常处理类,但是在项目中还会经常使用自定义异常,其主要原因是 Java 提供的异常类在某些情况下还是不能满足实际需球。例如以下情况:
1、系统中有些错误是符合 Java 语法,但不符合业务逻辑。

2、在分层的软件结构中,通常是在表现层统一对系统其他层次的异常进行捕获处理。

如何实现一个 IOC 容器?

​ IOC(Inversion of Control),意思是控制反转,不是什么技术,而是一种设计思想,IOC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

​ 在传统的程序设计中,我们直接在对象内部通过 new 进行对象创建,是程序主动去创建依赖对象,而 IOC 是有专门的容器来进行对象的创建,即 IOC 容器来控制对象的创建。

​ 在传统的应用程序中,我们是在对象中主动控制去直接获取依赖对象,这个是正转,反转是由容器来帮忙创建及注入依赖对象,在这个过程过程中,由容器帮我们查找级注入依赖对象,对象只是被动的接受依赖对象。

​ 1、先准备一个基本的容器对象,包含一些 map 结构的集合,用来方便后续过程中存储具体的对象

​ 2、进行配置文件的读取工作或者注解的解析工作,将需要创建的 bean 对象都封装成 BeanDefinition 对象存储在容器中

​ 3、容器将封装好的 BeanDefinition 对象通过反射的方式进行实例化,完成对象的实例化工作

​ 4、进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象的创建,变成一个完整的 bean 对象,存储在容器的某个 map 结构中

​ 5、通过容器对象来获取对象,进行对象的获取和逻辑处理工作

​ 6、提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁

说说你对 Spring 的理解?

官网地址:https://spring.io/projects/spring-framework#overview

压缩包下载地址:https://repo.spring.io/release/org/springframework/spring/

源码地址:https://github.com/spring-projects/spring-framework

Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs. As of Spring Framework 5.1, Spring requires JDK 8+ (Java SE 8+) and provides out-of-the-box support for JDK 11 LTS. Java SE 8 update 60 is suggested as the minimum patch release for Java 8, but it is generally recommended to use a recent patch release.

Spring supports a wide range of application scenarios. In a large enterprise, applications often exist for a long time and have to run on a JDK and application server whose upgrade cycle is beyond developer control. Others may run as a single jar with the server embedded, possibly in a cloud environment. Yet others may be standalone applications (such as batch or integration workloads) that do not need a server.

Spring is open source. It has a large and active community that provides continuous feedback based on a diverse range of real-world use cases. This has helped Spring to successfully evolve over a very long time.

Spring 使创建 Java 企业应用程序变得更加容易。它提供了在企业环境中接受 Java 语言所需的一切,,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并可根据应用程序的需要灵活地创建多种体系结构。 从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8+),并且已经为 JDK 9 提供了现成的支持。

Spring支持各种应用场景, 在大型企业中, 应用程序通常需要运行很长时间,而且必须运行在 jdk 和应用服务器上,这种场景开发人员无法控制其升级周期。 其他可能作为一个单独的jar嵌入到服务器去运行,也有可能在云环境中。还有一些可能是不需要服务器的独立应用程序(如批处理或集成的工作任务)。

Spring 是开源的。它拥有一个庞大而且活跃的社区,提供不同范围的,真实用户的持续反馈。这也帮助Spring不断地改进,不断发展。

你觉得 Spring 的核心是什么?

​ spring 是一个开源框架。

​ spring 是为了简化企业开发而生的,使得开发变得更加优雅和简洁。

​ spring 是一个IOCAOP的容器框架。

​ IOC:控制反转

​ AOP:面向切面编程

​ 容器:包含并管理应用对象的生命周期,就好比用桶装水一样,spring 就是桶,而对象就是水

说一下使用 spring 的优势?

​ 1、Spring 通过 DI、AOP 和消除样板式代码来简化企业级 Java 开发

​ 2、Spring 框架之外还存在一个构建在核心框架之上的庞大生态圈,它将 Spring 扩展到不同的领域,如 Web 服务、REST、移动开发以及 NoSQL

​ 3、低侵入式设计,代码的污染极低

​ 4、独立于各种应用服务器,基于 Spring 框架的应用,可以真正实现 Write Once,Run Anywhere 的承诺

​ 5、Spring 的 IoC 容器降低了业务对象替换的复杂性,提高了组件之间的解耦

​ 6、Spring 的 AOP 支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用

​ 7、Spring 的 ORM 和 DAO 提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问

​ 8、Spring 的高度开放性,并不强制应用完全依赖于 Spring,开发者可自由选用 Spring 框架的部分或全部

Spring 是如何简化开发的?

​ 基于 POJO 的轻量级和最小侵入性编程

​ 通过依赖注入和面向接口实现松耦合

​ 基于切面和惯例进行声明式编程

​ 通过切面和模板减少样板式代码

说说你对 Aop 的理解?

​ AOP 全称叫做 Aspect Oriented Programming 面向切面编程。它是为解耦而生的,解耦是程序员编码开发过程中一直追求的境界,AOP 在业务类的隔离上,绝对是做到了解耦,在这里面有几个核心的概念:

  • 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级 Java 应用中有关横切关注点的例子。 在 Spring AOP 中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。

  • 连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在 Spring AOP 中,一个连接点总是代表一个方法的执行。

  • 通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多 AOP 框架,包括 Spring 在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。

  • 切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是 AOP 的核心:Spring 默认使用 AspectJ 切点语义。

  • 引入(Introduction): 声明额外的方法或者某个类型的字段。Spring 允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使 bean 实现 IsModified接口, 以便简化缓存机制(在 AspectJ 社区,引入也被称为内部类型声明(inter))。

  • 目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然 Spring AOP 是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。

  • AOP 代理(AOP proxy):AOP 框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在 Spring 中,AOP 代理可以是 JDK 动态代理或 CGLIB 代理。

  • 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用 AspectJ 编译器)、类加载时或运行时中完成。 Spring 和其他纯 Java AOP 框架一样,是在运行时完成织入的。

    这些概念都太学术了,如果更简单的解释呢,其实非常简单:

    任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,AOP 正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。

说说你对 IOC 的理解?

	IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
	IOC与大家熟知的依赖注入同理,. 这是一个通过依赖注入对象的过程 也就是说,它们所使用的对象,是通过构造函数参数,工厂方法的参数或这是从工厂方法的构造函数或返回值的对象实例设置的属性,然后容器在创建bean时注入这些需要的依赖。 这个过程相对普通创建对象的过程是反向的(因此称之为IoC),bean本身通过直接构造类来控制依赖关系的实例化或位置,或提供诸如服务定位器模式之类的机制。

​ 如果这个过程比较难理解的话,那么可以想象自己找女朋友和婚介公司找女朋友的过程。如果这个过程能够想明白的话,那么我们现在回答上面的问题:

1、谁控制谁:在之前的编码过程中,都是需要什么对象自己去创建什么对象,有程序员自己来控制对象,而有了IOC容器之后,就会变成由IOC容器来控制对象,
2、控制什么:在实现过程中所需要的对象及需要依赖的对象
3、什么是反转:在没有IOC容器之前我们都是在对象中主动去创建依赖的对象,这是正转的,而有了IOC之后,依赖的对象直接由IOC容器创建后注入到对象中,由主动创建变成了被动接受,这是反转
4、哪些方面被反转:依赖的对象

BeanFactory 和 ApplicationContext 有什么区别

相同:

  • Spring 提供了两种不同的 IOC 容器,一个是 BeanFactory,另外一个是 ApplicationContext,它们都是 Java interface,ApplicationContext 继承于 BeanFactory(ApplicationContext 继承 ListableBeanFactory。
  • 它们都可以用来配置 XML 属性,也支持属性的自动注入。
  • 而 ListableBeanFactory 继承 BeanFactory),BeanFactory 和 ApplicationContext 都提供了一种方式,使用 getBean(“bean name”)获取 bean。

不同:

  • 当你调用 getBean()方法时,BeanFactory 仅实例化 bean,而 ApplicationContext 在启动容器的时候实例化单例 bean,不会等待调用 getBean()方法时再实例化。
  • BeanFactory 不支持国际化,即 i18n,但 ApplicationContext 提供了对它的支持。
  • BeanFactory 与 ApplicationContext 之间的另一个区别是能够将事件发布到注册为监听器的 bean。
  • BeanFactory 的一个核心实现是 XMLBeanFactory 而 ApplicationContext 的一个核心实现是 ClassPathXmlApplicationContext,Web 容器的环境我们使用 WebApplicationContext 并且增加了 getServletContext 方法。
  • 如果使用自动注入并使用 BeanFactory,则需要使用 API 注册 AutoWiredBeanPostProcessor,如果使用 ApplicationContext,则可以使用 XML 进行配置。
  • 简而言之,BeanFactory 提供基本的 IOC 和 DI 功能,而 ApplicationContext 提供高级功能,BeanFactory 可用于测试和非生产使用,但 ApplicationContext 是功能更丰富的容器实现,应该优于 BeanFactory

简述 spring bean 的生命周期?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ONIcVJJL-1645802475736)(images/bean的生命周期.png)]

1、实例化 bean 对象

​ 通过反射的方式进行对象的创建,此时的创建只是在堆空间中申请空间,属性都是默认值

2、设置对象属性

​ 给对象中的属性进行值的设置工作

3、检查 Aware 相关接口并设置相关依赖

​ 如果对象中需要引用容器内部的对象,那么需要调用 aware 接口的子类方法来进行统一的设置

4、BeanPostProcessor 的前置处理

​ 对生成的 bean 对象进行前置的处理工作

5、检查是否是 InitializingBean 的子类来决定是否调用 afterPropertiesSet 方法

​ 判断当前 bean 对象是否设置了 InitializingBean 接口,然后进行属性的设置等基本工作

6、检查是否配置有自定义的 init-method 方法

​ 如果当前 bean 对象定义了初始化方法,那么在此处调用初始化方法

7、BeanPostProcessor 后置处理

​ 对生成的 bean 对象进行后置的处理工作

8、注册必要的 Destruction 相关回调接口

​ 为了方便对象的销毁,在此处调用注销的回调接口,方便对象进行销毁操作

9、获取并使用 bean 对象

​ 通过容器来获取对象并进行使用

10、是否实现 DisposableBean 接口

​ 判断是否实现了 DisposableBean 接口,并调用具体的方法来进行对象的销毁工作

11、是否配置有自定义的 destory 方法

​ 如果当前 bean 对象定义了销毁方法,那么在此处调用销毁方法

spring 支持的 bean 作用域有哪些?

① singleton

使用该属性定义 Bean 时,IOC 容器仅创建一个 Bean 实例,IOC 容器每次返回的是同一个 Bean 实例。

② prototype

使用该属性定义 Bean 时,IOC 容器可以创建多个 Bean 实例,每次返回的都是一个新的实例。

③ request

该属性仅对 HTTP 请求产生作用,使用该属性定义 Bean 时,每次 HTTP 请求都会创建一个新的 Bean,适用于 WebApplicationContext 环境。

④ session

该属性仅用于 HTTP Session,同一个 Session 共享一个 Bean 实例。不同 Session 使用不同的实例。

⑤ global-session

该属性仅用于 HTTP Session,同 session 作用域不同的是,所有的 Session 共享一个 Bean 实例。

Spring 框架中的单例 Bean 是线程安全的么?

​ Spring 中的 Bean 对象默认是单例的,框架并没有对 bean 进行多线程的封装处理

​ 如果 Bean 是有状态的,那么就需要开发人员自己来保证线程安全的保证,最简单的办法就是改变 bean 的作用域把 singleton 改成 prototype,这样每次请求 bean 对象就相当于是创建新的对象来保证线程的安全

​ 有状态就是由数据存储的功能

​ 无状态就是不会存储数据,你想一下,我们的 controller,service 和 dao 本身并不是线程安全的,只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制遍历,这是自己线程的工作内存,是最安全的。

​ 因此在进行使用的时候,不要在 bean 中声明任何有状态的实例变量或者类变量,如果必须如此,也推荐大家使用 ThreadLocal 把变量变成线程私有,如果 bean 的实例变量或者类变量需要在多个线程之间共享,那么就只能使用 synchronized,lock,cas 等这些实现线程同步的方法了。

spring 框架中使用了哪些设计模式及应用场景

​ 1.工厂模式,在各种 BeanFactory 以及 ApplicationContext 创建中都用到了

​ 2.模版模式,在各种 BeanFactory 以及 ApplicationContext 实现中也都用到了

​ 3.代理模式,Spring AOP 利用了 AspectJ AOP 实现的! AspectJ AOP 的底层用了动态代理

​ 4.策略模式,加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource 但他们都有共同的借口 Resource;在 Aop 的实现中,采用了两种不同的方式,JDK 动态代理和 CGLIB 代理

​ 5.单例模式,比如在创建 bean 的时候。

​ 6.观察者模式,spring 中的 ApplicationEvent,ApplicationListener,ApplicationEventPublisher

​ 7.适配器模式,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter,AfterReturningAdapter

​ 8.装饰者模式,源码中类型带 Wrapper 或者 Decorator 的都是

spring 事务的实现方式原理是什么?

​ 在使用 Spring 框架的时候,可以有两种事务的实现方式,一种是编程式事务,有用户自己通过代码来控制事务的处理逻辑,还有一种是声明式事务,通过@Transactional 注解来实现。

​ 其实事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,spring 对事务功能进行了扩展实现,一般我们很少会用编程式事务,更多的是通过添加@Transactional 注解来进行实现,当添加此注解之后事务的自动功能就会关闭,有 spring 框架来帮助进行控制。

​ 其实事务操作是 AOP 的一个核心体现,当一个方法添加@Transactional 注解之后,spring 会基于这个类生成一个代理对象,会将这个代理对象作为 bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给关系,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作。

TransactionInterceptor

spring 事务的隔离级别有哪些?

​ spring 中的事务隔离级别就是数据库的隔离级别,有以下几种:

​ read uncommitted

​ read committed

​ repeatable read

​ serializable

​ 在进行配置的时候,如果数据库和 spring 代码中的隔离级别不同,那么以 spring 的配置为主。

spring 的事务传播机制是什么?

​ 多个事务方法相互调用时,事务如何在这些方法之间进行传播,spring 中提供了 7 中不同的传播特性,来保证事务的正常执行:

​ REQUIRED:默认的传播特性,如果当前没有事务,则新建一个事务,如果当前存在事务,则加入这个事务

​ SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,则以非事务的方式执行

​ MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常

​ REQUIRED_NEW:创建一个新事务,如果存在当前事务,则挂起改事务

​ NOT_SUPPORTED:以非事务方式执行,如果存在当前事务,则挂起当前事务

​ NEVER:不使用事务,如果当前事务存在,则抛出异常

​ NESTED:如果当前事务存在,则在嵌套事务中执行,否则 REQUIRED 的操作一样

​ NESTED 和 REQUIRED_NEW 的区别:

​ REQUIRED_NEW 是新建一个事务并且新开始的这个事务与原有事务无关,而 NESTED 则是当前存在事务时会开启一个嵌套事务,在 NESTED 情况下,父事务回滚时,子事务也会回滚,而 REQUIRED_NEW 情况下,原有事务回滚,不会影响新开启的事务

​ NESTED 和 REQUIRED 的区别:

​ REQUIRED 情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否 catch 异常,事务都会回滚,而在 NESTED 情况下,被调用方发生异常时,调用方可以 catch 其异常,这样只有子事务回滚,父事务不会回滚。

spring 事务什么时候会失效?

​ 1、bean 对象没有被 spring 容器管理

​ 2、方法的访问修饰符不是 public

​ 3、自身调用问题

​ 4、数据源没有配置事务管理器

​ 5、数据库不支持事务

​ 6、异常被捕获

​ 7、异常类型错误或者配置错误

什么的是 bean 的自动装配,它有哪些方式?

​ bean 的自动装配指的是 bean 的属性值在进行注入的时候通过某种特定的规则和方式去容器中查找,并设置到具体的对象属性中,主要有五种方式:

​ no – 缺省情况下,自动配置是通过“ref”属性手动设定,在项目中最常用
​ byName – 根据属性名称自动装配。如果一个 bean 的名称和其他 bean 属性的名称是一样的,将会自装配它。
​ byType – 按数据类型自动装配,如果 bean 的数据类型是用其它 bean 属性的数据类型,兼容并自动装配它。
​ constructor – 在构造函数参数的 byType 方式。
​ autodetect – 如果找到默认的构造函数,使用“自动装配用构造”; 否则,使用“按类型自动装配”。

spring、springmvc、springboot 的区别是什么?

​ spring 和 springMvc:

  1. spring 是一个一站式的轻量级的 java 开发框架,核心是控制反转(IOC)和面向切面(AOP),针对于开发的 WEB 层(springMvc)、业务层(Ioc)、持久层(jdbcTemplate)等都提供了多种配置解决方案;

  2. springMvc 是 spring 基础之上的一个 MVC 框架,主要处理 web 开发的路径映射和视图渲染,属于 spring 框架中 WEB 层开发的一部分;

springMvc 和 springBoot:

1、springMvc 属于一个企业 WEB 开发的 MVC 框架,涵盖面包括前端视图开发、文件配置、后台接口逻辑开发等,XML、config 等配置相对比较繁琐复杂;

2、springBoot 框架相对于 springMvc 框架来说,更专注于开发微服务后台接口,不开发前端视图,同时遵循默认优于配置,简化了插件配置流程,不需要配置 xml,相对 springmvc,大大简化了配置流程;

总结:

1、Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa 等等。但他们的基础都是 Spring 的 ioc、aop 等. ioc 提供了依赖注入的容器, aop 解决了面向横切面编程,然后在此两者的基础上实现了其他延伸产品的高级功能;

2、springMvc 主要解决 WEB 开发的问题,是基于 Servlet 的一个 MVC 框架,通过 XML 配置,统一开发前端视图和后端逻辑;

3、由于 Spring 的配置非常复杂,各种 XML、JavaConfig、servlet 处理起来比较繁琐,为了简化开发者的使用,从而创造性地推出了 springBoot 框架,默认优于配置,简化了 springMvc 的配置流程;但区别于 springMvc 的是,springBoot 专注于单体微服务接口开发,和前端解耦,虽然 springBoot 也可以做成 springMvc 前后台一起开发,但是这就有点不符合 springBoot 框架的初衷了;

springmvc 工作流程是什么?

​ 当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEq4CGWm-1645802475737)(images/springmvc运行流程.jpg)]

1、DispatcherServlet 表示前置控制器,是整个 SpringMVC 的控制中心。用户发出请求,DispatcherServlet 接收请求并拦截请求。
2、HandlerMapping 为处理器映射。DispatcherServlet 调用 HandlerMapping,HandlerMapping 根据请求 url 查找 Handler。
3、返回处理器执行链,根据 url 查找控制器,并且将解析后的信息传递给 DispatcherServlet
4、HandlerAdapter 表示处理器适配器,其按照特定的规则去执行 Handler。
5、执行 handler 找到具体的处理器
6、Controller 将具体的执行信息返回给 HandlerAdapter,如 ModelAndView。
7、HandlerAdapter 将视图逻辑名或模型传递给 DispatcherServlet。
8、DispatcherServlet 调用视图解析器(ViewResolver)来解析 HandlerAdapter 传递的逻辑视图名。
9、视图解析器将解析的逻辑视图名传给 DispatcherServlet。
10、DispatcherServlet 根据视图解析器解析的视图结果,调用具体的视图,进行试图渲染
11、将响应数据返回给客户端

springmvc 的九大组件有哪些?

1.HandlerMapping
根据 request 找到相应的处理器。因为 Handler(Controller)有两种形式,一种是基于类的 Handler,另一种是基于 Method 的 Handler(也就是我们常用的)

2.HandlerAdapter
调用 Handler 的适配器。如果把 Handler(Controller)当做工具的话,那么 HandlerAdapter 就相当于干活的工人

3.HandlerExceptionResolver
对异常的处理

4.ViewResolver
用来将 String 类型的视图名和 Locale 解析为 View 类型的视图

5.RequestToViewNameTranslator
有的 Handler(Controller)处理完后没有设置返回类型,比如是 void 方法,这是就需要从 request 中获取 viewName

6.LocaleResolver
从 request 中解析出 Locale。Locale 表示一个区域,比如 zh-cn,对不同的区域的用户,显示不同的结果,这就是 i18n(SpringMVC 中有具体的拦截器 LocaleChangeInterceptor)

7.ThemeResolver
主题解析,这种类似于我们手机更换主题,不同的 UI,css 等

8.MultipartResolver
处理上传请求,将普通的 request 封装成 MultipartHttpServletRequest

9.FlashMapManager
用于管理 FlashMap,FlashMap 用于在 redirect 重定向中传递参数

springboot 自动配置原理是什么?

在之前的课程中我们讲解了 springboot 的启动过程,其实在面试过程中问的最多的可能是自动装配的原理,而自动装配是在启动过程中完成,只不过在刚开始的时候我们选择性的跳过了,下面详细讲解自动装配的过程。

1、在 springboot 的启动过程中,有一个步骤是创建上下文,如果不记得可以看下面的代码:

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            //此处完成自动装配的过程
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

2、在 prepareContext 方法中查找 load 方法,一层一层向内点击,找到最终的 load 方法

//prepareContext方法
	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
        //load方法完成该功能
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}


	/**
	 * Load beans into the application context.
	 * @param context the context to load beans into
	 * @param sources the sources to load
	 * 加载bean对象到context中
	 */
	protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
        //获取bean对象定义的加载器
		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}

	/**
	 * Load the sources into the reader.
	 * @return the number of loaded beans
	 */
	int load() {
		int count = 0;
		for (Object source : this.sources) {
			count += load(source);
		}
		return count;
	}

3、实际执行 load 的是 BeanDefinitionLoader 中的 load 方法,如下:

	//实际记载bean的方法
	private int load(Object source) {
		Assert.notNull(source, "Source must not be null");
        //如果是class类型,启用注解类型
		if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
        //如果是resource类型,启动xml解析
		if (source instanceof Resource) {
			return load((Resource) source);
		}
        //如果是package类型,启用扫描包,例如@ComponentScan
		if (source instanceof Package) {
			return load((Package) source);
		}
        //如果是字符串类型,直接加载
		if (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}

4、下面方法将用来判断是否资源的类型,是使用 groovy 加载还是使用注解的方式

	private int load(Class<?> source) {
        //判断使用groovy脚本
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			load(loader);
		}
        //使用注解加载
		if (isComponent(source)) {
			this.annotatedReader.register(source);
			return 1;
		}
		return 0;
	}

5、下面方法判断启动类中是否包含@Component 注解,但是会神奇的发现我们的启动类中并没有该注解,继续更进发现 MergedAnnotations 类传入了一个参数 SearchStrategy.TYPE_HIERARCHY,会查找继承关系中是否包含这个注解,@SpringBootApplication–>@SpringBootConfiguration–>@Configuration–>@Component,当找到@Component 注解之后,会把该对象注册到 AnnotatedBeanDefinitionReader 对象中

private boolean isComponent(Class<?> type) {
   // This has to be a bit of a guess. The only way to be sure that this type is
   // eligible is to make a bean definition out of it and try to instantiate it.
   if (MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class)) {
      return true;
   }
   // Nested anonymous classes are not eligible for registration, nor are groovy
   // closures
   return !type.getName().matches(".*\\$_.*closure.*") && !type.isAnonymousClass()
         && type.getConstructors() != null && type.getConstructors().length != 0;
}

	/**
	 * Register a bean from the given bean class, deriving its metadata from
	 * class-declared annotations.
	 * 从给定的bean class中注册一个bean对象,从注解中找到相关的元数据
	 */
	private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
			@Nullable BeanDefinitionCustomizer[] customizers) {

		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}

		abd.setInstanceSupplier(supplier);
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
		abd.setScope(scopeMetadata.getScopeName());
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
		if (qualifiers != null) {
			for (Class<? extends Annotation> qualifier : qualifiers) {
				if (Primary.class == qualifier) {
					abd.setPrimary(true);
				}
				else if (Lazy.class == qualifier) {
					abd.setLazyInit(true);
				}
				else {
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		if (customizers != null) {
			for (BeanDefinitionCustomizer customizer : customizers) {
				customizer.customize(abd);
			}
		}

		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}

	/**
	 * Register the given bean definition with the given bean factory.
	 * 注册主类,如果有别名可以设置别名
	 */
	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

//@SpringBootApplication
@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 {}

//@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

//@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {}

当看完上述代码之后,只是完成了启动对象的注入,自动装配还没有开始,下面开始进入到自动装配。

6、自动装配入口,从刷新容器开始

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
                // 此处是自动装配的入口
				invokeBeanFactoryPostProcessors(beanFactory);
            }

7、在 invokeBeanFactoryPostProcessors 方法中完成 bean 的实例化和执行

/**
	 * Instantiate and invoke all registered BeanFactoryPostProcessor beans,
	 * respecting explicit order if given.
	 * <p>Must be called before singleton instantiation.
	 */
	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        //开始执行beanFactoryPostProcessor对应实现类,需要知道的是beanFactoryPostProcessor是spring的扩展接口,在刷新容器之前,该接口可以用来修改bean元数据信息
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

		// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
		// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
		if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}
	}

8、查看 invokeBeanFactoryPostProcessors 的具体执行方法

	public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

		// Invoke BeanDefinitionRegistryPostProcessors first, if any.
		Set<String> processedBeans = new HashSet<>();

		if (beanFactory instanceof BeanDefinitionRegistry) {
			BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
			List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
			List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
			//开始遍历三个内部类,如果属于BeanDefinitionRegistryPostProcessor子类,加入到bean注册的集合,否则加入到regularPostProcessors
			for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
				if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
					BeanDefinitionRegistryPostProcessor registryProcessor =
							(BeanDefinitionRegistryPostProcessor) postProcessor;
					registryProcessor.postProcessBeanDefinitionRegistry(registry);
					registryProcessors.add(registryProcessor);
				}
				else {
					regularPostProcessors.add(postProcessor);
				}
			}

			// Do not initialize FactoryBeans here: We need to leave all regular beans
			// uninitialized to let the bean factory post-processors apply to them!
			// Separate between BeanDefinitionRegistryPostProcessors that implement
			// PriorityOrdered, Ordered, and the rest.
			List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

			// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
            //通过BeanDefinitionRegistryPostProcessor获取到对应的处理类“org.springframework.context.annotation.internalConfigurationAnnotationProcessor”,但是需要注意的是这个类在springboot中搜索不到,这个类的完全限定名在AnnotationConfigEmbeddedWebApplicationContext中,在进行初始化的时候会装配几个类,在创建AnnotatedBeanDefinitionReader对象的时候会将该类注册到bean对象中,此处可以看到internalConfigurationAnnotationProcessor为bean名称,容器中真正的类是ConfigurationClassPostProcessor
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            //首先执行类型为PriorityOrdered的BeanDefinitionRegistryPostProcessor
            //PriorityOrdered类型表明为优先执行
			for (String ppName : postProcessorNames) {
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    //获取对应的bean
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    //用来存储已经执行过的BeanDefinitionRegistryPostProcessor
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
            //开始执行装配逻辑
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();

			// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
            //其次执行类型为Ordered的BeanDefinitionRegistryPostProcessor
            //Ordered表明按顺序执行
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();

			// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
            //循环中执行类型不为PriorityOrdered,Ordered类型的BeanDefinitionRegistryPostProcessor
			boolean reiterate = true;
			while (reiterate) {
				reiterate = false;
				postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
				for (String ppName : postProcessorNames) {
					if (!processedBeans.contains(ppName)) {
						currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
						processedBeans.add(ppName);
						reiterate = true;
					}
				}
				sortPostProcessors(currentRegistryProcessors, beanFactory);
				registryProcessors.addAll(currentRegistryProcessors);
				invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
				currentRegistryProcessors.clear();
			}

			// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
            //执行父类方法,优先执行注册处理类
			invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
            //执行有规则处理类
			invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
		}

		else {
			// Invoke factory processors registered with the context instance.
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}

		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let the bean factory post-processors apply to them!
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

		// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
		// Ordered, and the rest.
		List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
			if (processedBeans.contains(ppName)) {
				// skip - already processed in first phase above
			}
			else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
			}
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
				orderedPostProcessorNames.add(ppName);
			}
			else {
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

		// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
		List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String postProcessorName : orderedPostProcessorNames) {
			orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

		// Finally, invoke all other BeanFactoryPostProcessors.
		List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String postProcessorName : nonOrderedPostProcessorNames) {
			nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

		// Clear cached merged bean definitions since the post-processors might have
		// modified the original metadata, e.g. replacing placeholders in values...
		beanFactory.clearMetadataCache();
	}

9、开始执行自动配置逻辑(启动类指定的配置,非默认配置),可以通过 debug 的方式一层层向里进行查找,会发现最终会在 ConfigurationClassParser 类中,此类是所有配置类的解析类,所有的解析逻辑在 parser.parse(candidates)中

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
                //是否是注解类
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
    	//执行配置类
		this.deferredImportSelectorHandler.process();
	}
-------------------
    	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(metadata, beanName));
	}
-------------------
    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
            //循环处理bean,如果有父类,则处理父类,直至结束
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}

10、继续跟进 doProcessConfigurationClass 方法,此方式是支持注解配置的核心逻辑

/**
	 * Apply processing and build a complete {@link ConfigurationClass} by reading the
	 * annotations, members and methods from the source class. This method can be called
	 * multiple times as relevant sources are discovered.
	 * @param configClass the configuration class being build
	 * @param sourceClass a source class
	 * @return the superclass, or {@code null} if none found or previously processed
	 */
	@Nullable
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

        //处理内部类逻辑,由于传来的参数是启动类,并不包含内部类,所以跳过
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass);
		}

		// Process any @PropertySource annotations
        //针对属性配置的解析
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
        // 这里是根据启动类@ComponentScan注解来扫描项目中的bean
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {

			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
                //遍历项目中的bean,如果是注解定义的bean,则进一步解析
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        //递归解析,所有的bean,如果有注解,会进一步解析注解中包含的bean
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
        //递归解析,获取导入的配置类,很多情况下,导入的配置类中会同样包含导入类注解
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// Process any @ImportResource annotations
        //解析@ImportResource配置类
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
        //处理@Bean注解修饰的类
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
        // 处理接口中的默认方法
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
        //如果该类有父类,则继续返回,上层方法判断不为空,则继续递归执行
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

11、查看获取配置类的逻辑

processImports(configClass, sourceClass, getImports(sourceClass), true);

	/**
	 * Returns {@code @Import} class, considering all meta-annotations.
	 */
	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}
------------------
    	/**
	 * Recursively collect all declared {@code @Import} values. Unlike most
	 * meta-annotations it is valid to have several {@code @Import}s declared with
	 * different values; the usual process of returning values from the first
	 * meta-annotation on a class is not sufficient.
	 * <p>For example, it is common for a {@code @Configuration} class to declare direct
	 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
	 * annotation.
	 * 看到所有的bean都以导入的方式被加载进去
	 */
	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}

12、继续回到 ConfigurationClassParser 中的 parse 方法中的最后一行,继续跟进该方法:

this.deferredImportSelectorHandler.process()
-------------
public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					deferredImports.forEach(handler::register);
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
---------------
  public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}
------------
    /**
		 * Return the imports defined by the group.
		 * @return each import with its associated configuration class
		 */
		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}
	}
------------
    public DeferredImportSelector getImportSelector() {
			return this.importSelector;
		}
------------
    @Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

微信公众号

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Java高级工程师面试题通常涵盖了Java高级特性、设计模式、多线程、性能优化等多个方面。以下是一些可能会在Java高级工程师面试中遇到的问和解答: 1. 请解释一下Java中的反射机制是什么?有什么应用场景? 反射是指在程序运行时动态地获取类的信息并操作类的方法、属性等。Java中的反射机制通过使用Class类来实现,可以在运行时动态创建对象、调用方法、访问属性等。它的应用场景包括框架开发、动态代理、单元测试等。 2. 请解释一下Java中的设计模式是什么?列举一些常用的设计模式。 设计模式是一种在软件开发中解决常见问的经验总结,它提供了一套可复用、可扩展的解决方案。常用的设计模式包括单例模式、工厂模式、观察者模式、装饰器模式、适配器模式等。 3. 请解释一下Java中的多线程是什么?如何实现多线程? 多线程是指在一个程序中同时执行多个线程,每个线程可以独立执行不同的任务。Java中实现多线程的方式有两种:一种是继承Thread类,重写run()方法,并调用start()方法启动线程;另一种是实现Runnable接口,实现run()方法,并通过Thread类的构造方法将Runnable对象传递给Thread对象。 4. 请解释一下Java中的垃圾回收机制是什么?如何进行垃圾回收? Java中的垃圾回收机制是指通过自动回收不再使用的内存资源,以避免内存泄漏和程序崩溃。Java的垃圾回收机制通过JVM自动进行,它会监测对象的引用情况,并在合适的时间自动回收没有引用的对象。可以通过System.gc()方法来显式地调用垃圾回收。 以上是一些可能会在Java高级工程师面试中涉及到的问和解答,希望对你有帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java面试题大全(整理版)1000+面试题 (面试准备+Java基础+高级特性+常见问+答案解析)](https://download.csdn.net/download/weixin_41784475/88221206)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Java高级工程师面试题总结及参考答案](https://blog.csdn.net/weixin_34187822/article/details/93967307)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java高级工程师常见面试题(答案)](https://blog.csdn.net/m0_67402235/article/details/125437777)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tellsea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值