0. 前言:
视频教程:黑马程序员Java设计模式详解,全网最全23种Java设计模式
23种设计模式学的差不多了,得找个具体的框架案例上手体验一把。
在Java开发实际工作当中用的最多的开源框架便是Spring家族,于是就从Spring的Ioc特性入手,运用一定的设计模式,从Spring源码入手回顾并自定义实现Spring的Ioc 🚀 🚀
参考博客:
1. 回顾Spring的使用
自定义spring框架前,先回顾一下spring框架的使用,从而分析spring的核心,并对核心功能进行模拟。
-
创建maven项目,并导入Spring依赖坐标:
<artifactId>spring-demo</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency> </dependencies>
-
创建dao (数据访问层)定义UserDao接口及其子实现类
public interface UserDao { public void add(); } public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("执行 UserDao 方法..."); } }
-
创建service(业务逻辑层)定义UserService接口及其子实现类
public interface UserService { public void add(); } public class UserServiceImpl implements UserService { //声明UserDao变量: private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void add() { System.out.println("执行 UserService 方法..."); userDao.add(); } }
-
创建applicationContext.xml配置文件,管理bean
在idea中可以用Spring Config模板快速创建
<?xml version="1.0" encoding="UTF-8"?> <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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--文件头为模板自带,下面的bean属自己配置--> <bean id="userDao" class="com.wayne.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.wayne.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean> </beans>
-
创建UserController 控制层,调用对应的方法
public class UserController { public static void main(String[] args) { //1.创建Spring的容器对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.从容器对象中获取UserService对象 UserService userService = applicationContext.getBean("userService", UserService.class); //3.调用UserService对象的add方法 userService.add(); } }
- 执行结果:
小结:
- 在main方法中,并没有创建/实例化 userService 和userDao,这两个类都交由Spring 容器对象管理。
- UserService中的userDao变量我们并没有进行赋值,但是可以正常使用,说明spring已经将UserDao对象赋值给了userDao变量。
- 在UserService类中,Spring还往其注入了UserDao类,并在其中调用了UserDao的方法。
上面三点体现了Spring框架的IOC(Inversion of Control)和DI(Dependency Injection, DI)
2. Spring核心功能结构
Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:
核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等,下面是 Spring 框架的总体架构图:
核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。
-
spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)。
-
BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。
-
BeanFactory属于延时加载,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。
- 代码验证:
-
在UserService 和 UserDao 中创建无参构造并输出 ”xx被创建语句“方便观察
-
创建BeanFactory加以验证:
BeanFactory applicationContext = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
-
运行结果:可以看到对象是被调用时才创建的
-
-
spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等。
-
ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。
-
与BeanFactory不同,ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
- 代码验证:
-
-
spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。
-
它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。
-
EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。
-
2.1 Bean概述:
Spring 就是面向 Bean
的编程(BOP,Bean Oriented Programming),Bean 在 Spring 中处于核心地位。Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有Bean也就没有Spring存在的意义。Spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系。
spring中bean用于对一个类进行封装。如下面的配置:
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
为什么Bean如此重要呢?
- spring 将bean对象交由一个叫IOC容器进行管理。
- bean对象之间的依赖关系在配置文件中体现,并由spring完成。
对Bean标签的详解:
2.1.1 Bean的配置:
属性或子元素名称 | 说明 |
---|---|
id | Bean的唯一标识符,Spring容器对 Bean 的配置,管理都通过该属性进行 |
name | Spring 容器通过此属性进行配置和管理,name 属性可以为Bean 指定多个名称,每个名称之间用逗号或分号隔开 |
class | 指定Bean的实现类,它必须使用类的全限定名 |
scope | 用于设定Bean实例的作用域,其属性值有singleton(单例),prototype(原型)、request、session,global Session,application 和 websocket,默认值为 singleton |
constructor-arg | <bean>元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index属性指定构造参数的序号(从0开始),type 属性指定构造参数的类型,参数值可以通过 ref 属性或value 属性直接指定,也可以通过ref或value 子元素指定 |
property | <bean>元素的子元素,用于调用Bean 实例中的 setter()方法完成属性赋值,从而完成依赖注入。该元素的name 属性指定 Bean 实例中的相应属性名,ref属性或value属性用于指定参数值 |
ref | <constructor-arg><property>等元素的属性或子元素,可以用于指定对Bean 工厂中某个Bean 实例的引用 |
value | <constructor-arg>,<property>等元素的属性或子元索,可以用于直接给定一个常量值 |
list | 用于封装List 或数组属性的依赖注入 |
set | 用于封装Set 类型属性的依赖注入 |
map | 用于封装Map 类型属性的依赖注入 |
entry | <map>元素的子元素,用于设置一个键值对.其key属性指定字符串类型的键值,ref属性或value 属性直接指定其值,也可以通过ref或value子元素指定其值 |
2.1.2 Bean的作用域:
作用域名称 | 说明 |
---|---|
singleton(单例) | 使用 singleton定义的Bean在Spring 容器中将只有一个实例,也就是说,无论有多少个Bean 引用它,始终将指向同一个对象,这也是 Spring 容器默认的作用域 |
prototype(原型) | 每次通过 Spring容器获取 prototype定义的Bean时,容器都将创建一个新的 Bean 实例 |
request | 在一次HTTP 请求中,容器会返回该Bean 的同一个实例,对不同的HTTP 请求则会产生一个新的Bean,而且该Bean 仅在当前HTTP Request 内有效 |
session | 在一次HTTP Session中,容器会返回该Bean的同一个实例,对不同的HTTP 请求则会产生一个新的Bean,而且该Bean 仅在当前HTTP Session 内有效 |
globalSession | 在一个全局的HTTP Session中,容器会返回该Bean的同一个实例,仅在使用 portlet上下文时有效 |
application | 为每个ServletContext对象创建一个实例,仅在Web 相关的ApplicationContext中有效 |
websocket | 为每个websocket 对象创建一个实例,仅在Web的ApplicationContext.中生效 |
2.1.3 Bean的装配方式:
-
基于XML装配:
Spring提供了两种基于XML的装配方式;:设值注入(Setter)和构造注入
在Spring实例化Bean的过程中,Spring首先会调用Bean的默认构造方法来实例化Bean对象,然后通过反射的方式调用setter()方法来注入属性值。因此,设置注入要求Bean必须满足以下两点要求:
- Bean类必须提供一个默认的无参构造方法。
- Bean类必须为需要注入的属性提供对应的setter()方法。
注入要点:
- 使用设置注入时,在Spring配置文件中需要使用< bean>元素的子元素<property>来为每个属性注入值;
- 而使用构造注入时,在配置文件中需要使用<bean>元素的子元素<constructor-arg>来定义构造方法的参数,可以使用其values属性(或子元素) 来设置该参数的值。
-
基于Annotation装配:
在Spring中,虽然可以使用XML配置文件可以实现Bean的装配工作,但是一旦应用中有很多Bean,就会导致XML配置文件过于臃肿。因此,Spring提供了对Annotation(注解)技术的全面支持。
注解名称 说明 @Component 可以使用此注解描述 Spring中的Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可 @Repository 用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与@Component 相同 @Service 通常作用在业务层(Service层),用于将业务层的类标识为 Spring中的Bean,其功能与@Component 相同 @Controller 通常作用在控制层(如 Spring MVC的 Controller),用于将控制层的类标识为 Spring中的 Bean,其功能与@Component 相同 @Autowired 用于对Bean 的属性变量,属性的 setter0)方法及构造方法进行标注,配合对应的注解处理器完成Bean的自动配置工作,默认按照Bean 的类型进行装配 @Resource 其作用与@Autowired一样,
区别在于@Autowired 默认按Bean类型装配,
而@Resource默认按照Bean实例名称进行装配.
@Resource中有两个重要属性:name 和type
Spring 将name 属性解析为 Bean 实例名称,type 属性解析为Bean 实例类型.
若指定 name 属性,则按实例名称进行装配;若指定type属性,则按Bean类型进行装配;
若都不指定,则先按 Bean 实例名称装配,不能匹配时再按照Bean类型进行装配;若都无法匹配,则抛出NoSuchBeanDefinitionException异常@Qualifier 与@Autowired 注解配合使用,会将默认的按Bean 类型装配修改为按Bean 的实例名 注意: 虽然@Repository、@Setvice和@Controller的功能与@Component注解的功能相同,但能使标注类本身用途更加清晰,建议使用@Repository、@Setvice和@Controller注解。
-
自动装配:
Spring的<bean>元素中包含一个autowire属性,我们可以通过设置autowire的属性值来自动装配Bean,所谓自动装配,就是将一个Bean自动注入其他Bean的Property中
autowire属性有5个值,如下表所示:
属性值 说明 defualt(默认值) <bean>的上级标签<beans>的 default-autowire属性值确定,例如<beans default-autowire=“byName”>,该<bean>元素中的 autowire 属性对应的属性值为byName byName 根据属性的名称自动装配.容器将根据名称查找与属性完全一致的 Bean,并将其属性自动装配 byType 根据属性的数据类型(Type)自动装配,如果一个Bean 的数据类型兼容另一个 Bean中属性的数据类型,则自动装配 constructor 根据构造函数参数的数据类型进行byType 模式的自动装配 no 在默认情况下,不适用自动装配,bean的以来必须通过ref元素来定义
3. Spring IOC相关接口分析
在此只是简单通过源码以及类图观察不同的接口,具体实现还等下回分解。
3.1 BeanFactory解析
Spring中Bean的创建是典型的工厂模式,通过简单工厂模式+配置文件的方式实现这一系列的Bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。
其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,观察其源码:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
//根据bean的名称获取IOC容器中的的bean对象
Object getBean(String name) throws BeansException;
//根据bean的名称获取IOC容器中的的bean对象,并指定获取到的bean对象的类型,这样我们使用时就不需要进行类型强转了
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
//判断容器中是否包含指定名称的bean对象
boolean containsBean(String name);
//根据bean的名称判断是否是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心Bean是如何定义及怎样加载的,以bean的产生结果为导向,正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的,所以后续会有注册类来为 BeanFactory加载bean。
3.1.1 BeanFactory 的子接口
BeanFactory有三个重要的子接口:
ListableBeanFactory
、HierarchicalBeanFactory
和 AutowireCapableBeanFactory
。
但是从类图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory
,它实现了所有的接口。
那么为何要定义这么多层次的接口呢?
每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。
ListableBeanFactory
接口表示这些Bean可列表化。HierarchicalBeanFactory
表示这些Bean 是有继承关系的,也就是每个 Bean 可能有父 BeanAutowireCapableBeanFactory
接口定义Bean的自动装配规则。
3.1.2 ApplicationContext接口
BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。
要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,比如:
ClasspathXmlApplicationContext
: 根据类路径加载xml配置文件,并创建IOC容器对象。FileSystemXmlApplicationContext
:根据系统路径加载xml配置文件,并创建IOC容器对象。AnnotationConfigApplicationContext
:加载注解类配置,并创建IOC容器。
3.2 BeanDefinition解析
Spring的ioc是靠简单工厂模式+配置文件实现的,那么读取了配置文件应该如何储存呢?这时就要用到BeanDefinition 对象,bean标签会封装成BeanDefinition对象为后续Spring创建与管理bean提供对象。
首先是读取bean的xml配置文件,然后解析xml文件中的各种bean的定义,将xml文件中的每一个<bean/>元素分别转换成一个BeanDefinition对象,其中保存了从配置文件中读取到的该bean的各种信息:
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
implements BeanDefinition, Cloneable {
//beanClass保存bean的class属性
private volatile Object beanClass;
//scop保存bean是否单例
private String scope = SCOPE_DEFAULT;
//abstractFlag保存该bean是否抽象
private boolean abstractFlag = false;
//lazyInit保存是否延迟初始化
private boolean lazyInit = false;
//autowireMode保存是否自动装配
private int autowireMode = AUTOWIRE_NO;
//dependencyCheck保存是否坚持依赖
private int dependencyCheck = DEPENDENCY_CHECK_NONE;
//dependsOn保存该bean依赖于哪些bean(这些bean必须提取初始化)
private String[] dependsOn;
//constructorArgumentValues保存通过构造函数注入的依赖
private ConstructorArgumentValues constructorArgumentValues;
//propertyValues保存通过setter方法注入的依赖
private MutablePropertyValues propertyValues;private String factoryBeanName;
//factoryBeanName和factoryMethodName用于factorybean,也就是工厂类型的bean
private String factoryMethodName;
//initMethodName和destroyMethodName分别对应bean的init-method和destory-method属性
private String initMethodName;
private String destroyMethodName;
//...
}
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
读完配置文件之后,得到了很多的BeanDefinition对象,
3.3 BeanDefinitionReader解析
应该如何读取并解析配置文件并封装成BeanDefinition对象呢?此时我们需要BeanDefinitionReader。
Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。
Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过BeanDefinitionReader来完成,看看Spring中BeanDefinitionReader的类结构图,如下图所示。
看看BeanDefinitionReader接口定义的功能来理解它具体的作用:
public interface BeanDefinitionReader {
//获取BeanDefinitionRegistry注册器对象
BeanDefinitionRegistry getRegistry();
@Nullable
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
/*
下面的loadBeanDefinitions都是加载bean定义,从指定的资源中
*/
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
3.4 BeanDefinitionRegistry解析
相关博客:
正如前面所说,BeanFactory 根本不管bean时怎么来的,只要里面有bean即可,接口中并没有提供往 BeanFactory 中添加 BeanDefinition 的方法,那么这一步由谁来做呢?
同理,我们读取的配置文件中定义了很多bean标签,解析的BeanDefinition对象存储到哪儿?又应该如何被BeanFactory调用?答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry。
public interface BeanDefinitionRegistry extends AliasRegistry {
//往注册表中注册bean
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
//从注册表中删除指定名称的bean
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
//获取注册表中指定名称的bean
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
//判断注册表中是否已经注册了指定名称的bean
boolean containsBeanDefinition(String beanName);
//获取注册表中所有的bean的名称
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String beanName);
}
/** 别名注册接口:
BeanDefinitionRegistry 还继承一个接口:AliasRegistry,看名字也能猜的出,这是一个提供 Bean 别名支持的接口
*/
public interface AliasRegistry {
/** 注册表中给name注册一个别名alias */
void registerAlias(String name, String alias);
/** 移除注册表中的别名alias */
void removeAlias(String alias);
/** 校验注册表中是否存在别名name */
boolean isAlias(String beanName);
/** 在注册表中获取给定那么的所有别名信息 */
String[] getAliases(String name);
}
继承结构图如下:
从上面类图可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:
-
DefaultListableBeanFactory
在该类中定义了如下代码,就是用来注册bean
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
-
SimpleBeanDefinitionRegistry
在该类中定义了如下代码,就是用来注册bean
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
3.5 创建容器
ClassPathXmlApplicationContext对Bean配置资源的载入是从refresh()方法开始的。refresh()方法是一个模板方法,规定了 IoC 容器的启动流程,有些逻辑要交给其子类实现。
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
private Resource[] configResources;
//无参构造
public ClassPathXmlApplicationContext() {
}
//直接传入一个ApplicationContext对象进行加载
public ClassPathXmlApplicationContext(ApplicationContext parent) {
//调用父类的构造
super(parent);
}
//根据传入的配置文件路径进行构造
//自动刷新上下文
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
//调本类的方法
this(new String[]{configLocation}, true, (ApplicationContext)null);
}
//根据传入的多个配置文件进行构造
//自动刷新上下文
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
//使用给定父类进行构建
//自动刷新上下文
public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
//根据传入的配置文件们进行构建
//自定义是否刷新
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, (ApplicationContext)null);
}
//根据传入的配置文件们以及父类进行构建
//自定义是否刷新
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
//调用父类构造
super(parent);
//设置参数
this.setConfigLocations(configLocations);
//如果判断成功则调用刷新方法
if(refresh) {
this.refresh();
}
}
//下面的几个方法都是通过加载类的路径从而实现ClassPathXmlApplicationContext,这里就不一一列举了
public ClassPathXmlApplicationContext(String path, Class<?> clazz) throws BeansException {
this(new String[]{path}, clazz);
}
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz) throws BeansException {
this(paths, clazz, (ApplicationContext)null);
}
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, ApplicationContext parent) throws BeansException {
super(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
this.configResources = new Resource[paths.length];
for(int i = 0; i < paths.length; ++i) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
this.refresh();
}
protected Resource[] getConfigResources() {
return this.configResources;
}
}
它对 Bean 配置资源进行载入,ClassPathXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()方法启动整个IoC容器对Bean定义的载入过程。