知识点汇总6

170 阅读25分钟

1、接口和实现类区别

  • 抽象类可以有普通成员函数、接口只能是public abstract方法
  • 抽象类的成员变量可以任意修饰,接口只能是public static final修饰
  • 抽象类单继承、接口多实现
  • 抽象类用于继承特性,像是is a的关系,它是对类本质的抽象;接口是用于约束行为的,像是like a,如Bird like a aircraft,就是告诉实现类可以做什么

2、List和Set区别

  • List:按照对象进入的顺序保存,可重复,允许多个null值,可用Iterator取出所有元素,还可以通过get(index)来拿
  • Set:无序,不可重复,1个null值,只能用Iterator来取出所有元素

3、hashCode和equals

hashCode()方法定义于Object中,会返回int类型整数。计算出的hash码,放入哈希表的索引位置。可以帮我们快速检测到key-value。

如果往hashSet中放,会根据hashCode来计算出索引位置,看它是否有值,没有直接放入,有值就用equals比较,如果一样就不放了,如果不一样就按照某些散列算法重新放入。这样就降低了equals的比较次数,提高效率。

4、ArrayList和LinkedList的区别

  • ArrayList是动态数组,是连续内存存储,适用于下标访问。插入和删除可能出现扩容机制:创建新数组,把旧数组拷贝到新数组中。使用尾插法并指定初始容量可以提升性能,甚至超过LinkedList,因为LinkedList需要创建Node对象,很耗费资源。

  • LinkedList是基于链表,是不连续的,适用于数据插入和删除操作,不适合查询。LinkedList必须用Iterator来遍历,不能使用for循环。

5、HashMap和HashTable的区别

HashTable是线程安全的,它里面的方法都用了synchronized。HashMap是线程不安全的。

HashMap允许key和value为空,HashTable不允许。

4、ConcurrentHashMap原理,jdk7和jdk8的区别

jdk7:

数据结构:ReentrantLocj + segment + HashEntry,一个Segment包含一个HashEntry数组,每个HashEntry又是一个链表结构。

元素查询:

jdk8:

5、如何实现一个IOC容器

  1. 配置文件,配置包扫描路径。
  2. 定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入层等等、依赖注入注解、获取配置文件注解等
  3. 从配置文件中获取需要扫描的包路径,递归获取这个包下的.class文件,把所有的.class文件放入一个Set集合中存储
  4. 遍历Set集合,获取在类上指定注解的类,交给IOC容器,定义一个安全按的Map存储这些对像
  5. 对需要注入的类进行依赖注入

6、什么是字节码?采用字节码的好处

什么是字节码

java中的编译器和解释器:在我们操作系统和编译程序中间加入了一层叫虚拟机,它在任何平台上都提供了一套通用的接口。

编译程序面向虚拟机,生成虚拟机可以看的懂得代码。然后解释器会把虚拟机代码转为指定系统可以看得懂的机器码。这种供虚拟机理解的代码叫做字节码。

不同的平台的解释器是不一样的,实现的虚拟机是相同的。

Java源码-->编译器-->jvm可以执行的java字节码(虚拟指令)-->jvm中解释-->机器可以执行的二进制机器码-->程序运行。

好处

实现了Java的跨平台,字节码可移植,可以在任何机器上执行。

7、Java有哪些类加载器

JDK有三个类加载器:Bootstrap ClassLoader、ExtClassLoader、AppClassLoader。

Bootstrap ClassLoader:是ExtClassLoader的父类加载器。默认负责加载的是%JAVA_HOME%lib下的jar包和class文件。

ExtClassLoader(扩展类加载器):是AppClassLoader的父类加载器。负责加载%JAVA_HOME%lib/ext文件夹下的jar包和class类。

AppClassLoader:是自定义的类加载器的父类,负责加载classpath下的类文件。

线程上下文类加载器、系统类加载器。

8、双亲委派模型

向上委派:向上查找缓存,看看是否加载了该类、有则直接返回、没有则继续向上。委派到顶以后、缓存中还是没有,就去加载路径中查找,有则加载返回、没有的话向下查找。

向下查找,查找加载路径,有则加载返回,没有继续向下查找。向下查找查找到发起加载的加载器为止。

img

为什么要有这个模型?

为了安全性:避免用户自己编写的类动态替换java的一些核心类,比如String。

同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类。

9、Java中的异常体系

顶级父类Throwable,两个子类Exception、Error。

Error是程序无法处理的错误,出现错误,程序停止运行。

Exception不会停止程序。又分为RuntimeException(运行时异常)和CheckedException(检查异常)。RuntimeException是程序运行时的异常,CheckedException是编译时就会报错。

img

10、GC如何判断对象可以被回收

  • 引用计数法:每个对象有引用计数法,新增一个引用计数时+1,释放时-1,计数为0的时候可以被回收。A\B对象相互引用的话就永远不会被回收。好处是效率很高。

  • 可达性分析:从GC Roots开始向下搜索,搜索所走过的路径成为引用链。当一个对象到GC Root没有任何引用连接时,则整明此对象不可用,可回收。

GC Roots的对象

  • 虚拟机栈引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法中JNI(一般说的Native方法引用的对象)

比如循环引用了,引用链断了一次就会被回收吗

第一次可达性分析发现没有与GC Roots相连的引用链,是不会直接回收的,而是第二次在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。

当对象变为了GC Roots不可达,GC会判断该对象是否覆盖了finalize方法,如果没覆盖,会回收。否则,若对象未执行finalize方法,将其放入F-Queue队列,由低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,不可达则回收,否则复活。

每个对象只能触发一次finalize方法。

由于finalize方法的运行代码比较高,一般不会覆盖。

11、线程的生命周期和线程状态

生命周期

创建、就绪、运行、阻塞、死亡。

阻塞又分三种:

(1)等待阻塞:运行线程的wait方法,该线程会释放占用的所有资源,JVM会把该线程放入等待池。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll来唤醒。wait是Object类的方法。

(2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则会把该线程放入锁池。

(3)其他阻塞:运行的线程执行sleep或者join方法,或者发出了I/O请求。JVM会把该线程设为组设状态。当sleep状态超时时,join等待线程终止或者超时、或者I/O处理完毕后,线程重新进入就绪状态、sleep是Thread类的方法。

线程状态

新建:创建线程对象

就绪:调用start方法,只是变为就绪状态、等待cpu分配

运行:获取cpu执行代码

阻塞:线程因为某种原因暂时停止运行,只有线程再进入就绪状态才能转到运行状态

死亡:线程执行完或因异常退出了run方法,结束生命周期。

12、sleep、wait、join、yield

  • 锁池

所有竞争同步锁的线程会放入锁池。没拿到锁的对象会在锁池中等待cpu分配。前一个线程释放锁后,后面的才可能拿到锁。

  • 等待池

调用wait方法,线程会进入等待池中,等待池中的线程不会去竞争同步锁。wait会把锁释放掉。只有调用notify或notifyAll后才会从等待池中去竞争锁。notify是随机从等待池中选出一个放入锁池,notifyAll是把所有的都放入锁池。

1、sleep是Thread类的静态本地方法,wait是Object类的本地方法

2、sleep不会释放锁,wait会释放锁并加入等待队列

3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字

4、sleep不需要被唤醒(休眠之后退出阻塞),wait需要被唤醒

5、sleep一般用于当前线程休眠,wait用于多线程之间的通信

6、sleep会让出CPU执行时间,且强制上下文切换,wait不一定,wait后可能还是有机会冲i性能竞争到锁继续执行的

yield()执行后线程执行进入就绪状态,马上释放cpu执行权,但是依然保留了cpu的执行资格,所以可能cpu下次还会进行调度让这个线程执行

join()执行后线程进入阻塞状态,比u线程B调用了A的join,线程B会进入阻塞队列,知道线程A结束或者中断

13、对线程安全的理解

不是线程安全、应该是内存安全,堆内存是共享的,可以被所有线程访问。栈是线程独有的,所以栈是线程安全的。

多个线程访问同一个对象,如果不进行额外的同步控制或者其他的协调操作,调用这个对象的行为都是可以获得正确的结果,我们说这就是线程安全的。

堆分为全局堆和局部堆,全局堆是没分配的空间,局部堆是分配了的空间。现在的计算机是多任务的,多个进程同时的运行,为了保证安全,每个进程都只能访问自己的内存空间,不能访问别的。

但是每个进程的内存弓箭都有一块特殊的公共空间叫做堆内存,进程中所有的线程都能访问它,这就造成了线程安全问题。

14、Thread和Runnable区别

Thread和Runnable本身是继承关系,Runnbale接口Thread去实现了它。单继承和多实现的区别。Thread提供了更多了功能。

15、对守护线程的理解

守护线程:为所有非守护线程提供服务的线程,任何一个守护线程都是整个JVM中所有非守护线程的保姆。其他线程结束了,没有要执行的了,那么就中断了。

因为守护线程无法控制,所以重要的IO、File不要分配到守护线程。

GC垃圾回收就是守护线程。

应用场景:

(1)来为其它线程提供服务支持的情况;

(2) 或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。

16、ThreadLocal的原理和使用场景

ThreadLocal原理

每个Thread对象都有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有的ThreadLocal对象及其对应的值。

ThreadLocalMap由一个个Entry对象组成。

Entry继承自weekReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成。所以,Entry的key是ThreadLocal对象,并且是个弱引用。当没指向key的强引用后,该key就会被垃圾回收器回收掉。

当执行set方法时,ThreadLocal会获取当前线程对象。当前线程中获取ThreadLocalMap对象,再以ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似。ThreadLocal会获取当前线程对象,然后获取ThreadLocalMap对象,以ThreadLocal对象为key获取value。

每一条线程都有私有的ThreadLocalMap,所以不会存在线程安全问题,不用考虑线程安全问题。

使用场景

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离

3、进行事务操作,可以存在ThreadLocal中

4、数据库连接、Session会话也可以用

17、ThreadLocal内存泄漏的原因,如何避免

原因

内存泄漏:不再被使用的对象不能被回收。

强引用和弱引用:

强引用:一个对象具有强引用不会被垃圾回收器回收。内存不足,抛OOM(out of memory)也不会回收这种对象

弱引用:WeakReference来修饰,JVM进行垃圾回收时,不论内存是否充足,都会被来及回收被弱引用关联的对象,而在缓存中大多使用弱引用

ThreadLocal实现原理:

每一个Thread维护一个ThreadLocalMap,线程私有,key使用弱引用的ThreadLocal。如下图所示。当ThreadLocal的引用(Stack中的Ref)消失的话,按理说ThreadLocal没有了引用ThreadLocalMap中的ThreadLocal会回收,它会被设置为null,也就是ThreadLocalMap的key会被设置为null,但是由于value有值,而它是强引用,所以它只会在CurrentThread停掉以后才会回收,但是如果长时间不回收,就会造成内存泄漏

ThreadLocal的内存泄露?什么原因?如何避免?

那为什么使用弱引用而不是强引用

我们看看Key使用的

  • key 使用强引用

当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

  • key 使用弱引用

当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。

避免方案

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据
  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

18、并行、并发、串行

并发:两个任务彼此干扰,交替进行

并行:在时间上重叠,两个任务在同一个时刻互不干扰

串行:在时间上不可能发生重叠,前一个任务没搞定,下一个任务等

19、并发的三大特性

原子性、可见性、有序性

  • 原子性

在一个操作中cpu不可以中途暂停然后再调度,不能被中断,要么全部执行、要么全部不执行。

比如自增操作其实不是原子性操作:i++;

四个步骤:

1)将i从主存读入工作内存中(每个线程都有自己的工作内存)

2)+1操作

3)将结果写入工作内存

4)将工作内存的值刷回主存(什么时候输入由操作系统决定,不一定的)

前三步,需要保证原子性。

  • 可见性

当多个线程访问同一变量的时候,一个线程修改了这个变量,其他线程可以立刻看到修改的值

两个协议:总线Lock、MESI。它们保证了变量的可见性。

可见性通过上面的协议保证了将结果写入工作内存和刷回主存(第三步和第四步)的原子性,不可中断。

这样原子性和可见性这两个都保证了,就能保证第一步到第四步都是原子性操作。

  • 有序性

JVM在编译的过程中,可能会对代码进行位置调换,这样不会对代码造成影响,但是可能会有线程安全的问题。

int a = 0;
boolean flag = false;

public void write(){
    a = 2;
    flag = true;		//这两行交换无所谓,但是对于下面方法可不一样了,多线程有可能有问题
}

public void multiply(){
    if(flag){
        int result = a * a;
    }
}

20、为什么用线程池,解释下线程池的参数

原因

1、降低资源消耗:提高线程利用率,降低创建和销毁的消耗

2、提高相应速度:不用先创建再执行了

3、提高线程的可管理性:线程是稀缺资源,使用线程可以统一分配监控

参数

  • 一、corePoolSize 线程池核心线程大小

    线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。

  • 二、maximumPoolSize 线程池最大线程数量

    一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

  • 三、keepAliveTime 空闲线程存活时间

    一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

  • 四、unit 空闲线程存活时间单位

    keepAliveTime的计量单位

  • 五、workQueue 工作队列

    新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

    • ①ArrayBlockingQueue

    基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

    • ②LinkedBlockingQuene

    基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

    • ③SynchronousQuene

    一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

    • ④PriorityBlockingQueue

    具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

  • 六、threadFactory 线程工厂

    创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

  • 七、handler 拒绝策略

    当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:

    • ①CallerRunsPolicy

    该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。

    • ②AbortPolicy

    该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。

    • ③DiscardPolicy

    该策略下,直接丢弃任务,什么都不做。

    • ④DiscardOldestPolicy

    该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

21、线程池的处理流程

img

22、线程池中阻塞队列的作用,为什么是先添加队列而不是先创建最大线程

阻塞队列的作用

一般的队列只是作为一个有限长度的缓冲区,如果超出长度,就无法保留当前线程了,阻塞队列可以保留当前想要继续入队的任务。

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu。

阻塞队列自带阻塞和唤醒功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一只占用cpu。

为什么是先添加队列而不是先创建最大线程

在创建新线程的时候,是要获取全局锁的,这个时候其他的就得阻塞,影响整体效率。

23、线程池中线程的复用原理

线程池将线程和任务解耦。摆脱了之前的创建的线程必须对应线程的一个任务的限制。

在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理就在于对Thread进行了封装,并不是每次都调用Thread.start来创建新线程,而是让每个线程去执行一个循环任务,这个循环任务中不停的检查是否有任务要执行,有的话就执行、调用run方法,将run方法当作一个普通方法,通过这个方式只使用固定的线程将所有的任务的run方法串联起来。

24、Spring是什么

轻量级开源的J2EE框架,容器框架中间层框架。可以集合其他框架。让企业开发更快捷、简洁。

Spring是一个轻量级的控制反转、面向切面的框架。

​ --从大小与开销很小

​ --IOC实现松耦合

​ --提供了面向切面编程的丰富支持,允许通过分离业务逻辑和系统级别服务进行内聚性开发(日志)

​ --包含应用对象管理和生命周期,容器

​ --简单的组件变为复杂的应用

25、AOP的理解

  • 系统是由许多不同的组件组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为他们会跨越系统的多个组件。
  • 当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,并不适合定义从左到右的关系。例如日志功能。
  • 日志代码往往水平地散步在所有对象层次中,而与它所散步到的对象的核心功能毫无关系。
  • 在OOP设计中,它导致了大量代码重复,而不利于各个模块的重用。

AOP:将程序中的交叉业务逻辑(比如安全、日志、事务等),封装成一个切面,然后注入到目标对象(具有业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。

26、IOC的理解

容器概念、控制反转、依赖注入

IOC容器:

  • 实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、@repository、@Service、@Controller、@Component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里。
  • 这个时候map里有各种对象了,接下来我们在代码里需要用到里面的对象时,在通过DI注入(@Autowired、@Resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或者id注入;id就是对象名)

控制反转:

  • 没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B,无论是创建还是使用对象B,控制权都在自己手上。
  • 引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行需要到对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
  • 通过前后的对比,不难看出来:对象A获得依赖对象B的过程,有主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
  • 全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂“的由来。

依赖注入:

  • “获得依赖对象的过程被反转了”。控制权反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

27、BeanFactory和ApplicationContext有什么区别?

ApplicationContext是BeanFactory的子接口

ApplicationContext提供了更完整的功能:

  • 继承MessageSource,因此支持国际化。
  • 统一的资源文件访问方式。
  • 提供在监听器注册bean的事件
  • 同时加载多个配置文件
  • 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用Web层。
  1. Beanfactory采用的是延迟加载形式来注入Bean的,即只有在用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean方法才抛出异常。
  2. ApplicationContext,它是在容器启动时,一次性创建所有的Bean。这样,在容器启动时,我们可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预加载入单实例bean,确保当你需要的时候,你就不用等待,因为他们已经创建好了。
  3. 相对于基本的BeanFactory,ApplicationContext 唯一的不足就是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  4. BeanFactory通过以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
  5. BeanFactory和ApplicationContext 都支持BeanPostProcessor、BeanFactoryPostPeocessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext 则是自动注册。

28、描述一下Spring Bean的生命周期

  1. 解析类得到BeanDefinition
  2. 如果有多个构造方法,则要推断构造方法
  3. 确定好构造方法后,进行实例化得到一个对象
  4. 对对象的加了@Autowired注解的属性进行属性填充
  5. 回调Aware方法,比如BeanNameAware,BeanFactoryAware
  6. 调用BeanPostProcessor的初始化前的方法
  7. 调用初始化方法
  8. 调用BeanPostProcessor的初始化的方法,在这里会进行AOP
  9. 如果当前创建的Bean是单例则会把Bean放到单例池
  10. 使用Bean
  11. Spring容器关闭时调用DisposableBean中destory()方法

29、Spring支持的bean作用域

  • singleton:默认,单例,每个容器中只有一个bean的实例。