精通Spring4.x-chapter4

在下载Maven依赖包的时候觉得很慢,搜索引擎找到了国内镜像站,设置setting.xml解决

https://blog.csdn.net/u013058936/article/details/104585376/

Bean的概念:

https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680

IoC概念:(Inverse of Control)

控制反转

控制:某一接口具体实现类的选择控制权

反转:将该控制权从调用类中移除,转交给第三方处理

DI:(Dependency Injection)

依赖注入

让调用类对某个接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。

结合书本的个人理解:

拍电影时,剧本中有角色,而角色的具体动作实现需要由具体某位演员实现,因此剧本、角色和演员有很强的耦合性,在实现剧本中某个情节的时候,需要引入角色接口,并创建具体演员。但这样子在每个剧情都要引入角色接口并创建具体演员,这样加大了剧本的剧情创作难度(因为每个剧情都要引入演员,而不是单单只有角色),因此需要有一个导演,来统筹兼顾,在剧本剧情需要的时候,为其注入一个演员。这里的控制便是剧本中的某个剧情要选择某个角色的具体演员的控制权,而反转便是这个选择权交由导演(也就是第三方)来决定,不由编剧决定了。

那么某一接口实现类的选择控制权便是指定具体演员

调用类便是剧本中的剧情

第三方便是导演

即导演选角,导演起到了一个统筹兼顾的重要角色,大大降低了剧本和角色的耦合性,实现解耦

这样也符合我们普遍认知中的投拍流程。

这篇文章可能有助于理解

https://www.cnblogs.com/youdutec/p/13393023.html

https://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html (防失效)

IoC的类型:

构造函数注入、属性注入和接口注入

Spring支持构造函数注入和属性注入

1、构造函数注入:

通过调用类的构造函数,将接口实现类通过构造函数变量传入

//剧本
public class MoAttack {
	private BEN ben;

	public MoAttack(BEN ben) {
		this.ben = ben;		
	}   //通过构造函数注入BEN的具体饰演者
	
	public void cityGateAsk() {
		ben.responseAsk("墨者革离");
	}
}
//剧本不关心是谁饰演BEN这个角色,只需要传入演员让其完成具体动作即可
//导演
public class Director {
   public void direct(){
		//指定角色的饰演者
	   BEN ben = new LiuDeHua();
		//注入具体饰演者的剧本中
	   MoAttack moAttack = new MoAttack(ben);
	   moAttack.cityGateAsk();
   }
}

2、属性注入

有时候并不是每个场景都有该角色(主角也没有),因此在具体有需要的场景中注入所需依赖即可

public class MoAttack {
	private GeLi geli;
  //属性注入方法
	public void setGeli(GeLi geli) {
		this.geli = geli;		
	}
	
	public void cityGateAsk() {
		geli.responseAsk("墨者革离");
	}
}
public class Director {
   public void direct(){
	   GeLi geli = new LiuDeHua();
	   MoAttack moAttack = new MoAttack();
	   //通过属性setGeli方法注入
		 moAttack.setGeli(geli);
	   moAttack.cityGateAsk();
   }
}

3、接口注入

将调用类所有依赖注入的方法抽取到一个接口中,调用类通过该接口实现该接口提供相应的注入方法。

因此首先要先声明一个接口:

public interface ActorArrangable {
   void injectGeli(GeLi geli);
}

然后剧本通过调用该接口实现具体要求

public class MoAttack implements ActorArrangable {
	private GeLi geli;

	public void injectGeli(GeLi geli) {
		this.geli = geli;		
	}
	
	public void cityGateAsk() {
		geli.responseAsk("墨者革离");
	}
}

由于接口注入需要额外声明一个接口,增加了类的数目,而其效果与属性注入无本质区别,因此一般不使用接口注入

通过容器完成依赖关系的注入

通过第三方使得剧本、导演、演员各司其职,完成解耦。

容器帮助完成类的初始化和装配工作。

Spring通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入工作。

配置文件示例:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="<http://www.springframework.org/schema/beans>"
	xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
	xmlns:p="<http://www.springframework.org/schema/p>"
	xsi:schemaLocation="<http://www.springframework.org/schema/beans> 
       <http://www.springframework.org/schema/beans/spring-beans-4.0.xsd>">
   <!--实现类实例化-->
   <bean id="geli" class="LiuDeHua"/>
   <bean id="moAttack" class="com.smart.ioc.MoAttack"
         p:geli-ref="geli"/> <!--通过geli-ref建立依赖关系-->
</beans>

JAVA反射机制

package com.smart.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTest {
    public static Car initByDefaultConst() throws Throwable{
				//通过类装载器获取Car类对象
        
				//获取当前线程的ClassLoader
				ClassLoader loader = Thread.currentThread().getContextClassLoader();
        //通过指定的全限定类名“com.xxx...”装载Car类对应的反射实例
				Class<?> clazz = loader.loadClass("com.smart.reflect.Car");
				
		
				//获取类的默认构造器对象并通过它实例化Car
        
				//通过Car的反射类对象获取Car的构造函数对象cons
				Constructor<?> cons = clazz.getDeclaredConstructor((Class<?>[])null);
        //通过构造函数对象的newInstrance()方法实例化Car对象
				Car car = (Car)cons.newInstance();//效果等同于 new Car()
				
				//通过反射方法设置属性

				//通过Car的反射类对象的getMethod(String methodName,Class paramClass)
        //获取属性的setter方法对象,第一个参数为目标Class的方法名,第二个参数时方法入参的对象类型
        //接着通过invoke(Object obj,Object param)方法调用目标类的方法
				//第一个参数为操作的目标类对象实例,第二个参数时目标方法的入参
				Method setBrand = clazz.getMethod("setBrand",String.class);
        setBrand.invoke(car,"红旗CA72");
        Method setColor = clazz.getMethod("setColor",String.class);
        setColor.invoke(car,"黑色");
        Method setMaxSpeed = clazz.getMethod("setMaxSpeed",int.class);
        setMaxSpeed.invoke(car,200);
        return car;
    }
    public static void main(String[] args) throws Throwable{
        Car car = initByDefaultConst();
        car.introduce();
    }
}

一开始run时报错,原因是Class、Constructor后面没有加泛型类型,因此加上完成编译运行。

附上链接:

泛型中和有何区别:

https://www.cnblogs.com/jpfss/p/9929045.html

类装载器ClassLoader

工作机制:

寻找类的字节码文件并构造出类在JVM内部表示对象的组件。

步骤:

1、装载:查找和导入class文件

2、链接:执行校验准备解析步骤,其中解析步骤可选

(1)校验:检查载入Class文件数据的正确性

(2)准备:给类的静态变量分配存储空间

(3)解析:将符号引用转换成直接引用

3、初始化:对类的静态变量、静态代码块执行初始化工作

类装载工作由ClassLoader和它的子类负责,JVM在运行时产生个ClassLoader:

根装载器、ExtClassLoader(扩展类装载器)、AppClassLoader(应用类装载器)

其中根装载器不是ClassLoader的子类,使用C++编写,它福着装载JRE的核心类库

ECL负责装载JRE扩展目录ext中的JAR类包,ACL负责装载Classpath路径下的类包,他们都是ClassLoader的子类。

三个装载器存在父子层关系:(从左到右按照父子关系排列) 父装载器

根装载器→ExtClassLoader(扩展类装载器)→AppClassLoader(应用类装载器)

默认情况下,使用ACL装载应用程序的类

JVM装载类时使用“全盘负责委托机制”

“全盘负责”指当一个ClassLoader装载一个类的时候,除非显示地使用另一个ClassLoader,否则该类所依赖及引用的类也由这个ClassLoader载入;

“委托机制”指先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类,这样是出于安全目的考虑。否则编写一个恶意的基础类并装载到JVM中,后果很严重。

而“全盘负责委托机制”也会引发一些问题:

在类路径下放置了多个版本的类包,例如commons-lang 2.x.jar和4.x。

如果代码中用到了4.x,而这个方法在2.x中不存在,但JVM又碰巧从2.x中加载类,就会抛出java.lang.NoSuchMethodError的错误。

书本给了一个名为srcAdd.jsp的程序,将其放在Web应用的根路径下,通过

$http://localhost/srcAdd.jsp?className=java.net.URL$

可以查看JVM从哪个类包中加载指定类

书本工具还提供了一个ClassLocationUtils.java类,在IDEA断电调试的时候,按下Alt+F8组合键,弹出Evaluate Expression对话框,输入“ClassLocationUtils.where(<类名>.class)”也可以查看

ClassLoader的重要方法:

Class loadClass(String name)
//name参数指定装载器需要装载类的名字,注意,必须使用全限定类名,如com.smart.beans.Car
//该方法有一个重载方法
Class loadClass(String name,boolean resolve)
//该方法告诉类装载器是否需要解析该类,如果JVM只需要知道该类是否存在或找出该类的超类,则不需要解析

Class defineClass(String name,byte[]b,int off,int len)
//将类文件的字节数组转换成JVM内部的java.lang.Class对象。
//字节数组可从本地文件、远程网络获取,参数name为字节数组对应的全限定类名

Class findSystemClass(String name)
//从本地文件载入Class文件,是JVM默认的装载机制

Class findLoadedClass(String name)
//调用该方法来查看是否已经装载某个类,如果已装载,则返回java.lang.Class对象,否则返回null

ClassLoader getParent();
//获取类装载器的父装载器

JAVA反射类

**Constructor:**类的构造函数反射类

通过Class#getConstructors()方法可以活动类的所有构造函数反射对象数组

Constructor的一个主要方法是newInstance(Object[] initargs),用该方法创建一个对象类的实例,相当于new

**Method:**类方法的反射类

通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组

主要方法:invoke(Object obj, Object[] args), obj表示操作的目标对象;args为方法入参

**Field:**类的成员变量的反射类

通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredFields(String name)则可以获取某个特定名称的成员变量反射对象。

主要方法:set(Object obj, Object value), obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果变量为基础类型,则可以通过setBoolean(Object obj, boolean value)、setInt(Object obj, int value)等设置。

通过反射机制可以绕过限制,查看类的私有变量、调用类的私有方法,但注意要通过方法(变量).setAccessible(boolean access)方法取消Java语言检查。

资源访问利器

资源抽象接口

Spring设计了一个Resource接口,拥有对应不同资源类型的实现类。

主要方法:

boolean exists(); //资源是否存在

boolean isOpen(); //资源是否打开

URL getURL() throws IOException; //如果底层资源可以表示成URL
	                               //则返回对应的URL对象
File getFile() throws IOException; //如果底层资源可以表示成文件
                                    //则返回对应的File对象
InputStream getInputStream() throws IOException; //返回资源对应的输入流

Spring使用Resource装载各种资源:

https://raw.githubusercontent.com/mclaren8/images/master/20210424101012.png

WritableResource:可写资源接口,有两个实现类:FileSystemResource 和PathResource

ByteArrayResource:二进制数组表示的资源,二进制数组资源可以在内存中通过程序构造

ClassPathResource : 类路径下的资源,资源以相对于类路径的方式表示

FileSystemResource :文件系统资源,资源以文件系统路径的方式表示,如D:/kai/bean.xml。

InputStreamResource: 以输入流返回表示的资源

ServletContextResource :为访问Web容器上下文中的资源而设计的类,负责以相对于Web应用根目录的路径加载资源。支持以流和URL的方式访问,在WAR解包的情况下,也可以通过File方式访问。还可以直接通过JAR包访问

UrlResource :URL封装了java.net.URL,它使用户能访问任何通过URL表示的资源,如HTTP、FTP资源等

PathResource: 封装了java.net.URL、java.io.file.Paath、文件系统工资源,可以让用户访问任何可以通过URL、Path、系统文件路径表示的资源,如HTTP、FTP、文件系统的资源。

示例:

package com.smart.resource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.springframework.core.io.*;

public class FileSourceExample {
	
	public static void main(String[] args) {
		try {
			String filePath = "G:/masterspring/wangpan/chapter4/src/main/resources/conf/file1.txt";
			//使用系统文件路径方式加载文件
			WritableResource res1 = new PathResource(filePath);
			//使用类路径方式加载文件
			Resource res2 = new ClassPathResource("conf/file1.txt");
			//使用WritableResource接口写资源文件
			OutputStream stream1 = res1.getOutputStream();
			stream1.write("WHO\\nARE\\nYOU".getBytes());
			stream1.close();
			//使用Resource接口读资源文件
            InputStream ins1 = res1.getInputStream();
			InputStream ins2 = res2.getInputStream();
			//二进制输出流
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			int i;
			//读取文件内容
			while((i=ins1.read())!=-1){
				baos.write(i);
			}
			//转换为string类型输出
			System.out.println(baos.toString());
   			//获取资源文件名
            System.out.println("res1:"+res1.getFilename());
            System.out.println("res2:"+res2.getFilename());            
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

在Web应用中,用户可以通过ServletContextResource访问文件资源

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<jsp:directive.page import="org.springframework.web.context.support.ServletContextResource"/>
<jsp:directive.page import="org.springframework.core.io.Resource"/>
<jsp:directive.page import="org.springframework.web.util.WebUtils"/>
<%
   //注意文件资源地址以相对于Web应用根路径的方式表示
   Resource res3 = new ServletContextResource(application,"/WEB-INF/classes/conf/file1.txt");
   out.print(res3.getFilename()+"<br/>");
   out.print(WebUtils.getTempDir(application).getAbsolutePath());
%>

资源加载

资源地址表达式:

Spring提供了一些资源类型前缀,可以在不显示使用Resource实现类的情况下,通过这些前缀识别不同的资源类型。

其中,与**classpath:*对应的还有*classpath*:

假设有多个JAR包或文件系统路径有一个相同的包名。classpath:只会在第一个加载的包的类路径下查找,而classpath*: 会扫描所有的JAR包及类路径下出现的同样包名的类路径。

例如一个名为kai的应用分为三个模块,一个模块对应一个配置文件,分别为mcl1.xml、mcl2.xml、mcl3.xml,都放在com.smart目录下,每个模块单独打爆成JAR包。

使用**“classpath*:com/kai/mcl*.xml”可以加载三个模块的配置文件,而“classpath:com/kai/mcl*.xml”**只能加载一个模块的配置文件

Ant风格的资源地址支持3种匹配符:

?:匹配文件名中的一个字符

*:匹配文件名中的任意字符

**:匹配多层路径

用法示例:

资源加载器

Spring定义了一套资源加载的接口,并提供了实现类

注意:

BeanFactory & ApplicationContext

Spring通过一个配置文件描述Bean和Bean之间的依赖关系,利用反射功能实例化Bean并建立Bean之间的依赖关系。

简单划分:BF面向Spring本身而AC面向使用Spring的开发者

BeanFactory

称为IoC容器,可以创建并管理各个种类的对象的通用工厂

类体系结构:

https://raw.githubusercontent.com/mclaren8/images/master/20210424171309.png

BeanFactory接口:

它通过其他接口不断扩展功能

ListableBeanFactory //定义了访问容器中Bean基本信息的若干方法,如查看Bean个数等

HierarchicalBeanFactory // 父子级联IoC容器的接口,子容器可以通过接口方法访问父容器

ConfigurableBeanFactory //定义了设置类型装载器、属性编辑器、容器初始化后置处理器等方法

AutowireCapableBeanFactory //定义了将容器中的Bean按某种规则进行自动装配的方法

SingletonBeanRegistry //定义了允许在运行期间向容器注册单实例Bean的方法

BeanDefinitionRegistry //提供了向容器手工注册BeanDefinition对象的方法
				//Spring配置文件中每一个<bean>街道元素在Spring容器中通过一个BeanDefinition对象表示
		    //它描述了Bean的配置信息

初始化BeanFactory

配置好bean.xml文件后,使用XmlBeanDefinitionReader和DefaultListableBeanFactory启动IOC容器

//①下面两句装载配置文件并启动容器
 	   Resource res = new ClassPathResource("com/smart/beanfactory/beans.xml");

       BeanFactory bf= new DefaultListableBeanFactory();
       XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((DefaultListableBeanFactory)bf);
       reader.loadBeanDefinitions(res);

//④第一次从容器中获取car,将触发容器实例化该Bean,这将引发Bean生命周期方法的调用。
       Car car1 = (Car)bf.getBean("car");
       car1.introduce();
       car1.setColor("红色");

       //⑤第二次从容器中获取car,直接从缓存池中获取
       Car car2 = (Car)bf.getBean("car");

附上有关getbean()方法说明的链接:

https://www.jianshu.com/p/01bee649a0c9

ApplicationContext

主要实现类:

ClassPathXmlApplicationContext,默认从类路径加载配置文件

FileSystemXmlApplicationContext,默认从文件系统中配置文件

ApplicationContext初始化:

如果配置文件放在类路径下,使用ClassPathXmlApplicationContext实现类

如果配置文件在文件系统路径,使用FileSystemApplicationContext实现类

还可以指定一组配置文件,Spring会自动将多个配置文件“整合”成一个配置文件

两种类都可以显示使用带资源类型前缀的路径。

获取实例后,就可以像BeanFactory调用getBean()返回Bean。

BF和AC的初始化有一个区别:

BF在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例化;

而AC在初始化的时候就实例化所有单实例的Bean。

使用类注解的配置方式

使用一个标注@Configuration注解的POJO即可以提供所需的Bean配置信息

@Configuration
public class Beans {

	@Bean(name = "car")
	public Car buildCar() {
		Car car = new Car();
		car.setBrand("红旗CA72");
		car.setMaxSpeed(200);
		return car;
	}
}

该配置方法可以让我们很好地控制Bean初始化过程,比XML文件的配置方法更灵活

基于注解类,有一个专门的AC实现类:AnnotationConfigApplicationContext

public class AnnotationApplicationContext {

	 public static void main(String[] args) {
		//通过一个带@Configuration的POJO装载Bean配置
		ApplicationContext ctx = new AnnotationConfigApplicationContext(Beans.class);
		//该方法将加载Beans.class中的Bean定义并调用Beans.class中的方法实例化Bean
		Car car =ctx.getBean("car",Car.class);
	}
}

使用Groovy DSL 进行Bean定义配置。

基于Groovy的配置提供了专门的AC实现类:GenericGroovyApplicationContext

在配置groovy文件时,import包时报错“Cannot resolve symbol hibernate”和“… org.springframework.web.servlet”

在pom.xml配置dependency

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.4.Final</version>
        </dependency>
   <!--springframework.web.servlet在spring-web中-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>

WebAC类体系结构

专门为Web应用准备。从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境访问Spring应用上下文(WebAC)。

通过一个工具类WebApplicationContextUtils的getWebApplicationContext(ServletContext sc)的方法,从中获取WebAC实例

该方法内部实现方式:

WebAC定义了一个常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,在AC启动时,WebAC即依次为键放置在ServletContext的属性列表中,通过语句从Web容器中获取WebAC:

https://raw.githubusercontent.com/mclaren8/images/master/20210425214557.png

这样Spring的Web应用上下文(WebAC)和Web容器的上下文应用(ServletContext)可以实现互访

https://raw.githubusercontent.com/mclaren8/images/master/20210425222738.png

看书的时候对Web容器和Spring容器感觉有点混乱,附上一个链接:

https://www.pianshen.com/article/9371922223/

以及对spring上下文、web上下文的区别和联系:

https://www.cnblogs.com/panxuejun/p/5898540.html

ConfigurableWebApplicationContext扩展了WebAC,它有两个重要方法:

SetServletContext(ServletContext servletcontext):为spring设置web应用上下文

setConfigLocations(String[] configLocations):设置Spirng配置文件。一般来说地址是相对于Web根目录的地址,如WEB-INF/smart-dao.xml。但用户也可以用带资源类型前缀的地址,如classpath:com/smart/beans.xml

在非Web应用环境下,Bean中只有singleton和prototype两种作用域

WebAC种添加了三个作用域:request、session、global session

WebAC初始化

它需要ServletContext实例,这个在上面的链接中的spring启动过程可以看到,因此它要在拥有Web容器的前提下才能完成启动工作。

而启动该工作,有两种方式:

一是在web.xml中配置自启动的Servlet

二是在该文件中定义Web容器监听器(ServletContextListener)

<!-- 指定标注了Groovy的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:conf/spring-mvc.groovy
        </param-value>
    </context-param>

    <!-- ContextLoaderListener监听器将根据上面配置使用
         AnnotationConfigWebApplicationContext根据contextConfigLocation
         指定的配置类启动Spring容器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

自启动的Servlet引导:

https://raw.githubusercontent.com/mclaren8/images/master/20210425230558.png

由于WebAC需要使用日志功能,所以用户将Log4J的配置文件放置在类路径WEB-INF/classes 下,Log4J即可顺利启动。

如果放置在其他位置,则需要在web.xml中指定配置文件的位置。

https://raw.githubusercontent.com/mclaren8/images/master/20210425230658.png

注意启动顺序,先装载再初始化

如果使用Web监听器,则要将Log4JConfigListener放置在ContextLoaderListener的前面。该配置方式下,Spring默认使用XmlWebApplicationContext启动Spring容器。

使用标注@Configuration提供配置信息,则web.xml需要按照以下方式配置:

关键是contextClass参数

注意:该段代码按照Groovy DSL配置Bean信息,但实际上与标注java类配置大同小异

只需要在contextClass下改一下support后缀,以及指定标注了@Con…的配置类

<!--通过指定context参数,让Spring使用GroovyWebApplicationContext而非
    XmlWebApplicationContext或AnnotationConfigWebApplicationContext启动容器 -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.GroovyWebApplicationContext
        </param-value>
    </context-param>

    <!-- 指定标注了Groovy的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:conf/spring-mvc.groovy
        </param-value>
    </context-param>

    <!-- ContextLoaderListener监听器将根据上面配置使用
         AnnotationConfigWebApplicationContext根据contextConfigLocation
         指定的配置类启动Spring容器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

父子容器

Spring的IoC容器可以建立父子层级关联的容器体系,子容器可以访问父容器的Bean,但父容器无法访问子容器的Bean。

在容器内,Bean的id必须是唯一的,但子容器拥有一个可以和父容器的id相同的bean。

例如在SpringMVC中,展现层Bean位于一个子容器中,而业务层和持久层Bean在父容器中,那么展现层可以引用业务层和持久层,而后两者无法看到展现层

BeanFctory的生命周期

https://raw.githubusercontent.com/mclaren8/images/master/20210425234229.png

具体过程:

(1)调用者通过getBean(beanName)向容器请求某个Bean时,若容器注册了①org.springframework.beans.factory.config.②InstantiationAwareBeanPostProcessor接口,则在实例化Bean之前,将调用接口的postProcessBeforeInstantiation()方法。

(2)根据配置情况调用Bean构造函数或工厂方法实例化Bean。

(3)如果容器注册了②处接口,则在实例化Bean后,调用该接口的postProcessAfterInstantiation()方法,对实例化对象进行修饰。

(4)若Bean设置了属性信息(比如person中的name属性),容器将配置值设置到Bean对应的属性中,不过在设置每个属性之前将先调用②接口的postProcessPropertyValues()方法。

(5)调用Bean的属性设置方法设置属性值

(6)如果Bean实现了①.BeanNameAware接口,则调用setBeanName()接口方法,将配置文件中该Bean对应的名称设置到Bean中。

(7)如果Bean实现了①.BeanFactoryAware接口,则调用setBeanFactory()接口方法,将BeanFactory容器实例设置到Bean中。

(8)如果Bean实现了①.BeanPostProcessor后处理器,则将调用它的Object postProcessBeforeInitialization(Object bean,String beanName)接口方法对Bean进行加工操作。

返回的对象为加工处理后的Bean。

(9)如果Bean实现了InitializingBean接口,则调用接口的afterPropertiesSet()方法

(10)如果在< bean >中通过init-method属性定义了初始化方法,则将执行这个方法。

(11)如果实现了(8),则调用Object postProcessAfterInitialization(Object bean, String beanName)方法,再次对Bean进行加工处理

(12)如果在中指定Bean的作用范围为scope=“prototype”,则将Bean返回给调用者,调用者负责Bean后续声明管理,Spring不再管理声明周期;如果作用范围设置为"singleton",则将Bean放入Spring IoC容器的缓存池中,并将Bean引用返回给调用者,Spring继续对Bean进行后续生命管理。

(13)对应作用范围为“singleton”的Bean(默认),容器关闭时,将除非Spring对Bean后续生命周期的管理工作。如果Bean实现了DisposableBean接口,则将调用接口的destory()方法,可以再次编写释放资源、记录日志等操作。

(14)对应scope=“singleton”的Bean,如果通过的destroy-method属性指定了Bean的销毁方法,那么SPring将执行这个方法,完成Bean资源的释放等操作。

每个步骤涉及方法的调用,书本将其分为四类:

https://raw.githubusercontent.com/mclaren8/images/master/20210426154531.png

网上的说明:

https://www.huaweicloud.com/articles/e5a68ea453c0f3bdbab14eb384c7ee38.html

https://raw.githubusercontent.com/mclaren8/images/master/20210426160808.png

ApplicationContext中Bean的生命周期

https://raw.githubusercontent.com/mclaren8/images/master/20210426161100.png

AC和BF另一个最大不同之处:

前者利用JAVA反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,并自动将他们注册应用到上下文中,而后者需要在代码中手工调用addBeanPostProcessor()方法进行注册。因此应用开发普遍使用AC而很少用BF

使用工厂后处理器实例:

https://raw.githubusercontent.com/mclaren8/images/master/20210426162033.png