源码(码云):https://gitee.com/yin_zhipeng/implement-_spring_of_myself.git |
文章目录
- 一、手写Spring
- 二、Spring IoC高级应用面试常问知识点复习
-
- 1. springIoC基本概念和使用
-
- 1.1 纯xml模式
-
- 1.1.1 JavaEE版
- 1.1.2 web使用监听器版
- 1.1.3 Bean标签属性
-
- 1.1.3.1 Bean的常用创建方式
- 1.1.3.2 Bean的作用范围以及生命周期
- 1.1.3.3 Bean标签属性
- 1.2 纯xml模式的DI依赖注入
-
- 1.2.1 构造函数注入
- 1.2.2 复杂(集合)类型
- 1.3 xml+注解模式
-
- 1.3.1 使用xml+注解模式改造工程
- 1.3.2 引入外部文件配置
- 1.4 纯注解模式
- 2. spring 高级特性
-
- 2.1 lazy-Init延迟加载
- 2.2 FactoryBean和BeanFactory
- 3. Spring Bean的生命周期
-
- 3.1 spring bean的生命周期测试
- 3.2 实例化之前干的事
- 4. 后置处理器
-
- 3.1 BeanPostProcessor
- 3.2 BeanFactoryPostProcessor
- 三、Spring IoC源码
- 四、Spring AOP 高级应用和源码
一、手写Spring
篇幅限制,我将其放在这篇文章中:https://blog.csdn.net/grd_java/article/details/122625811 |
二、Spring IoC高级应用面试常问知识点复习
通过手写Spring,我们已经对spring思想有了基本了解,接下来进行高级应用讲解,在此之前,我们依然对一些基本概念做些回顾,同样是面试官经常问到的一些东西 |
- 纯xml,bean信息全部定义在xml文件中,我们可以通过如下方式,指定读取xml
AppliccationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AppliccationContext applicationContext = new FileSystemXmlApplicationContext("beans.xml");
ContextLoaderListener
- 纯注解,所有bean都用注解定义,加载方法如下
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
ContextLoaderListener
- xml+注解,部分bean配置在xml中,部分bean使用注解定义(加载方法,是xml和注解混合)
BeanFactory和ApplicationContext的区别 |
- 前面我们手写spring,使用BeanFactory生产bean,那么spring怎么做呢?
- 这里有一张spring的BeanFactory继承关系图,发现BeanFactory是IoC容器的顶层接口,而ApplicationContext是子接口,所以ApplicationContext的功能更强(国际化支持,资源访问例如xml,java配置类),我们常用ApplicationContext

- 可以发现,spring层次划分的很多,功能分工明确,没必要把所有功能都划分到BeanFactory
- BeanFactory就像一个领导,它不可能把所有细节的事都做了,它可以掌控大局,但完成整套工作,需要很多人才相互配合
- 我们将手写spring的例子复制一份,将其改造为使用真正的Spring

1. springIoC基本概念和使用
使用spring就得引入基本模块依赖,context,包含core、beans、aop和expression |
- 引入相关依赖

1.1 纯xml模式
1.1.1 JavaEE版
xml我们一直自己定义,虽然遵循spring规范,但是是我们人为模仿,我们需要引入spring的约束 |
- 给xml文件改名为applicationContext.xml(想不想改都行,只不过大家都叫这名字),然后添加xml约束

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
- 使用spring IoC获取bean实例

1.1.2 web使用监听器版
- 引入tomcat依赖和servlet依赖,让maven打包方式为war


- 编写Servlet,启动tomcat,然后访问8080端口Servlet

2. 引入spring-web模块依赖,编写web.xml |
- 引入spring-web依赖

- 编写web.xml之前,我们知道我们通过ContextLoaderListener这个监听器来创建bean,那么它其实有一些参数,比如我们可以指定它加载哪个xml配置文件,如下:

- 接下来我们编写web.xml(自己创建相关目录和文件即可),指定监听器,并且指定属性,告诉监听器加载那个配置文件

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Creadted Web Application</display-name>
<!--ContextLoaderListener的一个属性,指定容器的配置文件位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使用监听器启动Spring的IoC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
3. 改造Servlet 通过监听器获取bean,然后测试效果 |


1.1.3 Bean标签属性
1.1.3.1 Bean的常用创建方式
创建实例的不同方式:先前都使用无参构造来创建,下面给出不同方式 |
- 通过静态方法获取bean实例,一般配合工厂设计模式


<bean id="connectionUtils" class="spring.factory.CreateBeanFactory" factory-method="getInstanceStaticConnectionUtils"></bean>
- 通过非静态方法,一般配合工厂设计模式


1.1.3.2 Bean的作用范围以及生命周期
Bean的作用范围可变,spring框架管理Bean对象的创建,默认使用单例模式创建,它支持配置的方式改变作用范围 |
Scope(:不常用/:常用) |
Description |
singleton |
单例,使用单例模式创建的实例,整个IoC容器中就一个,默认方式 |
prototype |
原型(多例)模式,使用原型设计模式创建的实例,每次想要bean,都会拷贝给你一个新的bean |
request |
bean实例会在客户端每次进行Http请求时创建,在WebApplicationContext环境下使用,范围在一个请求中 |
session |
bean会在客户端与服务器的每一次会话中创建一个bean,不同会话创建不同对象,同一会话始终使用同一对象,范围在一个会话中 |
application |
范围在ServletContext的声明周期中,只有在web的Spring application上下文中有效 |
websocket |
范围在WebSocket,只在web-aware Spring ApplicationContext的上下文中有效 |
我们指定类ConnectionUtils为单例模式,启动tomcat后,查看两次bean是不是一个 |
- 指定bean的范围为单例(不指定scope属性,默认就是单例,所以这一步不配置也可以)

- 测试

我们指定类ConnectionUtils为原型模式,启动tomcat后,查看两次bean是不是一个 |
- 指定为原型模式,然后测试

单例模式和原型模式bean的生命周期(其它类型的bean压根就用不到,就不说了) |
- 单例singleton,生命周期和IoC容器相同
- 对象出生:当创建容器时,对象被创建
- 对象活动范围:只要容器还在,对象就会一直活这
- 对象死亡:容器销毁时,对象被销毁
- 多例prototype,使用时,IoC容器创建,多会销毁,取决于java垃圾回收器多会回收它
- 对象出生:使用对象时,创建新的对象实例
- 对象活动范围:只要对象在使用,就一直活着
- 对象死亡:被java垃圾回收器回收(没有引用指向,或者满足垃圾回收算法的条件,比如长时间不使用)
1.1.3.3 Bean标签属性
property(:不常用/:常用) |
Description |
id属性 |
用于给bean提供一个唯一标识,一个< beans>标签内部,标识必须唯一 |
class属性 |
用于创建Bean对象的全限定类名 |
name属性 |
用于给Bean提供一个或多个名称,多个名称用空格分隔 |
factory-bean属性 |
用于指定创建当前bean对象的工厂bean的唯一标识,指定此属性后,class属性失效 |
factory-method属性 |
指定创建当前bean对象的工厂方法,配合factory-bean属性使用时,class属性失效,配合class属性使用时,方法必须是static |
scope属性 |
用于指定bean对象的作用范围,默认singleton单例模式,常用的还有prototype原型模式 |
init-method属性 |
用于指定bean对象的初始化方法,此方法会在bean对象装配后调用,必须是一个无参方法 |
destory-method属性 |
用于指定bean对象的消耗方法,此方法会在bean对象销毁前执行,只能为scope是singleton时起作用 |
init-method和destory-method属性,是两个生命周期属性,可以在bean创建前和销毁前执行 |
- 在ConnectionUtils类中定义两个无参方法

- 配置xml

- 测试,销毁方法,需要调用IoC容器的close方法来关闭,只有部分子类有,也不常用就不测试了

1.2 纯xml模式的DI依赖注入
- 两种常用注入方式
- 构造函数注入:利用带参构造函数实现对类成员的数据赋值
- set方法注入:通过类成员的set方法实现数据的注入(使用最多)
- 按照注入的数据类型
- 基本类型和String:注入的数据类型是基本类型或者是字符串类型的数据
- 其他Bean类型:注入的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的,那么针对当前Bean来说,就是其他Bean了。
- 复杂类型(集合类型):注入的数据类型时Array,List,Set,Map,Properties中的一种类型
我们先前使用的都是set注入的方式,注入的数据类型是其他Bean类型,如果是基本类型或String,将ref换成value,里面输入值,例如value = “100.3”,很简单不过多介绍了 |

1.2.1 构造函数注入
- 为ConnectionUtils类添加几个参数,提供构造方法

- xml注入

1.2.2 复杂(集合)类型
- 为ConnectionUtils添加复杂类型变量个set方法

- xml注入(同样普通值用value,引用类型用ref,map的类型统一是entry,entry中的key和value如果是引用类型,加ref前缀即可,例如key-ref)

1.3 xml+注解模式
xml+注解,spring IoC容器启动依然从加载xml开始,下面介绍xml中标签对应的注解 |
- 自己写的类,用注解
- 第三方jar中的bean配置到xml中
xml(:不常用/:常用) |
Annontation |
< bean id = “connectionUtils”>标签 |
@Component(“connectionUtils”),加在类上,id属性是value(默认一个参数就是给它赋值),如果不配置,id默认为类名(第一个单词首字母小写,其它首字母大写),另外针对分层开发,还提供了3种别名:@Controller、@Service、@Repository,分别用于控制层,服务层,持久层的bean定义,4个注解功能用法完全一样,只是为了区分清晰 |
DI 依赖注入 |
@Autowired,用于属性(变量)上,采用策略为,按照类型注入,但是如果一个类型有多个bean值时,会不知道选哪个 |
DI 依赖注入,告诉Spring具体装配哪个bean |
@Qualifier(“bean的id”),配合@Autowired使用,告诉Spring具体装配哪个bean实例 |
Java原生注解 |
@Resource,和@Autowired相同,java原生注解,但是在JDK11移除了,可以同时指定id和类型,和@Autowired一样,如果写在字段上,不用提供set方法,也可直接写在setter方法上 |
< bean >标签的scope属性 |
Scope(“prototype”),加在类上,默认单例 |
< bean >标签的init-method属性 |
PostConstruct(),加在方法上,标识该方法为bean初始化后调用方法 |
< bean >标签的destory-method属性 |
PreDestory(),加在方法上,标识该方法为bean销毁前调用方法 |
1.3.1 使用xml+注解模式改造工程
- xml中配置第三方jar


- 使用注解配置ConnectionUtils

- 使用注解配置TransactionManager

- 使用注解配置proxyFactory

- 使用注解配置dao层

- 使用注解配置service



<?xml version="1.0" encoding="UTF-8" ?>
<!--beans 根标签,里面有若干个bean子标签-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:comtext="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
">
<!--开启注解扫描,base-package指定扫描的包路径-->
<comtext:component-scan base-package="com.yzpnb.advanced_application"></comtext:component-scan>
<comtext:component-scan base-package="spring"></comtext:component-scan>
<!--第三方bean配置到xml中-->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/bank"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>

1.3.2 引入外部文件配置


1.4 纯注解模式
纯注解模式,完全不需要xml了,和使用springboot时基本相同,基本注解如下 |
Description(:不常用/:常用) |
Annontation |
标识当前类是一个配置类 |
@Configuration |
替代context:compoent-scan,开启注解驱动 |
@ComponentScan |
引入外部配置文件 |
@PropertySource |
重点引入其他配置类,如果配置类有多个,可以用这个注解,统一引入到一个配置类中 |
@Import |
对变量赋值,可以直接赋值,或者使用${}读取资源配置文件中信息 |
@Vlaue |
将方法返回对象加入SpringIoC容器中 |
@Bean |
那么spring毕竟不是springboot,没有启动类,我们将这些注解全部配置到配置类上 |
- 创建一个SpringConfiguration配置类,用@Configuration注解标识

- 使用@ComponentScan注解,替代context:compoent-scan,开启注解驱动

- 使用@PropertySource注解引入外部资源文件

- 使用@Vlaue‘注解’注入配置文件中的值,使用@Bean()注解,配置第三方jar的bean实例

没有启动类,我们怎么加载配置类,然后使用我们的配置呢 |
- javaEE版

- web版:纯注解已经不需要xml了,web程序需要监听器,做如下更改,监听器配置保留,并指定监听器使用注解配置类,而且指定xml配置文件的配置需要改变,改为指定配置类

- 测试

2. spring 高级特性
2.1 lazy-Init延迟加载
一种可以在一定程度上提高容器启动和运转性能的机制,对于不常用的bean设置懒加载,偶尔使用时再加载,没必要一开始这个bean就占用资源 |
Bean的延迟加载(延迟创建),就是懒加载,按需加载,第一次向容器索取bean的时候,才会实例化 |
- ApplicationContext容器的默认行为是启动服务器时将所有singleton bean提前进行实例化,意味着实例化作为初始化过程的一部分,ApplicationContext实例会创建并配置所有的singleton bean。
- 如果不指定lazy-Init参数,则默认为false,不进行懒加载
<bean id="testBean" class = "com.yzpnb.testBean" />
等价于
<bean id="testBean" class = "com.yzpnb.testBean" lazy-init="false"/>
<bean id="testBean" class = "com.yzpnb.testBean" lazy-init="true"/>
- 什么时候是需要一个bean的时候,当我们getBean的时候,或者bean1引用了开启懒加载的bean2,bean1实例化时,bean2也会实例化
- < beans>标签上使用default-lazy-init属性,来控制< bean>标签的懒加载
<!--容器层次,控制懒加载初始化-->
<beans default-lazy-init = "true">
<!--经管没指定lazy-init="true",它也开启了懒加载,因为<beans>标签统一设置了-->
<bean id="testBean" class = "com.yzpnb.testBean" />
</beans>
@Component("connectionUtils")
@Lazy
public class ConnectionUtils {
}
做个简单测试,其实它已经初始化了,只不过放在了一个map中(缓存),需要的时候,从缓存中搞出来,后面看源码会讲 |



- 加注解

- 测试:因为我们配置类中,有其它bean依赖于connectionUtils,你可以自己创建一个单独的bean测试,这里我只告诉方法,后面会重点讲源码

2.2 FactoryBean和BeanFactory
BeanFactory我们前面已经提到了,是IoC容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,具体使用它下面的子接口类型,例如ApplicationContext |
FactoryBean:工厂bean,spring中有普通bean和工厂bean两种,FactoryBean可以生成某一类型的Bean实例 返回给我们,也就是说,我们可以借助它自定义Bean的创建过程 |
前面我们介绍了创建bean的三种方式,构造方法,静态方法,实例方法,FactoryBean类似于静态方法和实例方法,但是FactoryBean是常用的方法,静态和实例方法,用的并不多 |
一般用来创建复杂对象,就是xml和注解没办法创建的对象 |
- 新建一个类Company

- 创建一个生产此对象的工厂,继承实现FactoryBean

如何使用呢,依然需要通过注解或者xml指定使用FactoryBean来生产复杂对象 |
- xml


- Annontation,就是简单的配置类中配置了


3. Spring Bean的生命周期
Created with Raphaël 2.3.0 第一步:实例化Bean 第二步:设置属性值 第三步:调用BeanNameAware的setBeanName方法 第四步:调用BeanFactoryAware的setBeanFactory方法 第五步:调用ApplicationContextAware的setApplicationContext方法 第六步:调用BeanPostProcessor的预初始化方法 第七步:调用InitializingBean的afterPropertiesSet方法 第八步:调用定制的初始方法init-method 第九步:调用BeanPostProcessor的后初始化方法
- 第九步,有两种可能,如果是prototype(原型模式)的bean,就直接交给调用者
- 如果是singleton(单例模式)的bean,就放入spring缓存池中准备就绪的bean
Created with Raphaël 2.3.0 Spring缓存池中准备就绪的bean,当Bean销毁时 调用destory-method属性配置的销毁方法(没有先后顺序) 调用DisposableBean的destory方法(没有先后顺序)
- 根据配置情况调用Bean构造方法或工厂方法实例化Bean
- 利用依赖注入完成Bean中所有属性值的配置注入
- 如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName()方法传入当前Bean的id值
- 如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory()方法传入当前工厂实例的引用
- 如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext()方法传入当前ApplicationContext实例的引用。
- 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessBeforeInitialzation()对Bean进行加工操作,此处非常重要,Spring的AOP就是利用它实现的
- 如果Bean实现了InitializingBean接口,则Spring将调用afterPropertiesSet()方法.
- 如果在配置文件中通过init-method属性指定了初始化方法,则调用该初始化方法。
- 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的初始化方法postProcessAfterInitialization()。此时,Bean已经可以被应用系统使用了。
- 如果在< bean>中指定了该Bean的生命周期管理;如果在< bean>中指定了该Bean的作用范围为scope=“prototype”,则将实例交给调用者,剩下的不归Spring管
- 如果Bean实现了DisposableBean接口,则Spring会调用destory()方法将Spring中的Bean销毁;如果在配置文件中通过destory-method属性指定了Bean的销毁方法,则Spring将调用该方法对Bean进行销毁
注意,虽然Spring为Bean提供了细致全面的生命周期过程,通过实现特定的接口或< bean>的属性设置,都可以对Bean生命周期进行管理,虽然可以随意配置< bean>的属性,但是不建议过多使用Bean实现接口,因为这样会导致代码和Spring的高耦合 |
3.1 spring bean的生命周期测试
-
第一、二、三步:创建Result类,即可测试生命周期第一步,而设置一些属性注入,即可测试生命周期第二步,调用BeanNameAware的setBeanName()为第三步,,所以我们实现BeanNameAware,并实现setBeanName方法

-
第四步:调用BeanFactoryAware的setBeanFactory方法

-
第五步:调用ApplicationContextAware的setApplicationContext方法

-
第六步:调用BeanPostProcessor的预初始化方法:这个是后置处理器,这里先略过,下面就会讲
-
第七步:调用InitializingBean的afterPropertiesSet方法

-
第八步:调用定制的初始方法init-method,可以用xml配置

-
第九步:调用BeanPostProcessor的后初始化方法
-
Bena销毁前
- 如果是单例模式,调用DisposableBean的destory方法,调用destory-method属性配置的销毁方法

- 测试


3.2 实例化之前干的事
- 实例化之前会读取xml,涉及到了一个对象BeanDefinition(Spring容器启动过程中,会将Bean解析成Spring内部的BeanDefinition结构)

- 其实就是将Bean的定义信息存储到BeanDefinition相应的属性中,类名、scope、属性、构造函数参数列表、依赖的bean、是否单例、是否懒加载等等
- 后面对Bean的操作,就是直接对BeanDefinition进行,例如拿到这个对象,根据类名,构造函数等利用反射,创建实例
当然,在Bean生命周期前,是有工厂已经存在的,那么针对工厂的后置处理器BeanFactoryPostProcessor,也会进行工作,具体看下面 |
4. 后置处理器
spring 提供两种后置处理bean的扩展接口,BeanPostProcessor(针对bean对象的后置处理器)和BeanFactoryPostProcessor(针对bean工厂的后置处理器) |
3.1 BeanPostProcessor
针对Bean基本的处理,可以针对某个具体的bean,看下面源码 |
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
- 创建类,然后实现BeanPostProcessor,让其特定监听lazyResult这个Bean

3.2 BeanFactoryPostProcessor
在BeanFactory初始化前后可以使用BeanFactoryPostProcessor进行后置处理,针对整个Bean工厂进行处理,典型应用是PropertyPlaceholderConfigurer。看下源码,不过多介绍了 |
源码如下,是个函数式接口,可以使用lambda表达式直接用,只有一个方法,方法参数是ConfigurableListableBeanFactory |
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
ConfigurableListableBeanFactory ,提供了getBeanDefinition方法,可以根据此方法,找到我们定义bean的BeanDefinition对象,然后我们可以对定义的属性进行修改 |

三、Spring IoC源码
由于篇幅限制,我将其放在这篇文章:https://blog.csdn.net/grd_java/article/details/122716151 |
四、Spring AOP 高级应用和源码
由于篇幅原因,我将其放在这篇文章:https://blog.csdn.net/grd_java/article/details/122858872 |