Spring 6(二)【IOC原理】

2023-12-17 12:54:01

前言

????????

1、IOC

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

1.1、控制反转

  • 控制反转不是技术,而是一种思想。

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

  • 控制反转,反转的是什么?

    • 将对象的创建权利交出去,交给第三方容器(IOC 容器)负责。

    • 将对象和对象之间关系的维护权交出去,交给第三方容器负责。

  • 控制反转这种思想如何实现呢?

    • DI(Dependency Injection):依赖注入

依赖注入(DI):

  • 指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

依赖注入常见的实现方式包括两种:

  • 第一种:set注入

  • 第二种:构造器注入

所以,IOC 就是一种控制反转的思想, 而 DI 是对 IOC的一种具体实现。

1.2、获取 Bean 的 3 种方式

其实也就是 getBean()?的三种传参方式,下面对我们上一节的 User 类进行测试。

<bean id="user" class="com.lyh.study.User"/>

1.2.1、getBean(String id)

因为返回的是一个 Object 类型所以需要强转一下。
@Test
public void testHelloWorld1(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
    User user = (User)ac.getBean("user");
    user.add();
}

1.2.2、 getBean(Class<? extends Object> ?requiredType)

因为传入的就是一个类,所以可以自动推断出返回的类型。

注意:这种方式的配置文件中属于该类(com.lyh.study.User)的 bean 只能有一个,这个很好理解,如果有多个,它怎么知道你到底要取哪一个 bean。

@Test
public void testHelloWorld1(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
    User user = ac.getBean(User.class);
    user.add();
}

1.2.3、getBean(String id,Class<? extends Object> ?requiredType)

这种方式同样指定了 bean 的类型,所以返回的直接就是该类型的 bean 对象。

@Test
public void testHelloWorld1(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
    User user = ac.getBean("user",User.class);
    user.add();
}

个人建议使用这种方式,不用咱们自己强转,代码看起来也比较直观。?

1.3、依赖注入的 2 种方式

?1.3.1、setter 注入

(1)创建 bean 目录,编写 Student 类:

package com.lyh.study.bean;

public class Student {

    private Integer id;

    private String name;

    private Integer age;

    private String sex;

    public Student() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }

}

注意:这里的 Bean 必须有 setter 方法,否则属性无法注入(idea 直接就会在配置文件中爆红);如果不设置 getter 方法,我们的私有属性无法获取(被public 修饰的属性可以通过 对象.属性(比如 student1.id) 直接获取,但是被 private 修饰的属性必须通过显示的方法(比如 student1.getId())来获取)。

(2)配置 bean 时给属性赋值:

<bean id="student1" class="com.lyh.study.bean.Student">
        <property name="id" value="1001"/>
        <property name="name" value="lyh"/>
        <property name="age" value="20"/>
        <property name="sex" value="男"/>
    </bean>

(3)测试:

    @Test
    public void testStudent(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        Student student1 = ac.getBean("student1", Student.class);
        System.out.println(student1);
    }

(4)运行结果:

Student{id=1001, name='lyh', age=20, sex='男'}

1.3.2、构造器注入

(1)在我们的 JavaBean 中添加有参构造器:

public Student(Integer id, String name, Integer age, String sex) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.sex = sex;
}

(2)配置 bean?

默认是按照我们构造器的顺序直接赋值:

<bean id="student2" class="com.lyh.study.bean.Student">
        <constructor-arg value="1002"/>
        <constructor-arg value="燕双鹰"/>
        <constructor-arg value="28"/>
        <constructor-arg value="男"/>
    </bean>

也可以通过属性来指定属性的顺序:

  • index 属性:从 0 开始(分别对应 id、name、age、sex)
  • name 属性:直接指定参数名,然后通过 value 赋值

(3)测试

    @Test
    public void testStudent(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        Student student2 = ac.getBean("student2", Student.class);
        System.out.println(student2);
    }

(4)运行结果:

Student{id=1002, name='燕双鹰', age=28, sex='男'}

1.4、给对象类型的属性注入的 3 种方式

给 Student 类添加一个 Address 类型的属性:

(1)创建 Address 类

package com.lyh.study.bean;

public class Address {
    private String province;
    private String county;
    private String village;

    public Address(){
    }

    public Address(String province, String county, String village) {
        this.province = province;
        this.county = county;
        this.village = village;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCounty() {
        return county;
    }

    public void setCounty(String county) {
        this.county = county;
    }

    public String getVillage() {
        return village;
    }

    public void setVillage(String village) {
        this.village = village;
    }

    @Override
    public String toString() {
        return "Address{" +
                "province='" + province + '\'' +
                ", county='" + county + '\'' +
                ", village='" + village + '\'' +
                '}';
    }
}

完了给我们的 Student 类添加一个 Adress 类型的属性,并设置 getter 和 setter 方法。?

1.4.1、外部引用 Bean

第一种方式:我们通过外部 Bean 引用来给我们的属性赋值:

(1)配置文件添加(使用 ref 属性来引用其它 bean):

<bean id="address1" class="com.lyh.study.bean.Address">
        <property name="province" value="山西省"/>
        <property name="county" value="天水县"/>
        <property name="village" value="英雄村"/>
    </bean>

    <bean id="student3" class="com.lyh.study.bean.Student">
        <property name="id" value="1003"/>
        <property name="name" value="姜伯约"/>
        <property name="age" value="26"/>
        <property name="sex" value="男"/>
        <property name="address" ref="address1"/>
    </bean>

(2)测试:

@Test
public void testStudent(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
    Student student3 = ac.getBean("student3", Student.class);
    System.out.println(student3);
    System.out.println(student3.getAddress());
}

(3)运行结果:?

Student{id=1003, name='姜伯约', age=26, sex='男'}
Address{province='山西省', county='天水县', village='英雄村'}

1.4.2、内部 Bean

(1)配置文件:

<bean id="student4" class="com.lyh.study.bean.Student">
        <property name="id" value="1004"/>
        <property name="name" value="李大喜"/>
        <property name="age" value="20"/>
        <property name="sex" value="男"/>
        <property name="address">
            <!-- 内部bean不能给外部使用,所以可以省略 id 属性 -->
            <bean class="com.lyh.study.bean.Address">
                <property name="province" value="山西省"/>
                <property name="county" value="英雄县"/>
                <property name="village" value="农民村"/>
            </bean>
        </property>
    </bean>

这里省略测试,和上面的结果是一样的。?

1.4.3、级联属性赋值

使用外部?bean 重新给属性赋值:

<bean id="address1" class="com.lyh.study.bean.Address">
        <property name="province" value="山西省"/>
        <property name="county" value="天水县"/>
        <property name="village" value="英雄村"/>
    </bean>

    <bean id="student5" class="com.lyh.study.bean.Student">
        <property name="id" value="1005"/>
        <property name="name" value="狄仁杰"/>
        <property name="age" value="28"/>
        <property name="sex" value="男"/>
        <property name="address" ref="address1"/>
        <property name="address.province" value="山西省"/>
        <property name="address.county" value="杨柳县"/>
        <property name="address.village" value="大柳树村"/>
    </bean>

1.5、特殊类型属性的注入

我们的对象类型还可能是其它类型,比如数组、集合类型。这些特殊类型属性的注入无非就是配置文件的写法变化罢了。

1.5.1、数组类型

我们给上面的 Student 类添加一个 Int[] 类型的 hobbies 属性,记得添加 setter (没有 setter 方法就无法注入)和 getter 方法。

<bean id="student6" class="com.lyh.study.bean.Student">
    <property name="id" value="1006"/>
    <property name="name" value="狄如燕"/>
    <property name="age" value="19"/>
    <property name="sex" value="女"/>
    <property name="address" ref="address1"/>
    <property name="hobbies">
        <array>
            <value>唱歌</value>
            <value>舞剑</value>
        </array>
    </property>
</bean>

1.5.2、集合类型

(1)List 类型

我们给上面的 Student 类添加一个 List<Student>?类型的 students 属性并添加 setter 和 getter 方法。

<bean id="student7" class="com.lyh.study.bean.Student">
        <property name="id" value="1006"/>
        <property name="name" value="狄如燕"/>
        <property name="age" value="19"/>
        <property name="sex" value="女"/>
        <property name="address" ref="address1"/>
        <property name="students">
            <list>
                <ref bean="student4"/>
                <ref bean="student5"/>
                <ref bean="student6"/>
            </list>
        </property>
    </bean>

注意:如果是 Set 集合,只需要将其中的list标签改为set标签即可。

(2)Map 类型

我们给上面的 Student 类添加一个 Map<String,Student>?类型的 studentMap 属性并添加 setter 和 getter 方法。

<bean id="student8" class="com.lyh.study.bean.Student">
        <property name="id" value="1006"/>
        <property name="name" value="狄如燕"/>
        <property name="age" value="19"/>
        <property name="sex" value="女"/>
        <property name="address" ref="address1"/>
        <property name="studentMap">
            <map>
                <entry>
                   <key>
                       <value>1001</value>
                   </key>
                    <!-- 如果value是基本类型,直接使用<value>标签即可 -->
                    <ref bean="student1"/>
                </entry>
                <entry>
                    <key>
                        <value>1002</value>
                    </key>
                    <ref bean="student2"/>
                </entry>
            </map>
        </property>
    </bean>

1.5.3、通过 <util:> 标签实现集合类型属性的注入

要使用 util 标签就必须先引入它,在配置文件头部需要加入以下内容:

使用 util 标签实现 Map 集合类型注入:

    <bean id="student8" class="com.lyh.study.bean.Student">
        <property name="id" value="1008"/>
        <property name="name" value="狄如燕"/>
        <property name="age" value="19"/>
        <property name="sex" value="女"/>
        <property name="address" ref="address1"/>
        <property name="studentMap" ref="map"/>
    </bean>

    <util:map id="map">
        <entry>
            <key>
                <value>1001</value>
            </key>
            <ref bean="student1"/>
        </entry>
        <entry>
            <key>
                <value>1002</value>
            </key>
            <ref bean="student2"/>
        </entry>
    </util:map>

可以看到,效果和上面直接通过 <map> 标签注入是一样的,这个 <util:map> 标签就相当于一个 Map 对象。

1.6、P 命名空间注入

命名空间是啥东西,其实就是我们 Spring 配置文件的头部那些带有 xlms: xxx 的部分(比如 xmls:util 就是 util 命名空间),所以?P 命名空间注入其实就是加这么一行:

有啥用呢?其实也就是用来简化配置文件的代码量:

<bean id="student10" class="com.lyh.study.bean.Student" p:id="1010" p:name="武则天" p:age="58" p:address-ref="address1" p:studentMap-ref="map"/>

可以看到,引入 p命名空间后,我们的属性直接变成了 bean 标签的一个属性值?p:属性名,引用类型的属性也可以通过?p:属性名-ref 的方式来引用。

1.7、引入外部属性文件

我们经常需要把一些常用但是又经常需要修改的类写入到 Spring 配置文件(比如 MySQL 工具类),而这些 Bean 的属性值的修改就需要通过外部属性文件来进行注入了,这样更加灵活,方便维护。

(1)需求:

????????把一些特定的固定值,放到一个特定的外部文件中去(比如 db.properties),在 Spring 配置文件中进行引入,这样我们要进行修改时,只需要修改外部文件,而不需要修改代码。

(2)导入依赖:

 <!-- 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>

(3)创建外部属性文件 db.properties

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

1.7.1、引入属性文件

(1)引入 context 名称空间:

(2)引入外部属性文件(db.properties)

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

(3)配置连接池对应的 Bean

这个 Bean druid已经帮我们实现了,相当于一个工具类,我们都不用自己实现,只需要让 Spring 帮我管理即可。

<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>

(4)测试:

    @Test
    public void testDataSource() throws SQLException {
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        DataSource druidDataSource = ac.getBean("druidDataSource", DataSource.class);
        Connection connection = druidDataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

(5)运行结果:

com.mysql.cj.jdbc.ConnectionImpl@5b56b654

1.8、Bean 的作用域

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围:

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

也就是我上一节说的,单例对象和多例对象的区别。

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

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

我是应该用不上了。

这里说一下单例模式(singleton)和非单例模式(prototype)的区别:

  • 单例模式下,每次 getBean 都会返回同一个对象(在内存中的地址相同,可以用 == 进行测试)
  • 非单例模式下,每次 getBena 都会创建一个新的对象,尽管我们在配置文件中设置的它们的属性是一样的,但是它们指向的是不同的内存地址。

1.9、Bean 的生命周期

  1. bean对象创建(调用无参构造器)
  2. 给bean对象设置属性(调用我们自己写的 setter 方法)
  3. bean的后置处理器(初始化之前)
  4. bean对象初始化(需在配置bean时指定初始化方法)
  5. bean的后置处理器(初始化之后)
  6. bean对象就绪可以使用
  7. bean对象销毁(需在配置bean时指定销毁方法)
  8. IOC容器关闭

1.9.1、初始化和销毁方法

上面的 Bean 对象的初始化方法和销毁方法是我们自己实现的,然后通过给 <bean> 标签添加属性来实现:

<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.lyh.study.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
    <property name="id" value="1001"></property>
    <property name="name" value="朱重八"></property>
    <property name="age" value="18"></property>
    <property name="sex" value="男"></property>
</bean>

1.8.2、后置处理器

bean 的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现 BeanPostProcessor 接口,且配置到IOC容器中。

需要注意的是:bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行 ?

package com.lyh.study;
    
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 {
        // 初始化之前的处理代码
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 初始化之后的处理代码
        return bean;
    }
}

?在 IOC 容器内配置后置处理器(放进去就行了):

<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.lyh.study.MyBeanProcessor"/>

?1.10、FactoryBean

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

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

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

(1)实现 FactoryBean 接口:

package com.lyh.study;

import com.lyh.study.bean.Student;
import org.springframework.beans.factory.FactoryBean;

public class StudentFactoryBean implements FactoryBean<Student> {

    @Override
    public Student getObject() throws Exception {
        return new Student();
    }

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

(2)添加到 IOC 容器:

<bean id="studentFactoryBean" class="com.lyh.study.StudentFactoryBean"/>

(3)测试:

    @Test
    public void testBeanFactory(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        // 转换出来的不是 StudentBeanFactory 对象,而是 Student 对象
        Student studentBeanFactory = (Student) ac.getBean("studentFactoryBean");
        System.out.println(studentBeanFactory);
    }

(4)运行结果:

Student{id=null, name='null', age=null, sex='null'}

当 Spring 容器遇到一个实现了 FactoryBean 接口的 Bean 时,它不会直接实例化这个 Bean,而是会调用该 Bean 的 getObject() 方法来获取对象。这样,我们就可以在 getObject() 方法中编写自定义的对象创建逻辑,从而实现与第三方框架的整合。

1.11、基于 XML 的自动装配

所谓自动装配其实就是为了减少我们配置 Spring 配置文件的工作量,比较一个 JavaBean 如果有很多属性的话,我们自己一个一个去配置添加属性太复杂了,所以就有了自动装配这个概念。

自动装配有两种方式:通过属性类型来自动装配(byType),通过属性名来自动装配(byName)。

  • byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
    • 如果在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值 null
    • 如果在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
  • byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值

这里我们写一个测试模拟我们开发的一个的场景,controller 负责响应通过 service 来实现,service 会调用 dao 层来实现持久化。

(1)编写 Service 层代码:

package com.lyh.study.service;

public interface UserService {
    void addUser();
}
package com.lyh.study.service;

import com.lyh.study.dao.UserDao;

public class UserServiceImpl implements UserService {

    private UserDao userDao;

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

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}

(2)编写 Dao 层代码 :

package com.lyh.study.dao;

public interface UserDao {
    void addUser();
}
package com.lyh.study.dao;

public class UserDaoImpl implements UserDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }
}

(3)编写 controller 层代码:?

package com.lyh.study.controller;

import com.lyh.study.service.UserService;

public class UserController {

    private UserService userService;

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

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

}

1.11.1、byType 自动装配

<bean id="userController" class="com.lyh.study.controller.UserController" autowire="byType"/>
    <bean id="userService" class="com.lyh.study.service.UserServiceImpl" autowire="byType"/>
    <bean id="userDao" class="com.lyh.study.dao.UserDaoImpl"/>

1.11.2、byName 自动装配

<bean id="userController" class="com.lyh.study.controller.UserController" autowire="byName"/>
    <bean id="userService" class="com.lyh.study.service.UserServiceImpl" autowire="byName"/>
    <bean id="userDao" class="com.lyh.study.dao.UserDaoImpl"/>

测试:

    @Test
    public void testAutoWireByType(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        UserController userController = ac.getBean(UserController.class);
        userController.addUser();
    }

运行结果:

addUser() 方法执行
添加成功

?2、基于注解管理 Bean

除了上面的直接手动配置 Spring 配置文件以外,我们实际用的更多应该就是使用注解注入了。

2.1、开启扫描组件

????????Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

也就是在 Spring 配置文件中加这么一行:

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

?注意:在使用 context:component-scan 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 <beans> 中添加 context 相关的约束。

2.1.1、默认扫描方式

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

也就是我们上面演示的,它会扫描 com.lyh.study 包下所有被 @Component 注解标注的类并帮我们注册到 IOC 容器中管理。

2.1.2、指定要排除的组件

<context:exclude>标签

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

2.1.3、仅扫描指定组件

<context:include> 标签

<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <context:component-scan base-package="com.lyh.study" 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.lyh.study.controller.UserController"/>
    </context:component-scan>

2.2、使用注解定义 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 相同。

2.3、@Autowired 注入

单独使用 @Autowired注解时,默认根据类型装配(byType)

2.3.1、属性注入

上面 2.1 中我们的 UserController?中有一个属性是 UserService 类型的对象,而?UserService 这个类当中也有一个类型为 UserDao 的属性;所以我们当时必须提供?setter 方法,因为它是是通过 setter 方法进行注入的;但是现在使用注解开发我们就可以省去 setter 方法:

package com.lyh.study.service;

import com.lyh.study.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }
}
package com.lyh.study.controller;

import com.lyh.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Autowired
    private UserService userService;

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

}

这次我们使用了注解 @Service、Repository、Controller 分别标注了我们的 UserController、UserDao 和 UserController ,而且即使它们的 Bean 中包含了一些属性,我们并没有提供 setter 方法,因为使用注解开发时,不需要给 Bean 提供 setter 方法

测试:

    @Test
    public void testAutoWireByType(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        UserController userController = ac.getBean("userController",UserController.class);
        userController.addUser();
    }

运行结果:

addUser() 方法执行
添加成功

2.3.2、set 注入

上面的属性注入中,我们把 @Autowired 这个注解标注在了属性上,这种方式不需要我们实现属性的 setter 方法;而 set 注入是直接把 @Autowired 这个注解标注在方法上:

package com.lyh.study.controller;

import com.lyh.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    
    private UserService userService;

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

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

}
package com.lyh.study.service;

import com.lyh.study.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    
    private UserDao userDao;

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

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}

这里不再测试运行结果了,效果和属性注入是一样的。个人推荐这种方式,尽量养成一个给属性添加 setter 方法的好习惯,而且属性注入 Idea 会给一个警告提示,虽然也用起来没问题,但是强迫症实在受不了。

2.3.3、构造方法注入

和上面两种方法的注入方式差不多,就是把 @Autowired 这个注解标注在了构造器上,这种方式同样不需要给属性提供 setter 方法。

package com.lyh.study.service;

import com.lyh.study.dao.UserDao;
import com.lyh.study.dao.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Autowired
    public UserServiceImpl(UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}
package com.lyh.study.controller;

import com.lyh.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    private UserService userService;

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

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

}

效果和上面一致,不做演示。

2.3.4、形参注入

把 @Autowired 标注在形参上。

package com.lyh.study.service;

import com.lyh.study.dao.UserDao;
import com.lyh.study.dao.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public UserServiceImpl(@Autowired UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}
package com.lyh.study.controller;

import com.lyh.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    private UserService userService;

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

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

}

同样不做测试。

2.4.5、只有一个构造函数,无注解

当我们的 Bean 只有一个构造函数时,可以不需要注解。我们也可以从 Idea 的只能提示中看出来,当只有一个构造函数时它会自动被 Spring IOC 容器所管理(当然,我们的 Service 类上的 @Service 还是得有的)。

注意:再添加一个无参构造函数就失效了(有参构造和无参构造只能有一个)!!!

package com.lyh.study.service;

import com.lyh.study.dao.UserDao;
import com.lyh.study.dao.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public UserServiceImpl(UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}
?
package com.lyh.study.controller;

import com.lyh.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    private UserService userService;

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

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

}

?

2.4.6、@Autowired注解和@Qualifier注解联合

假设我们需要扩展一个名为 UserOracleDaoImpl 的类,用来把数据持久化到 Oracle 数据库中。

package com.lyh.study.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserOracleDaoImpl implements UserDao{
    
    @Override
    public void addUser() {
        System.out.println("用户被添加到 Oracle 数据库中");
    }
}

当我们进行测试的时候,会发现报错,甚至 Idea 自动会检测到异常不允许编译通过。原因就是 UerDao 类型的类 =?2 ,根本原因其实就是我们使用的 byType 自动装配,它要求我们的 IOC 容器中只能包含一个这种类型的 Bean。

怎么解决呢?其实很简单,换 byName 自动装配就 OK 了,也就是把 @Autowired注解和@Qualifier注解联合使用(标注在 setter 方法或者 属性上都是可以的):

因为我们上面的 UserDaoImpl 和 UserOracleDaoImpl 都已经被 @Respository 标注过了,所以它俩已经都被注册进了我们的 IOC 容器中,切换不同的实现类只需要修改 id :

使用 userDaoImpl :

package com.lyh.study.service;

import com.lyh.study.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Autowired
    @Qualifier("userDaoImpl")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}

要换用 userOrcaleDaoImpl 直接修改 id 即可:

package com.lyh.study.service;

import com.lyh.study.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Autowired
    @Qualifier("userOracleDaoImpl")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void addUser() {
        System.out.println("addUser() 方法执行");
        userDao.addUser();
    }
}

总结

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

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

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

2.4、@Resource 注入

文章来源:https://blog.csdn.net/m0_64261982/article/details/135022303
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。