一、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、ApplicationContext:BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。其主要实现类如下:
类型名 | 简介 |
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 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 < 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:list、util: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("执行成功");
}