四、容器IoC-基于XML管理Bean
搭建子模块 spring6-ioc-xml
创建User类
package com.atguigu.spring6;
public class User {
private String name;
private Integer age;
public void run() {
System.out.println("run...");
}
}
bean.xml
<?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="user" class="com.atguigu.spring6.User"></bean>
</beans>
获取bean
package com.atguigu.spring6;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.jupiter.api.Assertions.*;
class UserTest {
@Test
void run() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 方式一:根据id获取bean
User user1 = (User) context.getBean("user");
System.out.println(user1);
// 方式二:根据类型获取bean
User user2 = context.getBean(User.class);
System.out.println(user2);
// 方式三:根据id和类型
User user3 = context.getBean("user", User.class);
System.out.println(user3);
}
}
注意的地方
当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个
扩展知识
如果组件类实现了接口,根据接口类型可以获取 bean 吗?
可以,前提是bean唯一
如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?
不行,因为bean不唯一
结论
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。
java中,instanceof运算符用于判断前面的对象是否是后面的类,或其子类、实现类的实例。如果是返回true,否则返回false。也就是说:用instanceof关键字做判断时, instanceof 操作符的左右操作必须有继承或实现关系
依赖注入
手动注入的示例
package com.atguigu.spring6;
public class Student {
private String name;
private Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
// 通过set对属性赋值
Student student1 = new Student();
student1.setAge(18);
student1.setName("Tom");
// 通过构造器对属性赋值
Student student2 = new Student("Jack", 23);
}
}
依赖注入之setter注入
bean-student.xml
<!--通过set注入-->
<bean id="student" class="com.atguigu.spring6.Student">
<property name="age" value="20"></property>
<property name="name" value="Tom"></property>
</bean>
测试
class StudentTest {
@Test
public void testSetter(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean-student.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
// Student{name='Tom', age=20}
}
}
依赖注入之构造器注入
<!-- 通过构造器注入 -->
<bean id="studentConstructor" class="com.atguigu.spring6.Student">
<constructor-arg name="age" value="23"></constructor-arg>
<constructor-arg name="name" value="Jack"></constructor-arg>
</bean>
class StudentTest {
@Test
public void testConstructor(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean-student.xml");
Student student = context.getBean("studentConstructor", Student.class);
System.out.println(student);
// Student{name='Jack', age=23}
}
}
特殊值处理
1、字面量赋值
<property name="name" value="张三"/>
2、null值
<property name="name">
<null />
</property>
3、xml实体
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a < b"/>
4、CDATA节
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>
对象类型属性赋值
部门员工案例
部门和员工关系:一对多
package com.atguigu.spring6.company;
/**
* 部门
*/
public class Dept {
private String dname;
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public void info(){
System.out.println("info: " + this.dname);
}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
package com.atguigu.spring6.company;
/**
* 员工
*/
public class Emp {
private String ename;
private Integer age;
private Dept dept;
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public void work(){
System.out.println(this.ename + " work... " + this.age);
this.dept.info();
}
@Override
public String toString() {
return "Emp{" +
"ename='" + ename + '\'' +
", age=" + age +
'}';
}
}
配置文件 bean-emp.xml
1、引用外部bean
<bean id="dept" class="com.atguigu.spring6.company.Dept">
<property name="dname" value="财务部"></property>
</bean>
<bean id="emp" class="com.atguigu.spring6.company.Emp">
<property name="ename" value="Tom"></property>
<property name="age" value="20"></property>
<property name="dept" ref="dept"></property>
</bean>
ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值
2、内部bean
<bean id="emp2" class="com.atguigu.spring6.company.Emp">
<property name="ename" value="Tom"></property>
<property name="age" value="20"></property>
<property name="dept">
<bean id="dept2" class="com.atguigu.spring6.company.Dept">
<property name="dname" value="财务部"></property>
</bean>
</property>
</bean>
在一个bean中再声明一个bean就是内部bean
内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性
3、级联属性赋值
<bean id="dept3" class="com.atguigu.spring6.company.Dept">
<property name="dname" value="财务部"></property>
</bean>
<bean id="emp3" class="com.atguigu.spring6.company.Emp">
<property name="ename" value="Tom"></property>
<property name="age" value="20"></property>
<property name="dept" ref="dept3"></property>
<property name="dept.dname" value="研发部"></property>
</bean>
测试类
package com.atguigu.spring6;
import com.atguigu.spring6.company.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class EmpTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-emp.xml");
Emp emp = context.getBean("emp", Emp.class);
emp.work();
// Tom work... 20
// info: 财务部
}
}
为数组类型属性赋值
增加属性
private String[] hobbies;
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
<bean id="emp" class="com.atguigu.spring6.company.Emp">
<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
</bean>
测试类
@Test
public void testArray() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-array.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(Arrays.toString(emp.getHobbies()));
// [抽烟, 喝酒, 烫头]
}
为List集合类型属性赋值
private List<Emp> empList;
<bean id="emp1" class="com.atguigu.spring6.company.Emp">
<property name="ename" value="Tom"></property>
<property name="age" value="20"></property>
</bean>
<bean id="emp2" class="com.atguigu.spring6.company.Emp">
<property name="ename" value="Jack"></property>
<property name="age" value="30"></property>
</bean>
<bean id="dept" class="com.atguigu.spring6.company.Dept">
<property name="empList">
<list>
<ref bean="emp1"></ref>
<ref bean="emp2"></ref>
</list>
</property>
</bean>
若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可
为Map集合类型属性赋值
public class Teacher {
private Integer teacherId;
private String teacherName;
}
public class Student{
private Map<String, Teacher> teacherMap;
}
<bean id="teacherOne" class="com.atguigu.spring6.bean.Teacher">
<property name="teacherId" value="10010"></property>
<property name="teacherName" value="大宝"></property>
</bean>
<bean id="teacherTwo" class="com.atguigu.spring6.bean.Teacher">
<property name="teacherId" value="10086"></property>
<property name="teacherName" value="二宝"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" 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">
<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>
引用集合类型的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="clazzTwo" class="com.atguigu.spring6.bean.Clazz">
<property name="clazzId" value="4444"></property>
<property name="clazzName" value="Javaee0222"></property>
<property name="students" ref="students"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" 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>
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>
引入p命名空间后,可以通过以下方式为bean的各个属性赋值
<bean id="studentSix"
class="com.atguigu.spring6.bean.Student"
p:id="1006"
p:name="小明"
p:clazz-ref="clazzOne"
p:teacherMap-ref="teacherMap"
></bean>
引入外部属性文件
①加入依赖
<!-- 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>
②创建外部属性文件
jdbc.properties
jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver
③引入属性文件
引入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>
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
注意:在使用 <context:property-placeholder>
元素加载外包配置文件功能前,首先需要在 XML 配置的一级标签 <beans>
中添加 context 相关的约束。
④配置bean
<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>
⑤测试
@Test
public void testDataSource() throws SQLException {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
DataSource dataSource = ac.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
bean的作用域
①概念
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
取值 | 含义 | 创建对象的时机 |
---|---|---|
singleton(默认) | 在IOC容器中,这个bean的对象始终为单实例 | IOC容器初始化时 |
prototype | 这个bean在IOC容器中有多个实例 | 获取bean时 |
如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用):
取值 | 含义 |
---|---|
request | 在一个请求范围内有效 |
session | 在一个会话范围内有效 |
②创建类User
package com.atguigu.spring6.bean;
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
// getset方法省略
}
③配置bean
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<bean class="com.atguigu.spring6.bean.User" scope="prototype"></bean>
④测试
@Test
public void testBeanScope(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-scope.xml");
User user1 = ac.getBean(User.class);
User user2 = ac.getBean(User.class);
System.out.println(user1==user2);
}
bean生命周期
①具体的生命周期过程
bean对象创建(调用无参构造器)
给bean对象设置属性
bean的后置处理器(初始化之前)
bean对象初始化(需在配置bean时指定初始化方法)
bean的后置处理器(初始化之后)
bean对象就绪可以使用
bean对象销毁(需在配置bean时指定销毁方法)
IOC容器关闭
②修改类User
public class User {
private String name;
public User() {
System.out.println("生命周期:1、创建对象");
}
public void initMethod(){
System.out.println("生命周期:3、初始化");
}
public void destroyMethod(){
System.out.println("生命周期:5、销毁");
}
}
注意其中的initMethod()和destroyMethod(),可以通过配置bean指定为初始化和销毁的方法
③配置bean
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.spring6.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="1001"></property>
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
<property name="age" value="23"></property>
</bean>
④测试
@Test
public void testLife(){
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
User bean = ac.getBean(User.class);
System.out.println("生命周期:4、通过IOC容器获取bean并使用");
ac.close();
}
⑤bean的后置处理器
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
创建bean的后置处理器:
package com.atguigu.spring6.process;
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("☆☆☆" + beanName + " = " + bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("★★★" + beanName + " = " + bean);
return bean;
}
}
在IOC容器中配置后置处理器:
<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.atguigu.spring6.process.MyBeanProcessor"/>
FactoryBean
①简介
FactoryBean是Spring提供的一种整合第三方框架
的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
②创建类UserFactoryBean
package com.atguigu.spring6.bean;
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
③配置bean
<bean id="user" class="com.atguigu.spring6.bean.UserFactoryBean"></bean>
④测试
@Test
public void testUserFactoryBean(){
//获取IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-factorybean.xml");
User user = (User) ac.getBean("user");
System.out.println(user);
}
基于xml自动装配
自动装配:
根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值
①场景模拟
创建类UserController
package com.atguigu.spring6.autowire.controller;
public class UserController {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void saveUser(){
userService.saveUser();
}
}
创建接口UserService
package com.atguigu.spring6.autowire.service;
public interface UserService {
void saveUser();
}
创建类UserServiceImpl实现接口UserService
package com.atguigu.spring6.autowire.service.impl;
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void saveUser() {
userDao.saveUser();
}
}
创建接口UserDao
package com.atguigu.spring6.autowire.dao;
public interface UserDao {
void saveUser();
}
创建类UserDaoImpl实现接口UserDao
package com.atguigu.spring6.autowire.dao.impl;
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}
②配置bean
使用bean标签的autowire属性设置自动装配效果
自动装配方式:byType
byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null
若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
<bean id="userController" class="com.atguigu.spring6.autowire.controller.UserController" autowire="byType"></bean>
<bean id="userService" class="com.atguigu.spring6.autowire.service.impl.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.atguigu.spring6.autowire.dao.impl.UserDaoImpl"></bean>
自动装配方式:byName
byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
<bean id="userController" class="com.atguigu.spring6.autowire.controller.UserController" autowire="byName"></bean>
<bean id="userService" class="com.atguigu.spring6.autowire.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userServiceImpl" class="com.atguigu.spring6.autowire.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.atguigu.spring6.autowire.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.atguigu.spring6.autowire.dao.impl.UserDaoImpl"></bean>
③测试
@Test
public void testAutoWireByXML(){
ApplicationContext ac = new ClassPathXmlApplicationContext("autowire-xml.xml");
UserController userController = ac.getBean(UserController.class);
userController.saveUser();
}