IOC操作(Bean管理)
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做**依赖注入(Dependency Injection,简称DI**)
什么是Bean管理
基于XML
创建对象
注入属性
注入特殊值
外部Bean
内部Bean和级联赋值
xml注入集合属性
工厂Bean
Bean作用域
Bean生命周期
Xml自动装配
什么是Bean管理
Bean管理指的是下面两个操作:
Spring创建对象 与 Spring注入属性(属性可以是对象里的字段,或者另一个对象)
主要有两种实现方式:xml和注解。
基于xml
创建对象
在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建
创建对象时,默认执行无参构造方法,不管类里面是否有有参构造方法(所以这一步小心报错)
使用
bean中常用属性
id:并不是指对象的名字,而是指给对象创建一个标识,通过这个标识可以得到一个对象
class:要创建对象的全路径(包类路径)
name:比较早期的属性,作用跟id没差别,但是允许特殊符号
注入属性
DI:依赖注入(其实就是注入属性),是IOC的一种具体实现
以前的方式
set注入
基础注入方式是使用set方法
有参构造注入
也可以使用带参构造器注入属性
Spring方式
set注入
set注入涉及标签为property,里面有两个属性,name和value
name:表示的是类里面的字段名
value:表示字段的值
注意:①此时类里面涉及到的属性(字段)要有set方法
②property对接的就是set方法
③此时不需要构造函数带参
配置文件:
具体类:
测试类测试方法:
有参构造注入
有参构造涉及标签是constructor-arg,内有两个主要属性name,value
name:对应有参构造内的某个参数
value:要注入的属性值
注意:该标签中还有index属性,其值为0到i-1,分别表示对应第i个参数,其为name的代替(不过貌似没有什么人用,因为不够明显)
配置文件:
具体类:
测试类测试方法:
p名称空间注入(了解)
使用p名称空间注入,主要是简化基于xml配置方式的注入
xml文件上的xml**开头的,其实是一种约束
p:**其实就是代替property的作用,底层还是set
添加p名称空间:
具体配置代码(拿set注入的例子做改进版例子):
注入特殊值
null值
不写value, 在property里面加上null标签就可
特殊符号
特殊符号注入主要有两种方法:
1、采用该符号的转移符号,比如:< 可以写成<
2、把带特殊符号的内容写到CDATA
外部Bean
场景复现
1、创建两个类service、dao
2、在service里面调用dao的方法
3、在Spring配置文件中进行配置
用到的属性:ref
接下来我们通过创建一些类和接口,以及对其进行测试来理解外部Bean的相关用法。
UserService类
package com.atguigu.spring5.service;
import com.atguigu.spring5.dao.UserDao;
public class UserService {
//创建UserDoa类型属性,并生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add(){
System.out.println("service add.....");
userDao.update();
}
}
UserDao接口
package com.atguigu.spring5.dao;
public interface UserDao {
void update();
}
UserDaoImpl类(接口实现类)
package com.atguigu.spring5.dao;
public class UserDaoImpl implements UserDao {
@Override
public void update(){
System.out.println("dao update......");
}
}
TestBean类(测试类)
public class TestBean {
@Test
public void testAdd(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
配置文件
<!-- 创建service和dao对象-->
<bean id="userService" class="com.atguigu.spring5.service.UserService">
<!-- 注入userDao对象-->
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
从配置文件可以看到,想要在一个注入类中使用另外一个类的方法,不仅另外一个类要注入,并且起始注入类的配置要做相应的改变。起始注入类的property的值不再是value,而是通过ref来注入,ref的值为另外一个需要注入的bean的id的值。
内部Bean和级联赋值
一对多关系
典型一对多关系:部门和员工
内部Bean
通常我们使用内部Bean会包含一对多的关系,比如员工需要一个属性表示其所属部门,这个时候我们需要在员工类里面实注入一个部门类,涉及类的注入需要用到bean标签。而在员工类的bean里面property的写法则需要改变。
员工类
public class Emp {
private String ename;
private String gender;
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void add(){
System.out.println(ename);
System.out.println(gender);
System.out.println(dept);
}
}
部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
配置文件
<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>
测试文件
@Test
public void testbean2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Emp emp = context.getBean("emp", Emp.class);
emp.add();
}
级联Bean
第一种写法
实质就是引用外部的bean给里面的属性赋值
配置文件:
<!--级联赋值-->
<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>
</bean>
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
第二种写法
<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">
</bean>
此时emp类里面要有dept的get方法:
public Dept getDept() {
return dept;
}
xml注入集合属性
xml注入集合属性主要有以下类型:
1.注入数组类型属性
2.注入List集合类型属性
3.注入Map集合类型属性
第一种情况
创建基本类
public class Stu {
private String[] courses;
private List<String> list;
private Map<String, String> map;
private Set<String> set;
public void setSet(Set<String> set) {
this.set = 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 test(){
System.out.println(Arrays.toString(courses));
System.out.println(list);
System.out.println(map);
System.out.println(set);
}
}
配置文件
数组-array
list-liat
map-map
set-set
<bean id="stu" class="com.atguigu.spring5.Collectiontype.Stu">
<property name="courses">
<array>
<value>java</value>
<value>sql</value>
</array>
</property>
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<property name="map">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PYTHON" value="python"></entry>
</map>
</property>
<property name="set">
<set>
<value>Mysql</value>
<value>Redis</value>
</set>
</property>
</bean>
测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.test();
}
第二种情况
上面的方式对于类型的参数是对象,就会显得捉襟见肘,所以我们需要一种新的方式
配置文件
这时候需要用到ref标签,里面的bean表示在外面声名的bean
<bean id="stu" class="com.atguigu.spring5.Collectiontype.Stu">
<property name="courseList">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>
<bean id="course1" class="com.atguigu.spring5.Collectiontype.Course">
<property name="cname" value="Spring5框架课程"/>
</bean>
<bean id="course2" class="com.atguigu.spring5.Collectiontype.Course">
<property name="cname" value="Spring5框架课程"/>
</bean>
创建基本类
public class Stu {
private List<Course> courseList;
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
public void test(){
System.out.println(courseList);
}
}
public class Course {
private String cname;
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Course{" +
"cname='" + cname + '\'' +
'}';
}
}
测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.test();
}
第三种情况
对于一些公共用到的属性,我们可以把它们提取出来
之前提到过可以用p命名空间,现在使用一种新的命名空间(不要求掌握)
xmlns:util="http://www.springframework.org/schema/util"
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!-- 公共部分-->
<util:list id="bookList">
<value>one</value>
<value>two</value>
<value>three</value>
</util:list>
<!-- 属性注入-->
<bean id="book" class="com.atguigu.spring5.Collectiontype.Book">
<property name="list" ref="bookList"/>
</bean>
</beans>
基本类:
public class Book {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
public void test(){
System.out.println(list);
}
}
测试
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.test();
}
工厂Bean
在Spring的Bean管理中,有两种Bean,一种是普通的Bean,另外一种是工厂Bean(FactoryBean)
注意:
FactoryBean(工厂Bean)可以生成某一个类型Bean的实例,作用是可以让我们自定义Bean的创建过程
BeanFactory(Bean工厂)是Spirng容器中的一个基本类,在BeanFactory可以创建和管理Spring容器中的Bean
普通Bean的特点与实现
普通Bean:在配置文件中bean的class定义的类型就是该bean返回的类型
比如:在bean2.xml配置文件中我这样写:
<bean id="book" class="com.atguigu.spring5.Collectiontype.Book">
<property name="list" ref="bookList"/>
</bean>
这个普通Bean返回的类型是Book,所以我用的时候要这么写:
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.test();
}
可以看到我们需要用一个Book引用去引用bean实例。
FactoryBean的特点与实现
FactoryBean:在配置文件中定义的Bean的类型可以和返回的类型可以不一致
具体步骤
1.创建类,让其作为工厂Bean,并实现 FactoryBean 接口
2.实现接口里的方法,在实现的方法中定义返回的Bean类型
案例
先定义MyBean类:
/**
* 这里要实现的是泛型接口,所以不要漏了类型定义
* 关于泛型的理解的博客:
* https://blog.youkuaiyun.com/I_r_o_n_M_a_n/article/details/115128604
*/
public class MyBean implements FactoryBean<Course> {
/**
* 定义返回的bean
* @return
*/
@Override
public Course getObject() {
Course course = new Course();
course.setCname("abd");
return course;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
FactoryBean接口传入的类可以是随便一个类,这里我沿用上面定义的Course类,同时重写该接口的三个方法(主要还是getObject())。
接下来在src目录下创建bean3.xml,同时定义一个Bean:
<?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 id="myBean" class="com.atguigu.spring5.FactoryBean.MyBean">
</bean>
</beans>
上面的class就是我们上面定义的MyBean。
接下来做测试:
@Test
public void test3(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
可以看到,我上面返回的Bean类型不再是MyBean,而是Course,同时getBean的第二个参数也变成了Course.class
这里返回的Bean主要是要和我们重写的getObject()方法的返回类型对应上
测试:
可以看到,测试成功。
Bean作用域
Bean的作用域可以理解成Bean是单实例还是多实例
默认情况下,Spring中的Bean是单实例
单实例
接下来我们通过一个例子来理解什么叫单实例,比如说下面的测试代码:
<bean id="book" class="com.atguigu.spring5.Collectiontype.Book">
<property name="list" ref="bookList"/>
</bean>
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book1 = context.getBean("book", Book.class);
Book book2 = context.getBean("book", Book.class);
System.out.println(book1);
System.out.println(book2);
}
我们对同一个xml配置文件内的同一个Bean,用两个引用来获取Bean实例,那么输出的结果是怎么样的呢?如果使用多实例,那么他们两个的地址应该是不相同的,但是实际结果确是这样:
可以看到他们的输出是一样的,这证明默认创建的,就是单实例。
多实例
在Spring的Bean标签里面有一个属性
scope
可以设置该标签是单实例还是多实例
scope
有两个值:singleton
(默认值),表示该Bean是单实例对象;prototype
,表示该Bean是多实例对象。
这时我们为bean添加scope
属性,赋值prototype
:
<bean id="myBean" class="com.atguigu.spring5.FactoryBean.MyBean" scope="prototype">
</bean>
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book1 = context.getBean("book", Book.class);
Book book2 = context.getBean("book", Book.class);
System.out.println(book1);
System.out.println(book2);
}
同样对该段代码进行测试,结果发现他们是不一样的对象:
singleton
和prototype
除了表示单实例与多实例的区别外,还有下面这个特点:如果
scope
的值为singleton
时,加载Spring配置文件时就会创建该对象(单实例),如果值为prototype
时,在调用getBean才会创建该对象(多实例、懒加载)。
Bean生命周期
从对象创建到对象销毁的过程
生命周期简述
-
通过构造器创建bean实例(类的无参数构造)
-
为bean的属性设置值和对其他bena的引入(调用set方法)
-
调用bean的初始化的方法(想看到这步需要自行配置)
-
可以使用bean了(获取到对象了)
-
当容器关闭的时候,调用bean的销毁的方法(想看到这步需要自行配置)
重现过程
为了更高地看明白bean的生命周期,我们可以简单地整一个案例。
首先是实体类Orders:
public class Orders {
private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("这里执行了第二步,设置属性的值");
}
public Orders(){
System.out.println("这里执行了第一步,调用无参构造创建Bean实例");
}
/**
* Bean初始化方法
*/
public void initMethod(){
System.out.println("这里执行了第三步,调用bean的初始化方法");
}
/**
* Bean注销的方法
*/
public void destroyMethod(){
System.out.println("这里执行了第五步,调用bean的销毁方法");
}
}
上面的实体类中只有一个字段oname,还有Orders的无参构造方法、oname的set方法,以及用来手动显示Bean初始化和注销过程的init和destroy方法。
下面是配置文件:
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"/>
</bean>
上面的bean标签内多了两个属性,init-method
属性表示初始化的时候调用的方法,destroy-method
属性表示注销bean的时候调用的方法,他们的值分别对应Orders类里面的方法。
接下来是测试方法:
@Test
public void test4(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("这里执行了第四步,获取到Bean实例,并且可以使用");
System.out.println(orders);
//注销
context.close();
}
上面除了调用getBean方法之外,还调用了close方法,该方法表示注销对应配置文件内的Bean实例,同时还应当注意的是:由于调用了close方法,context
的引用类型不再是ApplicationContext
,而是ClassPathXmlApplicationContext
如果还是想使用ApplicationContext引用的化,那么就请将注销的代码改成这样:
((ClassPathXmlApplicationContext)context).close
,也就是说加一个强转(但是现在已经不推荐这种写法了)。
接下来是测试:
可以看到测试结果符合我们的预期,第一步调用了无参构造方法,第二部设置属性的值,第三步调用我们手动设置的初始化方法,第四步获取bean实例,第五步调用被我们手动设置了的注销方法。
Bean的后置处理器
上面的生命周期其实是没有加上Bean后置处理器的,如果加上后置处理器,那么Bean的生命周期将分为七步,具体改变在第三步的前后。
-
通过构造器创建bean实例(类的无参数构造)
-
为bean的属性设置值和对其他bena的引入(调用set方法)
-
把Bean实例传递给Bean的后置处理器的方法(postProcessBeforeInitialization)
-
调用bean的初始化的方法(想看到这步需要自行配置)
-
把Bean实例传递给Bean的后置处理器的方法(postProcessAfterInitialization)
-
可以使用bean了(获取到对象了)
-
当容器关闭的时候,调用bean的销毁的方法(想看到这步需要自行配置)
演示添加后置处理器之后的效果
演示后置处理器,我们需要实现一个BeanPostProcessor
接口,该接口内有postProcessBeforeInitialization
方法和postProcessAfterInitialization
方法,实现类MyBeanPost
需要重写这两个方法。
实现类MyBeanPost
:
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
配置文件中需要配置后置处理器,它其实也是一个Bean:
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"/>
当配置文件中有了上诉的bena之后,Spring就会认为该配置文件内的所有Bean都会加上后置处理器。
测试方法与之前的无差别。
下面是测试的结果:
可以看到已经调用了后置处理器的两个方法,这就是完整的Bean生命周期。
xml自动装配
我们可以根据指定装配规则(根据属性名或者属性类型进行装配),让Spring自动将匹配的属性值进行注入。
以往的手动装配
以往我们注入外部bean时,需要在bean标签内时使用property标签手动装配
<bean id="emp" class="com.atguigu.spring5.autowire.Emp">
<property name="dept" ref="dept"/>
</bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"/>
自动装配
bean标签有一个属性autowire
,该属性表示所属标签注入属性值时使用自动装配,其主要使用的有两个值:byName、byType。
-
byName:根据属性名称注入,注入的bean的id值应和需要装配属性的set方法的名称一样。
-
byType:根据属性类型注入。
比如:
Emp类中有:
private String dept;
public void setDept(String dept) {
this.dept = dept;
}
那么我们就可以使用自动装配来注入Dept类:
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"/>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"/>
实际xml自动装配不常用,有时候反而让人感觉更加麻烦