Spring 6(二)【IOC原理】

前言

        

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;}@Overridepublic 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)测试:

    @Testpublic 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)测试

    @Testpublic 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;}@Overridepublic 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)测试:

    @Testpublic 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 初始化之前的处理代码return bean;}@Overridepublic 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";@NullableT getObject() throws Exception;@NullableClass<?> 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> {@Overridepublic Student getObject() throws Exception {return new Student();}@Overridepublic Class<?> getObjectType() {return Student.class;}
}

(2)添加到 IOC 容器:

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

(3)测试:

    @Testpublic 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;}@Overridepublic 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{@Overridepublic 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"/>

测试:

    @Testpublic 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 {@Autowiredprivate UserDao userDao;@Overridepublic void addUser() {System.out.println("addUser() 方法执行");userDao.addUser();}
}
import org.springframework.stereotype.Repository;@Repository
public class UserDaoImpl implements UserDao{@Overridepublic 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 {@Autowiredprivate UserService userService;public void addUser(){userService.addUser();}}

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

测试:

    @Testpublic 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;@Autowiredpublic 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;@Autowiredpublic void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic 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;@Autowiredpublic UserServiceImpl(UserDao userDao){this.userDao = userDao;}@Overridepublic 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;@Autowiredpublic 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;}@Overridepublic 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;}@Overridepublic 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{@Overridepublic 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;}@Overridepublic 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;}@Overridepublic void addUser() {System.out.println("addUser() 方法执行");userDao.addUser();}
}

总结

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

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

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

2.4、@Resource 注入

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/227932.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

visual Studio MFC 平台实现图片的傅里叶变换

图片的傅里叶变换 本文主要讲解傅里叶变换的基本数学概念与物理概念&#xff0c;并本文使用visual Studio MFC 平台实现对傅里叶变换在图片上进行了应用。 一、傅里叶变换的原理 在这里推荐一篇讲得非常形象的文章通俗讲解&#xff1a;图像傅里叶变换 1.1 傅里叶变换原理的说明…

idea__SpringBoot微服务11——整合Druid数据源(新依赖)(新注解)

整合JDBC 一、导入依赖二、配置Druid————————创作不易&#xff0c;如觉不错&#xff0c;随手点赞&#xff0c;关注&#xff0c;收藏(*&#xffe3;︶&#xffe3;)&#xff0c;谢谢~~ 接着 第10的 新注解&#xff1a; ConfigurationProperties ConfigurationPropert…

【人工智能】实验四:遗传算法求函数最大值实验与基础知识

实验四&#xff1a;遗传算法求函数最大值实验 实验目的 熟悉和掌握遗传算法的原理、流程和编码策略&#xff0c;并利用遗传算法求解函数优化问题&#xff0c;理解求解流程并测试主要参数对结果的影响。 实验内容 采用遗传算法求解函数最大值。 实验要求 1. 用遗传算法求解…

3. cgal 示例 GIS (Geographic Information System)

GIS (Geographic Information System) 地理信息系统 原文地址: https://doc.cgal.org/latest/Manual/tuto_gis.html GIS 应用中使用的许多传感器&#xff08;例如激光雷达&#xff09;都会生成密集的点云。此类应用程序通常利用更先进的数据结构&#xff1a;例如&#xff0c;不…

053:vue工具--- 英文字母大小写在线转换

第047个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

QEMU源码全解析 —— virtio(13)

接前一篇文章&#xff1a; 通过前文书&#xff08;从QEMU源码全解析 —— virtio&#xff08;9&#xff09;开始&#xff09;对整个流程以及各个相关函数的解析&#xff0c;可以看到从virtio PCI代理设备的具现化到virtio设备的具现化过程。但前述分析还遗漏了一部分&#xff0…

LeetCode(69)对称二叉树【二叉树】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 对称二叉树 1.题目 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,nu…

朱卫明:从韶关走向世界的创作型歌手

朱卫明&#xff0c;艺名Aming&#xff0c;是一位来自广东韶关的杰出唱作音乐人。他以其独特的创作才华和深情的嗓音&#xff0c;赢得了众多歌迷的喜爱。作为一名创作型歌手&#xff0c;朱卫明用音乐传递情感&#xff0c;用歌声打动人心。 一、早年经历与音乐启蒙 朱卫明出生于…

Python开发工具PyCharm v2023.3全新发布——全面推出AI Assistant工具

JetBrains PyCharm是一种Python IDE&#xff0c;其带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具。此外&#xff0c;该IDE提供了一些高级功能&#xff0c;以用于Django框架下的专业Web开发。 PyCharm v2023.3正式版下载 在 PyCharm 2023.3 中&#xff0c;每…

Linux Shell——输入输出重定向

输入输出重定向 1. 重定向输入2. 重定向输出 总结 最近学习了shell语法&#xff0c;总结一下关于输入输出重定向的知识。 一般情况下&#xff0c;linux每次执行命令其实都会打开三个文件&#xff0c;分别是&#xff1a; 标准输入stdin 文件描述符为0 标准输出stdout 文件描述符…

Text2SQL学习整理(一) 综述

数据库由一张或多张表格构成&#xff0c;表格之间的关系通过共同的列&#xff08;外键&#xff09;关联&#xff0c;人们使用数据库来方便的记录和存储信息。SQL是广泛应用的关系型数据库查询语言&#xff0c;但是对于普通用户而言&#xff0c;编写SQL语句有一定的难度。 Text…

Xpath注入

这里学习一下xpath注入 xpath其实是前端匹配树的内容 爬虫用的挺多的 XPATH注入学习 - 先知社区 查询简单xpath注入 index.php <?php if(file_exists(t3stt3st.xml)) { $xml simplexml_load_file(t3stt3st.xml); $user$_GET[user]; $query"user/username[name&q…

方案分享:如何做好云中的DDoS防御?

所有企业都会有遭受DDoS攻击的风险。由于目前DDoS即服务&#xff08;DaaS&#xff09;的售价低廉&#xff0c;因此对于恶意攻击者来说&#xff0c;发起攻击比以往任何时候都更加容易&#xff0c;技术门槛也更低。分析公司IDC一项关于DDoS防御的调查显示&#xff0c;超过50%的IT…

数据结构-06-散列/哈希表

1-什么是散列表 散列表用的是数组支持按照下标随机访问数据的特性&#xff0c;所以散列表其实就是数组的一种扩展&#xff0c;由数组演化而来。可以说&#xff0c;如果没有数组&#xff0c;就没有散列表。散列表中的元素在数组的位置(index)是通过散列函数得到的。 2-散…

【MyBatis-Plus】MyBatis进阶使用

目录 一、MyBatis-Plus简介 1.1 介绍 1.2 优点 1.3 结构 二、MyBatis-Plus基本使用 2.1 配置 2.2 代码生成 2.3 CRUD接口测试 三、MyBatis-Plus策略详解 3.1 主键生成策略 3.2 雪花ID生成器 3.3 字段自动填充策略 3.4 逻辑删除 四、MyBatis-Plus插件使用 4.1 乐…

从零开始:VuePress2 + GitHub Pages 搭建你的第一个免费博客网站

可能你也想拥有一个属于自己的博客网站&#xff0c;但是自己搭个博客网站不知道从何下手&#xff0c;而且还需要租个云服务器&#xff0c;虽然一个月只需几十块钱&#xff0c;但是我们的博客网站是要长期维护的&#xff0c;日积月累也要不少钱呢。 现在我就教你用 VuePress2 …

JVM-1-运行时数据区

程序计数器&#xff08;Program Counter Register&#xff09; 是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里[1]&#xff0c;字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令&…

【ICCV 2022】Masked Autoencoders Are Scalable Vision Learners

何凯明一作文章&#xff1a;https://arxiv.org/abs/2111.06377 本文的出发点&#xff1a;是BERT的掩码自编码机制&#xff1a;移除一部分数据并对移除的内容进行学习。mask自编码源于CV但盛于NLP&#xff0c;恺明对此提出了疑问&#xff1a;是什么导致了掩码自编码在视觉与语言…

Apache OfBiz 反序列化命令执行漏洞(CVE-2023-49070)

项目介绍 Apache OFBiz是一个非常著名的电子商务平台&#xff0c;是一个非常著名的开源项目&#xff0c;提供了创建基于最新J2EE/XML规范和技术标准&#xff0c;构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类WEB应用系统的框架。OFBiz最主要的特…

自定义注解

自定义注解 自定义注解 以实战案例为驱动,快速掌握此怎么自己自定义注解,也好出去自己吹牛逼~哈哈哈 假设我们打车,需要检验验证码,我们需要一个注解字来进行核验,我们怎么操作呢? 大纲总览 ​​ 1.定义注解 可以自己创一个包单门存放自己的注解: 如​constraints​ 包 然后…