天天看点

【Spring】IOC【Spring】IOC一、IOC 底层原理三、Bean 管理(基于 XML)四、Bean 管理(基于注解)

【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 生命周期

生命周期是指对象从创建到销毁的过程

  1. 通过构造器创建 bean 实例(调用构造函数)
  2. 为 bean 的属性设置值和对其它 bean 的引用(调用 set 方法)
  3. 调用 bean 的初始化方法(需要配置初始化的方法)
  4. bean 可以使用了(对象获取到了)
  5. 当容器关闭时,调用 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 步

  1. 通过构造器创建 bean 实例(调用构造函数)
  2. 为 bean 的属性设置值和对其它 bean 的引用(调用 set 方法)
  3. 把 bean 实例传递给 bean 后置处理器的方法 postProcessBeforeInitialization
  4. 调用 bean 的初始化方法(需要配置初始化的方法)
  5. bean 可以使用了(对象获取到了)
  6. 把 bean 实例传递给 bean 后置处理器的方法 postProcessAfterInitialization
  7. 当容器关闭时,调用 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】专栏中讲解