IOC容器和Bean的配置
1. 什么是IOC和DI
IOC(反转控制)
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
DI(依赖注入)
IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
总结: IOC 就是一种反转控制的思想, 而DI是对IOC的一种具体实现。
2. IOC容器在Spring中的实现
前提:
Spring中有IOC思想, IOC思想必须基于 IOC容器来完成, 而IOC容器在最底层实质上就是一个对象工厂
- 在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
- Spring提供了IOC容器的两种实现方式
- BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
- ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
3. ApplicationContext的主要实现类
- ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
- FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
- 在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的
4. ConfigurableApplicationContext
-
是ApplicationContext的子接口,包含一些扩展方法
-
refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。
5. WebApplicationContext
专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
(一)通过类型获取bean
从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的。
代码
//初始化容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取对象常用的三种方法
//1. 通过getBean()获取对象,传入参数id,具有唯一id的对象
Person person = (Person)ac.getBean("personOne");
//2. 使用此方法获取对象时,要求spring所管理的此类型的对象只能有一个
Person person = ac.getBean(Person.class);
//3. 综合以上两种方法,目标更明确
Person person = ac.getBean("personTwo", Person.class);
配置文件:applicationContext.xml
<?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>:定义spring所管理的一个对象
id:该对象的唯一标示,注意:不能重复,在通过类型获取bean的过程中可以不设置
class:此对象所属类的全限定名
-->
<bean id="personOne" class="com.atguigu.spring.mod.Person">
<!--
<property>:为对象的某个属性赋值
name:属性名
value:属性值
-->
<property name="id" value="1111"></property>
<property name="name" value="小明"></property>
</bean>
<bean id="personTwo" class="com.atguigu.spring.mod.Person">
<property name="id" value="2222"></property>
<property name="name" value="小红"></property>
</bean>
</beans>
(二)给bean的属性赋值
1. 依赖注入的方式
1)通过bean的setXxx()方法赋值
<bean id="s1" class="com.atguigu.spring.di.Student">
<!-- value子节点 -->
<property name="id">
<value>10010</value>
</property>
<!-- 这里的property标签里的name="name" 相当于调用实体类的setName()方法 -->
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>
2)通过bean的构造器赋值
- Spring自动匹配合适的构造器
<bean id="s2" class="com.atguigu.spring.di.Student">
<constructor-arg value="10086"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="24"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>
- 通过索引值指定参数位置,并通过类型区分重载的构造器
<bean id="s3" class="com.atguigu.spring.di.Student">
<constructor-arg value="10022"></constructor-arg>
<constructor-arg value="王五"></constructor-arg>
<!--
index="2"表示构造器的第三个参数,
type="java.lang.Double"表示这个参数是Double类型的
-->
<constructor-arg value="90" index="2" type="java.lang.Double"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>
2. p名称空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring 从2.5版本开始引入了一个新的p命名空间,可以通过
使用p命名空间后,基于XML的配置方式将进一步简化。
<bean id="s4" class="com.atguigu.spring.di.Student"
p:id="10033" p:name="赵六" p:age="26" p:sex="男"
p:teacher-ref="teacher">
</bean>
3. 可以使用的值
1)字面量
-
可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
-
基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
-
若字面值中包含特殊字符,可以使用把字面值包裹起来
2)null值
对于引用数据类型,如果不需要赋值
<property name="id">
<null/>
</property>
3)给bean的级联属性赋值
<property name="teacher.tname" value="小红"></property>
4)外部已声明的bean、引用其他的bean
<bean id="s5" class="com.atguigu.spring.di.Student">
<property name="id" value="10055"></property>
<property name="name" value="张三三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
<property name="teacher" ref="teacher"></property>
<property name="teacher.tname" value="小红"></property>
</bean>
<bean id="teacher" class="com.atguigu.spring.di.Teacher">
<property name="tid" value="10000"></property>
<property name="tname" value="小明"></property>
</bean>
5)内部bean
<bean id="s6" class="com.atguigu.spring.di.Student">
<property name="id" value="10066"></property>
<property name="name" value="崔八"></property>
<property name="age" value="18"></property>
<property name="sex" value="男"></property>
<property name="teacher">
<bean id="tt" class="com.atguigu.spring.di.Teacher">
<property name="tid" value="2222"></property>
<property name="tname" value="admin"></property>
</bean>
</property>
</bean>
注意:
定义在某个bean内部的bean,只能在当前bean中使用
5. 集合属性
1)数组和List
配置java.util.List类型的属性,需要指定标签,在标签里包含一些元素。这些标签 可以通过
注意
数组的定义和List一样,都使用元素。
配置java.util.Set需要使用
<!-- 参数值为基本类型时 -->
<bean id="t1" class="com.atguigu.spring.di.Teacher">
<property name="tid" value="10001"></property>
<property name="tname" value="佟刚"></property>
<property name="cls">
<list>
<value>A</value>
<value>B</value>
<value>C</value>
</list>
</property>
</bean>
<!-- 参数值为引用类型时 -->
<bean id="t2" class="com.atguigu.spring.di.Teacher">
<property name="tid" value="10002"></property>
<property name="tname" value="婷姐"></property>
<property name="students">
<list>
<ref bean="s1"/>
<ref bean="s2"/>
<ref bean="s3"/>
</list>
</property>
</bean>
2)Map
Java.util.Map通过
必须在
因为键和值的类型没有限制,所以可以自由地为它们指定
可以将Map的键和值作为
<bean id="t3" class="com.atguigu.spring.di.Teacher">
<property name="tid" value="10003"></property>
<property name="tname" value="admin"></property>
<property name="bossMap">
<map>
<entry>
<key>
<value>10001</value>
</key>
<value>佟老师</value>
</entry>
<entry>
<key>
<value>10002</value>
</key>
<ref bean="teacher"/>
</entry>
</map>
</property>
</bean>
3)集合类型的bean
如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。
配置集合类型的bean需要引入util名称空间
<!-- 在<beans>标签内写入以下语句 -->
xmlns:util="http://www.springframework.org/schema/util"
<bean id="t4" class="com.atguigu.spring.di.Teacher">
<property name="tid" value="10004"></property>
<property name="tname" value="root"></property>
<property name="students" ref="students"></property>
</bean>
<util:list id="students">
<ref bean="s4"/>
<ref bean="s5"/>
<ref bean="s6"/>
</util:list>
<util:map id="map">
<entry>
<key>
<value>1</value>
</key>
<value>张三</value>
</entry>
</util:map>
6. FactoryBean
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。
工厂bean必须实现org.springframework.beans.factory.FactoryBean
接口。
<bean id="factory" class="com.atguigu.spring.factorybean.MyFactory"></bean>
import org.springframework.beans.factory.FactoryBean;
public class MyFactory implements FactoryBean<Car> {
@Override
public Car getObject() throws Exception {
Car car = new Car();
car.setBrand("奥迪");
car.setPrice(200000.0);
return car;
}
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Car.class;
}
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
}
}
/**********************************************/
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("factory-bean.xml");
//返回值是MyFactory中getObject()方法的返回值
Object object = ac.getBean("factory");
System.out.println(object);
}
}
7. bean的作用域
在Spring中,可以在
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
<bean id="student" class="com.atguigu.ioc.scope.Student" scope="singleton">
<property name="sid" value="1001"></property>
<property name="sname" value="张三"></property>
</bean>
其他的作用域:
- singleton:在SpringIOC容器中仅存在一个Bean实例,Bean以单例的方式存在
- prototype:每次调用getBean()时都会返回一个新的实例
- request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
- session:同一个HTTP Session 共享一个Bean ,不同的HTTP Session 使用不同的Bean。该作用仅适用于WebApplicationContext环境
注意:当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象。
8. bean的生命周期
-
SpringIOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
-
SpringIOC容器对bean的生命周期进行管理的过程:
- 通过构造器或工厂方法创建bean实例
- 为bean的属性设置值和对其它bean的引用
- 调用bean的初始化方法
- bean可以使用了
- 当容器关闭时,调用bean的销毁方法
-
在配置bean时,通过init-method 和 destroy-method 属性为bean指定初始化和销毁方法
<bean id="person" class="com.atguigu.ioc.life.Person" init-method="init" destroy-method="destory"> <property name="id" value="1001"></property> <property name="sex" value="男"></property> </bean> <!-- 配置后置处理器 --> <bean class="com.atguigu.ioc.life.AfterHandler"></bean>
-
bean的后置处理
-
bean 后置处理器允许在调用初始化方法前后对bean 进行额外的处理。
-
bean 后置处理器对IOC容器里的所有bean 实例逐一处理,为非单一实例。
其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。
-
bean 后置处理器需要实现接口:
org.springframework.beans.factory.config.BeanPostProcessor
。在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:- postProcessBeforeInitialization(Object, String)
- postProcessAfterInitialization(Object, String)
public class AfterHandler implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { Person person = (Person) bean; if(person.getSex().equals("男")) { person.setName("张无忌"); }else { person.setName("赵敏"); } return person; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 注意此处不做处理时,要返回原 bean return bean; } }
-
-
添加 bean 后置处理器后 bean 的生命周期
- 通过构造器或工厂创建 bean 实例
- 为 bean 的属性设置值和对其他 bean 的引用
- 将 bean 实例传递给bean 后置处理器的postProcessBeforeInitialization()方法
- 调用 bean 的初始化方法
- 将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
- bean 可以使用了
- 当容器关闭时,调用 bean 的销毁方法
(三)引用外部属性文件
以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置。
1. 直接配置
<!-- 直接配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="jdbcUrl" value="jdbc:mysql:///test"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>
2. 使用外部的属性文件
-
创建properties属性文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root jdbc.password=123456
-
引入 context 名称空间
-
指定 properties 属性文件位置
-
从 properties 属性文件中引入属性值
<!-- 加载资源文件(方式1) -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="db.properties"></property>
</bean>
<!-- 加载资源文件(方式2) -->
<context:property-placeholder location="db.properties"/>
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 从 properties 属性文件中引入属性值 -->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
(四)自动装配
1. 概念
- 手动装配:以value或ref的方式明确指定属性值都是手动装配
- 自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中
2. 装配模式
- 根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配。
- 根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同。
- 通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。
<!--
autowire:根据某种策略自动为非字面量属性赋值
autowire="byName|byType"
byName:通过属性名和spring容器中bean的id进行比较,若一致则可直接赋值
byType:通过spring容器中bean的类型,为兼容性的属性赋值
在使用byType的过程中,要求spring容器中只能有一个能为属性赋值的bean
选用建议: 当设置autowire属性,会作用于该bean中所有的非字面量属性,因此谁都不用
-->
<bean id="emp" class="com.atguigu.ioc.auto.Emp" autowire="byType">
<property name="eid" value="1001"></property>
<property name="ename" value="张三"></property>
</bean>
3. 选用建议
以上两种均不建议使用
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。
(五)通过注解配置 bean(重点)
1. 使用注解标识组件
普通组件
@Component :标识一个受 SpringIOC 容器管理的组件
持久化层组件
@Repository :标识一个受Spring IOC容器管理的持久化层组件
业务逻辑层组件
@Service :标识一个受Spring IOC容器管理的业务逻辑层组件
表述层控制器组件
@Conntroller :标识一个受Spring IOC容器管理的表述层控制器组件
注意:事实上Spring 并没有能力识别一个组件到底是不是它标记的类型,即使将@Respository 注解用在一个表述层控制器组件上面也不会产生任何错误,所以@Respository、@Service、@Controller 这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色。说白了就是,以上四种注释的作用完全一样,只是为了让开发人员便于区分
2. 扫描组件
组件只有被上述注解标识后还需要通过Spring 进行扫描才能够侦测到
- 指定被扫描的package
- base-package 属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其包中的所有类。
- 当需要扫描多个包时可以使用逗号分隔
- 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern 属性过滤特定类
- 包含和排除
<!--
<context:component-scan>:扫描组件,对设置的包下面的类进行扫苗,会将加上注解的类作为spring的组件进行加载
组件:指spring中管理的bean
作为spring的组件进行加载:会自动在spring的配置文件中生成相对应的bean,这些bean的id会以类的首字母小写为值
<context:include-filter>:在设定的包结构下,再次通过注解或类型具体包含到某个或某几个类
注意:在使用包含时,一定要设置use-default-filters="false",将默认的过滤(即扫描包下所有的类)关闭
<context:exclude-filter>:在设定的包结构下,再次通过注解或类型排除某个或某几个类
注意:在使用排除时,一定要设置use-default-filters="true",将默认的过滤(即扫描包下所有的类)打开
切记:一个<context:component-scan>中可以出现多个include,也可以同时出现多个exclude,但是两个不能同时出现
-->
<context:component-scan base-package="com.atguigu.ioc.userMod" use-default-filters="true">
<!--
type="annotation" :表示注解类型
type="assignable" :表示类的类型
-->
<!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> -->
<!-- <context:include-filter type="assignable" expression="com.atguigu.ioc.userMod.service.UserServiceImpl"/> -->
<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> -->
<!-- <context:exclude-filter type="assignable" expression="com.atguigu.ioc.userMod.dao.UserDaoImpl"/> -->
</context:component-scan>
完成组件化管理的过程:
- 在需要被spring管理的类上加上相应注解
- 在配置文件中通过
<context:component-scan>
对所设置的包结构进行扫描,就会将加上注解的类,作为spring的组件进行加载
组件:
指spring中管理的bean
作为spring的组件进行加载:会自动在spring的配置文件中生成相对应的bean,这些bean的id会以类的首字母小写为值;也可以通过@Controller("beanId")为自动生成的bean指定id
//@Controller(value="aaa")
@Controller("aaa")
public class UserController {
@Autowired
private UserService userService;
public void addUser() {
userService.addUser();
}
public UserController() {
System.out.println("UserController");
}
}
3. 基于注解的自动装配(重重点)
在需要赋值的非字面量属性上,加上@Autowired,就可以在spring容器中,通过不同的方式匹配到相对应的bean。
@Autowired装配时,会默认使用byType的方式,此时要求spring容器中只有一个bean能够为其赋值。
当byType实现不了装配时,会自动切换到byName,此时要求spring容器中,有一个bean的id和属性名一致。
若自动装配时,匹配到多个能够复制的bean,可使用@Qualifier(value="beanId")指定使用的bean
@Autowired和@Qualifier(value="beanId")可以一起作用域一个带形参的方法上,此时,@Qualifier(value="beanId"),所指定的bean作用于形参。
@Autowired
@Qualifier(value="userDaoMybatisImpl")
public void setUserDao(UserDao userDao) {//自动装配的是形式参数 userDao
this.userDao = userDao;
}