Spring IOC 容器

  一、IOC 容器

        IoC Inversion of Control 的简写,译为 “控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。

        Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。

        IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。

1.1 控制反转

        1、控制反转是一种思想。

        2、控制反转是为了降低程序耦合度,提高程序扩展力。

        3、控制反转,反转的是什么?是将对象的创建权利交出去,交给第三方容器负责。是将对象和对象之间关系的维护权交出去,交给第三方容器负责。

        4、控制反转这种思想如何实现呢?通过 DI (依赖注入)实现的。

1.2 依赖注入

        DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想。依赖注入是指 Spring 创建对象的过程中,将对象依赖属性通过配置进行注入。

        依赖注入常见的实现方式包括两种:set 注入和构造注入。

1.3 IoC 容器在 Spring 的实现

        Spring IoC 容器就是 IoC 思想的一个落地的产品实现。IoC 容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建 IoC 容器。Spring 提供了IoC 容器的两种实现方式:

        1、BeanFactory:这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

        2、ApplicationContextBeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。其主要实现类如下:

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

二、基于 xml 管理 Bean

2.1 获取 bean

        首先创建一个 HelloWorld 类和 bean.xml,内容如下:

public class HelloWorld {
    public HelloWorld() {
        System.out.println("无参数构造方法执行");
    }

    public void sayHello(){
        System.out.println("helloworld");
    }
}
<?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="helloWorld" class="com.spring6.bean.HelloWorld"></bean>

</beans>

2.1.1 根据 id 获取

        由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。

public class HelloWorldTest {
    @Test
    public void testHelloWorld1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        HelloWorld bean =(HelloWorld) ac.getBean("helloWorld");
        bean.sayHello();
    }
}

2.1.2 根据类型获取

public class HelloWorldTest {
    @Test
    public void testHelloWorld1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        HelloWorld bean = ac.getBean(HelloWorld.class);
        bean.sayHello();
    }
}

2.1.3 根据 id 和类型获取

public class HelloWorldTest {
    @Test
    public void testHelloWorld1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        HelloWorld bean = ac.getBean("helloWorld", HelloWorld.class);
        bean.sayHello();
    }
}

2.1.4 注意

        当根据类型获取 bean 时,要求 IOC 容器中指定类型的 bean 有且只能有一个,当 IOC 容器中一共配置了两个,如下:

<bean id="helloWorld" class="com.spring6.bean.HelloWorld"></bean>
<bean id="helloWorld2" class="com.spring6.bean.HelloWorld"></bean>

        使用根据类型获取 bean 时就会抛出异常,如下:

2.1.5 扩展知识

        1、如果一个类实现了接口,根据接口类型可以获取 bean 吗?可以的,前提 bean 是唯一,没有其他的实现类。

        2、如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?不行,因为 bean 不唯一。

        3、根据类型来获取 bean 时,在满足 bean 唯一性的前提下,其实只是看:对象 instanceof 指定的类型的返回结果,只要返回的是 true 就可以认定为和类型匹配,能够获取到。

        4、java 中,instanceof 运算符用于判断前面的对象是否是后面的类,或其子类、实现类的实例。如果是返回 true,否则返回 false。也就是说:用 instanceof 关键字做判断时, instanceof  操作符的左右操作必须有继承或实现关系。

2.2 setter 注入

        创建学生类 Student,内容如下:

public class Student {
    private Integer id;

    private String name;

    public Student() {

    }
    // setter、toString()
}

        配置 bean 时为属性赋值

<bean id="student" class="com.spring6.bean.Student">
	<property name="id" value="1001"></property>
	<property name="name" value="张三"></property>
</bean>

        测试代码如下:

public class StudentTest {
    @Test
    public void testHelloWorld1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        Student student = ac.getBean(Student.class);
        System.out.println(student.toString());
    }
}

2.3 构造注入

        创建学生类 Student,内容如下:

public class Student {
    private Integer id;

    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
    // toString()
}

       配置 bean 时为属性赋值

<bean id="student" class="com.spring6.bean.Student">
	<constructor-arg name="id" value="001"></constructor-arg>
	<constructor-arg name="name" value="张三"></constructor-arg>
</bean>

        测试代码如下:

public class StudentTest {
    @Test
    public void testHelloWorld1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        Student student = ac.getBean(Student.class);
        System.out.println(student.toString());
    }
}

2.4 特殊值处理

2.4.1 字面量赋值

        什么是字面量?int a = 10;声明一个变量 a,初始化为 10,此时 a 就不代表字母 a 了,而是作为一个变量的名字。当我们引用 a 的时候,我们实际上拿到的值是 10

        而如果 a 是带引号的 'a',那么它现在不是一个变量,它就是代表 a 这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。

<!-- 使用 value 属性给 bean 的属性赋值时,Spring 会把 value 属性的值看做字面量 -->
<property name="name" value="张三"/>

2.4.2 null 值

        如果有一个属性想给它初始赋空值,可以这样写

<property name="name">
    <null />
</property>

2.4.3 xml 实体

        小于号在 XML 文档中用来定义标签的开始,不能随便使用,如果想要使用就需要转义

<!-- 解决方案一:使用 XML 实体来代替 -->
<property name="expression" value="a &lt; b"/>

2.4.4 CDATA 节

        我不想用转义的写法,就需要使用 CDATA

<property name="expression">
    <!-- 解决方案二:使用CDATA节 -->
    <!-- CDATA 中的 C 代表Character,是文本、字符的含义,CDATA 就表示纯文本数据 -->
    <!-- XML 解析器看到 CDATA 节就知道这里是纯文本,就不会当作 XML 标签或属性来解析 -->
    <!-- 所以 CDATA 节中写什么符号都随意 -->
    <value><![CDATA[a < b]]></value>
</property>

2.5 对象类型属性赋值

2.5.1 引用外部 bean

public class Student {
    private Clazz clazz;
    private String name;
    private Integer id;

    public Clazz getClazz() {
        return clazz;
    }
    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }
    // setter、getter、toString
}
public class Clazz {
    private Integer clazzId;

    private String clazzName;
    public Clazz() {
    }

    public Clazz(Integer clazzId, String clazzName) {
        this.clazzId = clazzId;
        this.clazzName = clazzName;
    }

    // setter、getter、toString()
<bean id="clazzOne" class="com.spring6.bean.Clazz">
    <property name="clazzId" value="1111"></property>
    <property name="clazzName" value="财源滚滚班"></property>
</bean>

<bean id="student" class="com.spring6.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <!-- ref属性:引用 IOC 容器中某个 bean 的 id,将所对应的 bean 为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
</bean>

2.5.2 内部 bean

<bean id="student" class="com.spring6.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="clazz">
        <!-- 在一个 bean 中再声明一个 bean 就是内部 bean -->
        <!-- 内部 bean 只能用于给属性赋值,不能在外部通过 IOC 容器获取,因此可以省略 id 属性 -->
        <bean id="clazzInner" class="com.spring6.bean.Clazz">
            <property name="clazzId" value="2222"></property>
            <property name="clazzName" value="远大前程班"></property>
        </bean>
    </property>
</bean>

2.5.3 级联属性赋值

<bean id="clazzOne" class="com.spring6.bean.Clazz">
    <property name="clazzId" value="1111"></property>
    <property name="clazzName" value="财源滚滚班"></property>
</bean>

<bean id="studentFour" class="com.spring6.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="clazz" ref="clazzOne"></property>
	<!-- 此处设置的属性值会覆盖上面的设置的属性值-->
    <property name="clazz.clazzId" value="3333"></property>
    <property name="clazz.clazzName" value="最强王者班"></property>
</bean>

2.6 数组类型属性赋值

public class Student {
    private Clazz clazz;
    private String name;
    private Integer id;
    private String [] hobbies;

    public Clazz getClazz() {
        return clazz;
    }
    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }
    // setter、getter、toString
}
<bean id="student" class="com.spring.bean6.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="clazz" ref="clazzOne"></property>
    <property name="hobbies">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
</bean>

2.7 集合类型属性赋值

2.7.1 List 类型

public class Clazz {
    private Integer clazzId;
    private String clazzName;
    private List<Student> students;

    public Clazz() {
    }

    public Clazz(Integer clazzId, String clazzName) {
        this.clazzId = clazzId;
        this.clazzName = clazzName;
    }

    // setter、getter、toString()

        若为 Set 集合类型属性赋值,只需要将其中的 list 标签改为 set 标签即可。

<bean id="studentOne" class="com.spring6.bean.Student">
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="clazz" ref="clazzOne"></property>
</bean>
<bean id="studentTwo" class="com.spring6.bean.Student">
    <property name="id" value="1002"></property>
    <property name="name" value="李四"></property>
    <property name="clazz" ref="clazzOne"></property>
</bean>
<bean id="studentThree" class="com.spring6.bean.Student">
    <property name="id" value="1003"></property>
    <property name="name" value="赵六"></property>
    <property name="clazz" ref="clazzOne"></property>
</bean>

<bean id="clazzOne" class="com.spring6.bean.Clazz">
    <property name="clazzId" value="4444"></property>
    <property name="clazzName" value="Javaee0222"></property>
    <property name="students">
        <list>
            <ref bean="studentOne"></ref>
            <ref bean="studentTwo"></ref>
            <ref bean="studentThree"></ref>
        </list>
    </property>
</bean>

2.7.2 Map 集合类型

public class Teacher {
    private Integer teacherId;

    private String teacherName;

    // setter、getter、toString
}
public class Student {
    private String name;
    private Integer id;
    private Map<String, Teacher> teacherMap;

    // setter、getter
}
<bean id="teacherOne" class="com.spring6.bean.Teacher">
	<property name="teacherId" value="10010"></property>
	<property name="teacherName" value="大宝"></property>
</bean>

<bean id="teacherTwo" class="com.spring6.bean.Teacher">
	<property name="teacherId" value="10086"></property>
	<property name="teacherName" value="二宝"></property>
</bean>

<bean id="student" class="com.spring6.bean.Student">
	<property name="id" value="1004"></property>
	<property name="name" value="赵六"></property>
	<property name="teacherMap">
		<map>
			<entry>
				<key>
					<value>10010</value>
				</key>
				<ref bean="teacherOne"></ref>
			</entry>
			<entry>
				<key>
					<value>10086</value>
				</key>
				<ref bean="teacherTwo"></ref>
			</entry>
		</map>
	</property>
</bean>

2.7.3 引用集合类型的 bean

<!-- list 集合类型的 bean-->
<util:list id="students">
    <ref bean="studentOne"></ref>
    <ref bean="studentTwo"></ref>
    <ref bean="studentThree"></ref>
</util:list>
<!-- map 集合类型的 bean-->
<util:map id="teacherMap">
    <entry>
        <key>
            <value>10010</value>
        </key>
        <ref bean="teacherOne"></ref>
    </entry>
    <entry>
        <key>
            <value>10086</value>
        </key>
        <ref bean="teacherTwo"></ref>
    </entry>
</util:map>
<bean id="clazz" class="com.spring6.bean.Clazz">
    <property name="clazzId" value="4444"></property>
    <property name="clazzName" value="Javaee0222"></property>
    <property name="students" ref="students"></property>
</bean>
<bean id="student" class="com.spring6.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="hobbies">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
    <property name="teacherMap" ref="teacherMap"></property>
</bean>

        如果想要使用 util:listutil:map 标签必须引入相应的命名空间,如下:

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

2.8 p 命名空间注入

        首先需要引入 p 命名空间

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

        然后可以通过以下方式为 bean 的各个属性赋值 

<bean id="studentSix" class="com.spring6.bean.Student"
    p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap"></bean>

2.9 引入外部属性文件

        1、添加 maven 依赖

 <!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

<!-- 数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>

        2、resources 目录下创建 db.properties 文件,内容如下:

jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver

         3、引入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>

        4、引入外部属性文件

<context:property-placeholder location="classpath:db.properties"/>

        5、配置 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: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:db.properties"/>

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

        6、测试

public class DruidDataSourceTest {
    @Test
    public void testHelloWorld1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        DruidDataSource student = ac.getBean(DruidDataSource.class);
        System.out.println(student.getUsername());
    }
}

2.10 bean 的作用域

2.10.1 概念

        在 Spring 中可以通过配置 bean 标签的 scope 属性来指定 bean 的作用域范围,各取值含义参加下表:

取值含义创建对象的时机
singleton(默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化时
prototype这个bean在IOC容器中有多个实例获取bean时

        如果是在 WebApplicationContext 环境下还会有另外几个作用域(但不常用):

取值含义
request在一个请求范围内有效
session在一个会话范围内有效

2.10.2 测试

        1、新建一个 User

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;

    public User() {
    }

    public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }
    // setter、getter
}

        2、配置 bean

<!-- scope 属性:取值 singleton(默认值),bean 在 IOC 容器中只有一个实例,IOC 容器初始化时创建对象 -->
<!-- scope 属性:取值 prototype,bean 在 IOC 容器中可以有多个实例,getBean() 时创建对象 -->
<bean class="com.spring6.bean.User" scope="prototype"></bean>

        3、测试

@Test
public void testBeanScope(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
	User user1 = ac.getBean(User.class);
	User user2 = ac.getBean(User.class);
	System.out.println(user1==user2);
}

 2.11 bean 生命周期

2.11.1 具体生命周期过程

        1、bean 对象创建(调用无参构造器)

        2、bean 对象设置属性值

        3、bean 的后置处理器(初始化之前)

        4、bean 对象初始化(需在配置bean时指定初始化方法)

        5、bean 的后置处理器(初始化之后)

        6、bean 对象就绪可以使用

        7、bean 对象销毁(需在配置bean时指定销毁方法)

        8、IOC 容器关闭

2.11.2 示例

        1、创建 User 类,注意其中的 initMethod() destroyMethod(),可以通过配置 bean 指定为初始化和销毁的方法。

public class User {

    private Integer id;
    private String username;
    public User() {
        System.out.println("1、bean 对象创建(调用无参构造器)");
    }
    public User(Integer id, String username) {
        this.id = id;
        this.username = username;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        System.out.println("2、bean 对象设置属性值");
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public void initMethod(){
        System.out.println("4、bean 对象初始化(需在配置 bean 时指定初始化方法)");
    }

    public void destroyMethod(){
        System.out.println("7、bean 对象销毁(需在配置bean时指定销毁方法)");
    }
}

        2、配置 bean

<!-- 使用 init-method 属性指定初始化方法 -->
<!-- 使用 destroy-method 属性指定销毁方法 -->
<bean class="com.spring6.bean.User" scope="singleton" init-method="initMethod" destroy-method="destroyMethod">
	<property name="id" value="1001"></property>
	<property name="username" value="admin"></property>
</bean>

        3、测试

@Test
public void testLife(){
	ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
	User bean = ac.getBean(User.class);
	System.out.println("6、bean 对象就绪可以使用");
	ac.close();// 销毁
}

        4、结果如下:

2.11.3 bean 的后置处理器

        bean 的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现 BeanPostProcessor 接口,且配置到 IOC 容器中,需要注意的是,bean 后置处理器不是单独针对某一个 bean 生效,而是针对 IOC 容器中所有 bean 都会执行。

        1、创建 bean 的后置处理器

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3、bean 的后置处理器(初始化之前)" + bean);
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5、bean 的后置处理器(初始化之后)" + bean);
        return bean;
    }
}

        2、IOC 容器中配置后置处理器

<bean id="myBeanProcessor" class="com.spring6.bean.MyBeanProcessor"/>

         3、执行测试,结果如下:

2.12 FactoryBean

2.12.1 简介

        FactoryBean Spring 提供的一种整合第三方框架的常用机制。和普通的 bean 不同,配置一个 FactoryBean 类型的 bean,在获取 bean 的时候得到的并不是 class 属性中配置的这个类的对象,而是 getObject() 方法的返回值。通过这种机制,Spring 可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

        将来我们整合 Mybatis 时,Spring 就是通过 FactoryBean 机制来帮我们创建 SqlSessionFactory 对象的。

2.12.2 示例

        1、创建类 UserFactoryBean

import org.springframework.beans.factory.FactoryBean;

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

        2、配置 bean

<bean id="user" class="com.spring6.bean.UserFactoryBean"></bean>

        3、测试

@Test
public void testUserFactoryBean(){
	// 获取IOC容器
	ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
	User user = (User) ac.getBean("user");
	System.out.println(user);
}

2.13 基于 xml 自动装配

2.13.1 简介

        自动装配:根据指定的策略,在 IOC 容器中匹配某一个 bean,自动为指定的 bean 中所依赖的类类型或接口类型属性赋值

2.13.2 示例

        1、创建类 UserController

public class UserController {

    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void saveUser(){
        userService.saveUser();
    }

}

         2、创建接口 UserService 和其实现类 UserServiceImpl

public interface UserService {

    void saveUser();

}

public class UserServiceImpl implements UserService {

    private UserDao userDao;

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

    @Override
    public void saveUser() {
        userDao.saveUser();
    }

}

        3、创建接口 UserDao 和其实现类 UserDaoImpl

public interface UserDao {

    void saveUser();

}
public class UserDaoImpl implements UserDao {

    @Override
    public void saveUser() {
        System.out.println("保存成功");
    }

}

        4、配置 bean,使用 bean 标签的 autowire 属性设置自动装配效果

<!--
	自动装配方式:byType
	byType:根据类型匹配 IOC 容器中的某个兼容类型的 bean,为属性自动赋值
	若在 IOC 中,没有任何一个兼容类型的 bean 能够为属性赋值,则该属性不装配,即值为默认值 null
	若在 IOC 中,有多个兼容类型的 bean 能够为属性赋值,则抛出异常 NoUniqueBeanDefinitionException
-->

<bean id="userController" class="com.spring6.bean.user.UserController" autowire="byType"></bean>
<bean id="userService" class="com.spring6.bean.user.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.spring6.bean.user.UserDaoImpl"></bean>
<!--
	自动装配方式:byName
	byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
-->
<bean id="userController" class="com.spring6.bean.user.UserController" autowire="byName"></bean>
<bean id="userService" class="com.spring6.bean.user.UserServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.spring6.bean.user.UserDaoImpl"></bean>

        5、测试

@Test
public void testAutoWireByXML(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
	UserController userController = ac.getBean(UserController.class);
	userController.saveUser();
}

三、基于注解管理 Bean

        从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

        Spring 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring XML 配置。Spring 通过注解实现自动装配的步骤如下:

        1、引入依赖

        2、开启组件扫描

        3、使用注解定义 Bean

        4、依赖注入

3.1 创建子模块

        创建子模块 spring-annotation,并添加 maven 依赖,如下:

<dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.3</version>
    </dependency>

    <!--junit5测试-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
    </dependency>

    <!--log4j2的依赖-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.19.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j2-impl</artifactId>
        <version>2.19.0</version>
    </dependency>
</dependencies>

3.2 开启组件扫描

        Spring 默认不使用注解装配 Bean,因此我们需要在 Spring XML 配置中,通过 <context:component-scan> 元素开启 Spring Beans 的自动扫描功能。

        开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

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

    <!--开启组件扫描功能-->
    <context:component-scan base-package="com.spring6"></context:component-scan>
</beans>

3.2.1 基本扫描

        最基本的扫描方式,需要在 xml 中配置以下的标签

<context:component-scan base-package="com.spring6"></context:component-scan>

3.2.2 指定要排除的组件

        如果想要排除指定的组件,需要在 xml 中配置以下的标签

<context:component-scan base-package="com.spring6">
    <!-- context:exclude-filter标签:指定排除规则 -->
    <!-- 
 		type:设置排除或包含的依据
		type="annotation",根据注解排除,expression中设置要排除的注解的全类名
		type="assignable",根据类型排除,expression中设置要排除的类型的全类名
	-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <!--<context:exclude-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>-->
</context:component-scan>

3.2.3 仅扫描指定组件

        如果想要仅扫描指定组件,需要在 xml 中配置以下的标签

<context:component-scan base-package="com" use-default-filters="false">
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
    <!-- 
 		type:设置排除或包含的依据
		type="annotation",根据注解排除,expression中设置要排除的注解的全类名
		type="assignable",根据类型排除,expression中设置要排除的类型的全类名
	-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<!--<context:include-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>-->
</context:component-scan>

3.3 使用注解定义 bean

        Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。  使用时只需将该注解标注在相应类上即可。
@Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

3.4 @Autowired 注入

        单独使用 @Autowired 注解,默认根据类型装配,即 byType。该注解可以标注在构造方法上、方法上、形参上、属性上和注解上。

        该注解有一个 required 属性,默认值是 true,表示在注入的时候要求被注入的 Bean 必须是存在的,如果不存在则报错。如果 required 属性设置为 false,表示注入的 Bean 存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。

3.4.1 属性注入

        1、编写 UserService 接口和其实实现类 UserServiceImpl

public interface UserService {

    public void out();
}

@Service
public class UserServiceImpl implements UserService {

    @Override
    public void out() {
        System.out.println("Service层执行结束");
    }
}

        2、创建 UserController

@Controller
public class UserController {

    // 属性注入
    @Autowired
    private UserService userService;

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}

        3、测试

public void testAnnotation(){
	ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
	UserController userController = context.getBean("userController", UserController.class);
	userController.out();
	logger.info("执行成功");
}

        以上构造方法和 setter 方法都没有提供,经过测试,仍然可以注入成功。

3.4.2 set 注入

@Controller
public class UserController {

    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}

3.4.3 构造方法注入

@Controller
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}

3.4.4 形参上注入

@Controller
public class UserController {

    private UserService userService;

    public UserController(@Autowired UserService userService) {
        this.userService = userService;
    }

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}

3.4.5 只有一个构造函数可省略注解

@Controller
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}

        测试通过,当有参数的构造方法只有一个时,@Autowired 注解可以省略。如果有多个构造方法时会测试报错。

3.4.6 @Autowired注解和@Qualifier注解联合

        新建一个 UserService 的实现类 UserServiceImpl2,如下:

public interface UserService {

    public void out();
}

@Service
public class UserServiceImpl implements UserService {

    @Override
    public void out() {
        System.out.println("Service 层执行结束");
    }
}

@Service
public class UserServiceImpl2 implements UserService {

    @Override
    public void out() {
        System.out.println("Service2 层执行结束");
    }
}

        此时,再通过属性注入就会报错,如下:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}

        错误信息中说:不能装配,UserServices 这个 Bean 的数量等于 2,怎么解决这个问题呢?当然要 byName,根据名称进行装配了。如下:

@Controller
public class UserController {

    @Autowired
    @Qualifier("userServiceImpl2")
    private UserService userService;

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}

3.4.7 总结

        1、@Autowired 注解可以出现在:属性上、构造方法上、构造方法的参数上、setter 方法上。

        2、 当带参数的构造方法只有一个,@Autowired 注解可以省略。

        3、 @Autowired 注解默认根据类型注入。如果要根据名称注入的话,需要配合 @Qualifier 注解一起使用。

3.5 @Resource注入

        @Resource 注解也可以完成属性注入。那它和 @Autowired 注解有什么区别?

3.5.1 区别

        1、来源不同:@Resource 注解是 JDK 扩展包中的,也就是说属于 JDK 的一部分。所以该注解是标准注解,更加具有通用性。而 @Autowired 注解是 Spring 框架自己的。

        2、默认装配类型不同:@Resource 注解默认根据名称装配 byName,未指定 name 时,使用属性名作为 name。通过 name 找不到的话会自动启动通过类型 byType 装配。而 @Autowired 注解默认根据类型装配 byType,如果想根据名称装配,需要配合 @Qualifier 注解一起用。

        3、作用范围不同:@Resource 注解用在属性上、setter 方法上。而 @Autowired 注解用在属性上、setter 方法上、构造方法上、构造方法参数上。

3.5.2 引入依赖

        @Resource 注解属于 JDK 扩展包,所以不在 JDK 当中,需要额外引入以下依赖。如果是 JDK8 的话不需要额外引入依赖。高于 JDK11 或低于 JDK8 需要引入以下依赖。

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>

3.5.3 name 注入

@Repository("myUserDao")
public class UserDaoImpl implements UserDao {

    @Override
    public void print() {
        System.out.println("Dao层执行结束");
    }
}
@Service
public class UserServiceImpl implements UserService {

    @Resource(name = "myUserDao")
    private UserDao myUserDao;

    @Override
    public void out() {
        myUserDao.print();
        System.out.println("Service层执行结束");
    }
}

3.5.4 name 未知注入

        当 @Resource 注解使用时没有指定 name 的时候,还是根据 name 进行查找,这个 name 是属性名。

@Repository("myUserDao")
public class UserDaoImpl implements UserDao {

    @Override
    public void print() {
        System.out.println("Dao层执行结束");
    }
}
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao myUserDao;

    @Override
    public void out() {
        myUserDao.print();
        System.out.println("Service层执行结束");
    }
}

3.5.5 类型注入

        当通过 name 找不到,并且通过属性名也没找到的时候,会启动 byType 进行注入。

@Repository("myUserDao")
public class UserDaoImpl implements UserDao {

    @Override
    public void print() {
        System.out.println("Dao层执行结束");
    }
}
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao1;

    @Override
    public void out() {
        userDao1.print();
        System.out.println("Service层执行结束");
    }
}

3.5.6 总结

        @Resource 注解:默认 byName 注入,没有指定 name 时把属性名当做 name,根据 name 找不到时,才会 byType 注入。byType 注入时,某种类型的 Bean 只能有一个。

3.6 Spring 全注解开发

        全注解开发就是不再使用 spring 配置文件了,写一个配置类来代替配置文件。

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

// 此注解表示这是一个配置类
@Configuration
// 扫描指定路径的包
@ComponentScan("com.spring6")
public class Spring6Config {

}

        测试类的写法也需要改下,如下:

@Test
public void testAllAnnotation(){
    ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
    UserController userController = context.getBean("userController", UserController.class);
    userController.out();
    logger.info("执行成功");
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的小三菊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值