当前位置: 代码网 > it编程>编程语言>Java > 简单上手SpringBean的整个装配过程

简单上手SpringBean的整个装配过程

2024年05月16日 Java 我要评论
在Spring6中定义一系列独立的bean定义出发,进而构建出一个对象间相互协作以达成目标的完全成型的应用程序。 ...

你好,这里是codetrend专栏“spring6全攻略”。

典型的企业级应用程序并非仅由单个对象(在spring术语中称为bean)组成。即使是最简单的应用程序,也会包含一些协同工作的对象,共同呈现出终端用户眼中连贯一致的应用程序形态。

以下mermaid流程图简单展示了spring工作过程。

graph lr a[业务类pojo] --> c[spring容器applicationcontext] b[配置元数据configuration metadata] --> c c --产生--> d[可执行的系统/应用程序]

业务类与配置元数据相结合,使得在spring容器applicationcontext被创建并初始化后,得到的是一个完全配置好且可执行的系统或应用程序。

下文将从定义一系列独立的bean定义出发,进而构建出一个对象间相互协作以达成目标的完全成型的应用程序。

配置元数据 configuration metadata

spring ioc 容器通过消费一种形式的配置元数据。这些配置元数据代表了您作为应用程序开发者告诉 spring 容器如何实例化、配置和组装应用程序中的对象。

配置元数据方式如下:

  • 基于 xml 格式配置
  • 基于 groovy 格式配置
  • 基于java类和注解进行配置

虽然配置的形式不一样,但是配置内容和api基本一样的。

以下是基于 xml 格式、groovy 格式和 java 类与注解的方式来配置 spring ioc 容器的示例:

  • 基于 xml 格式配置:
<!-- applicationcontext.xml -->
<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="userservice" class="com.example.userservice">
        <property name="userdao" ref="userdao"/>
    </bean>

    <bean id="userdao" class="com.example.userdao"/>

</beans>
  • 基于 groovy 格式配置:
// applicationcontext.groovy
beans {
    userservice(com.example.userservice) {
        userdao = ref('userdao')
    }

    userdao(com.example.userdao)
}
  • 基于 java 类和注解进行配置:
// appconfig.java
@configuration
public class appconfig {

    @bean
    public userservice userservice() {
        userservice userservice = new userservice();
        userservice.setuserdao(userdao());
        return userservice;
    }

    @bean
    public userdao userdao() {
        return new userdao();
    }
}

以上示例分别展示了使用 xml、groovy 和 java 类与注解的方式来配置 spring ioc 容器。无论使用哪种配置方式,都可以定义和组装应用程序中的对象,并且相应的 api 在实现上基本一致。

这三种配置方式各有优劣,开发者可以根据项目需求和个人喜好选择合适的方式。

ioc容器使用初体验

ioc容器在spring6框架中也就是各种beanfactory的实现类来创建和管理对象。

classpathxmlapplicationcontext就是通过读取xml配置初始化bean的一种方法。

启动类的代码如下:

/**
 * 宠物测试app
 * @author nine
 * @since 1.0
 */
public class petapp {
    public static void main(string[] args) {
        // 创建一个类路径下的xml应用上下文,并指定配置文件
        applicationcontext context = new classpathxmlapplicationcontext("s104/services.xml", "s104/daos.xml");
        // 从上下文中获取名为"petstore"的bean,其类型为petstoreserviceimpl,其中petstorealias是别名
        petstoreservice petstoreservice = context.getbean("petstorealias", petstoreserviceimpl.class);
        // 调用获取的bean的buypet方法
        petstoreservice.buypet(new pet("tom", "cat",1));
    }
}

bean的配置如下:

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

    <bean id="accountdao"
          class="io.yulin.learn.spring.s104.accountdao">
    </bean>

    <bean id="itemdao" class="io.yulin.learn.spring.s104.itemdao">
    </bean>
</beans>

<!-- services.xml -->
<?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
  https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- services -->
    <bean id="petstore" class="io.yulin.learn.spring.s104.petstoreserviceimpl" >
        <property name="accountdao" ref="accountdao"/>
        <property name="itemdao" ref="itemdao"/>
    </bean>
    <!--命名别名-->
    <alias name="petstore" alias="petstorealias" />
</beans>

对应bean如下。其中这些bean也就是简单的业务bean。

@slf4j
@data
public class petstoreserviceimpl implements petstoreservice {
    private  accountdao accountdao;
    private  itemdao itemdao;
    @override
    public boolean buypet(pet pet) {
        log.info("buy pet: {}", pet);
        accountdao.store(pet);
        itemdao.minus(pet);
        return true;
    }
}

@slf4j
public class itemdao {
    public void minus(pet pet) {
        log.info("minus pet num: {}", pet.getnum());
    }
}

@slf4j
public class accountdao {
    public void store(pet pet) {
        log.info("增加收入: {}", pet);
    }
}

这个过程与开发者编写工具类一样,没有任何注解、导入依赖这些配置。习惯使用springboot的开发者可能对此表示有点不习惯。

但是xml元数据配置+beanfactory一起,就组合成了一个单独的app。运行起来就和springboot无异。

讲上述代码修改为基于java类和注解进行配置的代码如下。

public class petappjavaconfig {
    public static void main(string[] args) {
        // 创建一个基于 java config 的应用上下文
        applicationcontext context = new annotationconfigapplicationcontext(appconfig.class);
        // 从上下文中获取名为"petstoreservice"的bean,其类型为petstoreservice
        petstoreservice petstoreservice = context.getbean(petstoreservice.class);
        // 调用获取的bean的buypet方法
        petstoreservice.buypet(new pet("tom", "cat", 1));
    }
}
@configuration
class appconfig {
    @bean
    public petstoreservice petstoreservice(accountdao accountdao, itemdao itemdao) {
        petstoreserviceimpl petstoreservice = new petstoreserviceimpl();
        petstoreservice.setaccountdao(accountdao);
        petstoreservice.setitemdao(itemdao);
        return petstoreservice;
    }
    @bean
    public accountdao accountdao() {
        return new accountdao();
    }
    @bean
    public itemdao itemdao() {
        return new itemdao();
    }
}

可以看出代码简洁明了不少,代码输出结果都是一致的。

11:38:11.646 [main] info io.yulin.learn.spring.s104.petstoreserviceimpl -- buy pet: pet(name=tom, type=cat, num=1)
11:38:11.653 [main] info io.yulin.learn.spring.s104.accountdao -- 增加收入: pet(name=tom, type=cat, num=1)
11:38:11.653 [main] info io.yulin.learn.spring.s104.itemdao -- minus pet num: 1

完整项目源码信息查看可以在gitee或者github上搜索r0ad查看。(外链审核太严格~木办法)

配置bean初体验

spring ioc容器管理一个或多个bean。这些bean是根据您提供给容器的配置元数据创建的(例如,以xml <bean/> 定义的形式)。

在容器内部,bean 定义被表示为 beandefinition 对象,其中包含(除其他信息外)以下元数据:

  • 一个包限定的类名:通常是所定义的 bean 的实际实现类。
  • bean 行为配置元素,用于说明 bean 在容器中应如何运行(作用域、生命周期回调等)。
  • 引用其他 bean,这些 bean 是该 bean 执行工作所需的。这些引用也称为协作者或依赖项。
  • 其他配置设置用于设置新创建对象中的值,例如,管理连接池的 bean 中的池大小限制或要使用的连接数。
/**
 * 说明备案definition的例子
 *
 * @author nine
 * @since 1.0
 */
public class beandefinitionprocessdemo {
    public static void main(string[] args) {
        // ️genericapplicationcontext 是一个【干净】的容器
        genericapplicationcontext context = new genericapplicationcontext();
        // 用原始方法注册三个 bean
        context.registerbean("bean1", bean1.class);
        // 初始化容器
        // 执行beanfactory后处理器, 添加bean后处理器, 初始化所有单例
        context.refresh();
        bean1 bean = context.getbean(bean1.class);
        bean.print();
        // 销毁容器
        context.close();
    }
}

@slf4j
class bean1 {
    public void print() {
        log.info("i am bean1");
    }
}

通过这个代码可以发现,bean1在调用registerbean接口后从一个普通的pojo类变成了一个bean。

org.springframework.context.support.genericapplicationcontext#registerbean为了方便使用有很多重载方法。

通过源码可以发现,普通类通过classderivedbeandefinition的构造函数转换为beandefinition。也就是该class通过setbeanclass成为beandefinition的属性beanclass

后续通过一些列操作,自定义、名字处理、注册容器等等添加了其他的属性信息或者进行二次处理。

具体源码如下。

public <t> void registerbean(@nullable string beanname, class<t> beanclass, @nullable supplier<t> supplier, beandefinitioncustomizer... customizers) {
    // 创建一个 classderivedbeandefinition 对象,用于封装 bean 的定义信息
    classderivedbeandefinition beandefinition = new classderivedbeandefinition(beanclass);

    // 如果存在 supplier,则设置到 beandefinition 中
    if (supplier != null) {
        beandefinition.setinstancesupplier(supplier);
    }

    // 对 beandefinition 进行定制处理
    for (beandefinitioncustomizer customizer : customizers) {
        customizer.customize(beandefinition);
    }

    // 如果指定了 beanname,则使用指定的名称,否则使用 beanclass 的名称
    string nametouse = (beanname != null ? beanname : beanclass.getname());

    // 将封装好的 beandefinition 注册到容器中
    registerbeandefinition(nametouse, beandefinition);
}

bean的生命周期回调

spring6 中 bean 的生命周期可以通过 initializingbean 和 disposablebean 接口、@postconstruct 和 @predestroy 注解以及配置文件中的 init-method 和 destroy-method 方法来管理。

把上面手动注入的bean的demo修改,增加实现 bean 的初始化和销毁回调:

public class beandefinitionprocessdemo {
    public static void main(string[] args) {
        // genericapplicationcontext 是一个干净的容器
        genericapplicationcontext context = new genericapplicationcontext();
        // 用原始方法注册 bean1,并指定初始化和销毁方法
        context.registerbean("bean1", bean1.class, bean1::new, beandefinition -> {
            beandefinition.setinitmethodname("init");
            beandefinition.setdestroymethodname("destroy");
        });
        // 初始化容器
        context.refresh();
        bean1 bean = context.getbean(bean1.class);
        bean.print();
        // 销毁容器
        context.close();
    }
}

@slf4j
class bean1 {
    public void print() {
        log.info("i am bean1");
    }
    public void init() {
        log.info("bean1 is being initialized");
    }
    public void destroy() {
        log.info("bean1 is being destroyed");
    }
}

通过输出可以发现,bean1的初始化和销毁回调被调用了。

15:14:23.631 [main] info io.yulin.learn.spring.s104.bean1 -- bean1 is being initialized
15:14:23.669 [main] info io.yulin.learn.spring.s104.bean1 -- i am bean1
15:14:23.670 [main] info io.yulin.learn.spring.s104.bean1 -- bean1 is being destroyed

把整个过程改为更熟悉的基于注解驱动开发的方式,代码如下。

import jakarta.annotation.postconstruct;
import jakarta.annotation.predestroy;
import lombok.extern.slf4j.slf4j;
import org.springframework.context.annotation.annotationconfigapplicationcontext;

/**
 * 通过注解方式配置bean
 * @author nine
 * @since 1.0
 */
public class beandefinitionprocessannotationdemo {
    public static void main(string[] args) {
        annotationconfigapplicationcontext context = new annotationconfigapplicationcontext();
        context.register(beanannotation1.class);
        context.refresh();

        beanannotation1 bean = context.getbean(beanannotation1.class);
        bean.print();
        context.close();
    }
}

@slf4j
class beanannotation1 {
    public void print() {
        log.info("i am beanannotation1");
    }

    @postconstruct
    public void init() {
        log.info("beanannotation1 is being initialized");
    }

    @predestroy
    public void destroy() {
        log.info("beanannotation1 is being destroyed");
    }
}

输出结果基本一致的。

通过这个转换过程可以更能清晰的发现,spring如何从基于xml配置、java配置、注解配置的转换。也能更加深刻体会到spring的强大兼容性。

实例化bean

需要使用bean就必须实例化这个类,最简单的方式就是new 一个对象。

但是在spring6框架中提供了更多的配置来实现实例化bean。

如果使用基于xml的配置元数据,可以在<bean/>元素的class属性中指定要实例化的对象的类型(或类)。

这个class属性(在beandefinition实例上内部是一个class属性)通常是必需的。

使用class属性的两种方式之一:

  • 通常情况下,为了指定要构造的bean类,在容器本身通过调用其构造函数反射性地直接创建bean的情况下,类似于使用new运算符的java代码。
  • 在较不常见的情况下,为了指定包含静态工厂方法的实际类,容器调用该类上的静态工厂方法来创建bean。从调用静态工厂方法返回的对象类型可以是相同的类,也可以是完全不同的类。

构造器实例化bean

当通过构造函数方式创建一个bean时,所有普通类都可以被spring使用并与之兼容。

也就是说,正在开发的类不需要实现任何特定的接口或以特定方式编码。只需指定bean类即可。

然而,根据为该特定bean使用的ioc类型,可能需要一个默认(空)构造函数。

spring ioc容器可以管理几乎任何希望它管理的类。它不局限于管理真正的javabeans。

大多数spring用户更喜欢具有仅默认(无参数)构造函数。

spring容器还可以管理非bean的类。例如,如果需要使用不符合javabean规范的传统连接池,spring也可以进行管理。

下面通过一个例子说明基于构造器实例化bean的例子。

import lombok.extern.slf4j.slf4j;
import org.springframework.context.applicationcontext;
import org.springframework.context.annotation.annotationconfigapplicationcontext;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;

/**
 * springbean创建demo
 *
 * @author nine
 * @since 1.0
 */
public class springbeancreatedemo {

    public static void main(string[] args) {
        // 创建一个基于 java config 的应用上下文
        applicationcontext context = new annotationconfigapplicationcontext(appcreateconfig.class);
        // 从上下文中获取名bean,其类型为petstoreservice
        myclass bean = context.getbean(myclass.class);
        // 调用获取的bean的方法
        bean.hello("jack");
    }
}

@configuration
@slf4j
class appcreateconfig {
    @bean
    public myclass examplebean() {
        return new myclass("exampleconstructorarg");
    }
}

@slf4j
class myclass {
    public myclass(string constructorarg) {
        log.info(constructorarg);
    }

    public void hello(string name) {
        log.info("hello " + name);
    }
}

输出结果如下。可以看到myclass类被正确初始化和被ioc容器管理。

09:44:57.211 [main] info io.yulin.learn.spring.s104.myclass -- exampleconstructorarg
09:44:57.262 [main] info io.yulin.learn.spring.s104.myclass -- hello jack

静态工厂方法实例化bean

在定义使用静态工厂方法创建的bean时,使用class属性指定包含静态工厂方法的类,并使用名为factory-method的属性指定工厂方法本身的名称。

应该能够调用这个方法(带有可选参数,如后面所述),并返回一个活动对象,随后将其视为通过构造函数创建的对象。

这样一个bean定义的用途之一是在遗留代码中调用静态工厂。

import lombok.extern.slf4j.slf4j;
import org.springframework.context.applicationcontext;
import org.springframework.context.annotation.annotationconfigapplicationcontext;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;

/**
 * springbean创建demo
 *
 * @author nine
 * @since 1.0
 */
public class springbeanfactorymethodcreatedemo {

    public static void main(string[] args) {
        // 创建一个基于 java config 的应用上下文
        applicationcontext context = new annotationconfigapplicationcontext(appfactoryconfig.class);
        // 从上下文中获取名bean,其类型为petstoreservice
        appfactoryconfig.mybean bean = context.getbean(appfactoryconfig.mybean.class);
        // 调用获取的bean的方法
        bean.hello();
    }
}

@slf4j
@configuration
class appfactoryconfig {

    @bean
    public mybean mybean() {
        // 调用带有可选参数的静态工厂方法创建 bean
        return mybeanfactory.createbean("tom");
    }

    static class mybean {
        private string name;

        public mybean(string name) {
            this.name = name;
        }

        public void hello() {
            log.info("hello, " + name);
        }
    }

    static class mybeanfactory {
        public static mybean createbean(string parameter) {
            return new mybean(parameter);
        }
    }
}

通过实例工厂方法实例化bean

类似于通过静态工厂方法进行实例化,使用实例工厂方法进行实例化会调用容器中现有 bean 的非静态方法来创建一个新的 bean。

要使用这种机制,将class属性留空,在factory-bean属性中指定当前(或父级或祖先)容器中包含要被调用以创建对象的实例方法的 bean 的名称。

使用factory-method属性设置工厂方法本身的名称。

这个例子是基于java config来实现的。

可以发现mybean通过现有名为mybeanfactory的bean来创建的。

import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;

@configuration
public class appbeancreateconfig {
    @bean
    public mybeanfactory mybeanfactory() {
        return new mybeanfactory();
    }

    @bean
    public mybean mybean(mybeanfactory mybeanfactory) {
        // 调用实例工厂方法创建 bean
        return mybeanfactory.createbean("optionalparameter");
    }

    static class mybean {
        private string name;

        public mybean(string name) {
            this.name = name;
        }

        public void hello() {
            system.out.println("hello, " + name);
        }
    }

    static class mybeanfactory {
        public mybean createbean(string parameter) {
            return new mybean(parameter);
        }
    }
}

确定bean的运行时类型

确定spring框架中一个特定bean的运行时类型确实需要考虑到多种复杂情况。

在bean元数据定义中指定的类只是一个初始类引用,可能与声明的工厂方法结合,或者是一个可能导致bean具有不同运行时类型的factorybean类,或者在实例级工厂方法的情况下根本没有设置(这是通过指定的工厂-bean 名称来解析的)。

此外,aop代理可能会用基于接口的代理包装一个bean实例,只暴露目标bean的实际类型(仅暴露其实现的接口)。

查找特定bean的实际运行时类型的推荐方法是使用beanfactory.gettype调用指定的bean名称。

这考虑了上述所有情况,并返回beanfactory.getbean调用将为相同的bean名称返回的对象类型。

以上面的通过实例工厂方法实例化bean为例说明使用beanfactory.gettype

import lombok.extern.slf4j.slf4j;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.config.configurablelistablebeanfactory;
import org.springframework.context.annotation.annotationconfigapplicationcontext;

@slf4j
public class beanruntimetypetest {

    @test
    public void test() {
        annotationconfigapplicationcontext context = new annotationconfigapplicationcontext(appbeancreateconfig.class);
        // 获取 beanfactory 实例
        configurablelistablebeanfactory beanfactory = context.getbeanfactory();
        // 使用 beanfactory.gettype 方法获取特定 bean 的运行时类型
        class<?> beantype = beanfactory.gettype("mybean");
        log.info("the runtime type of 'mybean' is: " + beantype.getname());
        // 使用 beanfactory.getbean 方法获取特定 bean 的实例对象
        appbeancreateconfig.mybean mybeaninstance = (appbeancreateconfig.mybean) beanfactory.getbean("mybean");
        mybeaninstance.hello();
        // 关闭应用上下文
        context.close();
    }
}

输出结果如下。可以看到实例工厂方法没有设置class,但是运行时类型为mybean。

10:51:25.111 [main] info io.yulin.learn.spring.s104.beanruntimetypetest -- the runtime type of 'mybean' is: io.yulin.learn.spring.s104.appbeancreateconfig$mybean
hello, optionalparameter

beanfactory.gettype()一些常见用途:

  • 类型检查:通过调用 gettype() 方法,可以获取特定 bean 的实际类型,并根据这些类型信息执行相应的操作。这对于在运行时进行类型检查和验证非常有用。
  • 动态处理:在某些情况下,您可能需要根据 bean 的类型来动态地决定如何处理该 bean。通过 gettype() 方法可以获取 bean 的类型信息,并根据需要执行相应的处理逻辑。
  • 条件化配置:在 spring 应用程序中,有时根据 bean 的类型来进行条件化的配置会很有用。通过 gettype() 方法可以获取 bean 的类型,从而根据不同的类型执行不同的配置。
  • 自定义逻辑:某些情况下,可能需要根据 bean 的类型来编写特定的业务逻辑。通过 gettype() 方法可以获取 bean 的准确类型信息,并在代码中编写相应的逻辑。

关于作者

来自全栈程序员nine的探索与实践,持续迭代中。

欢迎关注或者点个小红心~

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com