互联网轻量级框架整合之控制反转和注入

news2025/1/24 5:08:14

依赖注入和依赖查找

应该说IoC的工作方式有两种,一种是依赖查找,通过资源定位,把对应的资源查找出来,例如通过JNDI找到数据源,依赖查找被广泛使用在第三方的资源注入上,比如在Web项目中,数据源往往是通过服务器配置的,例如Tomcat的数据源配置,这种场景下便可以用JNDI的形式通过接口将其注入Spring IoC容器;另一种则是依赖注入,其主要是在容器内通过类型或者名称查找资源来管理Bean之间的依赖关系,而就依赖注入而言又可分为构造器注入和setter注入两种方式,setter的形式是Spring推荐的也是应用更广泛的形式

构造器注入

构造器注入依赖于构造方法的实现,而构造方法可以是有参数的或者是无参数的,在大部分情况下,我们通过类的构造方法创建类对象,Spring也可以采用反射的方式,通过使用构造方法完成注入,这便是构造器注入的原理

要让Spring更好的完成对应的构造注入,有必要描述具体的类、构造方法、设置对应的参数,如此Spring就会通过对应的信息用反射的形式创建对象

如下代码所示,首先定义一个新的Role pojo

package com.ssm.pojo;
/**
 * 角色类,用于表示角色信息
 */
public class RoleII {

    private Long id; // 角色编号
    private String roleName; // 角色名称
    private String note; // 备注信息

    /**
     * 无参数构造方法,用于创建一个空的角色对象。
     */
    public RoleII() {
    }

    /**
     * 带参数的构造方法,用于创建一个具有指定角色信息的角色对象。
     *
     * @param id 角色的唯一标识符。
     * @param roleName 角色的名称。
     * @param note 对角色的备注说明。
     */
    public RoleII(Long id, String roleName, String note) {
        this.id = id;
        this.roleName = roleName;
        this.note = note;
    }

    // 提供对id属性的访问和修改
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    // 提供对roleName属性的访问和修改
    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    // 提供对note属性的访问和修改
    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

要使用带参数的构方法创建对象,需要在配置文件spring-cfg.xml中进行适当的配置,如下所示

    <!-- 定义一个RoleII类型的bean,通过构造器进行初始化 -->
    <bean id="role1" class="com.ssm.pojo.RoleII">
        <!-- 构造器参数1:角色ID,这里设置为1 -->
        <constructor-arg index="0" value="1" />
        <!-- 构造器参数2:角色名称,这里设置为"总经理" -->
        <constructor-arg index="1" value="总经理" />
        <!-- 构造器参数3:角色描述,这里设置为"公司管理者" -->
        <constructor-arg index="2" value="公司管理者" />
    </bean>

其中constructor-arg元素用于定义构造方法的参数,index用于定位参数的位置,从0开始,通过这样的定义,Spring便会使用RoleII(Long id, String roleName, String note)方法创建对象

参数少的时候还好,参数多了这种构造形式可读性会比较差

setter注入

setter注入是Spring中最主流的注入方式,它利用JavaBean规范定义的setter方法完成注入,不但灵活而且可读性高,消除了使用构造器注入时出现多个参数可读性变差的问题,在RoleII的pojo里有个无参数的构造函数,通过配置Spring也可以通过Java反射技术注入

    <!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" -->
    <bean id="role2" class="com.ssm.pojo.RoleII" scope="prototype">
        <!-- 设置角色id为2 -->
        <property name="id" value="2" />
        <!-- 设置角色名为"高级工程师" -->
        <property name="roleName" value="高级工程师" />
        <!-- 设置备注为"重要人员" -->
        <property name="note" value="重要人员" />
    </bean>

scope属性定义了Spring容器管理的Bean的实例化策略,即Bean的作用域。不同的作用域会影响Bean的创建和生命周期管理。以下是Spring支持的主要作用域:

  • Singleton (单例): 这是默认的作用域。当一个Bean被配置为单例时,Spring容器只会创建该Bean的一个实例,并在第一次请求时初始化。之后的每次请求都将返回同一个实例。适合那些在整个应用中需要共享同一状态的对象。
  • Prototype (原型): 原型作用域表示每次请求都会创建一个新的Bean实例。这适用于那些需要有各自独立状态的对象,比如用户会话或者并发操作中的对象。
  • Request: 在Web应用中,每个HTTP请求都会创建一个新的Bean实例。这意味着对于每个请求,都会有一份独立的Bean副本。
  • Session: 也是在Web应用中,每个HTTP session会创建一个新的Bean实例。这意味着每个用户session都有自己的Bean副本,可以用于存储用户特定的信息。
  • Application: 在Web应用中,这个作用域的Bean在整个ServletContext(应用上下文)中是唯一的,类似于单例,但作用于整个Web应用而不是容器本身。
  • Global Session: 在Portlet应用中,全局session作用域的Bean在一个全局portlet session中是唯一的。这在多portlet环境中用于跨portlet共享状态

选择正确的scope有助于优化应用性能并确保正确管理对象的状态

这样Spring就会通过反射调用没有参数的构造方法生成对象,同时通过发射对应的setter方法注入配置的值

property 和 constructor-arg 是Spring框架中用于XML配置文件中进行依赖注入(Dependency Injection, DI)的两个不同方式。

  • constructor-arg 用于通过构造函数来注入依赖。它指定调用构造函数时传递给构造函数的参数。
  • 当类有无参构造函数或有参构造函数时,可以使用此标签来指定哪个构造函数应该被调用,并提供对应的参数值。
  • 参数可以通过value、ref、index等属性来设置,value用于直接注入基本类型或字符串,ref用于引用其他bean,index或name用于指定构造函数参数的位置或名称(如果构造函数有多个相同类型的参数)。
  • property 用于通过setter方法来注入依赖。它对应于Java对象的属性,即调用setter方法来设置对象的属性值。
  • 这种方式适用于类中有getter/setter方法的属性。
  • 和constructor-arg一样,property也可以通过value或ref来设置值。

主要区别:

  • 注入方式:constructor-arg是构造函数注入,而property是setter方法注入。
  • 初始化顺序:构造函数注入通常在对象实例化时发生,而setter注入可以在对象创建后任何时候进行。
  • 强制性:构造函数注入可能是强制性的,如果类只有一个无参构造函数,那么必须使用setter注入;如果有带参数的构造函数,Spring会根据提供的constructor-arg调用相应的构造函数。
  • 安全性:构造函数注入通常被认为更安全,因为它保证了对象在创建时就处于一致和完整状态,而setter注入的对象可能在初始化后才被完全设置好

依赖查找

有些资源并非来自系统,例如数据库连接资源完全可以在Tomcat下配置,然后通过JNDI形式获取,这种数据库连接资源属于工程外资源,就可以采用接口注入的形式获取它

顺便讲一下Tomcat配置JNDI(Java Naming and Directory Interface),主要涉及在Tomcat的配置文件中定义资源和资源引用

方式1:在context.xml中配置,找到conf/context.xml文件,添加以下元素来定义JNDI资源,例如一个数据源:

<Resource name="jndi/ssm" auth="Container"
          type="javax.sql.DataSource"
          driverClassName="com.mysql.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/ssm?useUnicode=true;characterEncoding=utf8""
          username="root" password="xxxx"
          maxActive="20" maxIdle="10" maxWait="10000"/>

方式2:在server.xml中配置全局资源,打开conf/server.xml文件,在元素内添加元素,这样配置的资源可以被所有应用共享

<GlobalNamingResources>
  ...
  <Resource name="jndi/globalSsm" auth="Container"
            type="javax.sql.DataSource"
            driverClassName="oracle.jdbc.driver.OracleDriver"
            url="jdbc:oracle:thin:@127.0.0.1:1521:ORCL"
            username="scott" password="tiger"
            maxActive="20" maxIdle="10" maxWait="10000"/>
  ...
</GlobalNamingResources>

然后在每个应用的context.xml文件中,通过元素引用全局资源, 如下所示

<ResourceLink name="jndi/localDemo" global="jndi/globalSsm" type="javax.sql.DataSource"/>

也可以在应用的WEB-INF/web.xml文件中,添加元素来声明应用将要使用的资源,完成资源引用:

<resource-ref>
  <description>DB Connection</description>
  <res-ref-name>jndi/localDemo</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
</resource-ref>

一旦在全局或某个应用的context.xml中定义了资源,接下来需要在具体的应用上下文中引用这些资源。这通常通过在应用的WEB-INF/web.xml文件中添加元素来完成,或者在应用的特定context.xml(如果有的话)使用来链接到全局资源

返回来,假设在Tomcat的context.xml文件中有如下JNDI配置

<Resource name="jndi/ssm" auth="Container"
          type="javax.sql.DataSource"
          driverClassName="com.mysql.cj.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/ssm?useUnicode=true;characterEncoding=utf8""
          username="root" password="xxxx"
          maxActive="20" maxIdle="10" maxWait="10000"/>

如果Tomcat的Web项目使用了Spring,那么可以通过Spring的机制,用JNDI获取Tomcat启动的数据库连接池,在webapp--->WEB-INF--->applicationContext.xml文件中加入配置

<!-- 定义一个数据源bean,通过JNDI技术从应用程序服务器中获取数据源 -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <!-- 设置JNDI名称,用于在Java EE服务器中查找对应的DataSource -->
    <property name="jndiName">
        <value>java:comp/env/jdbc/ssm</value>
    </property>
</bean>

这样就可以在Spring的IoC容器中获得Tomcat管理的数据库连接池了,这就是一种接口注入的方式

装配Bean

Spring提供了3种配置方式,在XML中显示配置、在Java的接口和类中实现配置、隐式Bean的发现机制和自动装配原则,在实际工作中,这三种方式都会被用到混合使用

  • 基于约定优于配置的原则,最优先的应该是通过隐式Bean的发现机制和基于自动装配的原则,这样的好处是减小程序开发者的决定权,简单又不失灵活
  • 在没有办法使用自动装配原则的情况下,应该优先考虑在Java接口和类中实现配置,这样的好处是减少XML配置的泛滥,例如一个父类有多个子类,通过IoC容器初始化一个父类,容器将无法知道使用哪个子类初始化,这个时候使用Java的注解配置指定
  • 在上述方法都无法使用的情况下,可以使用XML进行配置,也可以使用Java配置文件的new关键字创建对象配置,例如在实际工作中常常会用到第三方类库,常常无法修改里面的代码,这个时候可以通过这样的方式配置

如果配置的类是开发者自身正在开发的项目,那么应该考虑以Java配置为主,而Java配置又分为自动装配和Bean名称配置,优先使用自动装配可以减少大量的XML配置,如果所需配置的类是第三方的建议使用XML或者Java配置文件的方式

通过XML配置装备Bean

在使用XML装配Bean需要定义对应的XML,这里需要引入对应的XML模式(XSD)文件

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

    <!-- 定义一个BeanPostProcessor的实现类,用于bean创建后的处理 -->
    <bean id="beanPostProcessor" class="com.ssm.bean.BeanPostProcessorImpl" />

    <!-- 定义一个DisposableBean的实现类,用于bean销毁前的处理 -->
    <bean id="disposableBean" class="com.ssm.bean.DisposableBeanImpl" />

    <!-- 配置一个具有属性的bean,表示豆浆的原材料配置 -->
    <bean id="source" class="com.ssm.pojo.Source">
        <property name="bean" value="黑豆" />
        <property name="sugar" value="少糖" />
        <property name="size" value="大杯" />
    </bean>

    <!-- 配置一个延迟初始化的bean,表示豆浆机II,使用了初始化和销毁方法 -->
    <bean id="soybeanMilkMakerII" class="com.ssm.pojo.SoybeanMilkMakerII" lazy-init="true" init-method="init" destroy-method="destroy">
        <property name="beverageShop" value="贡茶" />
        <property name="source" ref="source" />
    </bean>

    <!-- 定义一个RoleII类型的bean,通过构造器进行初始化 -->
    <bean id="role1" class="com.ssm.pojo.RoleII">
        <!-- 构造器参数1:角色ID,这里设置为1 -->
        <constructor-arg index="0" value="1" />
        <!-- 构造器参数2:角色名称,这里设置为"总经理" -->
        <constructor-arg index="1" value="总经理" />
        <!-- 构造器参数3:角色描述,这里设置为"公司管理者" -->
        <constructor-arg index="2" value="公司管理者" />
    </bean>

    <!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" -->
    <bean id="role2" class="com.ssm.pojo.RoleII" scope="prototype">
        <!-- 设置角色id为2 -->
        <property name="id" value="2" />
        <!-- 设置角色名为"高级工程师" -->
        <property name="roleName" value="高级工程师" />
        <!-- 设置备注为"重要人员" -->
        <property name="note" value="重要人员" />
    </bean>
</beans>

在文件的头部,引入了一个元素的定义,它是一个根元素,同时XSD文件也被引入,使用该文件所定义的元素可以定义对应的Spring Bean

装配简单值
    <!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" -->
    <bean id="role2" class="com.ssm.pojo.RoleII">
        <!-- 设置角色id为2 -->
        <property name="id" value="2" />
        <!-- 设置角色名为"高级工程师" -->
        <property name="roleName" value="高级工程师" />
        <!-- 设置备注为"重要人员" -->
        <property name="note" value="重要人员" />
    </bean>

对应pojo里实体类的构造函数,就很清楚配置项的涵义

    /**
     * 带参数的构造方法,用于创建一个具有指定角色信息的角色对象。
     *
     * @param id 角色的唯一标识符。
     * @param roleName 角色的名称。
     * @param note 对角色的备注说明。
     */
    public RoleII(Long id, String roleName, String note) {
        this.id = id;
        this.roleName = roleName;
        this.note = note;
    }
  • id,它是Spring找bean的时候会找它的编号,也就是这个id,但id不是一个必须的属性,如果没有声明它,Spring将采用"全限定名#{number}"的格式生成编号,例如上边这个例子,如果没有id="role2",那么Spring将会为这个bean生成一个编号"com.ssm.pojo.RoleII#0"那么下一个没有声明id的bean就是"com.ssm.pojo.RoleII#1",很显然自动生成id并没有自己定义更清晰
  • class是pojo类的全限定名

这样定义很简单,稍微复杂一点的例如,在bean的配置中,需要注入一些自定义的类

    <!-- 配置一个具有属性的bean,表示豆浆的原材料配置 -->
    <bean id="source" class="com.ssm.pojo.Source">
        <property name="bean" value="黑豆" />
        <property name="sugar" value="少糖" />
        <property name="size" value="大杯" />
    </bean>

    <!-- 配置一个延迟初始化的bean,表示豆浆机II,使用了初始化和销毁方法 -->
    <bean id="soybeanMilkMakerII" class="com.ssm.pojo.SoybeanMilkMakerII" lazy-init="true" init-method="init" destroy-method="destroy">
        <property name="beverageShop" value="贡茶" />
        <property name="source" ref="source" />
    </bean>

如代码所示,首先定义了一个idsourcebean,然后再通过refidsoybeanMilkMakerIIbean引入

看一下对应的Pojo类

package com.ssm.pojo;
/**
 * Source类用于表示饮品的来源信息。
 */
public class Source {
    private String bean; // 饮品的类型
    private String sugar; // 饮品的糖分描述
    private String size; // 饮品的大小杯

    
    

    /**
     * 无参构造方法,用于创建一个新的Source对象。
     */
    public Source() {
        System.out.println("构造方法.....");
    }
    
    public Source(String bean, String sugar, String size) {
        this.bean = bean;
        this.sugar = sugar;
        this.size = size;
    }
    

    /**
     * 获取饮品类型。
     *
     * @return 返回当前饮品的类型。
     */
    public String getBean() {
        return bean;
    }

    /**
     * 设置饮品类型。
     *
     * @param bean 需要设置的饮品类型。
     */
    public void setBean(String bean) {
        this.bean = bean;
    }

    /**
     * 获取饮品的糖分描述。
     *
     * @return 返回当前饮品的糖分描述。
     */
    public String getSugar() {
        return sugar;
    }

    /**
     * 设置饮品的糖分描述。
     *
     * @param sugar 需要设置的饮品糖分描述。
     */
    public void setSugar(String sugar) {
        this.sugar = sugar;
    }

    /**
     * 获取饮品的大小杯。
     *
     * @return 返回当前饮品的大小杯。
     */
    public String getSize() {
        return size;
    }

    /**
     * 设置饮品的大小杯。
     *
     * @param size 需要设置的饮品大小杯。
     */
    public void setSize(String size) {
        this.size = size;
    }
}

package com.ssm.pojo;


import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;


/**
 * SoybeanMilkMakerII类实现了多种Spring接口,包括BeanNameAware, BeanFactoryAware,
 * ApplicationContextAware, InitializingBean,用于展示如何与Spring容器交互。
 * 这个类模拟了一个豆奶制作机,能够制作豆奶并提供自定义初始化和销毁逻辑。
 */
public class SoybeanMilkMakerII implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean{
    private String beverageShop = null; // 饮品店品牌
    private Source source = null; // 果汁原料描述

    // Getter 方法
    public String getBeverageShop() {
        return beverageShop;
    }

    // Setter 方法,确保参数类型与getter返回类型匹配
    public void setBeverageShop(String beverageShop) {
        this.beverageShop = beverageShop;
    }


    //Getter method
    public Source getSource() {
        return this.source;
    }

    // Setter method
    public void setSource(Source source) {
       this.source = source;
    }


    /**
     * SoybeanMilkMakerII的构造方法,打印构造信息。
     */
    public SoybeanMilkMakerII() {
        System.out.println("SoybeanMilkMakerII的构造方法");
    }

    /**
     * 制作豆奶的方法。
     * @return 返回一杯具有特定品牌、大小、糖分和水果的豆奶描述。
     */
    public String makeSoybeanMilk() {
        String soybeanMilk = "这是一杯由" + beverageShop + "饮品店,提供的" + source.getSize() + source.getSugar() + source.getBean();
        return soybeanMilk;
    }


    /**
     * 自定义初始化方法,展示如何在Bean初始化后执行特定逻辑。
     */
    public void init() {
        System.out.println("【" + this.getClass().getSimpleName() + "】执行自定义初始化方法");
    }

    /**
     * 自定义销毁方法,展示如何在Bean销毁前执行特定逻辑。
     */
    public void destroy() {
        System.out.println("【" + this.getClass().getSimpleName()  + "】执行自定义销毁方法");
    }


    /**
     * 实现BeanNameAware接口的方法,用于获取Bean的名称。
     * @param beanName Bean的名称。
     */
    @Override
    public void setBeanName(String beanName) {
        System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanNameAware接口的setBeanName方法");
    }

    /**
     * 实现BeanFactoryAware接口的方法,用于获取BeanFactory。
     * @param bf BeanFactory实例。
     * @throws BeansException 如果设置过程中发生错误。
     */
    @Override
    public void setBeanFactory(BeanFactory bf) throws BeansException {
        System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware接口的setBeanFactory方法");
    }

    /**
     * 实现ApplicationContextAware接口的方法,用于获取ApplicationContext。
     * @param ctx ApplicationContext实例。
     * @throws BeansException 如果设置过程中发生错误。
     */
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        System.out.println("【" + this.getClass().getSimpleName()  + "】调用ApplicationContextAware接口的setApplicationContext方法");
    }

    /**
     * 实现InitializingBean接口的方法,用于在所有属性设置完成后执行初始化操作。
     * @throws Exception 如果初始化过程中发生错误。
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("【" + this.getClass().getSimpleName() + "】调用InitializingBean接口的afterPropertiesSet方法");
    }

}
装配集合

有些时候要做复杂的装配工作,比如Set、Map、List、Array和Properties等,定义如下pojo

package com.ssm.pojo;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
 * ComplexAssembly类用于演示装配复杂数据类型。
 * 该类包含了一系列属性,分别是:id、list、map、props、set和array。
 * 这些属性通过对应的setter和getter方法进行访问和设置。
 */
public class ComplexAssembly {

    private Long id; // 唯一标识
    private List<String> list; // 字符串列表
    private Map<String, String> map; // 字符串键值对映射
    private Properties props; // 属性集合,常用于配置信息存储
    private Set<String> set; // 不含重复元素的集合
    private String[] array; // 字符串数组

    /**
     * 获取id属性。
     * @return 返回当前对象的唯一标识。
     */
    public Long getId() {
        return id;
    }

    /**
     * 设置id属性。
     * @param id 欲设置的唯一标识。
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * 获取list属性。
     * @return 返回当前对象的字符串列表。
     */
    public List<String> getList() {
        return list;
    }

    /**
     * 设置list属性。
     * @param list 欲设置的字符串列表。
     */
    public void setList(List<String> list) {
        this.list = list;
    }

    /**
     * 获取map属性。
     * @return 返回当前对象的字符串键值对映射。
     */
    public Map<String, String> getMap() {
        return map;
    }

    /**
     * 设置map属性。
     * @param map 欲设置的字符串键值对映射。
     */
    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    /**
     * 获取props属性。
     * @return 返回当前对象的属性集合。
     */
    public Properties getProps() {
        return props;
    }

    /**
     * 设置props属性。
     * @param props 欲设置的属性集合。
     */
    public void setProps(Properties props) {
        this.props = props;
    }

    /**
     * 获取set属性。
     * @return 返回当前对象的不含重复元素的集合。
     */
    public Set<String> getSet() {
        return set;
    }

    /**
     * 设置set属性。
     * @param set 欲设置的不含重复元素的集合。
     */
    public void setSet(Set<String> set) {
        this.set = set;
    }

    /**
     * 获取array属性。
     * @return 返回当前对象的字符串数组。
     */
    public String[] getArray() {
        return array;
    }

    /**
     * 设置array属性。
     * @param array 欲设置的字符串数组。
     */
    public void setArray(String[] array) {
        this.array = array;
    }

}

要装配这个pojo类,其xml配置如下

<!-- 定义一个复杂组件装配的bean -->
<bean id="complexAssembly"
      class="com.ssm.pojo.ComplexAssembly">
    <!-- 设置组件的id为1 -->
    <property name="id" value="1" />
    <!-- 设置一个字符串列表,包含多个值 -->
    <property name="list">
        <list>
            <value>value-list-1</value>
            <value>value-list-2</value>
            <value>value-list-3</value>
        </list>
    </property>
    <!-- 设置一个键值对映射,包含多个键值对 -->
    <property name="map">
        <map>
            <entry key="key1" value="value-map-1" />
            <entry key="key2" value="value-map-2" />
            <entry key="key3" value="value-map-3" />
        </map>
    </property>
    <!-- 设置一个属性集合,每个属性包含键和值 -->
    <property name="props">
        <props>
            <prop key="prop1">value-prop-1</prop>
            <prop key="prop2">value-prop-2</prop>
            <prop key="prop3">value-prop-3</prop>
        </props>
    </property>
    <!-- 设置一个无序的值集合 -->
    <property name="set">
        <set>
            <value>value-set-1</value>
            <value>value-set-2</value>
            <value>value-set-3</value>
        </set>
    </property>
    <!-- 设置一个值数组 -->
    <property name="array">
        <array>
            <value>value-array-1</value>
            <value>value-array-2</value>
            <value>value-array-3</value>
        </array>
    </property>
</bean>

上面代码是对字符串的各个集合的装载,有些时候可能需要更复杂的装载,例如一个List可以是一个系列类的对象,又如一个Map集合类,键是一个类对象,值也是一个类对象

package com.ssm.pojo;

public class RoleIII {

    private Long id; // 角色编号
    private String roleName; // 角色名称
    private String note; // 备注信息

    /**
     * 无参数构造方法,用于创建一个空的角色对象。
     */
    public RoleIII() {
    }

    /**
     * 带参数的构造方法,用于创建一个具有指定属性的角色对象。
     * @param id 角色的唯一标识符。
     * @param roleName 角色的名称。
     * @param note 关于角色的备注信息。
     */
    public RoleIII(Long id, String roleName, String note) {
        this.id = id;
        this.roleName = roleName;
        this.note = note;
    }

    /**** setters and getters ****/

    // 获取角色的ID
    public Long getId() {
        return id;
    }

    // 设置角色的ID
    public void setId(Long id) {
        this.id = id;
    }

    // 获取角色的名称
    public String getRoleName() {
        return roleName;
    }

    // 设置角色的名称
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    // 获取角色的备注信息
    public String getNote() {
        return note;
    }

    // 设置角色的备注信息
    public void setNote(String note) {
        this.note = note;
    }
}
package com.ssm.pojo;

/**
 * User类用于表示用户信息。
 */
public class User {
    private Long id; // 用户的唯一标识
    private String userName; // 用户名
    private String note; // 用户备注信息

    /**
     * 获取用户的唯一标识。
     * @return 用户的ID。
     */
    public Long getId() {
        return id;
    }

    /**
     * 设置用户的唯一标识。
     * @param id 要设置的用户ID。
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * 获取用户的用户名。
     * @return 用户的用户名。
     */
    public String getUserName() {
        return userName;
    }

    /**
     * 设置用户的用户名。
     * @param userName 要设置的用户名。
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    /**
     * 获取用户的备注信息。
     * @return 用户的备注信息。
     */
    public String getNote() {
        return note;
    }

    /**
     * 设置用户的备注信息。
     * @param note 要设置的用户备注信息。
     */
    public void setNote(String note) {
        this.note = note;
    }

}

package com.ssm.pojo;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * UserRoleAssembly 类用于组装和管理用户角色关系。
 * 它包含了对用户角色关系的不同类型集合的管理,如列表、映射和集合。
 */
public class UserRoleAssembly {
    private Long id; // 唯一标识符
    private List<RoleIII> list; // 角色列表
    private Map<RoleIII, User> map; // 角色到用户的映射
    private Set<RoleIII> set; // 角色集合

    /**** setters and getters ****/

    /**
     * 获取UserRoleAssembly的唯一标识符。
     * @return 返回此实例的唯一标识符。
     */
    public Long getId() {
        return id;
    }

    /**
     * 设置UserRoleAssembly的唯一标识符。
     * @param id 要设置的唯一标识符。
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * 获取角色列表。
     * @return 返回此实例的角色列表。
     */
    public List<RoleIII> getList() {
        return list;
    }

    /**
     * 设置角色列表。
     * @param list 要设置的角色列表。
     */
    public void setList(List<RoleIII> list) {
        this.list = list;
    }

    /**
     * 获取角色到用户的映射。
     * @return 返回此实例的角色到用户映射。
     */
    public Map<RoleIII, User> getMap() {
        return map;
    }

    /**
     * 设置角色到用户的映射。
     * @param map 要设置的角色到用户映射。
     */
    public void setMap(Map<RoleIII, User> map) {
        this.map = map;
    }

    /**
     * 获取角色集合。
     * @return 返回此实例的角色集合。
     */
    public Set<RoleIII> getSet() {
        return set;
    }

    /**
     * 设置角色集合。
     * @param set 要设置的角色集合。
     */
    public void setSet(Set<RoleIII> set) {
        this.set = set;
    }

}

则XML需要如下配置

<!-- 定义Role实体 Bean -->
<bean id="role_1" class="com.ssm.pojo.RoleIII">
    <property name="id" value="1" />
    <property name="roleName" value="role_name_1" />
    <property name="note" value="role_note_1" />
</bean>

<bean id="role_2" class="com.ssm.pojo.RoleIII">
    <property name="id" value="2" />
    <property name="roleName" value="role_name_2" />
    <property name="note" value="role_note_2" />
</bean>

<!-- 定义User实体 Bean -->
<bean id="user_1" class="com.ssm.pojo.User">
    <property name="id" value="1" />
    <property name="userName" value="user_name_1" />
    <property name="note" value="role_note_1" />
</bean>

<bean id="user_2" class="com.ssm.pojo.User">
    <property name="id" value="2" />
    <property name="userName" value="user_name_2" />
    <property name="note" value="role_note_1" />
</bean>

<!-- 定义UserRoleAssembly实体 Bean,用于组装用户和角色的关系 -->
<bean id="userRoleAssembly"
      class="com.ssm.pojo.UserRoleAssembly">
    <property name="id" value="1" />
    <property name="list">
        <list>
            <ref bean="role_1" />
            <ref bean="role_2" />
        </list>
    </property>
    <property name="map">
        <map>
            <entry key-ref="role_1" value-ref="user_1" />
            <entry key-ref="role_2" value-ref="user_2" />
        </map>
    </property>
    <property name="set">
        <set>
            <ref bean="role_1" />
            <ref bean="role_2" />
        </set>
    </property>
</bean>
命名空间装配

Spring还提供了对应的命名空间的定义,在使用命名空间的时候要先引入对应的命名空间和XML模式(XSD)文件, 如下代码所示

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       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">
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"

这两行定义了XML的命名空间,这样才能在内容里面使用p和c这样的前缀,如下代码所示

    <!-- 使用构造方法注入创建Role对象 -->
    <bean id="c_role" class="com.ssm.pojo.RoleIII" c:_0="8"
          c:_1="role_name_c" c:_2="role_note_c" />
    <!-- 使用setter注入创建Role对象 -->
    <bean id="p_role" class="com.ssm.pojo.RoleIII" p:id="9"
          p:roleName="role_name_p" p:note="role_note_p" />

在这段XML配置中,我们通过标签创建了两个Role对象。这里涉及到了两种不同的属性注入方式:C风格和P风格。

  • C风格的属性注入:
    • c:_0=“8” 表示注入第一个属性,根据Role类的定义,这可能是id属性。
    • c:_1=“role_name_c” 表示注入第二个属性,可能是roleName属性。
    • c:_2=“role_note_c” 表示注入第三个属性,可能是note属性。
  • P风格的属性注入:
    • p:id=“9” 明确指出了注入的属性名为id,其值为9。
    • p:roleName=“role_name_p” 明确指出了注入的属性名为roleName,其值为role_name_p。
    • p:note=“role_note_p” 明确指出了注入的属性名为note,其值为role_note_p。

这种方式在配置时更加直观,可以根据属性名直接确定注入的值,便于理解和维护

如下是通过命名空间定义UserRoleAssembly类实例

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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-4.0.xsd
		    http://www.springframework.org/schema/util
		    http://www.springframework.org/schema/util/spring-util.xsd">
    <!-- 使用构造方法注入创建RoleIII对象,通过构造函数参数初始化对象 -->
    <bean id="c_role" class="com.ssm.pojo.RoleIII"
          c:_0="8" c:_1="role_name_c" c:_2="role_note_c" />

    <!-- 使用setter方法注入创建RoleIII对象,通过调用setter方法初始化对象属性 -->
    <bean id="p_role" class="com.ssm.pojo.RoleIII"
          p:id="9" p:roleName="role_name_p" p:note="role_note_p" />

    <!-- 装配多个RoleIII和User对象示例,演示了通过构造方法和setter方法的混合注入 -->
    <bean id="role1" class="com.ssm.pojo.RoleIII"
          c:_0="1" c:_1="role_name_1" c:_2="role_note_1" />
    <bean id="role2" class="com.ssm.pojo.RoleIII"
          p:id="2" p:roleName="role_name_2" p:note="role_note_2" />
    <bean id="user1" class="com.ssm.pojo.User"
          p:id="1" p:userName="role_name_1" p:note="user_note_1" />
    <bean id="user2" class="com.ssm.pojo.User"
          p:id="2" p:userName="role_name_2" p:note="user_note_2" />

    <!-- 装配一个List对象,包含已定义的role1和role2两个Bean -->
    <util:list id="list">
        <ref bean="role1" />
        <ref bean="role2" />
    </util:list>

    <!-- 装配一个Map对象,将role和user Bean以键值对形式装配 -->
    <util:map id="map">
        <entry key-ref="role1" value-ref="user1" />
        <entry key-ref="role2" value-ref="user2" />
    </util:map>

    <!-- 装配一个Set对象,包含已定义的role1和role2两个Bean -->
    <util:set id="set">
        <ref bean="role1" />
        <ref bean="role2" />
    </util:set>

    <!-- 创建UserRoleAssembly对象,并将其与之前定义的List、Map、Set对象关联 -->
    <bean id="userRoleAssembly"
          class="com.ssm.pojo.UserRoleAssembly"
          p:id="1" p:list-ref="list" p:map-ref="map" p:set-ref="set" />

</beans>

通过注解装配Bean

实际上并不推荐使用XML的形式装配Bean,避免XML文件泛滥,使用注解的方式可以减少XML,注解的方式既能够实现XML的功能,也能够提供自动装配的功能,采用自动装配后,开发者所需做的决策就少了,更有利于程序的开发,这便是约定优于配置的理念

Spring提供了两种方式让Spring IoC容器发现Bean,大部分的项目都可以用Java配置完成,而不是XML,这样可以有效减少配置并避免引入大量XML,解决了在Sping3之前的版本需要配置大量XML的问题

  • 组件扫描:通过定义资源的方式,让SpringIoC容器扫描对应的包,从而把Bean装配起来
  • 自动装配:通过注解定义,使一些依赖关系可以通过注解完成

使用注解为主XML为辅的方式更加合理,比如当系统存在多个公共的配置文件时候(properties和xml),如果都写到注解里,显然代码里就分散了各种公共配置,不利于管理,这个时候XML的配置就更合理一些;又或者一些类来自第三方,而不是系统开发的配置文件,这时利用XML的方式会更加明确

使用注解@Component装配Bean
package com.ssm.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * RoleIV类表示一个角色实体,它包含了角色的基本信息。
 * 通过Spring的@Component注解将其标记为一个Bean,名称为"role"。
 */
@Component(value = "role")
public class RoleIV {
    // 通过@Value注解注入角色ID,这里硬编码为1。
    @Value("1")
    private Long id;
    // 通过@Value注解注入角色名称,这里硬编码为"admin"。
    @Value("admin")
    private String roleName;
    // 通过@Value注解注入角色备注信息,这里硬编码为"administrator"。
    @Value("administrator")
    private String note;
    
    /**
     * 无参构造函数,打印构造函数名称。
     */
    public RoleIV()
    {
        System.out.println("RoleIV()");
    }
    
    /**
     * 带参数的构造函数,用于初始化角色信息。
     * 
     * @param id 角色ID
     * @param roleName 角色名称
     * @param note 角色备注信息
     */
    public RoleIV(Long id, String roleName, String note)
    {
        System.out.println("RoleIV(Long id, String roleName, String note)");
        this.id = id;
        this.roleName = roleName;
        this.note = note;
    }
    
    /**
     * 获取角色名称。
     * 
     * @return 角色名称
     */
    public String getRoleName()
    {
        return roleName;
    }
    
    /**
     * 设置角色名称。
     * 
     * @param roleName 要设置的角色名称
     */
    public void setRoleName(String roleName)
    {
        this.roleName = roleName;
    }
    
    /**
     * 获取角色备注信息。
     * 
     * @return 角色备注信息
     */
    public String getNote()
    {
        return note;
    }
    
    /**
     * 设置角色备注信息。
     * 
     * @param note 要设置的角色备注信息
     */
    public void setNote(String note)
    {
        this.note = note;
    }
    
    /**
     * 获取角色ID。
     * 
     * @return 角色ID
     */
    public Long getId()
    {
        return id;
    }
    
    /**
     * 设置角色ID。
     * 
     * @param id 要设置的角色ID
     */
    public void setId(Long id)
    {
        this.id = id;
    }
    
    /**
     * 重写toString方法,便于打印角色信息。
     * 
     * @return 描述角色信息的字符串
     */
    @Override
    public String toString()
    {
        return "RoleIV [id=" + id + ", roleName=" + roleName + ", note=" + note + "]";
    }
}

@Component 是 Spring 框架中的一个注解,它是 Spring 的核心组件之一,用于标记一个 Java 类作为 Spring 容器管理的 Bean。当 Spring 容器启动时,它会扫描带有 @Component 注解的类,并将这些类实例化为 Bean,然后存储在 Spring 容器中,以便在需要时可以自动注入到其他依赖于它们的类中。

在代码中,@Component(value = “role”) 告诉 Spring 容器这个 RoleIV 类是一个 Bean,并且它的名称是 “role”。这样,其他类可以通过 @Autowired 或者 @Resource 注解来注入这个名为 “role” 的 Bean 实例,也可以简写成@Component(“role”),甚至不给value值,直接写成@Component,如果不写,SpringIoC容器就默认类名,以首字母小写的形式作为id,为其生成对象,装配到容器中

例如,如果你有一个其他类需要使用 RoleIV,可以这样做:

import org.springframework.beans.factory.annotation.Autowired;
import com.ssm.pojo.RoleIV;

public class SomeClass {
    private RoleIV role;
    
    @Autowired
    public void setRole(RoleIV role) {
        this.role = role;
    }
    // 其他方法...
}

Spring 会自动将 “role” Bean 注入到 SomeClass 的 role 字段中,无需手动创建 RoleIV 实例

然而有了这个pojo类还不够,SpringIoC并不知道去哪里扫描对象,这个时候可以使用一个java
Config告诉他

package com.ssm.pojo;

import org.springframework.context.annotation.ComponentScan;

/**
 * PojoConfig类用于配置POJO相关的设置。
 * 该类通过使用@ComponentScan注解来自动扫描并注册所有的组件,使得这些组件能够被Spring容器管理。
 *
 */
@ComponentScan
public class PojoConfig {
}

@ComponentScan表示启动扫描,默认扫描当前包的路径,然后便可以通过Spring定义好的Spring IoC容器的实现类AnnotationConfigApplicationContext初始化容器并生成Bean

package com.ssm.main;

import com.ssm.pojo.RoleIV;
import com.ssm.pojo.PojoConfig;
import com.ssm.config.ApplicationConfig;
import java.sql.SQLException;

import com.ssm.service.RoleIVService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 主函数类,用于演示基于注解的IoC容器的使用。
 */
public class AnnotationMain {

    /**
     * 程序入口点。
     * @param args 命令行参数
     * @throws SQLException 抛出SQLException异常
     */
    public static void main(String[] args) throws SQLException {
        testAnnotation();
    }
    /**
     * 测试注解配置的IoC容器功能。
     * 该方法不接受参数,也不返回任何值。
     * 主要步骤包括:
     * 1. 创建基于注解的IoC容器,并指定配置类;
     * 2. 通过容器获取RoleIV类型的Bean实例;
     * 3. 打印获取的Bean实例的id属性;
     * 4. 关闭容器。
     */
    public static void testAnnotation() {
        // 创建基于注解的IoC容器,并指定配置类
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);

        // 通过容器获取RoleIV类型的Bean实例
        RoleIV role = context.getBean(RoleIV.class);

        // 打印获取的Bean实例的id属性
        System.out.println(role.getId());

        // 关闭容器
        context.close();
    }
}

以上代码只是一个简单的例子,@Component存在几个非常实用的配置项,例如basePackages,用于配置一个Java包的数组,Spring会根据它扫描对应的包和子包;例如basePackageClasses,用于配置多个Java类,Spring会根据这个配置扫描,通过如下代码可以看到效果

首先定义接口,并定义接口方法,代码如下

package com.ssm.service;

import com.ssm.pojo.RoleIV;

/**
 * RoleService接口定义了角色服务的相关操作。
 */
public interface RoleIVService {

    /**
     * 打印角色信息。
     * @param roleIV 角色信息实体,包含角色相关的详细信息。
     */
    public void printRoleIVInfo(RoleIV roleIV);
}


Spring推荐使用接口定义方法,它可以将定义和实现分离,然后用集体的实现类实现

package com.ssm.service.iml;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.stereotype.Component;

/**
 * RoleIVService的实现类,提供具体的角色信息打印服务。
 */
@Component
public class RoleIVServiceImpl implements RoleIVService{

    /**
     * 打印角色信息。
     * @param roleIV 角色信息对象,包含角色的ID、名称和备注。
     */
    @Override
    public void printRoleIVInfo(RoleIV roleIV)
    {
        // 打印角色信息的方法实现
        System.out.println("RoleIVServiceImpl.printRoleInfo");
        System.out.println(roleIV.getId());
        System.out.println(roleIV.getRoleName());
        System.out.println(roleIV.getNote());
    }
}

这里注解@Component表明它是一个Spring需要装配的Bean,而且也实现了对应的接口方法,然后使用@ComponentScan加上对应的扫描配置,如下代码所示

package com.ssm.config;

/**
 * ApplicationConfig类用于配置Spring框架的组件扫描。
 * 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。
 */

import org.springframework.context.annotation.ComponentScan;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例

@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。

public class ApplicationConfig {

}
// ApplicationConfig类结束

应该尽量避免多个注解配置,扫描混乱,尤其重复扫描,避免没必要的异常出现;另外如果包名经常变动的场景应尽量避免使用包来扫,往往包名改了如果配置没跟着都改IDE不报错,这时候用类来扫会更好IDE会报错

使用如下代码来验证效果

    /**
     * 测试组件扫描功能。
     * 该方法通过注解配置的应用上下文来启动Spring容器,从容器中获取RoleIV和RoleIVService的实例,
     * 然后调用服务方法处理角色信息,最后关闭应用上下文。
     */
    public static void testComponentScan() {
        // 创建注解配置的应用上下文,并指定应用配置类ApplicationConfig
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);

        // 从应用上下文中获取RoleIV类型的Bean实例
        RoleIV role = context.getBean(RoleIV.class);
        // 从应用上下文中获取RoleIVService类型的Bean实例
        RoleIVService roleIVService = context.getBean(RoleIVService.class);

        // 调用服务方法,打印角色信息
        roleIVService.printRoleIVInfo(role);

        // 关闭应用上下文
        context.close();
    }
自动装配@Autowired

SpringIoC先完成Bean的定义,再初始化和寻找需要注入的资源,所谓自动装配就是由Spring发现对应的Bean,自动完成装配工作的方式,如下代码所示:

package com.ssm.service;

/**
 * RoleIVServiceII接口定义了角色IV的相关服务操作
 */

public interface RoleIVServiceII {

    /**
     * 打印角色IV的信息
     * 该方法没有参数和返回值
     */
    public void printRoleIVInfo();
}

其实现类代码如下

package com.ssm.service.iml;

import com.ssm.service.RoleIVServiceII;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ssm.pojo.RoleIV;

/**
 * RoleIVServiceIIImpl类实现了RoleIVServiceII接口,用于打印RoleIV对象的信息。
 * 通过@Autowired注解自动注入RoleIV对象。
 */
@Component(value = "roleIVServiceII")
public class RoleIVServiceIIImpl implements RoleIVServiceII{

    @Autowired
    private RoleIV roleIV; // 自动注入的RoleIV对象

    /**
     * 打印RoleIV对象的信息,包括ID、角色名和备注。
     * 该方法没有参数和返回值。
     */
    @Override
    public void printRoleIVInfo() {
        System.out.println(roleIV.getId() + " " + roleIV.getRoleName() + " " + roleIV.getNote());
    }
}

这里使用了@Autowired,表示在SpringIoC定位所有的Bean后,这个字段需要按类型注入,然后Spring IoC容器会寻找资源找到后将其注入,也就是将实例roleIV注入进来,这里需要注意的是@Autowired会按照类型进行注入

SpringIoC容器有时候会寻找失败,在默认情况下会抛异常,因为SpringIoC容器认为一定要找到对应的Bean注入这个属性,如果要改变这个特性,可以通过@Autowired的配置项required改变它,类似这样@Autowired(required=false), 也就是说默认情况下值必须注入成功的,也就是这个required的值默认为true,声明修改为false后,表明如果在已经定义好的Bean中找不到对应的类型,则允许不注入,也不会抛异常,这个时候这个字段可能为空值,需要开发者自己校验,从而避免类似空指针异常等异常出现

注解@Autowired除可以配置在属性外,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入,如下代码所示:

package com.ssm.service.iml;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVServiceII;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 这是一个实现RoleIVServiceII接口的服务类,用于操作RoleIV实体。
 */
@Component(value = "roleIVServiceII")
public class RoleIVServiceIIImplII implements RoleIVServiceII{
    
    private RoleIV roleIV; // RoleIV实体对象
    
    /**
     * 通过@Autowired注解自动注入RoleIV实体。
     * @param roleIV 要注入的RoleIV实体
     */
    @Autowired
    public void setRoleIV(RoleIV roleIV){
        this.roleIV = roleIV;
    }
    
    /**
     * 打印RoleIV实体的信息。
     * 该方法没有参数和返回值。
     */
    public void printRoleIVInfo() {
        System.out.println(roleIV.getId() + " " + roleIV.getRoleName() + " " + roleIV.getNote());
    }
}

使用@Autowired这是SpringIoC自动装配完成的,使得配置大幅度减少,满足约定优于配置的原则,也不会降低程序的健壮性

自定义装配的歧义性(注解@Primary和注解@Qualifier)

Spring建议在大部分情况下使用接口编程,但定义一个接口并不一定只有一个实现类,例如接口

package com.ssm.service;

import com.ssm.pojo.RoleIV;

/**
 * RoleService接口定义了角色服务的相关操作。
 */
public interface RoleIVService {

    /**
     * 打印角色信息。
     * @param roleIV 角色信息实体,包含角色相关的详细信息。
     */
    public void printRoleIVInfo(RoleIV roleIV);
}

有了一个实现类

package com.ssm.service.iml;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.stereotype.Component;

/**
 * RoleIVService的实现类,提供具体的角色信息打印服务。
 */
@Component
public class RoleIVServiceImpl implements RoleIVService{

    /**
     * 打印角色信息。
     * @param roleIV 角色信息对象,包含角色的ID、名称和备注。
     */
    @Override
    public void printRoleIVInfo(RoleIV roleIV)
    {
        // 打印角色信息的方法实现
        System.out.println("RoleIVServiceImpl.printRoleInfo");
        System.out.println("id="+roleIV.getId());
        System.out.println("roleName="+roleIV.getRoleName());
        System.out.println("note="+roleIV.getNote());
    }
}

还有另一个实现类

package com.ssm.service.iml;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.stereotype.Component;

/**
 * RoleIVServiceImplII类实现了RoleIVService接口,
 * 用于打印RoleIV对象的相关信息。
 */
@Component("RoleIVServiceImplII")
public class RoleIVServiceImplII implements RoleIVService {

    /**
     * 打印RoleIV对象的信息。
     *
     * @param roleIV RoleIV对象,包含角色的id、角色名和备注信息。
     *               该方法会打印出这些信息的字符串表示。
     */
    @Override
    public void printRoleIVInfo(RoleIV roleIV) {
        // 通过字符串拼接的方式,将roleIV对象的id、roleName和note属性打印出来
        System.out.println("{id=" + roleIV.getId() + ", roleName=" + roleIV.getRoleName() + ", note=" + roleIV.getNote() + "}");
    }
}

这个时候新建一个类,代码如下

package com.ssm.controller;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * RoleIVController类用于处理与RoleIV相关的请求。
 * 它通过注入RoleIVService来实现对RoleIV业务逻辑的调用。
 */
@Component
public class RoleIVController {
    
    // 自动注入RoleIVService,以便在controller中使用
    @Autowired
    private RoleIVService roleIVService;
    
    /**
     * 打印RoleIV的信息。
     * 该方法会调用roleIVService中的printRoleIVInfo方法,将roleIV的信息打印出来。
     * 
     * @param roleIV 角色信息对象,包含角色的详细信息。
     */
    public void printRoleIVInfo(RoleIV roleIV)
    {
        roleIVService.printRoleIVInfo(roleIV);
    }
}

它有一个字段是RoleService类型,并且自动注入的属性是roleService,RoleService是接口类型,但RoleService有两个实现类,这个时候SpringIoC容器就会混乱了,它是无法判断入住哪个对象的,于是就会抛出异常,这样通过@Autowired注入就会失败

究其原因,发生这样的情况是因为注解@Autowired采用的是按类型注入对象的方式,一个接口可以有多个实现类,一个抽象类也可以有多个非抽象子类,但类型相同,无法获取唯一的实例,导致了异常情况

在SpringIoC底层容器接口BeanFactory的定义,存在一个通过类型获取Bean的方法即:
<T> T getBean(Class<T> requiredType) throws BeansException;

这样仅仅通过类型(RoleIVService.class)作为参数无法判断使用哪个具体类实例返回,这便是自动装备的歧义,为了解决歧义,Spring提供了注解@Primary和注解@Qualifier用于消除歧义

@Primary

@Primary注解代表优先的,表示当Spring IoC通过一个接口或者抽象类注入对象,发现多个实现类,不能做出决策时,注解@Primary会告诉Spring IoC容器,优先将该类实例注入,如下代码所示

package com.ssm.service.iml;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;

/**
 * 这是一个实现RoleIVService接口的类,用于处理关于RoleIV实体的相关业务逻辑。
 * 通过实现printRoleIVInfo方法,打印RoleIV实体的信息。
 *
 * @Component("roleIVServiceIII") 标注此对象是一个Bean,并且在Spring容器中的名称为"roleIVServiceIII"。
 * @Primary 标注此Bean在同一个接口的多个实现中具有优先权。
 */
@Component("roleIVServiceIII")
@Primary
public class RoleIVServiceImplIII implements RoleIVService{

    /**
     * 打印RoleIV实体的信息。
     *
     * @param roleIV RoleIV实体,包含id、角色名和备注信息。
     * 该方法不返回任何内容,仅将实体信息以特定格式打印到控制台。
     */
    public void printRoleIVInfo(RoleIV roleIV)
    {
        // 格式化输出RoleIV实体的详细信息
        System.out.println("{id=" + roleIV.getId() + ", roleName=" + roleIV.getRoleName() + ", note=" + roleIV.getNote() + "}");
    }
}

@Primary告诉Spring IoC容器,当存在多个RoleIVService的实现类时,优先将RoleIVServiceImplIII的实例注入,然而@Primary只能解决优先问题,解决不了选择问题,例如同一个接口两个实现类,都挂上@Primary的话,代码可以这样写,但是注入的时候就会抛异常了

@Qualifier

出现歧义的一个重要原因是Spring在注入时是按类型,除了按类型查找Bean,SpringIoC容器底层的接口BeanFactory,也定义了按名称查找的方法<T> T getBean(String name, Class<T> requiredType) throws BeansException;,注解@Qualifier就是用来按名称查找的,修改RoleIVController代码如下

package com.ssm.controller;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * RoleIVController类用于处理与RoleIV相关的请求。
 * 它通过注入RoleIVService来实现对RoleIV业务逻辑的调用。
 */
@Component
public class RoleIVController {

    // 自动注入RoleIVService,以便在controller中使用
    @Autowired
    @Qualifier("roleIVServiceIII")
    private RoleIVService roleIVService;

    /**
     * 打印RoleIV的信息。
     * 该方法会调用roleIVService中的printRoleIVInfo方法,将roleIV的信息打印出来。
     *
     * @param roleIV 角色信息对象,包含角色的详细信息。
     */
    public void printRoleIVInfo(RoleIV roleIV)
    {
        roleIVService.printRoleIVInfo(roleIV);
    }
}

这个时候IoC容器不会再按照类型的方式注入,而是按照名称的方式注入,就不会存在歧义,再明确一下@Autowired的注入规则,先按照类型匹配,如果只有一个满足的Bean,则直接将其注入,结束;如果找到多个匹配的Bean类型,那么会按属性名查找Bean,例如上边这个代码private RoleIVService roleIVService;,就会先找roleIVService这个字符串,如果可以找到则结束注入过程,在这两个情况都找不到,并且又不允许注入为空时则抛异常

装载带有参数的构造方法类

通常构造方法都是带参数的,而带参数的构造方法也允许通过注解注入,如下代码所示

package com.ssm.controller;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * RoleIVController类,负责处理RoleIV相关的控制器逻辑。
 * 依赖于RoleIVService来执行具体的服务逻辑。
 */
@Component
public class RoleIVController {

    // RoleIVService的实例,用于执行角色IV相关的服务操作。
    private RoleIVService roleIVService;

    /**
     * RoleIVController的构造函数,通过依赖注入的方式初始化roleIVService。
     *
     * @param roleIVService 一个标记为@Qualifier("roleIVServiceIII")的RoleIVService实例,用于具体的服务逻辑处理。
     */
    public RoleIVController(@Autowired @Qualifier("roleIVServiceIII") RoleIVService roleIVService){
        this.roleIVService = roleIVService;
    }
    

    /**
     * 打印RoleIV的信息。
     * 该方法会调用roleIVService中的printRoleIVInfo方法,将roleIV的信息打印出来。
     *
     * @param roleIV 角色信息对象,包含角色的详细信息。
     */
    public void printRoleIVInfo(RoleIV roleIV)
    {
        roleIVService.printRoleIVInfo(roleIV);
    }
}

使用注解@Bean装配

以上都是通过@Component装配Bean,但注解@Component只能注解在类上,不能注解到方法上,对于Java而言大部分的开发都需要引入第三方的包,而且往往并没有这些包的资源,这时候将无法为这些包的类加入注解@Component,从而让它成为开发环境的Bean

这种情况开发者可以使用新类扩展(extends)其包内的类,然后在新类上使用注解@Component,但这样又会显得很奇怪,这个场景中Spring给了一个注解@Bean,它可以注解到方法上,并将方法返回的对象作为Spring的Bean,存放在IoC的容器中

例如如我需要使用DBCP数据源,就要引入关于它的包,然后装配数据源的Bean,如下代码所示

package com.ssm.config;

import java.util.Properties;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

/**
 * 数据源配置类,用于配置数据库连接池。
 */
public class DataSourceConfig {

    // 通过注解方式注入MySQL驱动类名
    @Value("com.mysql.jdbc.Driver")
    private String driverClassName = null;

    // 通过注解方式注入数据库URL
    @Value("jdbc:mysql://localhost:3306/ssm")
    private String url = null;

    // 通过注解方式注入数据库用户名
    @Value("root")
    private String username = null;

    // 通过注解方式注入数据库密码
    @Value("Ms123!@#")
    private String password = null;

    /**
     * 创建并返回DataSource Bean。
     *
     * @return 返回配置好的DataSource实例。
     */
    @Bean("dataSource")
    public DataSource getDataSource(){
        Properties props = new Properties();
        // 设置数据库连接池的属性
        props.setProperty("driverClassName", driverClassName);
        props.setProperty("url", url);
        props.setProperty("username", username);
        props.setProperty("password", password);
        DataSource datasource = null; // 初始化数据源变量
        try{
            // 通过属性创建数据源
            datasource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datasource;
    }
}

这样就能够装配一个Bean,当SpringIoC容器扫描它的时候,就会为其生成对应的Bean,和其他的Bean一样,它可以通过@Autowired或者@Qualifier等注解注入别的Bean中

注解自定义Bean的初始化和销毁方法

在Spring框架中,@Bean注解告诉Spring容器这个方法将会返回一个需要被管理的对象,即Bean,在该注解中可以自定义初始化方法和销毁方法等一系列操作,只需要运用注解@Bean的配置项

  • Bean的名称:默认情况下,@Bean注解的方法名就是Bean的ID。如果需要自定义Bean的名称,可以使用name属性, 如@Bean(name = "myBean")。如果有多个@Bean方法返回相同类型,可以通过@Qualifier注解来区分它们。
  • 初始化和销毁方法:initMethoddestroyMethod属性可以用来指定Bean初始化和销毁时要调用的方法。例如,@Bean(initMethod = "init", destroyMethod = "cleanup")
  • 依赖注入:Spring会自动处理@Bean方法之间的依赖关系,通过方法调用来注入。例如,如果Bean A需要Bean B,可以直接在A的@Bean方法中调用B的@Bean方法。如果需要注入其他已经注册的Bean,可以使用@Autowired注解,或者通过@Qualifier来指定特定的Bean。
  • 作用域:默认情况下,@Bean创建的Bean是单例(Singleton)的。如果你想创建原型(Prototype)或者其他作用域的Bean,可以使用scope属性,如@Bean(scope = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  • autowireCandidate属性是一个布尔值,它用于控制Spring容器在自动装配其他Bean时是否考虑当前Bean。默认情况下,autowireCandidate是true,意味着这个Bean可以作为自动装配的候选者。如果设置为false,那么这个Bean将被排除在自动装配的候选列表之外。当你有一个Bean,你并不希望它在自动装配过程中被其他组件使用,或者你有多个相同类型的Bean,但只想让其中一个参与自动装配,这时就可以使用autowireCandidate。
  • 属性值注入:可以使用@Value注解来注入属性值,比如常量或环境变量。对于复杂的属性配置,可以使用@ConfigurationProperties注解配合YAMLProperties文件来绑定。
  • Profile:@Profile注解允许你在特定的环境中激活或禁用Bean。例如,@Bean(@Profile("dev"))将只在开发环境下创建Bean。
  • 懒加载:使用@Lazy注解标记的@Bean表示该Bean将在第一次请求时才创建,而不是在容器启动时立即创建。
  • 复合Bean:@Bean方法可以返回另一个@Bean方法的引用,实现Bean的组合。
  • 自定义初始化逻辑:@Bean方法体中的代码会作为Bean的初始化逻辑执行。
  • 代理模式:Spring支持JDK动态代理和CGLIB代理,你可以通过proxyMode属性来控制。

@Bean注解通常与@Configuration注解一起使用,后者标识一个类作为配置源,提供了声明式的方式来定义Bean。这种方式比XML配置更简洁且易于维护

    /**
     * 创建并配置 SoybeanMilkMakerII 实例的 Bean。
     *
     * @return SoybeanMilkMakerII 实例,配置了特定的饮料店名称和制作原料来源。
     * @see SoybeanMilkMakerII
     */
    @Bean(name = "soybeanMilkMakerII", initMethod = "init", destroyMethod = "Mydestroy")
    public SoybeanMilkMakerII getSoybeanMilkMakerII() {
        // 创建 SoybeanMilkMakerII 实例
        SoybeanMilkMakerII soybeanMilkMakerII = new SoybeanMilkMakerII();
        // 设置所属饮料店为"贡茶"
        soybeanMilkMakerII.setBeverageShop("贡茶");
        // 创建原料来源并配置其属性
        Source source = new Source("豆子", "少糖", "中杯");
        soybeanMilkMakerII.setSource(source);
        return soybeanMilkMakerII;
    }

混合使用装配Bean

在使用注解的前提下,有些场景使用XML会更合适,这就产生了两个共同存在的情况,例如当引入了第三方包或者服务的时候,如果使用注解,那么这个第三方的配置可能会散落在各个地方,难于管理,这种情况下用XML会更合适

例如如下代码

package com.ssm.config;

import java.util.Properties;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

/**
 * 数据源配置类,用于配置数据库连接池。
 */
public class DataSourceConfig {

    // 通过注解方式注入MySQL驱动类名
    @Value("com.mysql.jdbc.Driver")
    private String driverClassName = null;

    // 通过注解方式注入数据库URL
    @Value("jdbc:mysql://localhost:3306/ssm")
    private String url = null;

    // 通过注解方式注入数据库用户名
    @Value("root")
    private String username = null;

    // 通过注解方式注入数据库密码
    @Value("xxxxxx")
    private String password = null;

    /**
     * 创建并返回DataSource Bean。
     *
     * @return 返回配置好的DataSource实例。
     */
    @Bean("dataSource")
    public DataSource getDataSource(){
        Properties props = new Properties();
        // 设置数据库连接池的属性
        props.setProperty("driverClassName", driverClassName);
        props.setProperty("url", url);
        props.setProperty("username", username);
        props.setProperty("password", password);
        DataSource datasource = null; // 初始化数据源变量
        try{
            // 通过属性创建数据源
            datasource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datasource;
    }
}

完全可以用

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:c="http://www.springframework.org/schema/c"
		xmlns:p="http://www.springframework.org/schema/p"
		xmlns:util="http://www.springframework.org/schema/util"
		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-4.0.xsd
		    http://www.springframework.org/schema/util 
		    http://www.springframework.org/schema/util/spring-util.xsd
		    http://www.springframework.org/schema/context  
            http://www.springframework.org/schema/context/spring-context-4.0.xsd ">
	
    
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/ssm" />
		<property name="username" value="root" />
		<property name="password" value="xxxxx" />
	</bean>
    
</beans>

假设有这个xml,xml定义了一个bean,其内容是数据库连接配置,就可以将这个xml引入到注解的体系中,如下代码所示

package com.ssm.config;

/**
 * ApplicationConfig类用于配置Spring框架的组件扫描。
 * 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。
 */

import org.springframework.context.annotation.ComponentScan;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.annotation.ImportResource;

@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/**
 * 导入资源文件到Spring上下文中。
 * 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。
 * 具体路径为classpath下的'spring-data.xml'文件。
 * 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。
 */
@ImportResource({"classpath:spring-data.xml"})

public class ApplicationConfig {

}
// ApplicationConfig类结束

@ImportResource中配置的内容是个数组,它可以配置多个XML配置文件,这样就可以引入多个XML定义的Bean了

先定义一个接口,如下所示

/**
 * RoleDataSourceService接口定义了角色数据源服务的相关操作。
 * 该接口主要负责通过ID获取角色信息的具体实现。
 */
package com.ssm.service;

import com.ssm.pojo.RoleIV;

public interface RoleDataSourceService {

    /**
     * 根据指定ID获取角色信息。
     *
     * @param id 角色的唯一标识符,类型为long。
     * @return 返回对应ID的角色信息,类型为RoleIV。
     */
    public RoleIV getRoleIV(Long id);
}

其实现类代码如下:

package com.ssm.service.iml;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleDataSourceService;

/**
 * 实现RoleDataSourceService接口,提供获取角色信息的功能
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class RoleDataSourceServiceImpl implements RoleDataSourceService {

    // 通过自动装配获取数据源
    @Autowired
    DataSource dataSource = null;

    /**
     * 根据角色ID获取角色信息
     * @param id 角色的ID
     * @return 返回对应的角色信息,如果没有找到则返回null
     */
    @Override
    public RoleIV getRoleIV(Long id) {
        Connection conn = null;
        ResultSet rs = null;
        PreparedStatement ps = null;
        RoleIV roleIV = null;
        try {
            // 获取数据库连接
            conn = dataSource.getConnection();
            // 构造查询SQL语句
            String sql = "select id, role_name, note from t_role where id = ?";
            ps = conn.prepareStatement(sql);
            // 设置查询参数
            ps.setLong(1, id);
            // 执行查询
            rs = ps.executeQuery();
            // 处理查询结果
            while (rs.next()) {
                // 构建角色对象
                roleIV = new RoleIV();
                roleIV.setId(rs.getLong("id"));
                roleIV.setRoleName(rs.getString("role_name"));
                roleIV.setNote(rs.getString("note"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭数据库连接及相关资源
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return roleIV;
    }
}

通过这样的形式就把XML配置的dataSource注入RoleIVDataSourceServiceImpl了,而有的时候所有的配置都放在一个ApplicationConfig类里会造成配置过多且复杂,因此开发者可能希望有多个类似于ApplicationConfig的配置类,例如ApplicationConfig2、ApplicationConfig3等,Spring也提供了这个机制,使用@Import的方式注入这些配置类

package com.ssm.config;

/**
 * ApplicationConfig类用于配置Spring框架的组件扫描。
 * 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。
 */

import com.ssm.pojo.PojoConfig;
import org.springframework.context.annotation.ComponentScan;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/**
 * 导入资源文件到Spring上下文中。
 * 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。
 * 具体路径为classpath下的'spring-data.xml'文件。
 * 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。
 */
@ImportResource({"classpath:spring-data.xml"})
/**
 * 使用@Import注解引入配置类
 * 该注解用于指定应用在启动时需要加载的配置类。在这里,我们引入了两个配置类:
 * 1. DataSourceConfig.class:用于数据源的配置,例如数据库连接池的配置。
 * 2. PojoConfig.class:用于POJO(Plain Old Java Object)的配置,例如实体类的映射配置。
 * 这两个配置类会被Spring上下文加载,以便应用在运行时可以使用其中定义的配置。
 */
@Import({DataSourceConfig.class, PojoConfig.class})


public class ApplicationConfig {

}
// ApplicationConfig类结束

在XML中也已使用这个机制,如下代码所示,在spring-bean.xml中引入spring-datasource.xml,就可以在spring-bean.xml中使用import元素来加载,如代码所示<import resource="spring-datasource.xml">;Spring不支持使用XML加载Java配置类,但Spring支持通过XML的配置扫描注解的包,如代码所示<context:component-scan base-package="com.ssm"/>

使用Profile

实际开发中,都存在多个环境,这样就有了在不同的系统中进行切换的需求,Spring也支持这样的场景,在Spring中我们可以定义Bean的Profile

使用注解@Profile配置
package com.ssm.bean;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
/**
 * ProfileDataSource类用于根据不同的环境配置数据源。
 * 通过@Profile注解,实现了根据Spring环境(dev或test)动态配置数据源。
 */
@Component
public class ProfileDataSource {

    /**
     * 当环境为dev时,创建并返回一个数据源。
     *
     * @return DataSource 返回一个数据源实例。
     */
    @Bean(name = "devDataSource")
    @Profile("dev")
    public DataSource getDevDataSource() {
        System.out.println("dev datasource");
        // 配置数据库连接属性
        Properties props = new Properties();
        props.setProperty("driver", "com.mysql.jdbc.Driver");
        props.setProperty("url", "jdbc:mysql://localhost:3306/ssm");
        props.setProperty("username", "root");
        props.setProperty("password", "123456");
        DataSource dataSource = null;
        // 尝试根据配置创建数据源
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    /**
     * 当环境为test时,创建并返回一个数据源。
     *
     * @return DataSource 返回一个数据源实例。
     */
    @Bean(name = "testDataSource")
    @Profile("test")
    public DataSource getTestDataSource() {
        System.out.println("test datasource");
        // 配置数据库连接属性,此处与dev环境配置相同,实际应用中可能不同
        Properties props = new Properties();
        props.setProperty("driver", "com.mysql.jdbc.Driver");
        props.setProperty("url", "jdbc:mysql://localhost:3306/ssm");
        props.setProperty("username", "root");
        props.setProperty("password", "123456");
        DataSource dataSource = null;
        // 尝试根据配置创建数据源
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }
}

如代码所示,使用注解@Profile配置了两个环境的数据库连接池

使用XML定义Profile
<?xml version='1.0' encoding='UTF-8' ?>
<!-- 定义Spring配置文件,指定配置文件的版本和命名空间 -->
<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-4.0.xsd"
       profile="dev">
    <!-- 定义数据源bean,使用Apache Commons DBCP2提供的BasicDataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <!-- 设置数据库驱动类名 -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <!-- 设置数据库连接URL -->
        <property name="url" value="jdbc:mysql://localhost:3306/ssm" />
        <!-- 设置数据库用户名 -->
        <property name="username" value="root" />
        <!-- 设置数据库密码 -->
        <property name="password" value="123456" />
    </bean>
</beans>

如上代码配置了一个Profile为dev的数据源,也可以配置多个Profile

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <!-- 定义在测试环境下的数据源配置 -->
    <beans profile="test">
        <bean id="devDataSource" class="org.apache.commons.dbcp2.BasicDataSource">
            <!-- 数据库驱动类名 -->
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <!-- 数据库连接URL -->
            <property name="url" value="jdbc:mysql://localhost:3306/ssm" />
            <!-- 数据库用户名 -->
            <property name="username" value="root" />
            <!-- 数据库密码 -->
            <property name="password" value="123456" />
        </bean>
    </beans>

    <!-- 定义在开发环境下的数据源配置,配置内容与测试环境相同 -->
    <beans profile="dev">
        <bean id="devDataSource" class="org.apache.commons.dbcp2.BasicDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/ssm" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
        </bean>
    </beans>
</beans>

如果使用的IDE是Idea,会弹出通知,点开后便可以选择配置
在这里插入图片描述

启动Profile

当启动Java或者XML配置的Profile时,这些Bean并不会被加载到Spring IoC容器中,需要自行激活Profile,常见激活Profile的方法有5种:

  • 在使用Spring MVC的情况下,可以配置Web上下文参数或者配置DispatchServlet参数
  • 作为JNDI条目
  • 配置环境变量
  • 配置JVM启动参数
  • 在集成测试环境中使用注解@ActiveProfile
package com.ssm;

import javax.sql.DataSource;

import com.ssm.config.ApplicationConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class) // 使用Spring提供的JUnit运行器
@ContextConfiguration(classes= ApplicationConfig.class) // 指定Spring配置类
@ActiveProfiles("test") // 指定使用的Spring Profile为"test"
public class ProfileTest {

    @Autowired
    private DataSource dataSource; // 自动注入数据源

    /**
     * 测试方法,用于验证在“test”环境下数据源是否能被正确注入。
     * 该方法没有参数和返回值。
     */
    @Test
    public void test() {
        System.out.println(dataSource.getClass().getName()); // 输出数据源类的名称
    }
}

以上代码是通过@ActiveProfiles注解来指定加载哪个Profile,有些时候程序要在一些服务器上运行,这个时候可以配置Java虚拟机的启动项,例如程序在Tomcat服务器上或者main方法上运行,Java虚拟机的参数如下:

  • spring.profiles.active:如果配置了该参数,那么spring.profiles.default配置项将失效
  • spring.profiles.default:默认的配置,如果没有配置关于Profile的参数,就使用这个默认配置

在上边这个例子中,配置应该是JAVA_OPTS="-Dspring.profiles.active=test"

在这里插入图片描述
在这里插入图片描述
都可以配置启动项,也可以根据自己的项目属性自行配置
在这里插入图片描述
如果是使用Spring MVC的Web项目,也可以设置Web环境参数或者DispatcherServlet参数,选择对应的Profile,例如修改web.xml配置Profile

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
	<!-- 配置Spring IoC配置文件路径 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/applicationContext.xml</param-value>
	</context-param>
	<!-- 设置Spring环境配置参数 -->
	<context-param>
		<!-- 参数名称:指定Spring环境的活动配置文件 -->
		<param-name>spring.profiles.active</param-name>
		<!-- 参数值:定义当前活动的环境配置,此处为'test'环境 -->
		<param-value>test</param-value>
	</context-param>
	<!-- 配置ContextLoaderListener用以初始化Spring IoC容器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- 配置DispatcherServlet -->
	<servlet>
		<!-- 注意:Spring MVC框架会根据servlet-name配置,找到/WEB-INF/dispatcher-servlet.xml作为配置文件载入Web工程中 -->
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 初始化参数配置 -->
		<init-param>
			<!-- 参数名称,用于指定Spring环境配置的激活 profil -->
			<param-name>spring.profiles.active</param-name>
			<!-- 参数值,此处设置为"test",表示激活的环境配置为test -->
			<param-value>test</param-value>
		</init-param>
		<!-- 使得Dispatcher在服务器启动的时候就初始化 -->
		<load-on-startup>2</load-on-startup>
	</servlet>
	<!-- Servlet拦截配置 -->
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

注意顺序,顺序反了要飘红的

加载属性文件

使用注解方式加载属性文件

同样是在ApplicationConfig.java文件中写入如下配置

/**
 * 使用该注解来向Spring Boot应用添加一个属性源。它会尝试从类路径下的"database-config.properties"文件中加载属性,
 * 并将其命名为"database.properties"。如果文件找不到,则不会报错,因为设置了ignoreResourceNotFound为true。
 * 该属性源的编码格式为UTF-8。
 *
 * 该注解通常用于配置类上,以在Spring应用启动时加载额外的配置属性。
 */
@PropertySource(
        value = "classpath:database-config.properties",
        name = "database.properties",
        ignoreResourceNotFound = true,
        encoding = "UTF-8"
)

这样还不够,Spring还无法将属性读入,因为缺少属性文件的解析配置器,即PropertySourcesPlaceHolderConfigurer,需在继续添加如下代码

package com.ssm.config;

/**
 * ApplicationConfig类用于配置Spring框架的组件扫描。
 * 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。
 */

import com.ssm.condition.DataSourceCondition;
import com.ssm.pojo.PojoConfig;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

import javax.sql.DataSource;
import java.util.Properties;


@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/**
 * 导入资源文件到Spring上下文中。
 * 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。
 * 具体路径为classpath下的'spring-data.xml'文件。
 * 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。
 */
@ImportResource({"classpath:spring-data.xml"})
/**
 * 使用@Import注解引入配置类
 * 该注解用于指定应用在启动时需要加载的配置类。在这里,我们引入了两个配置类:
 * 1. DataSourceConfig.class:用于数据源的配置,例如数据库连接池的配置。
 * 2. PojoConfig.class:用于POJO(Plain Old Java Object)的配置,例如实体类的映射配置。
 * 这两个配置类会被Spring上下文加载,以便应用在运行时可以使用其中定义的配置。
 */
@Import({DataSourceConfig.class, PojoConfig.class})

/**
 * 使用该注解来向Spring Boot应用添加一个属性源。它会尝试从类路径下的"database-config.properties"文件中加载属性,
 * 并将其命名为"database.properties"。如果文件找不到,则不会报错,因为设置了ignoreResourceNotFound为true。
 * 该属性源的编码格式为UTF-8。
 *
 * 该注解通常用于配置类上,以在Spring应用启动时加载额外的配置属性。
 */
@PropertySource(
        value = "classpath:database-config.properties",
        name = "database.properties",
        ignoreResourceNotFound = true,
        encoding = "UTF-8"
)


public class ApplicationConfig {


    /**
     * 创建并返回一个PropertySourcesPlaceholderConfigurer的实例。
     * 这个配置器是用来处理属性文件占位符的,它可以在Spring配置文件中启用属性文件的引用。
     * 通过这个方法,可以在Spring Bean的配置中使用占位符来引用属性文件中的属性值。
     *
     * @return PropertySourcesPlaceholderConfigurer 返回一个配置了属性文件解析器的实例。
     */
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
    /**
     * 创建并配置数据源 bean。
     *
     * @param driver JDBC驱动程序的类名。
     * @param url 数据库连接的URL。
     * @param username 连接数据库所需的用户名。
     * @param password 连接数据库所需的密码。
     * @return 配置好的数据源实例。
     */
    @Bean(name = "dataSource")
    public DataSource getDataSource(
            // 从属性文件中注入的JDBC配置
            @Value("${jdbc.database.driver}") String driver,
            @Value("${jdbc.database.url}") String url,
            @Value("${jdbc.database.username}") String username,
            @Value("${jdbc.database.password}") String password) {
        // 设置数据库连接的属性
        Properties props = new Properties();
        props.setProperty("driver", driver);
        props.setProperty("url", url);
        props.setProperty("username", username);
        props.setProperty("password", password);

        DataSource dataSource = null;
        // 尝试根据属性创建数据源
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            // 异常处理,打印堆栈跟踪
            e.printStackTrace();
        }
        return dataSource;
    }

}

测试配置代码如下

  /**
     * 测试应用程序配置属性的方法。
     * 该方法通过IoC容器获取应用程序配置中的数据库连接信息,并打印出来。
     *
     * @throws SQLException 如果获取数据库连接或元数据时发生错误
     */
    public static void testProperties() throws SQLException {
        // 创建IoC容器,使用注解配置
        ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        
        // 获取应用程序的环境配置
        Environment env = context.getEnvironment();

        // 从环境配置中获取jdbc数据库url
        String url = env.getProperty("jdbc.database.url");

        // 从IoC容器中获取DataSource Bean
        DataSource ds = context.getBean(DataSource.class);

        // 打印数据库连接的URL
        System.out.println(ds.getConnection().getMetaData().getURL());
    }

在ApplicationConfig.java文件中写了如下方法,并加上了@Bean注解

    /**
     * 创建并返回一个PropertySourcesPlaceholderConfigurer的实例。
     * 这个配置器是用来处理属性文件占位符的,它可以在Spring配置文件中启用属性文件的引用。
     * 通过这个方法,可以在Spring Bean的配置中使用占位符来引用属性文件中的属性值。
     *
     * @return PropertySourcesPlaceholderConfigurer 返回一个配置了属性文件解析器的实例。
     */
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

其作用是让Spring能够解析属性占位符,能够解析属性占位符,Spring提供了@Value注解和占位符的形式,来使用占位符
先看一下属性文件database-config.properties情况

jdbc.database.driver=com.mysql.cj.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/ssm
jdbc.database.username=root
jdbc.database.password=Ms123!@#
package com.ssm.config;

import java.util.Properties;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * 数据源配置类,用于配置数据库连接池。
 */
@Component
public class DataSourceConfigII {

    // 通过注解方式注入MySQL驱动类名
    @Value("${jdbc.database.driver}")
    private String driverClassName = null;

    // 通过注解方式注入数据库URL
    @Value("${jdbc.database.url}")
    private String url = null;

    // 通过注解方式注入数据库用户名
    @Value("${jdbc.database.username}")
    private String username = null;

    // 通过注解方式注入数据库密码
    @Value("${jdbc.database.password}")
    private String password = null;

    /**
     * 创建并返回DataSource Bean。
     *
     * @return 返回配置好的DataSource实例。
     */
    @Bean("dataSource")
    public DataSource getDataSource(){
        Properties props = new Properties();
        // 设置数据库连接池的属性
        props.setProperty("driverClassName", driverClassName);
        props.setProperty("url", url);
        props.setProperty("username", username);
        props.setProperty("password", password);
        DataSource datasource = null; // 初始化数据源变量
        try{
            // 通过属性创建数据源
            datasource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datasource;
    }
}

使用XML加载属性文件

用XML的方式也可以加载属性文件,只需要使用<comtext:property-placeholder>配置项,如下代码所示

<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 配置属性占位符,指定属性文件位置并设置是否忽略未找到的资源 -->
    <context:property-placeholder
            ignore-resource-not-found="false"
            location="classpath:database-config.properties" />

    <!-- 数据源配置,使用Apache Commons DBCP2提供的BasicDataSource实现 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <!-- 通过属性文件中的配置动态设置数据源的属性 -->
        <property name="driverClassName" value="${jdbc.database.driver}" />
        <property name="url" value="${jdbc.database.url}" />
        <property name="username" value="${jdbc.database.username}" />
        <property name="password" value="${jdbc.database.password}" />
    </bean>
</beans>

特别要注意ignore-resource-not-found="false", 表示是否允许文件不存在,为false时不允许文件不存在,如果不存在Spring会抛出异常location="classpath:database-config.properties"location是文件路径,可以配置单个或多个,用逗号隔开即可

也可以使用如下方式,避免配置多个属性文件的时候过长,可读性差

<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 配置属性占位符,指定属性文件位置并设置是否忽略未找到的资源 -->
    <!--
    <context:property-placeholder
            ignore-resource-not-found="false"
            location="classpath:database-config.properties" />
           -->

    <!-- 数据源配置,使用Apache Commons DBCP2提供的BasicDataSource实现 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <!-- 通过属性文件中的配置动态设置数据源的属性 -->
        <property name="driverClassName" value="${jdbc.database.driver}" />
        <property name="url" value="${jdbc.database.url}" />
        <property name="username" value="${jdbc.database.username}" />
        <property name="password" value="${jdbc.database.password}" />
    </bean>

    <!-- 定义一个bean来配置属性源占位符解析器 -->
    <bean id="propertySourcesPlaceholderConfigurer"
          class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <!-- 配置属性文件的位置 -->
        <property name="locations">
            <array>
                <!-- 指定数据库配置文件的位置 -->
                <value>classpath:database-config.properties</value>
                <!-- 指定日志配置文件的位置 -->
                <value>log4j.properties</value>
            </array>
        </property>
        <!-- 配置是否忽略资源未找到的错误 -->
        <property name="ignoreResourceNotFound" value="true" />
    </bean>
</beans>

条件化装配Bean

在某些条件下,不需要装配某些Bean,比如当没有database-config.properties属性配置时,就不需要创建数据源,需要有个条件判断,Spring提供了注解@Conditional去配置,通过它可以配置一个或者多个类,只需要这些类实现实现Condition接口即可(org.springframework.context.annotation.Condition),代码如下:

package com.ssm.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.stereotype.Component;

/**
 * 数据源条件类,用于根据环境配置决定是否加载特定的Bean。
 * 实现了Spring的Condition接口,重写了matches方法来判断条件是否满足。
 */
@Component
public class DataSourceCondition implements Condition {

    /**
     * 判断条件是否满足。
     * @param context 条件上下文,提供了环境和类型元数据的信息。
     * @param metadata 注解类型元数据,用于获取类上的注解信息。
     * @return boolean 返回true表示条件满足,false表示条件不满足。
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        // 检查环境配置中是否包含了必要的jdbc属性
        return env.containsProperty("jdbc.database.driver")
                && env.containsProperty("jdbc.database.url")
                && env.containsProperty("jdbc.database.username")
                && env.containsProperty("jdbc.database.password");
    }
}

DataSourceCondition 实现了Condition 接口的matches方法,该方法有两个参数,一个是ConditionContext,它可以获得Spring的运行环境,另一个是AnnotatedTypeMetadata,它可以获取关于该Bean的注解信息,这段代码优先获取了运行上下文的环境,然后判断在环境中属性文件是否配置了数据库的相关参数,如果配置了返回true,Spring创建对应的Bean否则不创建,接着就可以配置数据源了

package com.ssm.config;

/**
 * ApplicationConfig类用于配置Spring框架的组件扫描。
 * 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。
 */

import com.ssm.condition.DataSourceCondition;
import com.ssm.pojo.PojoConfig;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

import javax.sql.DataSource;
import java.util.Properties;


@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。

@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/**
 * 导入资源文件到Spring上下文中。
 * 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。
 * 具体路径为classpath下的'spring-data.xml'文件。
 * 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。
 */
@ImportResource({"classpath:spring-data.xml"})
/**
 * 使用@Import注解引入配置类
 * 该注解用于指定应用在启动时需要加载的配置类。在这里,我们引入了两个配置类:
 * 1. DataSourceConfig.class:用于数据源的配置,例如数据库连接池的配置。
 * 2. PojoConfig.class:用于POJO(Plain Old Java Object)的配置,例如实体类的映射配置。
 * 这两个配置类会被Spring上下文加载,以便应用在运行时可以使用其中定义的配置。
 */
@Import({DataSourceConfig.class, PojoConfig.class})

/**
 * 使用该注解来向Spring Boot应用添加一个属性源。它会尝试从类路径下的"database-config.properties"文件中加载属性,
 * 并将其命名为"database.properties"。如果文件找不到,则不会报错,因为设置了ignoreResourceNotFound为true。
 * 该属性源的编码格式为UTF-8。
 *
 * 该注解通常用于配置类上,以在Spring应用启动时加载额外的配置属性。
 */
@PropertySource(
        value = "classpath:database-config.properties",
        name = "database.properties",
        ignoreResourceNotFound = true,
        encoding = "UTF-8"
)


public class ApplicationConfig {


    /**
     * 创建并返回一个PropertySourcesPlaceholderConfigurer的实例。
     * 这个配置器是用来处理属性文件占位符的,它可以在Spring配置文件中启用属性文件的引用。
     * 通过这个方法,可以在Spring Bean的配置中使用占位符来引用属性文件中的属性值。
     *
     * @return PropertySourcesPlaceholderConfigurer 返回一个配置了属性文件解析器的实例。
     */
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
    /**
     * 创建并配置数据源 bean。
     *
     * @param driver JDBC驱动程序的类名。
     * @param url 数据库连接的URL。
     * @param username 连接数据库所需的用户名。
     * @param password 连接数据库所需的密码。
     * @return 配置好的数据源实例。
     */
    @Bean(name = "dataSource")
    // 根据特定条件决定是否创建该bean
    @Conditional({DataSourceCondition.class})
    public DataSource getDataSource(@Value("${jdbc.database.driver}") String driver, @Value("${jdbc.database.url}") String url, @Value("${jdbc.database.username}") String username, @Value("${jdbc.database.password}") String password) {
        // 设置数据库连接的属性
        Properties props = new Properties();
        props.setProperty("driver", driver);
        props.setProperty("url", url);
        props.setProperty("username", username);
        props.setProperty("password", password);

        DataSource dataSource = null;
        // 尝试根据属性创建数据源
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            // 异常处理,打印堆栈跟踪
            e.printStackTrace();
        }
        return dataSource;
    }
}

Bean的作用域

所谓Bean的作用域是指Bean在应用中的有效范围,默认情况下,SpringIoC容器只会对Bean创建唯一实例,然后在Spring IoC容器的生命周期中有效,用如下代码测试一下

    /**
     * 测试作用域
     * 该方法通过创建一个注解配置的应用上下文,并从该上下文中获取RoleDataSourceService类型的bean实例,
     * 以此来验证Spring容器中bean的作用域特性。
     * 该方法不接受参数且没有返回值。
     */
    public static void testScope() {
        // 创建一个注解配置的应用上下文,并指定配置类ApplicationConfig
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);

        // 从上下文中获取RoleDataSourceService类型的bean实例
        RoleDataSourceService service1 = context.getBean(RoleDataSourceService.class);
        RoleDataSourceService service2 = context.getBean(RoleDataSourceService.class);

        // 打印两个bean实例是否为同一个对象的引用
        System.out.println(service1 == service2);

        // 关闭应用上下文
        context.close();
    }

这里Spring IoC容器通过类型的方式获取Bean,然后通过==比较两次获取的Bean的结果,这是一个位比较,比较service1service2是否为同一个对象,很显然结果会返回true,换句话说在默认情况下,SpringIoC容器只会为配置的Bean生成一个实例,而不是多个

而在互联网对性能有基本要求的场景下,有时候我们希望每请一次就产生一个独立的对象,这样多个实例可以在不同的线程运行,在对性能有要求的场景中就能发挥作用,这些是由Spring的作用域决定的,Spring IoC容器提供了2种作用域(单例和原型),单例(singleton)是默认选项,在整个应用中,Spring只为其生成一个Bean的实例;原型(prototype)当每次从Spring IoC容器获取Bean时,Spring都会为它创建一个新的实例

在Spring中,可以用注解@Scope指定作用域,如下代码所示

package com.ssm.service.iml;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleDataSourceService;

/**
 * 实现RoleDataSourceService接口,提供获取角色信息的功能
 */
@Service
/**
 * RoleDataSourceServiceImpl 类实现了 RoleDataSourceService 接口,
 * 用于提供角色相关的数据源服务。该类的作用范围被注解为原型作用域(SCOPE_PROTOTYPE),
 * 意味着每次请求都会创建一个新的实例。
 */
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RoleDataSourceServiceImpl implements RoleDataSourceService {

    // 通过自动装配获取数据源
    @Autowired
    DataSource dataSource = null;

    /**
     * 根据角色ID获取角色信息
     * @param id 角色的ID
     * @return 返回对应的角色信息,如果没有找到则返回null
     */
    @Override
    public RoleIV getRoleIV(Long id) {
        Connection conn = null;
        ResultSet rs = null;
        PreparedStatement ps = null;
        RoleIV roleIV = null;
        try {
            // 获取数据库连接
            conn = dataSource.getConnection();
            // 构造查询SQL语句
            String sql = "select id, role_name, note from t_role where id = ?";
            ps = conn.prepareStatement(sql);
            // 设置查询参数
            ps.setLong(1, id);
            // 执行查询
            rs = ps.executeQuery();
            // 处理查询结果
            while (rs.next()) {
                // 构建角色对象
                roleIV = new RoleIV();
                roleIV.setId(rs.getLong("id"));
                roleIV.setRoleName(rs.getString("role_name"));
                roleIV.setNote(rs.getString("note"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭数据库连接及相关资源
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return roleIV;
    }
}

然后再执行测试代码

    /**
     * 测试作用域
     * 该方法通过创建一个注解配置的应用上下文,并从该上下文中获取RoleDataSourceService类型的bean实例,
     * 以此来验证Spring容器中bean的作用域特性。
     * 该方法不接受参数且没有返回值。
     */
    public static void testScope() {
        // 创建一个注解配置的应用上下文,并指定配置类ApplicationConfig
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);

        // 从上下文中获取RoleDataSourceService类型的bean实例
        RoleDataSourceService service1 = context.getBean(RoleDataSourceService.class);
        RoleDataSourceService service2 = context.getBean(RoleDataSourceService.class);

        // 打印两个bean实例是否为同一个对象的引用
        System.out.println(service1 == service2);

        // 关闭应用上下文
        context.close();
    }

就会返回false了,也就是当我们从SpringIoC容器中获取对象时,获取的都是新的实例,不同的对象

在互联网应用中,Spring会使用实现了WebApplicationContext接口(该接口继承了ApplicationContext)的实现类作为IoC容器,此容器有两种常见的作用域(会话和请求),会话(Session)在Web应用中使用,在会话过程中只创建一个实例;请求(request)在Web应用中使用,在一次请求中Spring会创建一个实例,该配置可以在Java代码中进行,也可以在XML配置文件中进行,如下所示

import org.springframework.web.context.WebApplicationContext;

/**
 * 实现RoleDataSourceService接口,提供获取角色信息的功能
 */
@Service
/**
 * 为Spring Web应用程序定义会话级别的作用域。
 * 使用此注解的bean将被存储在HTTP会话中,意味着它们在同一个会话内的所有HTTP请求中都是可用的。
 * 这是针对需要在多个相关请求之间共享状态的情况而设计的,例如用户的登录状态。
 *
 * 注意:此注解仅适用于Web应用程序上下文。
 */
@Scope(WebApplicationContext.SCOPE_SESSION)

public class RoleDataSourceServiceImpl implements RoleDataSourceService {

    // 通过自动装配获取数据源
    @Autowired
    DataSource dataSource = null;
    <!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" -->
    <bean id="role2" class="com.ssm.pojo.RoleII" scope="prototype">
        <!-- 设置角色id为2 -->
        <property name="id" value="2" />
        <!-- 设置角色名为"高级工程师" -->
        <property name="roleName" value="高级工程师" />
        <!-- 设置备注为"重要人员" -->
        <property name="note" value="重要人员" />
    </bean>

使用Spring表达式

Spring还提供了更灵活的方式,那就是Spring表达式(Spring EL), 它远比其他注入方式更强大,其大致内容如下

Spring Expression Language (Spring EL) 是Spring框架中的一种表达式语言,它主要用于在运行时查询和操作对象图。Spring EL设计的目标是简化数据绑定和表达式评估,特别是在Spring应用程序上下文中。以下是一些关于Spring EL的关键点:

  • 对象导航:Spring EL允许你通过点号.来导航对象的属性,例如 person.name 获取 person 对象的 name 属性。
  • 方法调用:支持调用对象的方法,例如 list.sort() 或 date.format(‘yyyy-MM-dd’)。
  • 集合操作:可以索引和遍历集合,如 list[0] 或 list[0…2] 选取前三个元素。
  • 条件和逻辑运算:支持 if、and、or、not 等逻辑运算符,以及比较运算符(==、>、< 等)。
  • 算术运算:支持基本的数学运算,如加减乘除 (+, -, *, /) 和取余数 (%)。
  • 类型转换:可以显式地转换类型,例如 (int)number。
  • 变量和参数:在表达式中可以引用变量和方法参数。
  • 上下文访问:可以访问Spring容器中的bean,例如 @myBean 引用名为 myBean 的bean。
  • 表达式结果:表达式的结果可以是任何Java类型,包括null。
  • ** spel:expression**:在XML配置中,使用 #{} 语法来包含Spring EL表达式,例如<property name="someProperty" value="#{myBean.someMethod()}" />
Spring EL相关的类

在这里插入图片描述

 /**
     * 测试表达式解析功能。
     * 该方法演示了如何使用Spring表达式语言(SpEL)解析一个简单的字符串表达式并获取其值。
     *
     */
    public static void testExpression(){
        // 创建SpEL表达式解析器
        ExpressionParser parser = new SpelExpressionParser();

        // 解析一个字符串表达式,并输出其值
        Expression expression = parser.parseExpression("'Hello World'");
        String str = (String) expression.getValue();
        System.out.println(str);

        // 解析并执行字符串的charAt方法,输出第一个字符
        expression = parser.parseExpression("'Hello World'.charAt(0)");
        char ch = (char) expression.getValue();
        System.out.println(ch);

        // 解析字符串并获取其字节表示
        expression = parser.parseExpression("'Hello World'.bytes");
        byte[] bytes = (byte[]) expression.getValue();
        System.out.println(bytes);

        // 获取字符串的字节长度
        expression = parser.parseExpression("'Hello World'.bytes.length");
        int length = (int) expression.getValue();
        System.out.println(length);

        // 使用SpEL构造一个新的字符串对象
        expression = parser.parseExpression("new String('abc')");
        String str2 = (String) expression.getValue();
        System.out.println(str2);
    }

如代码所示,是一个简单的使用Spring EL的例子, 通过表达式可以创建对象、调用对象的方法或者获取属性

SpringEL还支持变量的解析,使用变量解析时常常用到一个接口EvaluationContext,可以有效解析表达式中的变量,它有一个实现类StandardEvaluationContext,用变量解析表达式会使表达式更加灵活,在针对SpringIoC容器进行解析的时候,可以直接获取配置的属性值,但使用这些表达式会降低可读性,不适合处理复杂的问题

    /**
     * 测试SpEL表达式的求值功能。
     * 该方法首先演示了如何使用SpEL获取和设置对象属性的值,接着展示了如何对列表元素进行操作。
     */
    public static void testEvaluation() {
        // 创建SpelExpressionParser实例以解析表达式
        ExpressionParser parser = new SpelExpressionParser();
        // 创建RoleIV实例,用于在表达式求值时提供属性值
        RoleIV roleIV = new RoleIV(1L, "admin", "administrator");

        // 解析表达式以获取"note"属性的值
        Expression expression = parser.parseExpression("note");
        String note = (String) expression.getValue(roleIV);
        System.out.println(note);

        // 创建表达式求值的上下文环境,并将roleIV设置为上下文中的根对象
        EvaluationContext ctx = new StandardEvaluationContext(roleIV);
        // 设置"note"属性的新值为"new_administrator"
        parser.parseExpression("note").setValue(ctx, "new_administrator");
        // 获取更新后的"note"属性值
        note = parser.parseExpression("note").getValue(ctx, String.class);
        System.out.println(note);
        // 获取RoleIV实例的"getRoleName()"方法返回值
        String roleName = parser.parseExpression("getRoleName()").getValue(ctx, String.class);
        System.out.println(roleName);

        // 初始化并创建一个字符串列表,用于演示如何在SpEL中操作集合
        List<String> list = new ArrayList<String>();
        list.add("value1");
        list.add("value2");
        // 将列表绑定到上下文中,以在表达式中使用
        ctx.setVariable("list", list);
        // 更新列表的第一个元素的值为"new_value1"
        parser.parseExpression("#list[0]").setValue(ctx, "new_value1");
        // 获取更新后的列表的第一个元素的值
        System.out.println(parser.parseExpression("#list[0]").getValue(ctx));
    }

EvaluationContext使用了它的实现类StandardEvaluationContext进行实例化,在构造方法中将角色对象传递给它,它就会基于这个类进行解析

Bean的属性和方法

使用注解的方式需要用到@Value,在属性文件中的读取需要$,在Spring EL中需要#,在角色类中使用使用Spring EL初始化,如下代码所示

package com.ssm.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * ELRole类用于演示通过Spring的注解方式注入属性值。
 * 该类被标记为@Component,表示它是一个Spring组件,可以被其他组件依赖。
 * 其中使用的@Value注解用于注入具体的属性值。
 */
@Component("elRole")
public class ELRole {

    // 使用@Value注解注入id的值,此处使用SpEL表达式的方式注入,示例值为1。
    @Value("#{1}")
    private Long id;

    // 使用@Value注解注入roleName的值,此处使用字符串方式注入。
    @Value("#{'role_name_1'}")
    private String roleName;

    // 使用@Value注解注入note的值,此处使用字符串方式注入。
    @Value("#{'note_1'}")
    private String note;

    /**
     * 获取角色ID
     * @return 返回角色的ID值
     */
    public Long getId() {
        return id;
    }

    /**
     * 设置角色ID
     * @param id 角色的ID值
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * 获取角色名称
     * @return 返回角色名称字符串
     */
    public String getRoleName() {
        return roleName;
    }

    /**
     * 设置角色名称
     * @param roleName 角色名称字符串
     */
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    /**
     * 获取角色备注信息
     * @return 返回角色的备注信息字符串
     */
    public String getNote() {
        return note;
    }

    /**
     * 设置角色备注信息
     * @param note 角色的备注信息字符串
     */
    public void setNote(String note) {
        this.note = note;
    }


}

这样就定义了一个BeanName为elRole的角色类,同时给它所有的属性都进行了初始化,然后可以通过另一个Bean引用它的属性或者调用它的方法,如下代码所示

package com.ssm.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * ELBean类用于演示和展示Spring Expression Language (EL)的使用。
 * 通过注解@Value从Spring表达式语言中获取值并注入到类的属性中。
 */
@Component("elBean")
public class ELBean {

    // 从Java Math类中获取PI值
    @Value("#{T(java.lang.Math).PI}")
    private double pi;

    // 获取一个随机数
    @Value("#{T(Math).random()}")
    private double random;

    // 根据elRole的id值加1赋值给num
    @Value("#{elRole.id+1}")
    private int num;

    // 将elRole的roleName和note拼接成一个字符串
    @Value("#{elRole.roleName + elRole.note}")
    private String str;

    // 判断elRole的id是否等于1
    @Value("#{elRole.id == 1}")
    private boolean equalNum;

    // 判断elRole的note是否等于'note_1'
    @Value("#{elRole.note eq 'note_1'}")
    private boolean eqaulString;

    // 判断elRole的id是否大于2
    @Value("#{elRole.id > 2}")
    private boolean greater;

    // 判断elRole的id是否小于2
    @Value("#{elRole.id < 2}")
    private boolean less;

    // 根据条件判断选择赋值5或1给max
    @Value("#{elRole.id > 1 ? 5 : 1}")
    private int max;

    // 如果elRole的note存在,则赋值note,否则赋值'hello'
    @Value("#{elRole.note?: 'hello'}")
    private String defaultString;

    // 通过beanName获取ELRole类型的bean并注入
    @Value("#{elRole}")
    private ELRole elRole;

    // 获取ELRole的id属性
    @Value("#{elRole.id}")
    private Long id;

    // 调用ELRole的getNote方法,获取note属性的值
    @Value("#{elRole.getNote().toString()}")
    private String note;

    /**
     * 获取ELRole对象
     * @return 返回ELRole对象
     */
    public ELRole getElRole() {
        return elRole;
    }

    /**
     * 设置ELRole对象
     * @param elRole 要设置的ELRole对象
     */
    public void setElRole(ELRole elRole) {
        this.elRole = elRole;
    }

    /**
     * 获取实体的ID
     * @return 返回实体的ID值
     */
    public Long getId() {
        return id;
    }

    /**
     * 获取圆周率π的值
     * @return 返回圆周率π的近似值
     */
    public double getPi() {
        return pi;
    }

    /**
     * 设置圆周率π的值
     * @param pi 要设置的圆周率π的近似值
     */
    public void setPi(double pi) {
        this.pi = pi;
    }

    /**
     * 获取一个随机数
     * @return 返回一个随机数值
     */
    public double getRandom() {
        return random;
    }

    /**
     * 设置一个随机数
     * @param random 要设置的随机数值
     */
    public void setRandom(double random) {
        this.random = random;
    }

    /**
     * 设置实体的ID
     * @param id 要设置的实体ID
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * 获取备注信息
     * @return 返回备注信息字符串
     */
    public String getNote() {
        return note;
    }

    /**
     * 设置备注信息
     * @param note 要设置的备注信息字符串
     */
    public void setNote(String note) {
        this.note = note;
    }
}

可以通过BeanName注入,也可以通过OGNL获取其属性或者调用其方法注入其他的Bean,注意表达式"#{elRole.getNote().toString()}"的注入,getNote()方法可能返回null,然后导致toString()抛异常,可以写成"#{elRole.getNote()?.toString()}",表达式中的问号事先判断是否非空,如果不是非空则不再调用toString方法

使用类的静态常量和方法
    // 从Java Math类中获取PI值
    @Value("#{T(java.lang.Math).PI}")
    private double pi;

    // 获取一个随机数
    @Value("#{T(Math).random()}")
    private double random;

Math是java.lang.*包下的Math类,在Java代码中使用该类不需要import,对于Spring EL也是,也可以像代码所示给全限定名

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1681290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序|锁定查询功能如何使用?

学生或家长想要实现自己查询完成后&#xff0c;任何人都无法再次查询&#xff0c;老师应该如何设置&#xff1f;易查分的【锁定查询功能】就可实现&#xff0c;下面教大家如何使用吧。 &#x1f4cc;使用教程 &#x1f512;锁定查询功能介绍 ✅学生或家长自主锁定&#xff1a;开…

酷开科技丨女性群像大戏《惜花芷》在酷开系统热播中

在这个国产剧市场蓬勃发展的时代&#xff0c;酷开科技通过其生态智能电视系统&#xff0c;为剧迷们打造了一个精彩的观剧平台。通过酷开科技的智能推荐算法&#xff0c;消费者能够轻松地发掘并观看各种题材的高质量剧集&#xff0c;无论是扣人心弦的金融较量、深刻的家庭代际关…

QCustomPlot的了解

&#xff08;一&#xff09;QCustomPlot常见属性设置、多曲线绘制、动态曲线绘制、生成游标、矩形放大等功能实现-CSDN博客 关键代码&#xff1a; QT core gui printsupport 使用上面文章中的代码跑起来的程序效果图&#xff1a; 我的学习过程&#xff1a; 最开始初…

位图和布隆过滤器:位图

在《unordered_map 和 unordered_set》 中提到过&#xff1a; 哈希是一种思想&#xff0c;通过哈希函数将数据转化为一个或多个整型 —— 映射关系&#xff1b;通过这种映射关系&#xff0c;可以做到以 O(1) 的时间复杂度查找数据。 本文即将介绍的 位图 和 布隆过滤器 就是两个…

【漏洞复现】泛微OA E-Cology GetLabelByModule SQL注入漏洞

漏洞描述&#xff1a; 泛微OA E-Cology是一款面向中大型组织的数字化办公产品&#xff0c;它基于全新的设计理念和管理思想&#xff0c;旨在为中大型组织创建一个全新的高效协同办公环境。泛微OA E-Cology getLabelByModule存在SQL注入漏洞&#xff0c;允许攻击者非法访问和操…

ubuntu下安装pwndbg

安装pwndbg 如果可以科学上网 首先安装git apt install git 然后拉取git库 git clone GitHub - pwndbg/pwndbg: Exploit Development and Reverse Engineering with GDB Made Easy 进入到pwngdb的文件夹中 cd pwngdb 执行 ./setup.sh 而后输入gdb 出现红色pwndgb就是安装成功…

图形程序复用新纪元 探讨云库安全分享计划

在公司的开放式办公室中&#xff0c;卧龙与凤雏相邻而坐。周围的同事们都在忙碌地工作&#xff0c;键盘敲击声不绝于耳。卧龙眉头紧锁&#xff0c;全神贯注地调试着复杂的代码&#xff0c;仿佛在挑战编程世界的极限。而凤雏则在完成了一段代码编写后&#xff0c;轻松地伸展着身…

EtherCat:打通EtherCat奇经八脉(一)

一、EtherCat背景介绍 EtherCAT是一种用于实时以太网通信的现场总线协议。它由德国Beckhoff公司于2003年开发&#xff0c;并在2014年成为国际电机工程师协会&#xff08;IEC&#xff09;的国际标准&#xff08;IEC61158-12&#xff09;。 EtherCAT的设计目标是实现高性能的实时…

【独家发布】公司搭建绩效考核体系的方案(一)

某车辆公司成立于上世纪七十年代&#xff0c;其悠久历史可以追溯到洋务运动时期&#xff0c;曾开创了中国近代工业的先河。该公司拥有近五十年的车辆生产经验及积累&#xff0c;全球共有13个生产基地&#xff0c;22个整车发动机工厂和3个独立变速器工厂&#xff0c;1个技术中心…

JavaScript 进阶(二)

一、深入对象 1. 创建对象的三种方式 利用 new Object 创建对象 2. 构造函数 【注意事项】 【例】 这样子写好之后&#xff0c;想要添加一个新的结构类似的对象&#xff0c;直接照着红圈中写&#xff0c;最后改相应的数据就好了 注意&#xff1a;红色是第一步&#xff0c;黄…

大模型MoE技术深度解读,引领AI走向新高度

大模型系列之解读MoE Mixtral 8x7B的亮相&#xff0c;引领我们深入探索MoE大模型架构的奥秘。MoE究竟是什么&#xff1f;一起揭开它的神秘面纱。 1. MoE溯源 MoE&#xff0c;源自1991年的研究论文《Adaptive Mixture of Local Experts》&#xff0c;与集成学习方法相契合&…

水泡传感器内部结构

水泡传感器内部结构&#xff1a; 水泡传感器放大电路 电路是基于1.6V做的TIA I2V&#xff0c; 也就是输出部分基于1.6V做电压的增加或减少。

五分钟带大家理解什么是网络代理

网络代理是指一种特殊的网络服务&#xff0c;允许一个网络终端&#xff08;一般为客户端&#xff09;通过这个服务与另一个网络终端&#xff08;一般为服务器&#xff09;进行非直接的连接&#xff0c;一些网关、路由器等网络设备都具备网络代理功能。它的功能就是代理网络用户…

【prometheus】prometheus基于consul服务发现实现监控

目录 一、consul服务发现简介 1.1 consul简介 二、prometheus配置 2.1 node-exporter服务注册到consul 2.2 修改prometheus配置文件 【Prometheus】概念和工作原理介绍_prometheus工作原理-CSDN博客 【Prometheus】k8s集群部署node-exporter 【prometheus】k8s集群部署p…

什么是TCP的粘包、拆包问题?

一、问题解析 TCP粘包和拆包问题是指在进行TCP通信时&#xff0c;因为TCP是面向流的&#xff0c;所以发送方在传输数据时可能会将多个小的数据包粘合在一起发送&#xff0c;而接收方则可能将这些数据包拆分成多个小的数据包进行接收&#xff0c;从而导致数据接收出现错误或者数…

PHP开发中的不安全反序列化

序列化是开发语言中将某个对象转换为一串字节流的过程&#xff0c;转换后的字节流可以方便存储在数据库中&#xff0c;也可以方便在网络中进行传输。而反序列化则是将数据库取出的字节流或从网络上接收到的字节流反向转换为对象的过程。概念虽如此&#xff0c;但不同的开发语言…

SerDes系列之电路技术概述

现在的高速电路设计中&#xff0c;SerDes的应用几乎无处不在&#xff0c;如下图所示的一款SoC&#xff0c;其外设接口除了少量普通的IO&#xff0c;几乎都是SerDes专用接口&#xff0c;因此&#xff0c;电路设计中对于SerDes接口电路的熟知程度&#xff0c;几乎就决定了设计的成…

[数据集][目标检测]电力场景电力目标检测数据集VOC+YOLO格式476张5类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;476 标注数量(xml文件个数)&#xff1a;476 标注数量(txt文件个数)&#xff1a;476 标注类别…

在Linux系统上使用nmcli命令配置各种网络(有线、无线、vlan、vxlan、路由、网桥等)

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; 在Linux系统上使用nmcli命令配置各种网络&#xff08;有线、无线、vlan、vxlan等&#xff09;https://myweb.myskillstree.cn/123.html 更新于2024/5/13&…

在Python中防止某些字段被Pickle序列化

在Python中&#xff0c;如果你想防止某些字段被pickle序列化&#xff0c;可以使用__reduce__()方法来自定义pickle行为。__reduce__()方法允许你返回一个元组&#xff0c;其中包含要在对象被pickle时调用的函数以及传递给该函数的参数。下面就是我遇到的问题以及最终解决方案。…