Spring——IOC


目录
  • 什么是IOC
  • BeanFactory
  • IOC 操作 Bean 管理
  • XML配置文件实现Bean管理
    • 创建对象
    • 属性注入
      • setter方法注入
      • 构造方法注入
      • p名称空间注入
    • FactoryBean
    • Bean的作用域
    • Bean的生命周期
    • 自动装配
    • 外部属性文件引用
  • 注解实现Bean管理
    • 创建对象
    • 注解扫扫描配置
    • 属性注入
    • 纯注解开发

本文章使用的spring版本为5.2.18

什么是IOC

  • 控制反转(IOC)就是把对象创建和对象之间的调用过程,交给 Spring 进行管理
  • 使用 IOC 目的:为了耦合度降低
  • IOC底层使用了xml 解析、工厂模式、反射

BeanFactory

  1. IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
  2. Spring 提供 IOC 容器实现两种方式:(两个接口)
  • BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供给开发人员进行使用

    • 加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
  • ApplicationContextBeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用

    • 加载配置文件时候就会把在配置文件对象进行创建
  1. ApplicationContext 接口的实现类
  • ClassPathXmlApplicationContext
    通过读取类路径下的spring配置文件来创建容器
// 类路径下的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
  • FileSystemXmlApplicationContext
    通过读取在磁盘中的spring配置文件来创建容器
// 磁盘路径的配置文件
ApplicationContext context = new FileSystemXmlApplicationContext("E:\\IdeaProjects\\Spring\\01_Hello\\src\\application.xml");

IOC 操作 Bean 管理

  • Bean 管理指的是两个操作

    • Spring 创建对象
    • Spirng 注入属性
  • Bean 管理的两种方式

    • 基于 xml 配置文件方式实现
    • 基于注解方式实现

XML配置文件实现Bean管理

使用到的实体类

public class User {
    private Integer id;
    private String name;

    public void sayHello(){
        System.out.println("hello!");
    }
}

创建对象

在配置文件中使用bean标签来注册对象


  • id:是该对象的唯一标识,通过id属性来获取对象
  • class:是需要创建的对象的全类名

获取对象的方式是通过容器的getBean方法传入id来获取我们子配置文件中注册的bean

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    User user = context.getBean("user", User.class);
    user.sayHello();
}

spring是通过无参构造方法来创建对象的,可以通过在无参构造中打印信息证明

如果覆盖掉无参构造方法时就会报错org.springframework.beans.factory.BeanCreationException

public User(Integer id, String name) {
    this.id = id;
    this.name = name;
}

所以当我们自己写了构造方法时,一定需要写一个无参构造

属性注入

属性的注入又叫依赖注入(DI)
属性注入有两种方式

  • 通过构造方法注入
  • 通过setter方法注入

setter方法注入

  • 使用setter方法注入就一定要保证我们的Bean中有setter方法
  • 并且setter的方法一定要符合规setXxx的方式,Xxx是属性名首字母大写
  • 因为注入的时候是通过setter方法的名称来获取属性名称注入的,一旦setter方法不符合就会注入失败

通过xml配置为user注入属性


        
        

  • property标签是通过setter方法注入
  • name是属性的名称
  • value是属性的值

这样就为我们的user对象注入了属性
这样只是基本属性的注入,其他的一些特殊的属性如何注入呢?

引用类型注入

public class Teacher {
    private Integer id;
    private String name;

    //setter...
}

为User类中添加一个Teacher类型的属性,并且添加setter方法

private Teacher teacher;

通过外部bean注入




	

首先我们注册了一个Teacher对象
在user的属性注入中,使用了ref="teacher"来引用我们注册的Teacher对象,ref属性后面的值就是我们注册的Teacher对象的id

通过内部bean注入


	
		
		
	

直接在property标签内注册一个bean来注入

级联赋值


	
	



	

在外部bean注入时,可以注册的时候就为引用的对象赋值

另一种为引用对象赋值的方式


	



通过.来对引用类型属性的属性来赋值

注意:使用这种方法赋值时,需要有getter方法,这里的Teacher类中必须要有对应的getter方法


空值注入

如果说是要注入空字符串,我们可以什么都不写就可以达到目的


如果是要注入null值可以使用null标签来实现


	


特殊符号注入

当我们需要注入的属性值中包含特殊符号时,需要特殊处理
例如向name属性中注入值<>

使用转义符


使用CDATA注入


	>]]>

这两种方式都可以实现特殊符号的注入


map注入

private Map other;

	
		
		
	

通过使用map标签来为Map类型的属性注入


数组注入

private String[] strings;

	
		aaa
		bbb
		ccc
	


list注入

private List hobby;

	
		写代码
		撩妹
	

使用list标签来为List类型的属性注入,value标签中写上值

当list中存储的类型为引用类型时

private List userList;

	
		
		
		
	





list标签中用ref来引用对象

或者直接在list标签中使用bean标签


	
		
			
		
		
			
		
		
			
		
	


set集合注入

private Set set;

	
		1
		1
		2
		3
	


提取公共集合

首先需要导入util名称空间



写一个公共的list


	
	
	

使用这个公共的list
为userList属性注入这个公共的list


	

其他类型也是如此

private Map userMap;

	
	
	


	


properties注入

private Properties properties;

	
		wcy666
		123456
	

构造方法注入

public User(Integer id, String name) {
	this.id = id;
	this.name = name;
}

使用构造方法注入属性之后,创建对象也是通过该有参构造来创建

通过构造方法中参数的名称来注入


	
	


通过构造方法中参数的位置索引来注入


	
	


通过构造方法中参数的类型来注入


	
	

使用构造方法注入其他类型的属性时,和使用setter方法相同

p名称空间注入

使用p名称空间注入时,需要在配置文件头部导入p名称空间的规范

xmlns:p="http://www.springframework.org/schema/p"

使用p:属性名的方式来注入属性

这种注入方法也是通过setter方法来注入属性的

FactoryBean

Spring 有两种类型的 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)

  • 普通 bean:在配置文件中定义 bean 类型就是返回类型

  • 工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
    BeanFactory是IOC容器的基本实现方式,FactoryBean是Spring中的一种Bean类型

普通Bean
编写一个类MyBeanFactory

public class MyFactoryBean{

}

再去配置文件中注册


获取这个Bean,此时获取的是MyFactoryBean类型的对象,这就是普通类型的bean

@Test
public void test2() {
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    MyFactoryBean myFactoryBean = context.getBean("myFactoryBean", MyFactoryBean.class);
    System.out.println(myFactoryBean);
}

FactoryBean

继续使用刚刚的类,实现Factory接口

public class MyFactoryBean implements FactoryBean {

    /**
     * 通过该方法返回所需的对象
     */
    @Override
    public User getObject() throws Exception {
        return new User(1, "wcy");
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}

此时这个类就是FactoryBean

  • 泛型指定这个FactoryBean返回什么类型的对象
  • 通过getObject方法来返回这个对象

注册


此时获取的就是一个User对象了

@Test
public void test2() {
	ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
	User user = context.getBean("myFactoryBean", User.class);
	System.out.println(user);
}

Bean的作用域

spring中默认情况下,注册的对象都是单例的,全局唯一


@Test
public void test3() {
	ApplicationContext context = new ClassPathXmlApplicationContext("application1.xml");
	User user1 = context.getBean("user", User.class);
	User user2 = context.getBean("user", User.class);
	System.out.println(user1==user2);  // true
}

通过scope属性可以更改bean的作用域,socpe的常用属性值有两个

  • singleton 单例模式,对象是全局唯一的,这也是默认值
  • prototype 多实例模式,每一次获取的对象都是不一样的

bean的scope属性为singleton和prototype的区别

  • singleton 单实例,prototype 多实例
  • scope 值是 singleton 时,加载 spring 配置文件时候就会创建单实例对象
  • scope 值是 prototype 时,不是在加载 spring 配置文件时候创建就对象,而是在调用getBean方法时才创建对象

@Test
public void test3() {
	ApplicationContext context = new ClassPathXmlApplicationContext("application1.xml");
	User user1 = context.getBean("user", User.class);
	User user2 = context.getBean("user", User.class);
	System.out.println(user1==user2);  // false
}

Bean的生命周期

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

bean的生命周期

  1. 通过构造器创建 bean 实例(构造方法)
  2. 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
  3. 调用 bean 的初始化的方法(需要进行配置初始化的方法)
  4. bean 的使用(对象获取到了)
  5. 容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

创建一个Blog类查看bean的生命周期

public class Blog {
    private Integer id;
    private String title;

    public Blog() {
        System.out.println("无参构造方法");
    }

    public Blog(Integer id, String title) {
        this.id = id;
        this.title = title;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        System.out.println("set方法");
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * 初始化方法 名字没有要求
     */
    public void init(){
        System.out.println("初始化方法");
    }

    /**
     * 销毁方法 名字没有要求
     */
    public void destroy(){
        System.out.println("销毁方法");
    }

    @Override
    public String toString() {
        return "使用了Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

注册bean


	
	

获取bean,并且使用

@Test
public void test1(){
	// 由于Application接口中没有close方法,所以使用它的实现类ClassPathXmlApplicationContext
	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application1.xml");
	Blog blog = context.getBean("blog", Blog.class);
	System.out.println(blog);
	context.close();
}

执行结果

和我们说的生命周期顺序符合

当我们的bean有后置处理器时,生命周期变为了7步

  1. 通过构造器创建 bean 实例(构造方法)
  2. 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
  3. 把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
  4. 调用 bean 的初始化的方法(需要进行配置初始化的方法)
  5. 把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
  6. bean 的使用(对象获取到了)
  7. 容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

添加bean的后置处理器,编写一个类实现BeanPostProcessor接口

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化方法之前执行处理");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化方法之后执行处理");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

注册这个后置处理器,后置处理器会在该容器中所有的bean的初始化方法前后执行


执行结果

自动装配

自动装配是根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入

使用 bean 标签属性 autowire,配置自动装配

autowire 属性有两个值:

  • byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
  • byType 根据属性类型注入
public class Student {
    private Teacher teacher;

    //getter and setter
}


public class Teacher {

    private String name;

    //getter and setter
}

byName自动装配

使用byName自动装配时,spring会根据属性的名字取查找容器中注册的bean的id值,如果有一样的就会注入


        



  • 使用byName注入时,如果名字匹配上了但是类型不匹配会注入失败
  • 根据属性名查询,其实也是通过setter方法中的名字来查找的,如果setter方法中名字不匹配,就会注入失败

byType自动装配

使用byName自动装配时,spring会根据属性的类型取查找容器中注册的bean的类型,如果有一样的就会注入


        



  • 使用byType自动装配时,对属性的名字没有要求
  • 但是同类型的对象必须要唯一,如果不唯一就会报错,这里的例子中Teacher类型的对象必须唯一

外部属性文件引用

一般情况下,我们需要配置数据源时,把值固定在xml文件中,不利于更改


	
	
	
	

在spring中,可以通过引入外部配置文件的方法来动态的配置

首先需要导入context名称空间

<?xml version="1.0" encoding="UTF-8"?>


类路径下编写一个配置文件db.properties

jdbc.username=root
jdbc.password=1234
jdbc.url=jdbc:mysql://localhost:3306/wcy
jdbc.driver=com.mysql.jdbc.Driver

在xml中引用这个外部配置文件




``` ``` 在xml文件中通过`${key}`的方式获取外部配置文件中的属性值

注解实现Bean管理

什么是注解
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值..)
(2)使用注解,注解可以作用在类上面,方法上面,属性上面
(3)使用注解目的:简化 xml 配置

Spring 针对 Bean 管理中创建对象提供的

  • @Component
  • @Service
  • @Controller
  • @Repository

上面四个注解功能都可以用来创建 bean 实例,通常为了区分业务模块会在不同的层使用不同的注解

使用spring的注解功能需要导入spring-aop依赖
spring-aop-5.2.18.RELEASE.jar

创建对象

编写一个UserService类

@Service(value = "userService")
public class UserService {
    public void sayHello(){
        System.out.println("hello world!");
    }
}

@Service(value = "userService")的作用就相当于

  • 注解的value属性就和id一样,默认值是类名的首字母小写

配置文件
首先要引入context名称空间

<?xml version="1.0" encoding="UTF-8"?>



开启注解扫描base-package的值就是需要扫描的包





获取对象

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.sayHello();
}

注解扫扫描配置

指定要扫描那些注解

扫描@Controller注解和@Service注解


	
	

  • use-default-filters:是否使用默认的filters,默认值为true,扫描创建Bean的所有的注解,false就是不使用,不会扫描任何注解
  • context:include-filter: 配置需要扫描的注解,type是类型即注解,expression是注解的的全类名

指定不扫描那些注解

不扫描@Controller注解


	

  • context:exclude-filter: 配置不需要扫描的注解,type是类型即注解,expression是注解的的全类名

属性注入

首先创建一个UserServiceUserDao,注册为Bean

@Repository(value = "userDao")
public class UserDao {
    public void sayHello(){
        System.out.println("hello dao!");
    }
}

UserService中需要UserDao的实例

@Service(value = "userService")
public class UserService {

    UserDao userDao;

    public void sayHello(){
        System.out.println("hello service!");
        userDao.sayHello();
    }
}

@Autowired注入

  • @Autowired是通过属性的类型(byType)来进行自动注入的
  • @Autowired不需要setter方法就可以注入
  • @Autowired也可以放在setter方法上

需要向UserService中注入UserDao只需要加上注解即可

@Autowired
UserDao userDao;

或者说放在setter方法上同样可以注入,没有setter方法也可以注入

@Autowired
public void setUserDao(UserDao userDao) {
	this.userDao = userDao;
}

@Qualifier注入

  • @Qualifier是根据名称(byName)进行注入
  • @Qualifier需要和@Autowired一起使用
  • 不需要setter方法

使用该方法一样可以注入

@Autowired
@Qualifier("userDao")
UserDao userDao;

@Resource注入

  • @Resource可以根据类型注入,也可以根据名称注入
  • @Resource不是spring提供的注解,是javax包下的

通过类型注入

@Resource
UserDao userDao;

通过名称注入

@Resource(name = "userDao")
UserDao userDao;

@Value注入

  • @Value注解用于注入普通类型的属性
  • 不需要setter方法
@Value("1")
private Integer id;
@Value("wcy")
private String name;

纯注解开发

纯注解开发就是完全的舍弃配置文件,通过注解的方式来开发

首先需要有一个配置类

@Configuration
@ComponentScan(basePackages = {"com.wcy"})
public class SpringConfig {

}
  • @Configuration表示这是一个配置类
  • @ComponentScan 指定扫描的包

上面的配置就相当于在配置文件中的


但是现在我们完全舍弃了配置文件

获取配置的方式也发生改变

@Test
public void test2(){
	ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
	UserService userService = context.getBean("userService", UserService.class);
	userService.sayHello();
}
  • 通过使用AnnotationConfigApplicationContext类,传入我们的配置类的类对象来获取容器
  • 获取Bean的方式依然一样
  • 只有配置文件变为了配置类,其他的全部一样

结果和使用配置文件的作用一样

IOC的内容就基本告一段落~