Spring-IOC

IOC:控制反转,将创建对象的操作交给Spring处理,要使用某个对象,只需要从Spring容器中获取,主要目的是为了降低耦合

1. 示例

  • 创建一个maven项目

  • 在pom文件中引入spring的依赖spring-context,context依赖了spring的4大核心包,spring-core,spring-beans,spring-core,spring-expression

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>

  • 编写一个类Hello
1
2
3
4
5
6
7
8
9
10
11
package cn.aacopy.learn.spring;

/**
* @author cmyang
* @date 2022/9/9 0:14
*/
public class Hello {
public void hello() {
System.out.println("Hello World");
}
}
  • 编写一个配置bean的XML文件bean.xml放在classpath路径下
1
2
3
4
5
6
<?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="hello" class="cn.aacopy.learn.spring.Hello"></bean>
</beans>
  • 在main方法中获取hello对象,并调用hello方法
1
2
3
4
5
public static void main( String[] args ) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
Hello hello = applicationContext.getBean(Hello.class);
hello.hello();
}

2. 原理

通过xml解析,工厂模式,反射来实现

A类中引用B类的对象,最原始的方式是在A类中new一个B类的对象进行使用,这样B发生改变可能会影响到A类,为了降低耦合度,可以创建一个获取B对象的工厂类,A类需要B类对象时,只需要从工厂类中获取,这样就不需要关系B类是怎么创建的,降低了耦合度。在工厂类中,需要创建类的对象,可以统一通过从配置文件中获取全类名称和初始化参数,通过反射的方式来创建类,避免硬编码,所有需要通过工厂来创建的类,统一写在配置文件中

  • 获取IOC容器的两种方式

    • BeanFactory和ApplicationContext

      1
      2
      3
      4
      5
      6
          public static void main( String[] args ) {
      BeanFactory applicationContext = new ClassPathXmlApplicationContext("bean.xml");
      // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
      Hello hello = applicationContext.getBean(Hello.class);
      hello.hello();
      }
    • BeanFactory是IOC容器的基础实现,一般在spring内部使用,在加载配置文件的时候不会立即创建对象,在获取的bean对象的时候创建

    • ApplicationContext是BeanFactory的子接口,提供了更多的功能,在加载配置文件的时候,会立即创建对象

    • 继承关系

Bean管理:(1)创建对象,(2)注入属性

Bean管理的两种方式:(1)基于XML,(2)基于注解

3. 管理Bean

3.1 基于XML方式

<bean id="hello" class="cn.aacopy.learn.spring.Hello"></bean>

  • id:bean的唯一标识
  • class:类全路径

3.1.1 创建对象

spring创建对象时,默认调用类的无参构造器进行创建

3.1.2 注入属性

注入属性有两种方式,(1)通过调用set方法,(2)通过有参构造器

  • set方式注入

    • 创建一个User对象,包含name,age两个属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package cn.aacopy.learn.spring;

    /**
    * @author cmyang
    * @date 2022/9/9 22:17
    */
    public class User {
    private String name;
    private Integer age;

    public void setName(String name) {
    this.name = name;
    }

    public void setAge(Integer age) {
    this.age = age;
    }

    @Override
    public String toString() {
    return "User{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }
    • 在bean的xml配置文件中配置User,通过property配置,spring可以通过name找到对应的属性,并将value的值赋给属性
    1
    2
    3
    4
    <bean id="user" class="cn.aacopy.learn.spring.User">
    <property name="name" value="小七"></property>
    <property name="age" value="18"></property>
    </bean>
    • 编写测试类
    1
    2
    3
    4
    5
    public static void main( String[] args ) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    User user = applicationContext.getBean(User.class);
    System.out.println(user); //User{name='小七', age=18}
    }
  • 有参构造器

    • 在上面的User类中添加全参和无参构造器
    1
    2
    3
    4
    5
    6
    7
    public User() {
    }

    public User(String name, Integer age) {
    this.name = name;
    this.age = age;
    }
    • 在bean的xml配置文件中通过constructor-arg指定构造器的参数
    1
    2
    3
    4
    <bean id="user" class="cn.aacopy.learn.spring.User">
    <constructor-arg name="name" value="小七"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    </bean>

3.1.3 特殊值的注入

  • 在XML的配置中,如果要设置对应的值为null,可以使用<null/>,例如:<constructor-arg name="name"><null/></constructor-arg>

  • 如果配置的值有特殊符号,可以通过<![CDATA[]]>包裹

  • 如果需要引用其他bean,需要使用ref属性,例如:<property name="hello" ref="hello"></property>

  • 注入数组

1
2
3
4
5
6
<property name="arr">
<array>
<value>aa</value>
<value>bb</value>
</array>
</property>
  • 注入list
1
2
3
4
5
6
<property name="list">
<list>
<value>aa</value>
<value>bb</value>
</list>
</property>
  • 注入map
1
2
3
4
5
6
<property name="map">
<map>
<entry key="aa" value="AA"></entry>
<entry key="bb" value="BB"></entry>
</map>
</property>

3.2 基于注解的方式

3.2.1 创建对象

添加context命名空间

在bean.xml中开启注解扫描,base-package配置扫描的路径

<context:component-scan base-package="cn.aacopy"></context:component-scan>

使用:@Component,@Controller,@Service,@Repository等注解标识为spring容器的bean

  • 只扫描固定的注解下的类
1
2
3
<context:component-scan base-package="cn.aacopy" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  • 排除注解下的类
1
2
3
<context:component-scan base-package="cn.aacopy">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

3.2.2 属性注入

  • @Autowired
    • 根据类型进行装配
    • 如果同一个类型有多个实现类的bean,需要使用@Qualifier指定需要注入的bean名称
  • @Resource
    • 根据名称进行装配 (参数name指定bean的名称)
    • 也可以根据类型装配
  • @Value
    • 注入普通类型的值

3.2.3 通过注解方式

  • 编写一个配置类来代替bean.xml配置
1
2
3
4
@Configuration
@ComponentScan(basePackages = "cn.aacopy")
public class MyConfig {
}
  • 获取容器的方式改为注解方式
1
2
3
4
5
public static void main( String[] args ) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Hello hello = applicationContext.getBean(Hello.class);
hello.hello();
}

4. Bean类型

bean有两种类型,一种时普通bean,一种是工厂bean

  • 普通bean

    • 返回的就是创建的bean的类型
  • 工厂bean

    • 需要实现FactoryBean接口,其中getObject就是返回的bean对象,对象类型可以用泛型定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class MyFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
    return new User();
    }

    @Override
    public Class<?> getObjectType() {
    return User.class;
    }
    }
    1
    <bean name="myFactoryBean" class="cn.aacopy.learn.spring.MyFactoryBean"></bean>
    1
    2
    3
    4
    5
    public static void main( String[] args ) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    User user = applicationContext.getBean("myFactoryBean", User.class);
    System.out.println(user);
    }

5. Bean作用域

singleton:单例, 默认值,prototype:多例,request,session,global session

通过scope配置

6. Bean生命周期

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

  • 基本过程
    • 通过无参构造器创建bean对象
    • 调用set方法设置属性和其他对象
    • 调用初始化方法(需要设置初始化方法名)
    • 使用bean
    • 容器关闭时,调用销毁方法(需要设置销毁方法名)

在第三步初始化方法的前后,可以执行后置处理器,需要实现BeanPostProcessor接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class User {

private String name;

public User() {
System.out.println("调用构造器创建对象");
}

public void setName(String name) {
System.out.println("设置属性");
this.name = name;
}

public void say() {
System.out.println(name + ": Hello World");
}

public void init() {
System.out.println("初始化方法");
}

public void destroy() {
System.out.println("销毁方法");
}
}

创建后置处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBeanPostProcessor 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;
}
}

注入bean

1
2
3
4
5
<bean id = "user" class="cn.aacopy.tools.mytest.spring.User" init-method="init" destroy-method="destroy">
<property name="name" value="aacopy"></property>
</bean>

<bean class="cn.aacopy.tools.mytest.spring.MyBeanPostProcessor"></bean>

执行方法

1
2
3
4
5
6
7
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
User user = applicationContext.getBean(User.class);
user.say();
//关闭容器
((ClassPathXmlApplicationContext) applicationContext).close();
}

执行后的结果:

1
2
3
4
5
6
7
8
调用构造器创建对象
设置属性
初始化前置方法
初始化方法
初始化后置方法
aacopy: Hello World
15:46:51.601 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@b684286, started on Mon Sep 12 15:46:51 CST 2022
销毁方法

7. 自动装配

7.1 通过XML方式

  • 在User对象中添加属性private School school;
  • 添加School的bean
  • 修改User的配置文件进行自动装配,有两种类型,一种是byType,一种是byName
1
2
3
4
5
<bean id = "user" class="cn.aacopy.tools.mytest.spring.User" autowire="byName">
<property name="name" value="aacopy"></property>
</bean>

<bean id="school" class="cn.aacopy.tools.mytest.spring.School"></bean>

8. 引入配置文件数据

8.1 通过XML方式

  • 添加配置文件:aacopy.properties,放在classpath下面
1
2
aacopy.aa=AA
aacopy.bb=123
  • 在User对象里设置name,age两个属性,添加set方法
1
2
private String name;
private Integer age;
  • 配置bean.xml,首先需要用到context命名空间,要在头部引入,导入配置文件,使用${}获取配置文件中的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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">

<context:property-placeholder location="aacopy.properties"></context:property-placeholder>

<bean id = "user" class="cn.aacopy.tools.mytest.spring.User">
<property name="name" value="${aacopy.aa}"></property>
<property name="age" value="${aacopy.bb}"></property>
</bean>
</beans>