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)
@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 的生命周期
- bean对象创建(调用无参构造器)
- 给bean对象设置属性(调用我们自己写的 setter 方法)
- bean的后置处理器(初始化之前)
- bean对象初始化(需在配置bean时指定初始化方法)
- bean的后置处理器(初始化之后)
- bean对象就绪可以使用
- bean对象销毁(需在配置bean时指定销毁方法)
- 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 注入
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!