Spring是轻量级开源框架,以IOC反转控制和AOP面向切面编程为内核,提供了展现层的MVC和持久层JDBC以及业务层事务管理等技术,还能整合很多第三方框架和类库。
看下Spring 框架图,在Spring中有核心容器,Core Container即是IOC部分,这里面就有Bean。
在一般情况下我们写CSD三层代码的时候使用直接new的方式来创建一个对象,这样程序之间的耦合性太大,如果编译的时候有一个层的实现类不存在的的时候,则编译器将不会通过,这样以来,它就是一个编译器依赖。
在实际开发中可以把三层的对象的全类名都使用配置文件保存起来,当启动服务器应用加载的时候,创建这些对象的实例并保存在容器中. 在获取对象时,不使用new的方式,而是直接从容器中获取,这就是工厂设计模式.
1、使用反射来创建对象,避免使用new创建对象。
2、通过读取配置文件来获取要创建的对象权限定类名。
eg:
1、需要创建配置文件配置我们的Service和Dao
配置的内容:唯一标示=权限定类名(key=value)
2、通过读取配置文件配置的内容,反射创建对象(BeanFactory)
这个时候创建的多利对象。
这个时候可以用map进行存储Bean,根据name去获取Bean,保证单例对象。
IOC的概念为:
当使用new创建对象的时候,等于找对象是主动找的,资源是直接相连接,这个时候它们的联系是消除不掉的。
而使用工厂模式的时候,工厂控制资源,工厂提供给获取方资源,获取方和资源断开连接。
上面这种思想就叫IOC控制反转(为什么不叫降低依赖?因为它把找到相应的对象的权利丢弃,交给BeanFactory来通过名字获取Bean对象,工厂所能得到哪个Bean对象是根据配置所对应的权限和类名所决定的,使用它的类无法自助决定的。所以叫做控制反转)
所以IOC的作用就是消减计算机程序之间的耦合,解除代码之间的依赖关系。(注意只能是削减,不是完全解除耦合关系)
Ioc—Inversion of Control,不是什么技术,而是一种设计思想。
谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
建立存放Bean中id和Class的类——BeanDefinition。
这里存放的是Bean的内容和元数据,保存在BeanFactory当中,包装Bean的实体:
public class BeanDefinition {
//真正的Bean实例
private Object bean;
//Bean的类型信息
private Class beanClass;
//Bean类型信息的名字
private String beanClassName;
//用于bean的属性注入 因为Bean可能有很多熟属性
//所以这里用列表来进行管理
private PropertyValues propertyValues = new PropertyValues();
}
其中的PerpertyValues存放Bean的所有属性
public class PropertyValues {
private final List propertyValueList = new ArrayList();
}
其中ArrayList数组中的PropertyValue存放的是每个属性,可以看到两个字段,name和valu。name存放的就是属性名称,value是object类型,可以是任何类型
public class PropertyValue {
private final String name;
private final Object value;
}
把Bean装进容器的过程,其实就是其BeanDefinition构造的过程,那么怎么把一些类装入的Spring容器呢?
//TODO:后面分析下如何处理的。
依赖注入(Dependency Injection)是spring框架核心ioc的具体实现.
通过控制反转,我们把创建对象托管给了spring,但是代码中不可能消除所有依赖,例如:业务层仍然会调用持久层的方法,因此业务层类中应包含持久化层的实现类对象.
我们等待框架通过配置的方式将持久层对象传入业务层,而不是直接在代码中new某个具体的持久化层实现类,这种方式称为依赖注入.
“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”
因为我们是通过反射的方式来创建属性对象的,而不是使用new关键字,因此我们要指定创建出对象各字段的取值.
通过类默认的构造函数来给创建类的字段赋值,相当于调用类的构造方法.
涉及的标签: 用来定义构造函数的参数,其属性可大致分为两类:
public class AccountServiceImpl implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
配置
在类中提供需要注入成员属性的set方法,创建对象只调用要赋值属性的set方法.
涉及的标签: ,用来定义要调用set方法的成员. 其主要属性可大致分为两类:
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
集合字段及其对应的标签按照集合的结构分为两类: 相同结构的集合标签之间可以互相替换.
只有键的结构:
- 数组字段: 标签表示集合,标签表示集合内的成员.
- List字段: 标签表示集合,标签表示集合内的成员.
- Set字段: 标签表示集合,标签表示集合内的成员.
其中,,标签之间可以互相替换使用.
键值对的结构:
- Map字段: 标签表示集合,标签表示集合内的键值对,其key属性表示键,value属性表示值.
- Properties字段: 标签表示集合,标签表示键值对,其key属性表示键,标签内的内容表示值.
其中,标签之间,,标签之间可以互相替换使用.
我们常用的容器有三种: ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,AnnotationConfigApplicationContext.
使用配置文件实现IOC,要将托管给spring的类写进bean.xml配置文件中.
作用: 配置托管给spring的对象,默认情况下调用类的无参构造函数,若果没有无参构造函数则不能创建成功
属性:
默认情况下会根据默认无参构造函数来创建类对象,若Bean类中没有默认无参构造函数,将会创建失败.
创建静态工厂如下:
// 静态工厂,其静态方法用于创建对象
public class StaticFactory {
public static IAccountService createAccountService(){
return new AccountServiceImpl();
}
使用StaticFactory类中的静态方法createAccountService创建对象,涉及到标签的属性:
其实,类的构造函数也是静态方法,因此默认无参构造函数也可以看作一种静态工厂方法
创建实例工厂如下:
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
先创建实例工厂对象instanceFactory,通过调用其createAccountService()方法创建对象,涉及到标签的属性:
这些注解的作用相当于bean.xml中的标签
这些注解的作用相当于bean.xml中的标签.
这些注解的作用相当于bean.xml中的标签的scope属性.
这些注解的作用相当于bean.xml中的标签的init-method和destroy-method属性
Spring IoC 容器的设计主要是基于以下两个接口:
从上图中我们可以几乎看到, BeanFactory 位于设计的最底层,它提供了 Spring IoC 最底层的设计,为此,我们先来看看该类中提供了哪些方法:
由于这个接口的重要性,所以有必要在这里作一下简短的说明:
【getBean】 对应了多个方法来获取配置给 Spring IoC 容器的 Bean。
① 按照类型拿 bean:
bean = (Bean) factory.getBean(Bean.class);
注意:要求在 Spring 中只配置了一个这种类型的实例,否则报错。(如果有多个那 Spring 就懵了,不知道该获取哪一个)
② 按照 bean 的名字拿 bean:
bean = (Bean) factory.getBean(“beanName”);
注意:这种方法不太安全,IDE 不会检查其安全性(关联性)
③ 按照名字和类型拿 bean:(推荐)
bean = (Bean) factory.getBean(“beanName”, Bean.class);
【isSingleton】 用于判断是否单例,如果判断为真,其意思是该 Bean 在容器中是作为一个唯一单例存在的。而【isPrototype】则相反,如果判断为真,意思是当你从容器中获取 Bean,容器就为你生成一个新的实例。
注意:在默认情况下,【isSingleton】为 ture,而【isPrototype】为 false
关于 type 的匹配,这是一个按 Java 类型匹配的方式
【getAliases】方法是获取别名的方法
这就是 Spring IoC 最底层的设计,所有关于 Spring IoC 的容器将会遵守它所定义的方法。
其中 ApplicationContext 是 BeanFactory 的子接口之一,换句话说:BeanFactory 是 Spring IoC 容器所定义的最底层接口,而 ApplicationContext 是其最高级接口之一,并对 BeanFactory 功能做了许多的扩展,所以在绝大部分的工作场景下,都会使用 ApplicationContext 作为 Spring IoC 容器。
ApplicationContext的三个常用实现类:
ClassPathXmlApplicationContext: 它可以加载路径下的配置文件,要求配置文件必须在路径下,否则加载不了
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
FileSyetemXmlApplicationContext:它可以加载磁盘下任意路径下的配置文件(必须有访问权限)
加载方式如下:
ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\user\\greyson\\...")
AnnotationConfigApplicationContext:它是用于读取注解创建容器的
核心容器的两个接口引发出来的问题
spring底层容器,定义了最基本的容器功能,注意区分FactoryBean。
BeanFactory
BeanFactory是IOC最基本的容器,负责生产和管理bean,它为其他具体的IOC容器提供了最基本的规范,例如DefaultListableBeanFactory, XmlBeanFactory,ApplicationContext 等具体的容器都是实现了BeanFactory,再在其基础之上附加了其他的功能。
FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上&。
区别
BeanFactory和FactoryBean其实没有什么比较性的,只是两者的名称特别接近,所以有时候会拿出来比较一番,BeanFactory是提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,FactoryBean可以说为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,我们可以在getObject()方法中灵活配置。其实在Spring源码中有很多FactoryBean的实现类,要想深入准确的理解FactoryBean,只有去读读Spring源码了。
扩展于BeanFactory,拥有更丰富的功能。例如:添加事件发布机制、父子级容器,一般都是直接使用ApplicationContext。
bean配置文件,一般为xml文件。可以理解为保存bean信息的文件。
beandifinition定义了bean的基本信息,根据它来创造bean
通过上述三个步骤我们将配置在配置文件中的节点,全部封装到上述的Map
这个过程大概可以理解为,
注意: Bean 的定义和初始化在 Spring IoC 容器是两大步骤,它是先定义,然后初始化和依赖注入的。
Bean 的定义分为 3 步:
做完了以上 3 步,Bean 就在 Spring IoC 容器中被定义了,而没有被初始化,更没有完成依赖注入,也就是没有注入其配置的资源给 Bean,那么它还不能完全使用。
对于初始化和依赖注入,Spring Bean 还有一个配置选项——【lazy-init】,其含义就是是否初始化 Spring Bean。在没有任何配置的情况下,它的默认值为 default,实际值为 false,也就是 Spring IoC 默认会自动初始化 Bean。如果将其设置为 true,那么只有当我们使用 Spring IoC 容器的 getBean 方法获取它时,它才会进行 Bean 的初始化,完成依赖注入。
此过程根据上述的BeanDefition,
这个过程大概可以理解为
到此,spring容器就可以对外提供服务了。
注意:
在分析 Spring Bean 实例化过程中提到 Spring 并不是一启动容器就开启 bean 的实例化进程,只有当客户端通过显示或者隐式的方式调用 BeanFactory 的 getBean() 方法来请求某个实例对象的时候,它才会触发相应 bean 的实例化进程,当然也可以选择直接使用 ApplicationContext 容器,因为该容器启动的时候会立刻调用注册到该容器所有 bean 定义的实例化方法。当然对于 BeanFactory 容器而言并不是所有的 getBean() 方法都会触发实例化进程,比如 signleton 类型的 bean,该类型的 bean 只会在第一次调用 getBean() 的时候才会触发,而后续的调用则会直接返回容器缓存中的实例对象。
容器启动的过程可以分为2大步
spring的注解配置可以与xml配置并存,也可以只使用注解配置
在半注解配置下,spring容器仍然使用ClassPathXmlApplicationContext类从xml文件中读取IOC配置,同时在xml文件中告知spring创建容器时要扫描的包.
例如,使用半注解模式时,上述简单实例中的beans.xml内容如下:
然后将spring注解加在类的定义中.
在纯注解配置下,我们用配置类替代bean.xml,spring容器使用AnnotationApplicationContext类从spring配置类中读取IOC配置