美文网首页
Spring IOC 的主要用法、Bean的生命周期、XML还是

Spring IOC 的主要用法、Bean的生命周期、XML还是

作者: overflowedstack | 来源:发表于2021-08-13 19:50 被阅读0次

Spring Framework的核心之一在于IOC(控制反转)。它是通过DI(依赖注入)来实现的。
本文是IOC的使用总结,围绕Spring Framework 官方文档展开,不涉及它的底层实现。

  • 版本:
    Spring Framework 5.2.16.RELEASE

一. 创建对象

1. 通过xml中定义bean来创建对象

传统方式直接new一个对象:

    @Test
    public void test01() {
        QueryService service = new QueryServiceImpl();
        service.query();
    }

而spring的控制反转,将bean交给容器去管理。通过xml中定义bean来创建对象:

<?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="queryService" class="service.impl.QueryServiceImpl"></bean>
</beans>
    @Test
    public void test02() {
        String config = "beans.xml";

        //创建表示spring容器的对象 ApplicationContext
        //ClassPathXmlApplicationContext 表示从类路径中加载spring的配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        QueryService service = (QueryService) ac.getBean("queryService");
        service.query();
    }

创建非自定义对象:

<bean id="mydate" class="java.util.Date" />
    @Test
    public void test04() {
        String config = "beans.xml";

        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        Date mydate = (Date) ac.getBean("mydate");
        System.out.println("date: " + mydate);
    }

2. spring默认创建对象的时机:创建spring容器时,会创建配置文件中所有的对象

public class QueryServiceImpl implements QueryService {
    public QueryServiceImpl() {
        System.out.println("QueryServiceImpl constructor without parameters");
    }

    @Override
    public void query() {
        System.out.println("Executing QueryServiceImpl.query");
    }
}
    @Test
    public void test02() {
        String config = "beans.xml";

        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        //QueryService service = (QueryService) ac.getBean("queryService");
        //service.query();
    }

输出为:

QueryServiceImpl constructor without parameters

Process finished with exit code 0

从上面的例子,可以看出来,spring容器初始化时,就将xml中定义的bean都创建好了。关于底层细节,移步Spring - IOC容器初始化

3. spring bean scope = prototype

spring bean有个属性scope,默认为singleton,也就是上面的例子。对于singleton的bean,容器初始化时就会创建,是单例的,用时就从容器中取出来,取的是同一个bean。
而如果将scope设置为prototype,那么容器初始化时就不会创建,而会等到使用时才会创建bean,每次会创建新的bean,不是单例。

<bean id="queryService" class="service.impl.QueryServiceImpl" scope="prototype"></bean>
  • 容器初始化时不创建bean
    @Test
    public void test03() {
        String config = "beans.xml";

        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

//        QueryService service = (QueryService) ac.getBean("queryService");
//        QueryService service1 = (QueryService) ac.getBean("queryService");
//        service.query();
    }

输出为:

Process finished with exit code 0
  • 使用时创建bean
    @Test
    public void test03() {
        String config = "beans.xml";
        
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        QueryService service = (QueryService) ac.getBean("queryService");
        QueryService service1 = (QueryService) ac.getBean("queryService");
        service.query();
    }

输出为:

QueryServiceImpl constructor without parameters
QueryServiceImpl constructor without parameters
Executing QueryServiceImpl.query

Process finished with exit code 0

4. lazy-init

如果一个bean被定义为lazy-init,那么容器初始化时不会创建这个bean。会在第一次使用这个bean时,才创建它。

<bean id ="queryService" class="service.impl.QueryServiceImpl" lazy-init="true"/>

二. 给Java对象的属性赋值

1. set 注入 - 简单类型 - spring调用类的set方法

1.1 示例
    <bean id="student" class="model.Student">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
    </bean>
1.2 删除age的set方法,执行报错
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'student' defined in class path resource [applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'age' of bean class [model.Student]: Bean property 'age' is not writable or has an invalid setter method. Did you mean 'name'?

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'student' defined in class path resource [applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'age' of bean class [model.Student]: Bean property 'age' is not writable or has an invalid setter method. Did you mean 'name'?
1.3 spring直接调用(set+属性)方法

student类中并没有email属性,但是有setEmail方法。在xml中给email赋值。spring会直接调用setEmail方法,不要求email一定是存在的属性。

    <bean id="student" class="model.Student">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
        <property name="email" value="zhangsan@qq.com" />
    </bean>
    @Test
    public void test04() {
        String config = "applicationContext.xml";

        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        Student student = (Student) ac.getBean("student");
        System.out.println("student: " + student);
    }

输出:

Email: zhangsan@qq.com
student: Student{name='zhangsan', age=20}

Process finished with exit code 0

2. set 注入 - 引用类型赋值

2.1 引用类型赋值
    <bean id="student" class="model.Student">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
        <property name="school" ref="school" />
    </bean>

    <bean id="school" class="model.School">
        <property name="name" value="tsinghua" />
        <property name="address" value="Beijing" />
    </bean>
2.2 引用类型自动赋值 - byName 按名称注入

如果不给school赋值,那么school为null:

    <bean id="student" class="model.Student">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
<!--        <property name="school" ref="school" />-->
    </bean>

    <bean id="school" class="model.School">
        <property name="name" value="tsinghua" />
        <property name="address" value="Beijing" />
    </bean>
student: Student{name='zhangsan', age=20, school=null}

可以通过bean的autowire属性,配置其为byName赋值.
byName需要引用类型的属性名,与bean id一致,并且还需要数据类型一致:

    <bean id="student" class="model.Student" autowire="byName">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
<!--        <property name="school" ref="school" />-->
    </bean>

    <bean id="school" class="model.School">
        <property name="name" value="tsinghua" />
        <property name="address" value="Beijing" />
    </bean>
Set school School{name='tsinghua', address='Beijing'}
student: Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}
2.3 引用类型自动赋值 - byType 按类型注入

byType需要引用类型的属性名,与容器中的某个bean class是同源的关系。这样的bean能够赋值给引用类型。
同源指的是:
1)java类中引用类型的数据类型与bean的class的值是一样的。
2)java类中引用类型的数据类型与bean的class的值是父子类的关系。
3)java类中引用类型的数据类型与bean的class的值是接口与实现类的关系。

例如下面的例子,student的School类型属性,能够直接对应上class为School的bean。所以赋值成功:

    <bean id="student" class="model.Student" autowire="byType">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
<!--        <property name="school" ref="school" />-->
    </bean>

    <bean id="school1234" class="model.School">
        <property name="name" value="tsinghua" />
        <property name="address" value="Beijing" />
    </bean>

3. set 注入 - Collection

   <bean id="complexObject" class="model.ComplexObject">
        <property name="list">
            <list>
                <value>zhangsan</value>
                <value>lisi</value>
            </list>
        </property>
        <property name="map">
            <map>
                <entry key="k1" value="v1"/>
                <entry key ="k2" value="v2"/>
            </map>
        </property>
        <property name="set">
            <set>
                <value>lilei</value>
                <value>hanmeimei</value>
            </set>
        </property>
    </bean>

4. 构造注入 - spring调用类的有参构造方法,创建对象,在构造方法中完成赋值

<constructor-arg> 表示构造方法中的一个参数。
标签属性name,表示构造方法形参名。
index,表示形参位置。
value表示值。
ref表示引用类型对象。

使用name:

    <bean id="student" class="model.Student">
        <constructor-arg name="name" value="zhangsan" />
        <constructor-arg name="age" value="20" />
        <constructor-arg name="school" ref="school" />
    </bean>

    <bean id="school" class="model.School">
        <constructor-arg name="name" value="tsinghua" />
        <constructor-arg name="address" value="Beijing" />
    </bean>

使用index:

    <bean id="student" class="model.Student">
        <constructor-arg index="0" value="zhangsan" />
        <constructor-arg index="1" value="20" />
        <constructor-arg index="2" ref="school" />
    </bean>

    <bean id="school" class="model.School">
        <constructor-arg name="name" value="tsinghua" />
        <constructor-arg name="address" value="Beijing" />
    </bean>

三. 对bean的生命周期进行定制 - Customizing the Nature of a Bean

1. 生命周期回调

1.1 Initialization Callbacks
    <bean id="student" class="model.Student" init-method="init">
        <constructor-arg index="0" value="zhangsan" />
        <constructor-arg index="1" value="20" />
        <constructor-arg index="2" ref="school" />
    </bean>

    <bean id="school" class="model.School" init-method="init">
        <constructor-arg name="name" value="tsinghua" />
        <constructor-arg name="address" value="Beijing" />
    </bean>
    public void init() {
        System.out.println("School init method");
    }

输出:

School constructor with parameters
School init method
Student constructor with parameters
Student init method
student: Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}
1.2 Destruction Callbacks --- destroy-method
    <bean id="student" class="model.Student" init-method="init" destroy-method="cleanup">
        <constructor-arg index="0" value="zhangsan" />
        <constructor-arg index="1" value="20" />
        <constructor-arg index="2" ref="school" />
    </bean>

    <bean id="school" class="model.School" init-method="init" destroy-method="cleanup">
        <constructor-arg name="name" value="tsinghua" />
        <constructor-arg name="address" value="Beijing" />
    </bean>
    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
        System.out.println("School cleanup method");
    }
School constructor with parameters
School init method
Student constructor with parameters
Student init method
student: Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}
Student cleanup method
School cleanup method
1.3 默认初始化和销毁方法

在beans标签上指定默认的init和destroy方法,那么就不需要对每个bean指定init和destroy方法了。
spring容器会按照指定的方法名,去找每个bean是否实现了此方法。

<beans default-init-method="init" default-destroy-method="cleanup">
1.4 Lifecycle

Lifecycle是容器生命周期级别的。
Lifecycle接口定义了start和stop方法,任何交给spring管理的bean都可以实现这个接口。
当spring容器显式地被调用start和stop(或者close)方法时,spring容器就会执行bean的start或者stop方法。

<bean id="processLifeCycle" class="service.impl.ProcessLifeCycle"/>
public class ProcessLifeCycle implements Lifecycle {
    private boolean isRunning;

    @Override
    public void start() {
        System.out.println("ProcessLifeCycle start method");
        isRunning = true;
    }

    @Override
    public void stop() {
        System.out.println("ProcessLifeCycle stop method");
        isRunning = false;
    }

    @Override
    public boolean isRunning() {
        return isRunning;
    }
}
    @Test
    public void test05() {
        String config = "applicationContext.xml";

        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext(config);
        ac.start();
        Student student = (Student) ac.getBean("student");
        System.out.println("student: " + student);

        ac.stop();
        ac.close();
    }

输出:

School constructor with parameters
School init method
Student constructor with parameters
Student init method
ProcessLifeCycle start method
student: Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}
ProcessLifeCycle stop method
Student cleanup method
School cleanup method
1.5 SmartLifecycle

SmartLifecycle是Lifecycle的子类,对Lifecycle做了一些改进。
它会在容器加载和初始化好所有的bean之后,自动回调start方法,并不需要在代码中显式地调用容器的start方法。
实际使用时,基本都会用SmartLifecycle,而不是Lifecycle。

public class ProcessLifeCycle implements SmartLifecycle

2. ApplicationContextAware and BeanNameAware

作用:可以理解成一种设计模式,使得Bean能收到容器的通知,从而在Bean内部能够获得自身或外部的某些信息。Bean只要实现Aware接口就能收到对应的通知。

示例:ApplicationContextAware、BeanNameAware、MessageSourceAware等等。

缺点及解决:使用这些接口违反了“IoC”原则——相当于由Bean负责设置依赖的对象。解决:可以不使用这些接口,而是直接通过Autowired等注入,如 @Autoweired private ApplicationContext ctx;

3. BeanPostProcessor

Bean后置处理器允许在调用初始化方法前后对Bean进行额外的处理。它对IOC容器里的所有Bean实例逐一处理。
典型应用:检查Bean属性的正确性,或根据特定的标准更改Bean的属性。

public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor postProcessBeforeInitialization for bean " + bean.toString());
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor postProcessAfterInitialization for bean " + bean.toString());
        return bean;
    }
}

执行顺序,输出:

MyBeanPostProcessor postProcessBeforeInitialization for bean service.impl.ProcessLifeCycle@67205a84
MyBeanPostProcessor postProcessAfterInitialization for bean service.impl.ProcessLifeCycle@67205a84

School constructor with parameters
MyBeanPostProcessor postProcessBeforeInitialization for bean School{name='tsinghua', address='Beijing'}
School init method
MyBeanPostProcessor postProcessAfterInitialization for bean School{name='tsinghua', address='Beijing'}

Student constructor with parameters
MyBeanPostProcessor postProcessBeforeInitialization for bean Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}
Student init method
MyBeanPostProcessor postProcessAfterInitialization for bean Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}

ProcessLifeCycle start method

student: Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}

ProcessLifeCycle stop method
Student cleanup method
School cleanup method

4. BeanFactoryPostProcessor

BeanFactoryPostProcessor是在Bean instantiate前后执行,此时任何Bean都尚未instantiate。处理的对象是BeanFactory,影响的是Bean的instantiate过程。

BeanFactoryPostProcessor比BeanPostProcessor更早被调用。BeanFactoryPostProcessor中的方法只会被执行一次(在所有Bean instantiate之前)。

应用场景:修改Bean definition

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor postProcessBeanFactory");

        //往容器中手动注册一个Bean
        configurableListableBeanFactory.registerSingleton("testObject", new Object());
    }
}
    @Test
    public void test05() {
        String config = "applicationContext.xml";

        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext(config);
        Student student = (Student) ac.getBean("student");
        System.out.println("student: " + student);

        System.out.println("Get object " + ac.getBean("testObject"));

        ac.close();
    }

输出:

MyBeanFactoryPostProcessor postProcessBeanFactory

MyBeanPostProcessor postProcessBeforeInitialization for bean service.impl.ProcessLifeCycle@5d76b067
MyBeanPostProcessor postProcessAfterInitialization for bean service.impl.ProcessLifeCycle@5d76b067

School constructor with parameters
MyBeanPostProcessor postProcessBeforeInitialization for bean School{name='tsinghua', address='Beijing'}
School init method
MyBeanPostProcessor postProcessAfterInitialization for bean School{name='tsinghua', address='Beijing'}

Student constructor with parameters
MyBeanPostProcessor postProcessBeforeInitialization for bean Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}
Student init method
MyBeanPostProcessor postProcessAfterInitialization for bean Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}

ProcessLifeCycle start method
student: Student{name='zhangsan', age=20, school=School{name='tsinghua', address='Beijing'}}

java.lang.Object@22eeefeb

ProcessLifeCycle stop method
Student cleanup method
School cleanup method

四. Bean的生命周期总结

Bean的生命周期会经历这些阶段:
bean definition -> bean factory post processor -> bean instantiate 创建实例 -> bean post processor before initialization -> init -> bean post processor after initialization -> all bean initialized -> smart life cycle start method -> business logics -> smart life cycle stop method -> bean destroy

五. Spring框架中的注解

1. 使用注解的步骤

1)maven依赖中加入spring-context,间接引入了spring-aop,这是使用注解的基础。
2)在类中加入spring的注解。
3)在spring的配置文件中,加入一个组件扫描器的标签,说明注解在项目中的位置。

2. @Component

在类的上面@Component,表示创建对象,等同于<bean>的功能。属性value是对象的名称,也就是bean的id值。value的值是唯一的,创建的对象在spring容器中只有一个。

@Component(value = "myStudent")
public class Student {
//......
}

等同于

<bean id="myStudent" class="......">

3. 声明组件扫描器

注意需要在xml中引入context约束,否则配置文件中component-scan不能被识别。

<?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:component-scan base-package="model"/>
</beans>

4. Spring中和@component功能一致,创建对象的注解还有

1)@Repository (用在持久层类的上面):放在dao的实现类上面,表示创建dao对象,dao对象是能放为数据库的。
2)@Service(用在业务层类的上面):放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务等功能。
3)@Controller(用在控制器上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象,能够接收用户提交的参数,显示请求的处理结果。
以上三个注解的使用语法和@Component是一样的。都能创建对象,但是这三个注解还有额外的功能。它们是给项目的对象分层的。

5. @Value 简单类型赋值,一般配合properties文件使用

    @Value("zhangsan")
    private String name;

    @Value("20")
    private int age;

6. @Autowired 引用类型赋值

6.1 默认使用byType自动注入:
    @Autowired
    private School school;
@Component
public class School {
//......
}
6.2 可以用@Qualifier来指定特定的bean:
    @Autowired
    @Qualifier("mySchool")
    private School school;
@Component("mySchool")
public class School {
//......
}
6.3 @Autowired的required属性

默认为true,表示引用类型如果失败,程序报错,并且终止运行。

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

可以设置@Autowired(required = false),表示引用类型如果赋值失败,程序正常执行,引用类型是null。不过不推荐这么做,容易造成空指针异常。推荐保持默认true,能及早暴露问题。

7. @Resource - JDK注解,不是Spring注解

来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值。
默认是byName: 先使用byName自动注入,如果byName赋值失败,再使用byType。

若要只使用byName方式,需要增加name属性,例如@Resource(name="school")

六. XML还是注解

需要经常改的就用XML,不经常改的就用注解。

Spring文档中也提到了,是用XML,还是用注解?

Are annotations better than XML for configuring Spring?
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML. The short answer is “it depends.” The long answer is that each approach has its pros and cons, and, usually, it is up to the developer to decide which strategy suits them better. Due to the way they are defined, annotations provide a lot of context in their declaration, leading to shorter and more concise configuration. However, XML excels at wiring up components without touching their source code or recompiling them. Some developers prefer having the wiring close to the source while others argue that annotated classes are no longer POJOs and, furthermore, that the configuration becomes decentralized and harder to control.
No matter the choice, Spring can accommodate both styles and even mix them together. It is worth pointing out that through its JavaConfig option, Spring lets annotations be used in a non-invasive way, without touching the target components source code and that, in terms of tooling, all configuration styles are supported by the Spring Tools for Eclipse.

相关文章

网友评论

      本文标题:Spring IOC 的主要用法、Bean的生命周期、XML还是

      本文链接:https://www.haomeiwen.com/subject/xhwlbltx.html