Spring之IOC(容器,控制反转)

1、IOC(容器)

什么是容器?容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。

Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

 

1.1、IOC的基本介绍

Spring提供的容器又称为IoC容器,IoC全称Inversion of Control,直译为控制反转。IOC 是面向对象编程中的一种设计原则,可以用来减低代码之间的耦合度。在 spring 中,控制反转把对象创建和对象之间的调用过程,交给 spring 进行管理,减低了代码的耦合度。

 

谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。

 

用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:

IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

参考:https://www.cnblogs.com/NancyStartOnce/p/6813162.html

 

1.2、IOC的基本使用

下载完 sprig 解压文件后可以看到很多jar 包,将 spring 所必需的四个包和 commons-logging 包导入项目当中:

spring 框架依赖的jar包有 commons-logging,如果不添加的话会报错。

 

先编写一个简单的 User 类,然后在 src 目录下新建一个spring的配置文件即 xml 文件,该配置文件可自定义,比如 bean01.xml。

User 类:

package test;

public class User {
    public void add() {
        System.out.println("add。。。");
    }
}

spring 配置文件 bean01.xml 如下,class 里面写的是完整类名。

<?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="user" class="test.User"></bean>
</beans>

然后就可以随便建一个测试类来进行测试:

package test.testDemo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import test.User;

public class Test01 {

    public static void main(String[] args) {
        //加载spring配置文件,并创建了配置文件中配置的类的实例对象)
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取bean,即配置创建的对象
        User user = (User) context.getBean("user");   //getBean()方法里面的参数是 xml 配置文件中的bean节点的id
//调用 user.add(); } }

执行上面代码可以看到输出 User 类中的 add() 方法。

默认情况下,通过 bean 获取到的实例对象是单例的,即多次通过 getBean() 方法获取同一个 bean 时(在不同配置文件中,即使 bean 标签的 class 指向的是同一个类,也不是同一个bean,此时创建的不是同一个实例对象),获取到的实例对象实际上都是同一个对象。

 

1.3、IOC底层原理

原理:通过解析 xml 配置文件获取类名,然后通过反射来创建该类的一个实例对象并返回。

如果后面类发生了改变,比如类名改了,只需修改配置文件即可,所以大大降低了耦合度。(因为如果是传统程序,类名发生修改,在所有引用到该类的地方都需要进行修改)

 

2、bean介绍

在 Spring 的 IoC 容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。也就是一个 bean 标签指的就是一个 bean。

2.1、spring对于bean的操作

bean 管理指的是两个操作:

  1. spring 创建对象
  2. spring 注入属性

IoC又称为依赖注入(DI:Dependency Injection),依赖注入就是注入属性,注入属性是在创建对象的基础之上完成的,先创建对象,实际上是然后再注入属性。

 

bean 实现上述两个操作有两种方式:

  1. 基于xml配置文件方式实现
  2. 基于注解方式实现

Spring 的 IoC 容器同时支持属性注入和构造方法注入,并允许混合使用。

在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行。这种无侵入的设计有以下好处:

  1. 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;
  2. 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。

 

2.2、FactoryBean(工厂bean)

在普通的 bean 当中,class属性指的是什么类,则取到的就是该类的实例对象。但通过 FactoryBean 我们可以自定义 bean 的创建过程,包括自定义返回的类、是否单例、bean的类型。

定义一个工厂 bean,MyBean.java如下,可以看到,实际返回的是 User  bean:

package test;

import org.springframework.beans.factory.FactoryBean;

public class Mybean implements FactoryBean {

    @Override
    public User getObject() throws Exception {  //实际返回的对象实例
        User user = new User();
        return user;
    }

    @Override
    public Class<?> getObjectType() {  //Bean的类型
        return null;
    }

    @Override
    public boolean isSingleton() {  //是否为单例,true为单例,false为非单例
        return false;
    }
}

配置文件:

<?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="test.Mybean"></bean>
</beans>

测试代码:

定义FactoryBean后,Spring创建的Bean实际上是这个FactoryBeangetObject()方法返回的Bean。

package test.testDemo;

import dao.UserDao;
import dao.impl.UserDaoImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
import service.impl.UserServiceImpl;
import test.Mybean;
import test.User;
import test.User02;

public class Test01 {

    public static void main(String[] args) {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean04.xml");

        //获取配置创建的对象,实际将返回User类实例。这里的 getBean() 方法要同时写上 id 和类.class,否则可能报错
        User user = context.getBean("mybean", User.class);
        user.setName("wen");
        System.out.println(user);
    }
}

 

2.3、bean的作用域(作用范围)

bean 的作用域可以理解为 bean 的作用范围。Spring3 中为 Bean 定义了5种作用域,分别为 singleton(单例,默认值)、prototype、request、session 和 global session。

 

2.3.1、singleton作用域(单例,默认值)

singleton:单例模式,Singleton 作用域是Spring 中的默认作用域。在单例模式下,多次通过 getBean() 方法获取同一个 bean 时,获取到的实例对象实际上都是同一个对象。在不同配置文件中,即使 bean 标签的 class 指向的是同一个类,也不是同一个bean,此时创建的不是同一个实例对象。在同一个配置文件中,指向的是不同的class,也不会是同一个实例对象。

Singleton 是单例类型,在加载配置文件时即创建 bean 对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。

该模式在多线程下是不安全的。Singleton 作用域是Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式,配置为:

<bean id="..." class="..." scope="singleton"></bean>

 

2.3.1、prototype作用域

prototype:原型模式,多实例。在每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都会创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态。跟单例 singleton 不同, singleton 全局只有一个对象。

<bean id="SingletonBean" class="com.spring.demo.SingletonBean" scope="prototype"></bean>

 

2.4、bean的生命周期

bean 的生命周期,即 bean 从创建到销毁的过程。

bean 的生命周期如下:

  1. 实例化。通过构造器创建 bean 实例
  2. 属性赋值。设置 bean 的属性(依赖注入)
  3. 调用后置处理器的 postProcessBeforeInitialization() 方法(需在 xml 中配置后置处理器,跟配置一个bean一样,class指向后置处理器即可。后置处理器实际上就是一个实现了BeanPostProcessor接口的类)
  4. 初始化。调用 bean 标签 init-method 指定的类的方法,init-method 指定的方法由类定义,可进行一些初始化操作(需手动配置)。初始化完成后 bean 就可以使用了
  5. 调用后置处理器的 postProcessAfterInitialization() 方法
  6. 销毁。当容器关闭时,调用 bean 标签 destroy-method 指定的类的方法,destroy-method 指定的方法由类定义。(可调用ApplicationContextObj.close()方法来手动销毁bean)

 

3、IOC创建对象

3.1、基于XML配置文件创建对象

在spring配置文件中,使用 bean 标签就可以实现对象的创建。如下:

<bean id="user" class="test.User"></bean>

基于 xml 配置文件创建对象时,默认情况下是执行该类的无参数构造函数来创建对象的,所以如果该类没有无参构造函数程序将会报错。

 

bean 标签常见属性:

  • id:给该类指定一个唯一标识。每个<bean ...>都有一个id标识,相当于Bean的唯一ID。
  • class:类的完整类名

 

3.2、注解方式创建对象(@Component、@Service、@Controller、@Repository

使用Spring的IoC容器,实际上就是通过类似XML这样的配置文件,把我们自己的Bean的依赖关系描述出来,然后让容器来创建并装配Bean。一旦容器初始化完毕,我们就直接从容器中获取Bean使用它们。使用XML配置的优点是所有的Bean都能一目了然地列出来,并通过配置注入能直观地看到每个Bean的依赖。它的缺点是写起来非常繁琐,每增加一个组件,就必须把新的Bean配置到XML中。我们可以使用注解的方式进行配置,让Spring自动扫描Bean并组装它们。

spring 针对 bean 管理中创建对象提供了四种注解:

  1. @Component
  2. @Service 业务层
  3. @Controller web层
  4. @Repository 持久层

创建对象有四种注解,但目前来说,这四个注解的功能都是一样的。只是建议 @Service 用在业务层,@Controller 用在 web 层,@Repository 用在持久层,但并不是强制的,可以混用。有四个注解只是为了后续的版本当中进行功能扩展 。

 

使用注解创建对象:

先导入依赖包,除了 spring 的几个基本包外,还需要导入 aop 包:

然后需要开启组件扫描。开启组件扫描需要先引入命名空间,然后就可以使用 <context:component-scan base-package="要扫描的包路径"></context:component-scan> 标签开启组件扫描:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启组件扫描。base-package写包名,若要扫描多个包,可以用逗号隔开,或者直接写多个包共用的上级目录-->
    <context:component-scan base-package="test, service.impl"></context:component-scan>
</beans>

开启组件扫描后就可以给类添加注解了,下面的@Component可以换成 @Service等其它的几个注解,效果都一样。

package test;

import org.springframework.stereotype.Component;


//注解里面value属性名及值都可以省略不写,即写成@Compnent,此时bean的id默认是类名首字母小写后的名称
@Component(value = "user")   //相当于<bean id="user" class="完整类名"></bean>
public class User {
    public void add() {
        System.out.println("add...");
    }
}

验证,使用bean:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import test.User;

public class Test {
    public static void main(String[] args) {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取配置创建的对象
        User user = (User) context.getBean("user");
        System.out.println(user);
        user.add();
    }
}

 

3.2.1、组件扫描配置

开启组件扫描后,默认情况是配置的包下的所有类、所有注解都会扫描。我们可以配置只扫描哪些注解,或者不扫描哪些注解。

配置只扫描 @Controller 注解,注意,要加上user-default-filters标签:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="test, service" use-default-filters="false">   <!--use-default-filters表示不使用默认filter-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

</beans>

配置了只扫描 @Controller 注解后,其他注解将不会被扫描到,如果使用其他注解的 bean 程序将会报错:No bean named 'xxx' available...

 

配置不扫描@Controller 注解,注意,不能加上user-default-filters标签:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="test, service">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

</beans>

 

3.2.2、注解配置bean作用域(@Scope())

对于Spring容器来说,当我们把一个Bean标记为添加注解比如@Component后,它就会自动为我们创建一个单例(Singleton),即容器初始化时创建Bean,容器关闭前销毁Bean。在容器运行期间,我们调用getBean(Class)获取到的Bean总是同一个实例。

我们可以通过 @Scope() 注解来配置 bean 的作用域:

比如声明为 prototype 作用域:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
    ...
}

 

4、IOC依赖注入(注入属性)

4.1、基于XML配置文件的依赖注入

我们可以在 xml 文件中直接配置在创建该类的对象时,同时给该类配置属性。

基于 xml 配置文件来注入类属性有两种方式:

  1. 通过类的 set() 方法注入属性。类似 setName() 等等
  2. 通过类的有参构造函数注入属性

 

4.1.1、通过set()方法注入属性(property标签)

我们在类中定义了 setter,就可以在 spring 的配置文件中通过配置直接给该类实例指定属性值。代码示例如下:

User类:

public class User {
    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

配置文件:

<?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="user" class="springtest.User">
        <property name="name" value="wen"></property>
        <property name="age" value="12"></property>
    </bean>
</beans>

使用上述配置后,在创建 User 类时会自动给该类注入属性。

测试代码:

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import springtest.User;

public class test01 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取配置创建的对象
        User user = (User) context.getBean("user");

        System.out.println(user.getName() + user.geAge());  //输出 wen12
    }
}

 

4.1.2、通过有参构造函数注入属性(constructor-arg标签)

如果一个类中有有参构造函数,我们就可以通过有参构造来给该类的实例注入属性。当类中有有参构造函数,我们也只能使用有参构造来注入属性,否则将会报错。

代码示例如下:

User 类:

package springtest;

public class User {
    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    //有参构造。定义了有参构造则编译器不会自动创建无参构造,所以配置文件中如果不使用有参构造来注入属性的话程序会报错
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

配置文件:

<?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="user" class="springtest.User">
        <!-- 也可以使用索引的形式 <constructor-arg index="0" value="wen"></constructor-arg> -->
        <constructor-arg name="name" value="wen"></constructor-arg>   
        <constructor-arg name="age" value="11"></constructor-arg>
    </bean>
</beans>

使用上述配置后,在创建 User 类时会自动调用该类的有参构造来给该类注入属性。

测试代码:

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import springtest.User;

public class test01 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取配置创建的对象
        User user = (User) context.getBean("user");
        System.out.println(user.getName() + user.geAge());  //输出 wen11
    }
}

 

4.1.3、注入外部bean(注入类)

如果注入的属性值是booleanintString这样的数据类型,可以通过value注入。如果注入的属性值是类,则可以通过 ref 注入。

示例:

UserServiceImpl 类中有一个 setter 需要设置 userDao 属性为 UserDao 类:

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        this.userDao.add();
    }
}

在传统程序中,需要调用 setUserDao() 方法来主动将一个 UserDao 类注入。写法如下:

public class Test01 {

    public static void main(String[] args) {
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        UserDao userDao = new UserDaoImpl();
        userServiceImpl.setUserDao(userDao);   //调用 setUserDao 注入UserDao类
        userServiceImpl.add();
    }
}

 

使用spring配置文件可以直接将 UserDao bean作为属性注入 UserService当中,实际上相当于调用了 setUserDao() 方法。配置文件如下:

<?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 name="userdao" class="dao.impl.UserDaoImpl"></bean>

    <bean name="userservice" class="service.impl.UserServiceImpl">
        <property name="userDao" ref="userdao"></property>
    </bean>
</beans>

Bean的顺序不重要,Spring根据依赖关系会自动正确初始化。 

测试代码:

package test.testDemo;

import dao.UserDao;
import dao.impl.UserDaoImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
import service.impl.UserServiceImpl;
import test.User;

public class Test01 {

    public static void main(String[] args) {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean02.xml");

        UserServiceImpl userServiceImpl = context.getBean(UserServiceImpl.class);
        userServiceImpl.add();
    }
}

 

4.1.4、内部bean注入类

上面注入外部bean,实际上就是在外部建一个bean,然后将该bean赋值给 UserService bean的属性。除了上面的写法,我们还可以采用内部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 name="userservice" class="service.impl.UserServiceImpl">
        <property name="userDao">
            <bean id="userdao" class="dao.impl.UserDaoImpl"></bean>   <!--  如果该内部bean还需要注入属性,可以再给该内部bean配置property -->
        </property>
    </bean>
</beans>

 

4.1.5、注入集合

 给类注入集合,示例如下:

package test;

import java.util.*;

public class User02 {
    private String[] myArr;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String, String> myMap;public void setMyArr(String[] myArr) {
        this.myArr = myArr;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }
}

配置文件:

<?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="user02" class="test.User02">
        <!-- 给数组注入数据 -->
        <property name="myArr">
            <array>
                <value>AAA</value>
                <value>BBB</value>
            </array>
        </property>
<!-- 注入 list 集合数据 --> <property name="myList"> <list> <value>AAA</value> <value>BBB</value> </list> </property>
<!-- 注入 set 集合数据 --> <property name="mySet"> <set> <value>AAA</value> <value>BBB</value> </set> </property>
<!-- 注入 Map 数据 --> <property name="myMap"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value> </entry> </map> </property> </bean> </beans>

 

上面的集合当中的元素的值都是字符串,如果集合当中的元素是类的话,我们可以使用 ref 标签来注入:

<?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="user" class="test.User02">
        <!-- myList2集合的元素是User类实例对象 -->
        <property name="myList2">
            <list>
                <ref bean="user01"></ref>
                <ref bean="user02"></ref>
            </list>
        </property>
    </bean>

    <bean id="user01" class="test.User"></bean>
    <bean id="user02" class="test.User"></bean>
</beans>

 

上面将集合注入属性只是注入了某个类当中,我们可以把集合提取出来,多个类就可以重用这些集合。

首先我们需要先导入 util 命名空间,util命名空间提供了集合相关的配置,在使用命名空间前要导入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 http://www.springframework.org/schema/util/spring-util-4.1.xsd">

</beans>

下面提取出一个 List 并在 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"
       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 http://www.springframework.org/schema/util/spring-util-4.1.xsd">

    <util:list id="userList">
        <value>张三</value>
        <value>李四</value>
        <value>王五</value>
    </util:list>

    <bean id="user02" class="test.User02">
        <!-- 注入 list 集合数据 -->
        <property name="myList" ref="userList"></property>
    </bean>

</beans>

 

4.1.6、注入其它类型值(空值、特殊符号)

注入空值:

<bean id="user" class="test.User">
    <property name="name">
        <null></null>
    </property>
    <property name="age" value="12"></property>
</bean>

注入特殊符号:

XML中共有5个特殊的字符,分别是:&<> “’。如果配置文件中的注入值包括这些特殊字符,就需要进行特别处理。

有两种解决方法:

  1. 采用本例中的<![CDATA[ ]]>特殊标签,将包含特殊字符的字符串封装起来。<![CDATA[ ]]>的作用是让XML解析器将标签中的字符串当作普通的文本对待,以防止某些字符串对XML格式造成破坏。
  2. 使用XML转义序列表示这些特殊的字符,这5个特殊字符所对应XML转义序列如下:

 示例:给 name 属性赋值为 <<wen>>

<bean id="user" class="test.User">
    <property name="name" value="&lt;&lt;wen&gt;&gt;"></property>
    <property name="age" value="12"></property>
</bean>

 

<bean id="user" class="test.User">
    <property name="name">
        <value><![CDATA[<<wen>>]]></value>   <!-- 给name赋值为<<wen>> -->
    </property>
    <property name="age" value="12"></property>
</bean>

 

4.1.7、自动装配(autowire)

传统的XML方式配置 Bean 组件都是通过 <property> 标签为Bean的属性注入所需的值,当需要维护的Bean组件及需要注入的属性更多时,势必会增加配置的工作量,这时我们可以使用自动装配。

使用自动装配只需给 bean 标签添加 autowire 属性即可。配置示例:

<bean id="user" class="test.User" autowire="byName"/>

通过设置<bean>元素的autowire属性指定自动装配,代替了通过<property>标签显示指定Bean的依赖关系。由BeanFactory检查XML配置文件的内容,为Bean自动注入依赖关系。

Spring提供了多种自动装配方式,autowire属性常用的取值如下所示

  • no:不使用自动装配。Bean依赖关系必须通过property元素定义。
  • byName:根据属性名自动装配。BeanFactory查找容器中的全部Bean,找出 id 与属性的 setter 方法入参匹配的Bean。找到即自动注入,否则什么都不做。
  • byType:根据属性类型自动装配。BeanFactory查找容器中的全部Bean,如果正好有一个与依赖属性类型相同的Bean,就自动装配这个属性;但是如果有多个这样的Bean,Spring无法决定注入哪个Bean,就抛出一个致命异常;如果没有匹配的Bean,就什么都不会发生,属性不会被设置。
  • constructor:与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的Bean,那么将会抛出异常

 

自动装配的局限性:

  • 不是所有类型都可以使用自动装配,不能自动装配的数据类型有:Object、基本数据类型(Date、CharSequence、Number、URI、URL、Class、String)等等。因为自动装配是注入了一个bean。
  • 自动装配不如显式装配精确,如果可能的话尽量使用显式装配。

 

实例:

假设 User 类中有一个属性类型为 Animal 类:

public class User {
    private Animal animal;

    public Animal getAnimal() {
        return animal;
    }

    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

此时我们可以使用自动装配:

<?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="user" class="test.User" autowire="byName"></bean>

    <bean id="animal" class="test.Animal"></bean>
</beans>

上面使用 byName 类型的自动装配,id 为animal 的 bean 将匹配到 User 类中的 setter,所以会将 animal 类注入 User 中的 animal 属性当中。(上面配置直接改成byType也可以)

验证代码:

package UnitDemo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import test.User;
import test.User02;

public class Test {
    public static void main(String[] args) {
        ApplicationContext context2 = new ClassPathXmlApplicationContext("bean01.xml");
        User user = (User) context2.getBean("user");
        user.getAnimal().setName("cat");
        System.out.println(user.getAnimal().getName());
    }
}

 

4.1.8、引入properties配置文件

如果配置过多,在一个 xml 文件里面维护可能相对比较困难,这时我们可以在一个 propreties 配置文件中配置一些属性,然后再在 xml 配置文件中引入 propreties 文件的配置。比如可用于连接数据库的配置。

示例:

在项目的 src 目录下建一个 properties 类型文件 jdbc.properties,properties 类型文件的内容是键值对形式。往该文件写入以下内容:

prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDB
prop.username=root
prop.password=123456

然后就可以在 xml 配置文件中引入该文件,在引入之前我们需要先写 context 命名空间:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

最后就可以通过 <context> 标签将 properties 文件引入,并且可以用 ${} 来使用 properties 文件的值:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"></property>  <!--通过${}使用外部配置文件的值-->
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>
</beans>

 

4.2、注解方式依赖注入(@Autowired、@Qualifier、@Resource、@Value)

spring 注解注入属性提供了几种注解:

  1. @Autowired:根据属性类型进行自动装配,即 byType
  2. @Qualifier:根据属性名称进行自动装配,即 byName。需要配合@Autowired进行使用,用于在@Autowired匹配到多个类时,指定究竟使用哪个类来进行注入。
  3. @Resource:既可以根据类型注入,也可以根据名称注入。不指定 name 和 type 则自动按照 byName 方式进行装配,如果没有匹配成功,则回退为一个原始类型进行匹配,如果匹配成功则自动装配。
  4. @Value:注入基本数据类型数据

使用注解,比如@Autowired,就相当于把指定类型的Bean注入到指定的字段中。和XML配置相比,注解的方式大幅简化了注入,因为它不但可以写在set()方法上,还可以直接写在字段上,甚至可以写在构造方法中。

@Component
public class UserService {
    MailService mailService;

    public UserService(@Autowired MailService mailService) {
        this.mailService = mailService;
    }
    ...
}

 

4.2.1、@Autowired(根据类型装配)

@Autowired 注解会根据属性类型进行自动装配,即 byType。默认情况下它要求依赖对象必须存在,如果不存在,程序将会报错。但我们也可以设置它的required属性为false,来让它的依赖对象如果允许为 null 值,@Autowired(required = false)

 

实例:比如在 UserServiceImpl 类中的某个属性注入 UserDaoImpl 类型值。

UserDaoImpl 代码:只需给实现类添加注解即可,接口不能添加注解

package dao.impl;

import dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {

    @Override
    public void add() {
        System.out.println("userdaoimpl add...");
    }
}

UserServiceImpl 代码:

package service.impl;

import dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import service.UserService;

@Service
public class UserServiceImpl implements UserService {

    //不需要添加set方法
    @Autowired
    private UserDao userDao;

    @Override
    public void add() {
        System.out.println("userserviceimpl add...");
        userDao.add();
    }
}

验证代码:

package UnitDemo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
import test.User;

public class Test {
    public static void main(String[] args) {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取配置创建的对象
        UserService userService = (UserService) context.getBean("userServiceImpl");
        userService.add();   //将输出 userserviceimpl add...    userdaoimpl add...
    }
}

 

4.2.2、@Qualifier(通过名称标识唯一类注入@Autowired中)

使用 @Autowired 时,如果某个类有多个实现类,则 spring 无法识别究竟将哪个实现类来进行注入,此时程序将会直接报错。此时我们可以将 @Autowired 和 @Qualifier 搭配使用,@Qualifier 可通过名称来标识究竟使用哪个类来进行注入。

@Qualifier 用法示例:

假设 UserDao 有多个实现类:UserDaoImpl、UserDaoImpl02,UserDaoImpl02代码如下:

package dao.impl;

import dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl02 implements UserDao {
    @Override
    public void add() {
        System.out.println("userdaoimpl02 add...");
    }
}

此时如果我们直接使用 @Autowired 程序将会直接报错,因为 spring 无法识别究竟使用哪个类来进行注入。所以我们可以使用 @Qualifier("bean标识") 来指定究竟使用哪个类来进行注入:

package service.impl;

import dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import service.UserService;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    @Qualifier("userDaoImpl02")
    private UserDao userDao;

    @Override
    public void add() {
        System.out.println("userserviceimpl add...");
        userDao.add();
    }
}

验证代码:

package UnitDemo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
import test.User;

public class Test {
    public static void main(String[] args) {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取配置创建的对象
        UserService userService = (UserService) context.getBean("userServiceImpl");
        userService.add();   //将输出 userserviceimpl add...    userdaoimpl02 add...
    }
}

 

 4.2.3、@Resource(byType、byName)

匹配规则:

  1. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
  2. 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
  3. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

UserDaoImpl02 实现类:

package dao.impl;

import dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl02 implements UserDao {
    @Override
    public void add() {
        System.out.println("userdaoimpl02 add...");
    }
}

UserServiceImpl 实现类:

package service.impl;

import dao.UserDao;
import org.springframework.stereotype.Service;
import service.UserService;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {
    @Resource(name = "userDaoImpl02")
    private UserDao userDao;

    @Override
    public void add() {
        System.out.println("userserviceimpl add...");
        userDao.add();
    }
}

验证代码:

package UnitDemo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
import test.User;

public class Test {
    public static void main(String[] args) {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取配置创建的对象
        UserService userService = (UserService) context.getBean("userServiceImpl");
        userService.add();   //将输出 userserviceimpl add...   userdaoimpl02 add...
    }
}

 

4.2.4、@Value(注入基本数据类型)

@Value 注解即注入一个基本类型数据。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "user")
public class User {

    @Value(value = "wen")
    private String name;

    public void showName() {
        System.out.println(this.name);
    }
}

验证代码:

package UnitDemo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
import test.User;

public class Test {
    public static void main(String[] args) {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");

        //获取配置创建的对象
        User user = (User) context.getBean("user");
        user.showName();   //输出 wen
    }
}

 

4.2.5、注入list

有些时候,我们会有一系列接口相同,不同实现类的Bean。例如,注册用户时,我们要对email、password和name这3个变量进行验证。为了便于扩展,我们先定义验证接口:

public interface Validator {
    void validate(String email, String password, String name);
}

然后,分别使用3个Validator对用户参数进行验证:

@Component
public class EmailValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {
            throw new IllegalArgumentException("invalid email: " + email);
        }
    }
}

@Component
public class PasswordValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!password.matches("^.{6,20}$")) {
            throw new IllegalArgumentException("invalid password");
        }
    }
}

@Component
public class NameValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (name == null || name.isBlank() || name.length() > 20) {
            throw new IllegalArgumentException("invalid name: " + name);
        }
    }
}

最后,我们通过一个Validators作为入口进行验证:

@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {
        for (var validator : this.validators) {
            validator.validate(email, password, name);
        }
    }
}

注意到Validators被注入了一个List<Validator>,Spring会自动把所有类型为Validator的Bean装配为一个List注入进来,这样一来,我们每新增一个Validator类型,就自动被Spring装配到Validators中了,非常方便。

因为Spring是通过扫描classpath获取到所有的Bean,而List是有序的,要指定List中Bean的顺序,可以加上@Order注解:

@Component
@Order(1)
public class EmailValidator implements Validator {
    ...
}

@Component
@Order(2)
public class PasswordValidator implements Validator {
    ...
}

@Component
@Order(3)
public class NameValidator implements Validator {
    ...
}

 

4.2.5、初始化和销毁

有些时候,一个Bean在注入必要的依赖后,需要进行初始化(监听消息等)。在容器关闭时,有时候还需要清理资源(关闭连接池等)。我们通常会定义一个init()方法进行初始化,定义一个shutdown()方法进行清理,然后,引入JSR-250定义的Annotation:

在Bean的初始化和清理方法上标记@PostConstruct@PreDestroy

@Component
public class MailService {
    @Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();

    @PostConstruct
    public void init() {
        System.out.println("Init mail service with zoneId = " + this.zoneId);
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("Shutdown mail service");
    }
}

Spring容器会对上述Bean做如下初始化流程:

  • 调用构造方法创建MailService实例;
  • 根据@Autowired进行注入;
  • 调用标记有@PostConstructinit()方法进行初始化。

而销毁时,容器会首先调用标记有@PreDestroyshutdown()方法。Spring只会根据注解查找无参数方法,对方法名不作要求。

 

4.2.6、完全注解开发(@Configuration)

我们可以通过 spring 配置类来实现完全注解开发,即不需要 xml 配置文件。

用 @Configuration 注解来新建配置类,比如命名为 SpringConfig,以此替代 xml 配置文件:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"test", "dao", "service"})  //指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的<context:component-scan base-package=“org.woster”/>是一样的。
public class SpringConfig {
}

使用@ComponentScan来告诉容器需要扫描的包,如果不指定参数,即只有@ComponentScan,则会自动搜索当前配置类所在的包以及子包。

使用配置类获取 ApplicationContext 对象的语法跟使用配置文件的不太一样,除此之外,其他情况都一样:

package UnitDemo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import test.SpringConfig;
import test.User;

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = (User) context.getBean("user");
        user.showName();
    }
}

 

posted @ 2021-03-31 16:20  wenxuehai  阅读(194)  评论(0编辑  收藏  举报
//右下角添加目录