2、IOC

IOC与DI

Spring的IOC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)是Spring框架的核心概念,用于实现松耦合和可维护的应用程序。

基本概念

底层原理

  1. 读取配置:Spring容器会读取配置文件(如XML配置文件)或注解来获取Bean定义和依赖关系的信息。
  2. 创建Bean实例:Spring容器根据Bean定义使用反射机制实例化Bean对象。
  3. 属性注入:Spring容器根据配置中的依赖关系,将所需的依赖对象注入到目标Bean中,可以通过构造函数注入、Setter方法注入或接口实现注入来实现依赖注入。
  4. 初始化和生命周期管理:Spring容器对Bean实例进行初始化,包括调用初始化方法、注册销毁回调等。Spring提供了多种方式来管理Bean的生命周期。
  5. 提供Bean:Spring容器将创建好的Bean对象提供给应用程序,应用程序可以通过容器来获取所需的Bean实例。

主要技术

Spring在实现IOC时运用了一些常见的设计模式,如工厂模式、单例模式和依赖注入模式等。这些设计模式帮助Spring实现了对象的创建、组装和管理,提供了灵活、可扩展的编程模型。

基于XML配置文件管理Bean

创建对象

<bean id="userBean" class="org.example.bean.UserBean"></bean>

在Spring配置文件中,使用bean标签,添加相应的属性,实现对象的创建

创建对象的时候,默认执行无参的构造

注入属性

注入属性-value

方式一:Setter方法注入

实际上就是Spring在创建对象后,通过调用相应的Setter完成属性的注入

1、bean类中,创建属性,以及对应的Setter

public class Book {
    private String bookName;
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
}

2、在applicationContext.xml配置文件中,先配置对象的创建,再配置属性

<bean id="book" class="org.example.bean.Book">
    <!-- 使用property标签,完成属性注入
        name:表示需要注入的属性
        value:表示注入属性的值,如果注入的是对象,那么使用ref
     -->
    <property name="bookName" value="西游记"></property>
</bean>
方式二:通过有参构造注入

1、创建类,并提供有参构造

public class Person {
    private String personName;
    public Person() {
    }
    public Person(String personName) {
        this.personName = personName;
    }
}

2、在Spring配置文件中进行配置

<bean id="person" class="org.example.bean.Person">
    <!-- 使用constructor-arg标签,完成使用有参构造属性注入
        name:需要注入的属性,可以换成index,按照在有参构造中的属性的索引注入
        value:注入属性的值,如果注入的是对象,那么使用ref
    -->
    <constructor-arg name="personName" value="lucy"></constructor-arg>
</bean>
方式三:P命名空间注入

实际上是对于Setter注入的一种简化,底层使用的还是Setter

1、配置文件添加P命名空间xmlns:p="http://www.springframework.org/schema/p"

<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>

2、在bean标签中,进行操作

<bean id="book" class="org.example.bean.Book" p:bookName="西游记"></bean>
property标签

在Spring的XML配置文件中,<property>标签用于定义Bean的属性注入。通过<property>标签,可以将值或引用注入到Bean的属性中。

设置空值
<property name="bookName">
    <null/>
</property>
值中包含特殊符号

例如特殊符号<,可以使用如下两种方式注入值

方式一,使用转义字符

<property name="bookName" value="&lt;lucy&gt;"></property>

方式二,使用CDATA

<property name="bookName">
    <value>
        <![CADATA[<lucy>]]>
    </value>
</property>

注入属性-bean

外部bean

只需要将相关的对象,添加在配置文件中,由Spring创建,然后通过ref属性指定beanId,进行相应的属性(对象)注入,需要在调用类中,添加被调用类对象为属性,并且提供setter,例如,UserService中,需要调用UserDao

public class UserDao {
    public void add(){
        System.out.println("open in UserDao -- add");
    }
}
public class UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void showAdd(){
        userDao.add();
    }
}
<bean id="userDao" class="org.example.dao.UserDao"></bean>
<bean id="userService" class="org.example.service.UserService">
    <property name="userDao" ref="userDao"></property>
</bean>
内部bean、级联赋值

例如有Student、School两个实体类,Student实体类中有School属性,那么可以通过内部bean的方式进行注入,也可以通过外部bean,内部bean方式如下

public class School {
    private String schoolName;
    public String getSchoolName() {
        return schoolName;
    }
    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }
}
public class Student {
    private String studentName;
    private School school;
    public String getStudentName() {
        return studentName;
    }
    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }
    public School getSchool() {
        return school;
    }
    public void setSchool(School school) {
        this.school = school;
    }
}
<bean id="student" class="org.example.bean.Student">
    <property name="studentName" value="lucy"></property>
    <property name="school">
        <bean id="school" class="org.example.bean.School">
            <property name="schoolName" value="清华大学"></property>
        </bean>
    </property>
</bean>
自动注入

用于自动装配Bean的依赖项。它指定了如何解析Bean的依赖关系,常见值如下

public class Family {

    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "Family{" +
                "user=" + user +
                '}';
    }
}
public class User {

    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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 -->
    <bean id="user" class="top.ygang.springdemo.User">
        <property name="name" value="grady"/>
    </bean>
    
    <!-- 创建bean,并使用autowire注入属性 -->
    <bean id="family" class="top.ygang.springdemo.Family" autowire="byType"/>
</beans>

作用域

使用bean标签的scope属性进行指定Bean的作用域,控制Bean的生命周期和可见性。

<bean scope="singleton" ... />

延迟初始化

使用bean标签的lazy-init属性指定Bean的延迟初始化。当设置为true时,Bean将在第一次使用时才被初始化,而不是在应用程序启动时立即初始化。

<bean lazy-init="true" ... />

depends-on

使用bean标签的depends-on属性指定Bean依赖的其他Bean的名称。它确保在当前Bean实例化之前,指定的Bean已经被实例化。

<bean depends-on="userRepository" ... />

基于注解的方式管理Bean

注解 描述
@Component 通常用于普通Java类身上,表示这是一个需要被Spring容器进行管理的组件
@Controller 用于描述表现(控制)层controller的类,这个类需要被Spring容器进行管理,效果等同于@Component
@Service 用于描述业务层service的类,这个类需要被Spring容器进行管理,效果等同于@Component
@Repository 用于描述持久层mapper(dao)的类,这个类需要被Spring容器进行管理,效果等同于@Component
@Configuration 声明配置类,效果等同于@Component
@scope 声明该类的作用范围,例如@Scope("prototype")
@Bean 在配置类的方法上添加注解,方法返回的对象将被注册为bean
@Value 注入外部配置文件的值,例如@Value("${property.name}")
@Qualifier 指定具体的依赖对象,和@Autowired一起使用,通过指定bean的名称或标识符来精确注入依赖对象,例如:@Autowired @Qualifier("myBean")
@PostConstruct 在需要执行初始化操作的方法上添加注解,在构造函数执行之后执行初始化操作
@PreDestroy 在需要执行清理操作的方法上添加注解,在bean销毁之前执行清理操作
@Autowired 自动装配,由Spring框架提供,按照类型进行自动装配,找不到会抛出异常,可以设置属性required=false找不到也不会抛异常,更具有Spring的特性和功能,例如支持@Qualifier注解进行更精确的依赖注入,可以应用于类的构造方法、字段、Setter方法和任意方法上
@Resource 自动装配,由Java EE提供,先按照名称进行装配,找不到再按类型,可以应用于类的字段、Setter方法和任意方法上,但是不支持构造方法注入

context:component-scan

以上注解要使用的前提条件,必须在XML配置文件中开启注解扫描,指定要扫描的基础包,可以使用逗号分隔多个包名

<context:component-scan base-package="top.ygang.controller,top.ygang.service" />

标签指定了要扫描的基础包为top.ygang。Spring将在该包及其子包下扫描所有被注解标记的组件,并将其注册到容器中。

过滤条件,可以使用<context:include-filter><context:exclude-filter>来设置扫描的过滤条件

<context:component-scan base-package="top.ygang">
    <!-- 包含所有以org.springframework.stereotype.Service注解标记的类 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <!-- 排除所有以Impl结尾的类 -->
    <context:exclude-filter type="regex" expression=".*Impl$"/>
</context:component-scan>

context:annotation-config

<context:annotation-config />

用于启用基于注解的配置和自动装配的支持,让Spring能够自动解析和处理@Autowired@Resource等注解,从而实现依赖注入(2.5后的版本没必要加了)

在Spring2.5版本之前,还用来开启声明周期注解的使用@PostConstruct@PreDestroy,从Spring 2.5版本开始,这些生命周期注解的支持被默认启用,无需额外的配置。

@Value

注意:使用@Value注解时,通常需要将其应用于被Spring管理的组件(例如使用@Component@Service@Repository等注解标识的类)中,以便Spring能够扫描到该组件,并在实例化过程中处理@Value注解。

用于将值注入到类的字段、方法参数或构造函数参数中。它可以用于注入简单类型的值,如字符串、数字,也可以用于注入复杂类型,如对象、集合等

@Value("grady")
private String name;

// 使用Autowired注解可以在Spring容器创建当前类实例的时候自动调用,并且注入参数
@Autowired
public void setName(@Value("${name}")String name) {
    this.name = name;
}

也可以从配置文件中读取,Spring内置了解析.properties.yaml\yml的文件解析器,用来解析配置文件,需要在Spring配置文件中开启属性文件解析器,多个配置文件可以使用逗号,进行分隔,也可以使用通配符*来指定匹配,例如*.yml

<context:property-placeholder location="1.properties,2.yml" />
my.name=grady
my:
  name: grady
@Value("${my.name}")
private String name;

Bean的生命周期

在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。

在Spring框架中,每个Bean都有其生命周期,即它在容器中的创建、初始化和销毁过程中经历的一系列阶段。Spring框架中的Bean生命周期涉及多个阶段,包括实例化、依赖注入、初始化、使用和销毁。下面是Spring Bean的生命周期的详细解释:

代码体现

Spring配置文件

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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"
>
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="top.ygang"/>

    <!-- 开启注解支持 -->
    <context:annotation-config/>
</beans>

Bean

@Component
public class User implements ApplicationContextAware, InitializingBean, DisposableBean {

    private String name;

    public User(){
        System.out.println("Bean实例化:constructor");
    }

    public String getName() {
        return name;
    }

    @Autowired
    public void setName(@Value("grady") String name) {
        System.out.println("Bean依赖注入:@Autowired");
        this.name = name;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("Aware调用:setApplicationContext");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Bean初始化:afterPropertiesSet");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Bean销毁:destroy");
    }
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

BeanPostProcessor

@Configuration
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化前回调:postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化后回调:postProcessAfterInitialization");
        return bean;
    }
}

main方法

public static void main(String[] args) {
    System.out.println("容器启动:ClassPathXmlApplicationContext");
    ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    User bean = applicationContext.getBean(User.class);
    System.out.println("使用Bean");
    System.out.println(bean);
    System.out.println("容器关闭:ConfigurableApplicationContext.close()");
    applicationContext.close();
}

输出结果

容器启动:ClassPathXmlApplicationContext
Bean实例化:constructor
Bean依赖注入:@Autowired
Aware调用:setApplicationContext
初始化前回调:postProcessBeforeInitialization
Bean初始化:afterPropertiesSet
初始化后回调:postProcessAfterInitialization
使用Bean
User{name='grady'}
容器关闭:ConfigurableApplicationContext.close()
Bean销毁:destroy