Skip to content

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 注入其他属性

  1. null 值
xml
    <!--配置User对象-->
    <bean id="user" class="com.oymn.spring5.User">
        <property name="userName"> <null/> </property>
    </bean>
  1. 属性值包含特殊符号 假设现在 userName 属性需要赋值为 < haha > 如果像上面那样直接在 value 中声明的话会报错,因为包含特殊符号 <> 需要通过 <![CDATA[值]]> 来表示
  2. 注入属性——外部 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>
  1. 注入属性——内部 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>
  1. 注入属性——级联赋值 写法一:也就是上面所说的外部 bean,通过 ref 属性来获取外部 bean 写法二: emp 类中有 enamedept 两个属性,其中 deptdname 属性,写法二需要 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>
  1. 注入集合属性(数组,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>
  1. 上面的集合值都是字符串,如果是对象的话,如下: 写法: 集合+外部 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>
  1. 把集合注入部分提取出来 使用 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>
  1. FactoryBeanSpring 有两种 Bean,一种是普通 Bean,另一种是工厂 Bean(FactoryBean) 这块看不太懂,不知道有啥用,先放着。

2.2 Bean 的作用域

  • 在 Spring 中,默认情况下 bean 是单实例对象

执行结果是相同的:

  • 通过 bean 标签的 scope 属性 来设置单实例还是多实例。

Scope 属性值:

  • singleton:默认值,表示单实例对象。加载配置文件时就会创建单实例对象。
  • prototype:表示多实例对象。不是在加载配置文件时创建对象,在调用 getBean 方法时创建多实例对象。

执行结果不同了:

2.3 Bean 的生命周期

  1. bean 的生命周期:

(1)通过构造器创建 bean 实例(无参数构造)

(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization

(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)

(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization

(6)bean 可以使用了(对象获取到了)

(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

  1. 演示 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

例如配置数据库信息:

  1. 导入德鲁伊连接池 jar 包
  2. 创建外部属性文件,properties 格式文件,写数据库信息
  3. 引入 context 名称空间,并通过 context 标签引入外部属性文件,使用“${}”来获取文件中对应的值

3.IOC 操作 Bean 管理(基于注解)

  • 格式:@注解名称(属性名=属性值,属性名=属性值,……)
  • 注解可以作用在类,属性,方法。
  • 使用注解的目的:简化 xml 配置

3.1 基于注解创建对象

spring 提供了四种创建对象的注解:

  • @Component
  • @Service:一般用于 Service 层
  • @Controller:一般用于 web 层
  • @Repository:一般用于 Dao 层

流程:

  1. 引入依赖:
  2. 开启组件扫描:扫描 base-package 包下所有有注解的类并为其创建对象
xml
<context:component-scan base-package="com.oymn"></context:component-scan>
  1. com.oymn.spring5.Service 有一个 stuService 类
java
//这里通过@Component注解来创建对象,括号中value的值等同于之前xml创建对象使用的id,为了后面使用时通过id来获取对象
//括号中的内容也可以省略,默认是类名并且首字母小写
//可以用其他三个注解
@Component(value="stuService")
public class StuService {
    public void add(){
        System.out.println("addService");
    }
}
  1. 这样就可以通过 getBean 方法来获取 stuService 对象了
java
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
StuService stuService = context.getBean("stuService", StuService.class);
System.out.println(stuService);
stuService.add();

开启组件扫描的细节配置:

  1. 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>
  1. 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();
}

Released under the MIT License.