Spring——IOC

本文章使用的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标签来注册对象

<bean id="user" class="com.wcy.pojo.User"></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注入属性

<bean id="user" class="com.wcy.pojo.User">
        <property name="id" value="1"/>
        <property name="name" value="wcy"/>
</bean>
  • property标签是通过setter方法注入
  • name是属性的名称
  • value是属性的值

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

引用类型注入

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

    //setter...
}

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

private Teacher teacher;

通过外部bean注入

<bean id="teacher" class="com.wcy.pojo.Teacher"></bean>

<bean id="user" class="com.wcy.pojo.User">
	<property name="teacher" ref="teacher"/>
</bean>

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

通过内部bean注入

<property name="teacher">
	<bean class="com.wcy.pojo.Teacher">
		<property name="id" value="1"/>
		<property name="name" value="王老师"/>
	</bean>
</property>

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

级联赋值

<bean id="teacher" class="com.wcy.pojo.Teacher">
	<property name="id" value="1"/>
	<property name="name" value="王老师"/>
</bean>

<bean id="user" class="com.wcy.pojo.User">
	<property name="teacher" ref="teacher"/>
</bean>

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

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

<property name="teacher">
	<bean class="com.wcy.pojo.Teacher"/>
</property>
<property name="teacher.id" value="1"/>
<property name="teacher.name" value="王老师"/>

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

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


空值注入

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

<property name="name" value=""/>

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

<property name="name">
	<null/>
</property>

特殊符号注入

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

使用转义符

<property name="name" value="&lt;&lt;wcy&gt;&gt;">

使用CDATA注入

<property name="name">
	<value><![CDATA[<<wcy>>]]></value>
</property>

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


map注入

private Map<String, Object> other;
<property name="other">
	<map>
		<entry key="age" value="12"/>
		<entry key="sex" value="男"/>
	</map>
</property>

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


数组注入

private String[] strings;
<property name="strings">
	<array>
		<value>aaa</value>
		<value>bbb</value>
		<value>ccc</value>
	</array>
</property>

list注入

private List<String> hobby;
<property name="hobby">
	<list>
		<value>写代码</value>
		<value>撩妹</value>
	</list>
</property>

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

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

private List<User> userList;
<property name="userList">
	<list>
		<ref bean="user1"/>
		<ref bean="user2"/>
		<ref bean="user3"/>
	</list>
</property>

<bean id="user1" class="com.wcy.pojo.User"/>
<bean id="user2" class="com.wcy.pojo.User"/>
<bean id="user3" class="com.wcy.pojo.User"/>

list标签中用ref来引用对象

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

<property name="userList">
	<list>
		<bean id="user1" class="com.wcy.pojo.User">
			<property name="id" value="1"/>
		</bean>
		<bean id="user2" class="com.wcy.pojo.User">
			<property name="id" value="2"/>
		</bean>
		<bean id="user3" class="com.wcy.pojo.User">
			<property name="id" value="3"/>
		</bean>
	</list>
</property>

set集合注入

private Set<Integer> set;
<property name="set">
	<set value-type="java.lang.Integer">
		<value>1</value>
		<value>1</value>
		<value>2</value>
		<value>3</value>
	</set>
</property>

提取公共集合

首先需要导入util名称空间

<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"
       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 https://www.springframework.org/schema/util/spring-util.xsd">
</beans>

写一个公共的list

<util:list id="userList">
	<ref bean="user2"/>
	<ref bean="user3"/>
	<ref bean="user4"/>
</util:list>

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

<bean id="teacher1" class="com.wcy.pojo.Teacher">
	<property name="userList" ref="userList"/>
</bean>

其他类型也是如此

private Map<String, User> userMap;
<util:map id="userMap">
	<entry key="1" value-ref="user2"/>
	<entry key="2" value-ref="user3"/>
	<entry key="3" value-ref="user4"/>
</util:map>
<bean id="teacher1" class="com.wcy.pojo.Teacher">
	<property name="userMap" ref="userMap"/>
</bean>

properties注入

private Properties properties;
<property name="properties">
	<props>
		<prop key="username">wcy666</prop>
		<prop key="password">123456</prop>
	</props>
</property>

构造方法注入

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

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

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

<bean id="user2" class="com.wcy.pojo.User">
	<constructor-arg name="id" value="1"/>
	<constructor-arg name="name" value="wcy"/>
</bean>

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

<bean id="user3" class="com.wcy.pojo.User">
	<constructor-arg index="0" value="2"/>
	<constructor-arg index="1" value="wcy"/>
</bean>

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

<bean id="user4" class="com.wcy.pojo.User">
	<constructor-arg type="java.lang.Integer" value="1"/>
	<constructor-arg type="java.lang.String" value="wcy"/>
</bean>

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

p名称空间注入

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

xmlns:p="http://www.springframework.org/schema/p"
<bean id="user7" class="com.wcy.pojo.User" p:name="wcy" p:id="1" p:teacher-ref="teacher"/>

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

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

FactoryBean

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

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

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

普通Bean
编写一个类MyBeanFactory

public class MyFactoryBean{

}

再去配置文件中注册

<bean id="myFactoryBean" class="com.wcy.pojo.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<User> {

    /**
     * 通过该方法返回所需的对象
     */
    @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方法来返回这个对象

注册

<bean id="myFactoryBean" class="com.wcy.pojo.MyFactoryBean"/>

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

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

Bean的作用域

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

<bean id="user" class="com.wcy.pojo.User"/>
@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方法时才创建对象
<bean id="user" class="com.wcy.pojo.User" scope="prototype"/>
@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 id="blog" class="com.wcy.pojo.Blog" init-method="init" destroy-method="destroy">
	<property name="id" value="1"/>
	<property name="title" value="java入门"/>
</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();
}

执行结果
image
和我们说的生命周期顺序符合

当我们的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的初始化方法前后执行

<bean id="myBeanPostProcessor" class="com.wcy.pojo.MyBeanPostProcessor"/>

执行结果

image

自动装配

自动装配是根据指定装配规则(属性名称或者属性类型),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值,如果有一样的就会注入

<bean id="teacher" class="com.wcy.autowire.Teacher">
        <property name="name" value="王老师"/>
</bean>

<bean id="student" class="com.wcy.autowire.Student" autowire="byName"/>
  • 使用byName注入时,如果名字匹配上了但是类型不匹配会注入失败
  • 根据属性名查询,其实也是通过setter方法中的名字来查找的,如果setter方法中名字不匹配,就会注入失败

byType自动装配

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

<bean id="teacher" class="com.wcy.autowire.Teacher">
        <property name="name" value="王老师"/>
</bean>

<bean id="student" class="com.wcy.autowire.Student" autowire="byType"/>
  • 使用byType自动装配时,对属性的名字没有要求
  • 但是同类型的对象必须要唯一,如果不唯一就会报错,这里的例子中Teacher类型的对象必须唯一

外部属性文件引用

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

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<property name="name" value="root"/>
	<property name="password" value="123456"/>
	<property name="driver" value="com.mysql.jdbc.Driver"/>
	<property name="url" value="jdbc:mysql://localhost:3306/wcy"/>
</bean>

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

首先需要导入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 https://www.springframework.org/schema/context/spring-context.xsd">
</beans>

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

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

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

<!--引用外部配置文件-->
<context:property-placeholder location="classpath:db.properties"/>

``` ``` 在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")的作用就相当于<bean id="userService" class="com.wcy.service.UserService"/>

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

配置文件
首先要引入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>

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


<!--
    如果想精确扫多个包,可以使用逗号隔开包名
    <context:component-scan base-package="com.wcy.dao,com.wcy.service"/>
-->

<context:component-scan base-package="com.wcy"/>

获取对象

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

注解扫扫描配置

指定要扫描那些注解

扫描@Controller注解和@Service注解

<context:component-scan base-package="com.wcy" use-default-filters="false">
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
  • use-default-filters:是否使用默认的filters,默认值为true,扫描创建Bean的所有的注解,false就是不使用,不会扫描任何注解
  • context:include-filter: 配置需要扫描的注解,type是类型即注解,expression是注解的的全类名

指定不扫描那些注解

不扫描@Controller注解

<context:component-scan base-package="com.wcy">
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  • 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 指定扫描的包

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

<context:component-scan base-package="com.wcy"/>

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

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

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

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

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

posted @ 2021-12-07 11:01  茶音白  阅读(60)  评论(0)    收藏  举报