【Spring】IOC
文章目录
- 【Spring】IOC
- 一、IOC 底层原理
-
- 1.什么是 IOC
- 2.IOC 过程
- 3.IOC 接口
- 4.什么是 Bean 管理
- 三、Bean 管理(基于 XML)
-
- 1.创建对象和注入属性
- 2.注入其他类型的属性
- 3.注入集合属性
- 4.FactoryBean
- 5.bean 作用域
- 6.bean 生命周期
- 7.XML 自动装配
- 8.外部属性文件
- 四、Bean 管理(基于注解)
-
- 1.注解回顾
- 2.Spring 针对 Bean 管理中创建对象提供的注解
- 3.创建对象
- 4.开启组件扫描细节配置
- 5.实现属性注入
- 6.完全注解开发
控制反转,把创建对象过程交给 Spring 进行管理
一、IOC 底层原理
1.什么是 IOC
控制反转(Inversion of Control),把对象创建和对象之间的调用过程都交给 Spring 进行管理
目的:降低耦合度
底层原理:
- 工厂模式
- XML 解析
- 反射技术
2.IOC 过程
第一步 XML 文件配置创建的对象
第二步 创建工厂类
class UserFactory{
public static UserDao getDao(){
String classValue = class属性值; //通过 XML 解析
Class clazz = Class.forName(classValue); //通过反射技术
return (UserDao)clazz.newInstance();
}
}
3.IOC 接口
IOC 的思想基于 IOC 容器,而 IOC 容器底层就是对象工厂
Spring 提供 IOC 容器实现的两种方式
- BeanFactory
- ApplicationContext
BeanFactory
Spring 中 IOC 容器的最基本的实现方式,是 Spring 内部使用的接口,不是设计给开发人员使用的
加载配置文件时不会创建对象,在获取对象时才创建对象
ApplicationContext
BeanFactory 的子接口,提供更多更强大的功能,为开发人员设计使用的
加载配置文件的时候就会创建对象
ApplicationContext 的实现类
-
FileSystemXmlApplicationContext
参数为带盘符路径
-
ClassPathXmlApplicationContext
参数为 src 目录下路径
4.什么是 Bean 管理
Bean 管理指的是 Spring 创建对象和 Spring 注入属性,是 IOC 的一种操作
Bean 管理操作有两种方式
- 基于 XML 配置文件
- 基于注解方式
三、Bean 管理(基于 XML)
1.创建对象和注入属性
在 spring 配置文件中,使用 bean 标签,在标签里添加对应的属性,就可以实现对象创建
- id:唯一标识
- class:类全路径
创建对象时默认执行无参构造,如果没有无参构造会报错
基于 XML 方式注入属性
依赖注入(DI),就是注入属性,是 IOC 的一种实现
第一种方式:在类中新增字段并生成 set 方法,然后在 spring 配置文件中配置对象创建并配置属性注入
<bean id = "book" class = "com.sisyphus.dao.BookDao">
<property name = "bname" value = "易筋经"></property>
<property name = "bauthor" value = "达摩老祖"></property>
</bean>
第二种方式:定义属性、创建属性对应的有参构造,然后在 spring 配置文件中使用有参构造配置对象
<bean id = "book" class = "com.sisyphus.dao.BookDao">
<constructor-arg name = "bname" value = "易筋经"></constructor-arg>
<constructor-arg name = "bauthor" value = "达摩老祖"></constructor-arg>
</bean>
第三种方式:添加 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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
然后在 bean 标签内进行操作,底层实际上还是用的 set 方法
2.注入其他类型的属性
外部 bean
我们有两个类 UserService 和 UserDao 两个类,我们想在 UserService 中调用 UserDao 里的方法
原始的方式是在 UserService 中创建 userDao 对象,然后调用
在 Spring 中我们可以在 UserService 中创建 UserDao 属性,并生成 set 方法,然后在 Spring配置文件中配置
<bean id = "userService" class = "com.sisyphus.service.UserService">
<property name = "userDao" ref = "userDaoImpl"></property>
</bean>
<bean id = "userDaoImpl" class = "com.sisyphus.dao.UserDaoImpl"></bean>
注意:这里 UserDao 是接口,所以要使用实现类 UserDaoImpl
内部 bean
我们有两个类,部门类和员工类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
public class Emp{
private String ename;
private String gender;
//员工属于某一部门,使用属性形式表示
private Dept dept;
public void setEname(String ename){
this.ename = ename;
}
public void setGender(String gender){
this.gender = gender;
}
public void setDept(Dept dept){
this.dept = dept;
然后在 spring 配置文件中进行配置
<bean id = "emp" class = "com.sisyphus.dao.Emp">
<property name = "ename" value = "sisyphus"></property>
<property name = "gender" value = "男"></property>
<property name = "dept">
<bean id = "dept" class = "com.sisyphus.dao.Dept">
<property name = "dname" value = "Java"></property>
</bean>
</property>
</bean>
当然,使用上面外部 bean 的方式也是可以的
级联赋值
还是采用员工类和部门类的例子
第一种方式:
<bean id = "emp" class = "com.sisyphus.dao.Emp">
<property name = "ename" value = "sisyphus"></property>
<property name = "gender" value = "男"></property>
<property name = "dept" ref = "dept"></property>
</bean>
<bean id = "dept" class = "com.sisyphus.dao.Dept">
<property name = "dname" value = "Java"></property>
</bean>
第二种方式:
<bean id = "emp" class = "com.sisyphus.dao.Emp">
<property name = "ename" value = "sisyphus"></property>
<property name = "gender" value = "男"></property>
<property name = "dept" ref = "dept"></property>
<property name = "dept.dname" value = "Java"></property>
</bean>
<bean id = "dept" class = "com.sisyphus.dao.Dept"></bean>
这种方式需要在 Dept 类中生成 dname 的 get 方法
3.注入集合属性
创建类,定义数组、list、map、set 类型属性,生成对应的 set 方法
public class Stu {
//1 数组类型属性
private String[] courses;
//2 list 集合类型属性
private List<String> list;
//3 map 集合类型属性
private Map<String, String> maps;
//4 set 集合类型属性
private Set<String> sets;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
}
在 Spring 配置文件中配置
<bean id="stu" class="com.sisyphus.dao.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>Spring</value>
<value>SpringMVC</value>
<value>SpringBoot</value>
</array>
</property>
<!--list 类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--map 类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"/>
<entry key="PHP" value="php"/>
</map>
</property>
<!--set 类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
在集合内设置对象属性值
<!--创建多个 course 对象-->
<bean id="course1" class="com.sisyphus.dao.Stu">
<property name="cname" value="Spring"/>
</bean>
<bean id="course2" class="com.sisyphus.dao.Stu">
<property name="cname" value="MyBatis"/>
</bean>
<!--注入 list 集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
把集合注入部分提取出来复用
在 Spting 配置文件中引入命名空间 util
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
使用 util 标签完成 list 集合注入提取
<!--1 提取 list 集合类型属性注入-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<!--2 提取 list 集合类型属性注入使用-->
<bean id="book" class="com.sisyphus.dao.Stu">
<property name="list" ref="bookList"></property>
</bean>
4.FactoryBean
Spring 有两种类型的 bean,一种是普通的 bean,还有一种是工厂 bean
普通 bean:在配置文件中定义的 bean 类型就是返回类型
工厂 bean:在配置文件中定义 bean 类型可以和返回类型不一样
定义工厂 bean
第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean,在实现的方法中定义返回的 bean 类型
public class MyBean implements FactoryBean<Course> {
//定义返回 bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("Spring");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
第二步 在 Spring 配置文件中配置
第三步 测试
@Test
public void test1() {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
5.bean 作用域
在 Spring 里,我们可以设置创建的 bean 是单实例还是多实例,即 bean 的作用域
默认情况下,bean 是单实例的,那如何设置多实例呢?
我们可以通过 scope 的值来设置,singleton 为单实例,prototype 为多实例
<bean id="book" class="com.sisyphus.dao.Book" scope = "prototype">
<property name = "list" ref = "bookList"/>
</bean>
singleton 和 prototype 除了一个是单实例,一个是多是例外,还有一点不同:设置 scope 为 singleton 时,加载 spring 配置文件时就会创建单实例对象;设置 scope 为 prototype 时,在调用 getBean 方法时才会创建多实例对象
6.bean 生命周期
生命周期是指对象从创建到销毁的过程
- 通过构造器创建 bean 实例(调用构造函数)
- 为 bean 的属性设置值和对其它 bean 的引用(调用 set 方法)
- 调用 bean 的初始化方法(需要配置初始化的方法)
- bean 可以使用了(对象获取到了)
- 当容器关闭时,调用 bean 的销毁的方法(需要配置销毁的方法)
演示 bean 生命周期
public class Orders {
//无参构造
public Orders() {
System.out.println("第一步 执行无参数构造创建 bean 实例");
}
private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用 set 方法设置属性值");
}
//配置初始化的方法
public void initMethod() {
System.out.println("第三步 执行初始化的方法");
}
//配置销毁的方法
public void destroyMethod() {
System.out.println("第五步 执行销毁的方法");
}
}
<bean id="orders" class="com.sisyphus.dao.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"/>
</bean>
@Test
public void test1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建 bean 实例对象");
System.out.println(orders);
//手动让 bean 实例销毁
context.close();
}
如果加上后置处理器,bean 的生命周期有 7 步
- 通过构造器创建 bean 实例(调用构造函数)
- 为 bean 的属性设置值和对其它 bean 的引用(调用 set 方法)
- 把 bean 实例传递给 bean 后置处理器的方法 postProcessBeforeInitialization
- 调用 bean 的初始化方法(需要配置初始化的方法)
- bean 可以使用了(对象获取到了)
- 把 bean 实例传递给 bean 后置处理器的方法 postProcessAfterInitialization
- 当容器关闭时,调用 bean 的销毁的方法(需要配置销毁的方法)
演示添加后置的处理器
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
7.XML 自动装配
根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
根据属性名称
<bean id="emp" class="com.sisyphus。dao.Emp" autowire="byName">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.sisyphus.dao.Dept"></bean>
根据属性类型
<bean id="emp" class="com.sisyphus.dao.Emp" autowire="byType">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.sisyphus.dao.Dept"></bean>
需要注意,如果有相同类型的属性,会报错,那种情形就只能根据属性名称来自动装配了
此外,一般我们通过注解来实现自动装配,而不是 XML 方式,后面会讲解
8.外部属性文件
如果一个 bean 中的属性很多时,我们可以将属性值写在一个 properties 文件中,有利于修改
数据库连接池
第一步 配置连接池
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
第二步 引入 context 命名空间
<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/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
第三步 创建外部属性文件 jdbc.properties
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/myDB
prop.userName=root
prop.password=root
第四步 引入外部属性文件
四、Bean 管理(基于注解)
1.注解回顾
- 注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值)
- 注解可以作用在类、方法、属性上
- 使用注解的目的是简化 XML 配置
2.Spring 针对 Bean 管理中创建对象提供的注解
- @Component 普通注解
- @Service 用于业务逻辑层
- @Controller 用于控制层
- @Repository 用于持久层
只是为了开发人员看起来更加清晰,其实并没有本质的区别
3.创建对象
第一步 配置 XML 头文件
<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/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
第二步 开启组件扫描,扫描 com.sisyphus.service 下所有文件,如果要扫描多个包,用逗号隔开
第三步 创建类并添加注解
@Component(value = "userService") //<bean id="userService" class=".."/>
public class UserService {
public void add() {
System.out.println("service add.......");
}
}
根据注解的规则 ”value = “ 其实可以省略
4.开启组件扫描细节配置
示例1
use-default-filters=“false” 表示现在不使用默认 filter,自己配置 filter
context:include-filter 设置扫描哪些内容
<context:component-scan base-package="com.sisyphus" ues-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.sterotype.Controller"/>
</context:component-scan>
示例2
use-default-filters=“false” 表示现在不使用默认 filter,自己配置 filter
context:exclude-filter 设置不扫描哪些内容
<context:component-scan base-package="com.sisyphus" ues-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.sterotype.Controller"/>
</context:component-scan>
5.实现属性注入
Spring 提供了 3 个注解
- @Autowired 根据属性类型进行自动注入
- @Qualifier 根据属性名称进行注入
- @Value 注入普通类型属性
javax 也提供了一个注解,但是不推荐使用
- @Resource 可以根据类型注入,也可以根据名称注入
@AutoWired
第一步 创建 UserDao 接口、其实现类 UserDaoImpl、UserService
第二步 在 UserDaoImpl 添加 @Repository 注解,在 UserService 添加 @Service 注解
第三步 使用注解在 UserService 注入 UserDao 对象
@Service
public class UserService {
//定义 dao 类型属性
//不需要添加 set 方法
//添加注入属性注解
@Autowired
private UserDao userDao;
public void add() {
System.out.println("Service add...");
userDao.add();
}
}
@Qualifier
@Resource
@Resource //根据类型进行注入
private UserDao userDao;
@Resource(name = "userDaoImpl") //根据名称进行注入
private UserDao userDao;
@Value
@Service
public class UserService {
@Value("Sisyphus")
private String name;
@Autowired
private UserDao userDao;
public void add() {
System.out.println("Service add...");
userDao.add();
}
}
6.完全注解开发
我们已经可以通过注解创建对象和注入属性了,只有开启组件扫描需要使用 XML 文件,接着我们来讲解如何将这一步也用注解替代
第一步 创建配置类,替代 XML 文件
@Configuration //作为配置类,替代 xml 配置文件
@ComponentScan(basePackages = {"com.sisyphus"}) //数组
public class SpringConfig {
}
第二步 编写测试类
@Test
public void testService2() {
//加载配置类
ApplicationContext contex = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
但是实际上,我们用的更多的是通过 SpringBoot 来将这个操作进一步简化,我会在【SpringBoot】专栏中讲解