IoC(Inversion of Control)控制反转
对象的创建控制权由程序内部转移到外部,这种思想称为控制反转.
简单来说就是在程序中使用对象时,不使用new 产生对象,而由外部提供对象.
Spring 就提供了一个容器,称为IoC容器 ,该容器可以为程序内部提供对象,实现解耦
IoC容器负责对象的创建,初始化等工作,被创建或被管理的对象在Ioc容器中统称为bean
解释:bean可以看成一个'变量',这个'变量'的类型是一个对象,在Spring配置文件中配置bean的具体类型属性
思考:如果一个类的内部需要另一个类对象时,需要配置几个bean?这俩个bean什么关系?
IoC提供了一种解决方法 DI(dependcy Injection)依赖注入
具体意思就是把两个bean 之间建立依赖关系,称为依赖注入
注意并不是随意绑定的,绑定bean关系的前提是在程序内部一个类中需要使用另一个类的对象时,就把这两个类分别配置一个bean,然后对这两个bean进行绑定
IoC入门案例
分析:IoC是管理类对象的,IoC容器中可以由许多的bean,通过配置文件将被管理对象告知IoC容器,再通过接口以及接口方法获取IoC容器和bean
1.在pom.xml配置文件中添加Spring坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
导入坐标后右键resources -->new --> xml Configuration File --> Spring Config
创建一个Spring配置文件,起名最好是 applicationContext
2.配置bean
在applicationContext配置文件中使用<bean>标签来配置bean,格式如下
<bean id = " " class = " " />
其中bean标签可以是自闭和标签也可以是围堵标签
* id属性表示给bean起的名字,这个值唯一,且可以使用name属性起别名
* class 属性表示给bean定义的而类型,它是一个url 需要配置bean 的类的路径
例子<bean id = "person " class = "com.itxiaobai.user.Person" />
这样一个类型为Person的bean就配置好了
3.获取IoC容器
public class BeanTest{
public static void main(String args[]) throws Exception{
//ApplicationContext是一个接口,通过其实现类ClassPathXmlApplicationContext得到一个IoC容器
//通过传入配置applicationContext.xml文件让这个IoC容器中有bean
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
4.获取bean
public class BeanTest{
public static void main(String args[]) throws Exception{
//ApplicationContext是一个接口,通过其实现类ClassPathXmlApplicationContext得到一个IoC容器
//通过传入配置applicationContext.xml文件让这个IoC容器中有bean
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//上一步中创建了一个IoC容器对象ctx 通过调用getBean()方法以及IoC中管理的bean Id值生成一个Person对象p
Person p = ctx.getBean("person",Person.class);
p.say();
}
}
DI入门案例思路
分析下列代码:
代码1
public class Person {//一个Person类 里面有一个son成员和一个say方法
prviate son ;
public void say(){
System.out.println(" Person...")
son.say();
}
}
代码2
public class Son {//一个son类 里面有一个say方法
public void say(){
System.out.println(" Son...")
}
}
问题,从IoC入门案例中配置文件中只配置了Person的一个bean,但是通过IoC容器获取到属性为Person的对象后调用say方法,Person中的say方法会使用Son对象去调用Son的say方法,但是Son的对象并没有初始化所以会报异常,所以代码1优化一下就是
代码3
public class Person {//一个Person类 里面有一个son成员和一个say方法
prviate son ;
public void say(){
System.out.println(" Person...")
son.say();
}
//新增一个set方法对son进行初始化
public void setSon(Son son){
this.son =son;
}
}
最后去applicationContext 配置文件里添加一个Son类型的bean
<bean id = "son" class = "com.itxiaobai.user2.Son"></bean>
此时IoC容器里的两个bean并没有绑定,所以要在配置文件中绑定关系,分析出是Person类型的bean依赖Son类型的bean所以要在person中添加
<bean id = "person " class = "com.itxiaobai.user.Person" >
<property name = "son" ref = "son"/>
</bean>
使用<property>标签来绑定两个bean之间的关系
*name 属性代表的是Person类中的私有son成员的名称
*ref 属性代表的是IoC容器中已经配置的类型为Son类型的bean id值
bean详解
Spring容器中的Bean
对于开发者来说,开发者使用Spring框架主要是做两件事:①开发Bean;②配置Bean。对于Spring框架来说,它要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成"依赖注入"——这就是所谓IoC的本质。
容器中Bean的作用域
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下五种作用域:
- singleton: 单例模式,在整个Spring IoC容器中,singleton作用域的Bean将只生成一个实例。
- prototype: 每次通过容器的getBean()方法获取prototype作用域的Bean时,都将产生一个新的Bean实例。
- request: 对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效。
- session:该作用域将 bean 的定义限制为 HTTP 会话。 只在web-aware Spring ApplicationContext的上下文中有效。
- global session: 每个全局的HTTP Session对应一个Bean实例。在典型的情况下,仅在使用portlet context的时候有效,同样只在Web应用中有效。
如果不指定Bean的作用域,Spring默认使用singleton作用域。prototype作用域的Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成果,就可以重复使用。因此,应该尽量避免将Bean设置成prototype作用域。
创建Bean的3种方式
一.使用构造器创建Bean实例
使用构造器来创建Bean实例是最常见的情况,如果不采用构造注入,Spring底层会调用Bean类的无参数构造器来创建实例,因此要求该Bean类提供无参数的构造器。
采用默认的构造器创建Bean实例,Spring对Bean实例的所有属性执行默认初始化,即所有的基本类型的值初始化为0或false;所有的引用类型的值初始化为null
例子如下
例子<bean id = "person " class = "com.itxiaobai.user.Person" />
二.使用静态工厂方法创建Bean
使用静态工厂方法创建Bean实例时,class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类,Spring通过该属性知道由哪个工厂类来创建Bean实例。
除此之外,还需要使用factory-method属性来指定静态工厂方法,Spring将调用静态工厂方法返回一个Bean实例,一旦获得了指定Bean实例,Spring后面的处理步骤与采用普通方法创建Bean实例完全一样。如果静态工厂方法需要参数,则使用<constructor-arg.../>
元素指定静态工厂方法的参数。
例如有一个工厂类
public class PersonFactory {
public static person getPerson(){
return new Person();
}
}
那么想要通过静态工厂方法创建Bean实例,配置bean时这样写
<bean id="person" class="com.itxaiobai.factory.PersonFactory" factory-method="getPerson"/>
三.调用实例工厂方法创建Bean
实例工厂方法与静态工厂方法只有一个不同:调用静态工厂方法只需使用工厂类即可,而调用实例工厂方法则需要工厂实例。使用实例工厂方法时,配置Bean实例的<bean.../>
元素无须class属性,配置实例工厂方法使用factory-bean
指定工厂实例。
采用实例工厂方法创建Bean的<bean.../>
元素时需要指定如下两个属性:
- factory-bean: 该属性的值为工厂Bean的id。
- factory-method: 该属性指定实例工厂的工厂方法。
若调用实例工厂方法时需要传入参数,则使用<constructor-arg.../>
元素确定参数值。
例子如下
有一个实例工厂
//实例工厂创建对象
public class PersonFactory {
public UserDao getPerson(){
return new Person();
}
}
那么配置person的bean时要先配置实例工厂的bean然后通过工厂bean来创建person的bean
代码如下
<bean id="PFactory" class="com.itxaiobai.factory.PersonFactory"/> <bean id="person" factory-method="getPerson" factory-bean="PFactory"/>
解析,大概意思就是,一个创建Person对象的工厂类PersonFactory ,是实例工厂,所以想要调用其方法时必须是这个工厂类的实例对象.方法,这种形式去调用,所以在配置Person时需要先创建一个PersonFactory类型的bean,得到工厂类的对象,然后在配置Person时,加上工厂对象和工厂方法创建一个Person的bean
发现问题:可以看出这种方式存在很大的问题,一是PFactory这个bean 没有什么意义,就是为了创建Person而配置的,其次就是factory-method 中的方法名是硬编程,所以就有了改良版的调用实例工厂方法创建bean
三.改良版的调用实例工厂方法创建bean
代码如下
//此工厂类继承了接口 FactoryBean后面泛型是想要创造的bean类型
public class PersonFactoryBean implements FactoryBean<Person> {
//代替原始实例工厂中创建对象的方法
public Person getObject() throws Exception {
return new Person();
}
public Class<?> getObjectType() {
return Person.class;
}
public boolean isSingleton() {
return true;//创建对象为单例的 false 是非单例
}
}
bean的配置就可以简化一下,代码如下
<bean id="person" class="com.itxaiobai.factory.PersonFactoryBean"/>
这个是重点
bean的生命周期
在类中定义初始化和销毁方法init和destroy方法,然后在配置bean时,使用属性init-method 和destroy-method 并把类中定义的初始化方法和销毁方法传入
代码如下
public class Person {//一个Person类 里面有init方法和destroy方法
public void init(){
System.out.println("初始化方法被调用");
}
public void destroy(){
System.out.println("销毁方法被调用");
}
}
在配置bean如下
<bean id = "person" class = "com.itxiaobai.user.Person" init-method ="init" destroy-method ="destory"/>
bean的生命周期和Ioc容器有关系,加入Java 虚拟机在获取IoC容器后,主程序内没有close.ctx虚拟机会直接关掉,所以Ioc容器中的bean还没来得及执行销毁方法就没了
但是上述代码还可以再优化一下如下
class Person implements InitializingBean, DisposableBean {
public void say(){
System.out.println("Person say方法....");
}
//重写这两个方法
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet方法...");
}
public void destroy() throws Exception {
System.out.println("destroy方法.....");
}
}
配置Person类型的bean时就正常的给id值和class路径
<bean id="person" class="com.itheima.factory.Person"/>
在获取Ioc容器后直接获取Person类型的bean
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = ctx.getBean("person", Person.class);
person.say();
ctx.close();
可以看出,只要继承了InitializingBean, DisposableBean这个两个接口并且重写afterPropertiesSet和destroy方法,在获取对应bean时就会自动执行这两个方法。