2. IOC 容器
1. IOC 概念和原理
1.1 概念
- 控制反转,把对象创建和对象的调用过程交给 spring 进行管理。
- 目的:降低耦合度。
- 底层原理:xml,反射,工厂模式
- Spring 提供 IOC 容器两种实现方式(两个接口)
- BeanFactory:Spring 内部使用的接口,不提倡开发人员使用。特点:加载配置文件时不会创建对象,获取对象时才会创建对象。
- ApplicationContext:BeanFactory 的子接口,提供了更多更强大的功能,一般由开发人员使用。特点:加载配置文件时会把配置文件里的对象进行创建。
- ApplicationContext 两个常用实现类:
- FileSystemXmlApplicationContext:绝对路径,从盘符开始算起
- ClassPathXmlApplicationContext:相对路径,从 src 开始算起
什么是 Bean 管理?Bean 管理是指两个操作:Spring 创建对象 和 Spring 注入属性
Bean 管理有两种操作方式:基于 xml 配置文件方式实现 和 基于注解方式实现
2.IOC 操作 Bean 管理(基于 xml)
2.1 xml 实现 Bean 管理
2.1.1 基于 xml 方式创建对象
- 在 Spring 配置文件中使用 bean 标签来创建对象
- bean 标签有很多属性,常用属性:
- id:唯一标识
- class:类路径
- 创建对象时,默认执行无参构造函数
2.1.2 基于 xml 方式注入属性
第一种方法:使用 set 方法进行注入:
首先先为类的属性提供 set 方法:
java
public class User {
private String userName;
private String userAge;
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserAge(String userAge) {
this.userAge = userAge;
}
public String getUserName() {
return userName;
}
public String getUserAge() {
return userAge;
}
}
然后在 xml 配置文件中通过 property 标签进行属性注入
xml
<!--配置User对象-->
<bean id="user" class="com.oymn.spring5.User">
<property name="userName" value="haha"></property>
<property name="userAge" value="18"></property>
</bean>
这样就完成了
java
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println(user.getUserName() + " " + user.getUserAge());
第二种方法:使用有参构造函数进行注入
首先提供有参构造方法
java
public class User {
private String userName;
private String userAge;
public User(String userName, String userAge){
this.userName = userName;
this.userAge = userAge;
}
}
然后再 xml 配置文件中通过 constructor-arg 标签进行属性注入
xml
<!--配置User对象-->
<bean id="user" class="com.oymn.spring5.User">
<constructor-arg name="userName" value="haha"></constructor-arg>
<constructor-arg name="userAge" value="18"></constructor-arg>
</bean>
第三种方法:p 名称空间注入(了解即可)
首先在 xml 配置文件中添加 p 名称空间,并且在 bean 标签中进行操作
然后提供 set 方法
java
public class User {
private String userName;
private String userAge;
public User() {
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserAge(String userAge) {
this.userAge = userAge;
}
}
2.1.3 xml 注入其他属性
- null 值
xml
<!--配置User对象-->
<bean id="user" class="com.oymn.spring5.User">
<property name="userName"> <null/> </property>
</bean>
- 属性值包含特殊符号 假设现在
userName
属性需要赋值为< haha >
如果像上面那样直接在value
中声明的话会报错,因为包含特殊符号<>
需要通过
<![CDATA[值]]>
来表示 - 注入属性——外部 bean 有两个类:UserService 和 UserDaoImpl,其中 UserDaoImpl 实现 UserDao 接口 通过 ref 来指定创建 userDaoImpl
java
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
public void add(){
System.out.println("add");
}
}
xml
<bean id="userDaoImpl" class="com.oymn.spring5.UserDaoImpl"></bean>
<bean id="userService" class="com.oymn.spring5.UserService">
<property name="userDao" ref="userDaoImpl"></property>
</bean>
- 注入属性——内部 bean 不通过 ref 属性,而是通过嵌套一个 bean 标签实现
xml
<!--内部 bean-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
- 注入属性——级联赋值 写法一:也就是上面所说的外部
bean
,通过 ref 属性来获取外部 bean 写法二: emp 类中有ename
和dept
两个属性,其中dept
有dname
属性,写法二需要emp
提供dept
属性的get
方法。
xml
<!--级联赋值-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property> <property name="gender" value="女"></property>
<!--写法一-->
<property name="dept" ref="dept"></property>
<!--写法二-->
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
- 注入集合属性(数组,List,Map)
假设有一个 Stu
类
java
public class Stu {
private String[] courses;
private List<String> list;
private Map<String,String> map;
private Set<String> set;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setSet(Set<String> set) {
this.set = set;
}
}
在 xml 配置文件中对这些集合属性进行注入
xml
<bean id="stu" class="com.oymn.spring5.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--List类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--Map类型属性注入-->
<property name="map">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--Set类型属性注入-->
<property name="set">
<set>
<value>Mysql</value>
<value>Redis</value>
</set>
</property>
</bean>
- 上面的集合值都是字符串,如果是对象的话,如下: 写法: 集合+外部
bean
xml
<!--创建多个 course 对象-->
<bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="Spring5 框架"></property>
</bean>
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="MyBatis 框架"></property>
</bean>
<!--注入 list 集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
- 把集合注入部分提取出来 使用
util
标签,这样不同的bean
都可以使用相同的集合注入部分了。
xml
<!--将集合注入部分提取出来-->
<util:list id="booklist">
<value>易筋经</value>
<value>九阳神功</value>
</util:list>
<bean id="book" class="com.oymn.spring5.Book">
<property name="list" ref="booklist"></property>
</bean>
FactoryBean
Spring
有两种 Bean,一种是普通 Bean,另一种是工厂 Bean(FactoryBean) 这块看不太懂,不知道有啥用,先放着。
2.2 Bean 的作用域
- 在 Spring 中,默认情况下 bean 是单实例对象
执行结果是相同的:
- 通过 bean 标签的 scope 属性 来设置单实例还是多实例。
Scope 属性值:
- singleton:默认值,表示单实例对象。加载配置文件时就会创建单实例对象。
- prototype:表示多实例对象。不是在加载配置文件时创建对象,在调用 getBean 方法时创建多实例对象。
执行结果不同了:
2.3 Bean 的生命周期
- bean 的生命周期:
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
- 演示 bean 的生命周期
java
public class Orders {
private String orderName;
public Orders() {
System.out.println("第一步:执行无参构造方法创建bean实例");
}
public void setOrderName(String orderName) {
this.orderName = orderName;
System.out.println("第二步:调用set方法设置属性值");
}
//初始化方法
public void initMethod(){
System.out.println("第四步:执行初始化方法");
}
//销毁方法
public void destroyMethod(){
System.out.println("第七步:执行销毁方法");
}
}
java
//实现后置处理器,需要实现BeanPostProcessor接口
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步:将bean实例传递给bean后置处理器的postProcessBeforeInitialization方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:将bean实例传递给bean后置处理器的postProcessAfterInitialization方法");
return bean;
}
}
xml
<bean id="orders" class="com.oymn.spring5.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="orderName" value="hahah"></property>
</bean>
<!--配置bean后置处理器,这样配置后整个xml里面的bean用的都是这个后置处理器-->
<bean id="myBeanPost" class="com.oymn.spring5.MyBeanPost"></bean>
java
@Test
public void testOrders(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第六步:获取bean实例对象");
System.out.println(orders);
//手动让bean实例销毁
context.close();
}
执行结果:
2.4 自动装配
- 根据指定的装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
- 根据属性名称自动装配:要求 emp 中属性的名称 dept 和 bean 标签的 id 值 dept 一样,才能识别
xml
<!--指定autowire属性值为byName-->
<bean id="emp" class="com.oymn.spring5.Emp" autowire="byName"></bean>
<bean id="dept" class="com.oymn.spring5.Dept"></bean>
- 根据属性类型自动装配:要求同一个 xml 文件中不能有两个相同类型的 bean,否则无法识别是哪一个
xml
<!--指定autowire属性值为byType-->
<bean id="emp" class="com.oymn.spring5.Emp" autowire="byType"></bean>
<bean id="dept" class="com.oymn.spring5.Dept"></bean>
2.5 通过外部属性文件来操作 bean
例如配置数据库信息:
- 导入德鲁伊连接池 jar 包
- 创建外部属性文件,
properties
格式文件,写数据库信息 - 引入 context 名称空间,并通过 context 标签引入外部属性文件,使用“${}”来获取文件中对应的值
3.IOC 操作 Bean 管理(基于注解)
- 格式:@注解名称(属性名=属性值,属性名=属性值,……)
- 注解可以作用在类,属性,方法。
- 使用注解的目的:简化 xml 配置
3.1 基于注解创建对象
spring 提供了四种创建对象的注解:
- @Component
- @Service:一般用于 Service 层
- @Controller:一般用于 web 层
- @Repository:一般用于 Dao 层
流程:
- 引入依赖:
- 开启组件扫描:扫描 base-package 包下所有有注解的类并为其创建对象
xml
<context:component-scan base-package="com.oymn"></context:component-scan>
com.oymn.spring5.Service
有一个 stuService 类
java
//这里通过@Component注解来创建对象,括号中value的值等同于之前xml创建对象使用的id,为了后面使用时通过id来获取对象
//括号中的内容也可以省略,默认是类名并且首字母小写
//可以用其他三个注解
@Component(value="stuService")
public class StuService {
public void add(){
System.out.println("addService");
}
}
- 这样就可以通过 getBean 方法来获取
stuService
对象了
java
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
StuService stuService = context.getBean("stuService", StuService.class);
System.out.println(stuService);
stuService.add();
开启组件扫描的细节配置:
use-default-fileters
设置为false
表示不使用默认过滤器,通过include-filter
来设置只扫描 com.oymn 包下的所有@Controller
修饰的类。
xml
<context:component-scan base-package="com.oymn" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
exclude-filter
设置哪些注解不被扫描,例子中为@Controller
修饰的类不被扫描
xml
<context:component-scan base-package="com.oymn">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
3.2 基于注解进行属性注入
- @Autowired 根据属性类型自动装配 创建 StuDao 接口和 StuDaoImpl 实现类,为 StuDaoImpl 添加创建对象注解
StuService 类中添加 StuDao 属性,为其添加 @Autowire 注解,spring 会自动为 stuDao 属性创建 StuDaoImpl 对象 测试结果:
java
public interface StuDao {
public void add();
}
java
@Repository
public class StuDaoImpl implements StuDao {
@Override
public void add() {
System.out.println("StuDaoImpl");
}
}
java
@Component(value="stuService")
public class StuService {
@Autowired
public StuDao stuDao;
public void add(){
System.out.println("addService");
stuDao.add();
}
}
java
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
StuService stuService = context.getBean("stuService", StuService.class);
System.out.println(stuService);
stuService.add();
}
- @Qualifier:根据属性名称自动装配 当遇到一个接口有很多实现类时,只通过 @Autowire 是无法完成自动装配的,所以需要再使用 @Qualifier 通过名称来锁定某个类
java
@Component(value="stuService")
public class StuService {
@Autowired
@Qualifier(value="stuDaoImpl") //这样就能显式指定stuDaoImpl这个实现类
public StuDao stuDao;
public void add(){
System.out.println("addService");
stuDao.add();
}
}
- @Resource:可以根据类型注入,也可以根据名称注入
java
@Component(value="stuService")
public class StuService {
//@Resource //根据类型进行注入
@Resource(name="stuDaoImpl") //根据名称进行注入
public StuDao stuDao;
public void add(){
System.out.println("addService");
stuDao.add();
}
}
- @Value:注入普通类型属性
java
@Value(value = "abc")
private String name;
3.3 完全注解开发
创建配置类,替代 xml 配置文件
java
@Configuration //表明为一个配置类
@ComponentScan(basePackages = "com.oymn") //开启组件扫描
public class SpringConfig {
}
测试类:
java
@Test
public void test2(){
//创建AnnotationConfigApplicationContext对象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
StuService stuService = context.getBean("stuService", StuService.class);
System.out.println(stuService);
stuService.add();
}