02.spring framework的IOC

一、概念

1、IOC:控制反转,是一种思想,【获得依赖对象的过程】被反转了,由IOC容器进行控制。

控制反转,控制权的转移,即应用程序本身不负责对依赖对象的创建和维护,而由外部容器负责创建和维护

2、DI:依赖注入,是IOC的一种实现,所谓依赖注入,就是由IOC容器在运行期间,动态将某种依赖关系注入到对象之中

3、spring官方给出的spring IOC 容器图

二、好处

1、不必自己创建对象、管理对象之间的依赖关系了

2、依赖注入能够将组件及其协作的其他组件解耦

三、实践

说明1:Bean容器初始化(即加载application.xml配置文件)

说明2:Spring应用上下文中所有的bean都会给定一个ID,默认为类名的第一个字母变为小写

1.1 bean发现、bean装配

最佳实践:

①尽可能的使用自动化配置机制,显示配置越少越好

当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。

③只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。

1.1.1 方式1:在java代码中【显式】的配置

1、适用场景

  • 想要将第三方类库装配到你的应用中:此时必须显示的配置

2、使用说明

  • JavaConfig中不应该包含业务逻辑
  • JavaConfig也不应该侵入到业务逻辑代码之中
  • 通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了

3、使用方式

1)发现并创建bean

  • JavaConfig类需要适用@Configuration注解进行标记

@Configuration注解:表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节

@Bean注解:JavaConfig中创建一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解,方法中实现返回bean实例的逻辑,方法体返回了一个bean的实例。这里是使用Java来进行描述的,因此我们可以发挥Java提供的所有功能,只要最终生成一个bean实例即可

      @Bean(name = "给bean一个id,而不使用默认值,默认值为修饰方法首字母小写")

package com.mzj.springframework.ioc._02_JavaConfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
//@ComponentScan  //将隐式的自动扫码功能关闭,采用JavaConfig中显示的配置
public class CDPlayerConfig {

    @Bean
    public CompactDisc compactDisc() {
        return new SgtPeppers();
    }

    /**
     * 不推荐使用这种方式进行依赖注入:Spring会拦截对compactDisc()的调用并 确保返回的是Spring所创建的bean,也就是Spring本身在调 用compactDisc()时所创建的CompactDiscbean。
     *
     * 同时因为spring默认的bean是单例的,所以多次调用compactDisc()返回的是相同实例,这与看倒的代码不一致,所以不推荐
     * @return
     */
//    @Bean
//    public CDPlayer cdPlayer() {
//        return new CDPlayer(compactDisc());
//    }

    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc) {//当 Spring调用cdPlayer()创建CDPlayer bean的时候,它会自动装配 一个CompactDisc到方法中。
        return new CDPlayer(compactDisc);
    }

}

2)注入

采用JavaConfig方式时,bean之间的依赖关系,spring会通过JavaConfig中带有@Bean的方法参数(依赖)在其容器中进行查找,并自动完成依赖的创建:

@Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc) {//当 Spring调用cdPlayer()创建CDPlayer bean的时候,它会自动装配 一个CompactDisc到方法中。
        return new CDPlayer(compactDisc);
    }

本小节代码:https://github.com/mazhongjia/springframeworkspringinaction/tree/master/src/main/java/com/mzj/springframework/ioc/_02_JavaConfig

1.1.2 方式2:基于注解的、【隐式】的、bean发现机制和自动装配

spring从组件扫描、自动装配来实现自动化装配。

1、组件扫描:spring会自动发现应用上下文中创建的bean

2、自动装配:spring自动建立bean之间的依赖

组件扫描

1、通过注释标记类:告知Spring要为这个类创建bean

@Component注解:这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。

  • @Component("给bean一个id,而不使用默认值")

2、在Spring中启用组件扫描

@ComponentScan注解:在Spring中启用组件扫描。让spring去寻找带有@Component注解的类

  • @ComponentScan("pictureeditor.gdi"):扫描包
  • @ComponentScan(basePackages="pictureeditor.gdi"):扫描包
  • @ComponentScan(basePackages={"pictureeditor.gdi","package2"}):扫描多个包
  • @ComponentScan(basePackageClasses={A.class,B.class}):除了将包设置为简单的String类型之外,还可以设置扫描指定类,这些类所在的包将会作为组件扫描的基础包,可以在包中创建一个用来进行扫描的空标记接口以防止扫描的业务类以后被去掉
如果没有其他配置的话, @ComponentScan 默认会扫描与配置类相 同的包。因为CDPlayerConfig 类位于 soundsystem 包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component 注解的类。
如果你更倾向于使用 XML 来启用组件扫描的话,那么可以使用Spring context 命名空间的 <context:component-scan> 元素:
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:c="http://www.springframework.org/schema/c"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="pictureeditor.gdi" />

</beans>

自动装配

说明1:自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean

说明2:不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。

说明3:装配规则①假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来;②如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false;③如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配

@Autowired注解

  • @Autowired(required = false)避免找不到依赖时spring抛出异常:把required属性设置为false时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性 有可能会出现NullPointerException

方式:为了声明要进行自动装配,我们可以借助Spring的”@Autowired注解“

1、可以使用在构造器上如:

package com.mzj.springframework.ioc._01_componentScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {
  
  private CompactDisc cd;

  @Autowired
  public CDPlayer(CompactDisc cd) {
    this.cd = cd;
  }

  public void play() {
    cd.play();
  }

}

2、可以使用在set方法上:注意,这里@Autowired注解应用的set方法可以随意命名,不一定叫setXXX

特殊说明:@Autowired与@Component注解是spring的注解,可以使用java的依赖注入规范中注解:@Inject与@Named注解代替,以减少与spring的偶合程度。spring同时支持@Autowired与@Component、@Inject与@Named,@Inject和@Autowired之间有着一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。在@Inject@Autowired中,我没有特别强烈的偏向性。实际上,在有的项目中,我会发现我同时使用了这两个注解。

本小节代码:https://github.com/mazhongjia/springframeworkspringinaction/tree/master/src/main/java/com/mzj/springframework/ioc/_01_componentScan

1.1.3 方式3:直接在spring配置文件中【显式】配置bean

1、适用场景

  • 想要将第三方类库装配到你的应用中:此时必须显示的配置
  • 基于XML的配置是spring最初提供的配置方式,针对一些已有系统,大部分使用XML进行配置

2、使用说明

  • 缺点:XML配置并不能在编译器检查配置信息的正确性:通过借助IDE可以检查

3、使用方式

1)发现并创建bean

配置文件通过使用<bean>元素并指定class属性

spring-ioc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd" >
        
	<bean id="oneInterface" class="com.mzj.springframework.ioc.interfaces.OneInterfaceImpl"></bean>

 </beans>
id属性:只对那些需要按名字引用的bean (比如,你需要将对它的引用注入到另外一个 bean 中)进行明确地命名(赋予id属性)
 

OneInterfaceImpl.java

package com.mzj.springframework.ioc.interfaces;

public class OneInterfaceImpl implements OneInterface {
	
	public void say(String arg) {
		System.out.println("ServiceImpl say: " + arg);
	}

}

OneInterface.java

package com.mzj.springframework.ioc.interfaces;

public interface OneInterface {
	public void say(String arg);
}

2)注入

XML中声明DI时,会有多种可选的配置方案和风格:构造器注入、设置注入

最佳实践:对强依赖使用构造器注入,而对可选性的依赖使用设置注入

①构造器注入:

           方式1:<constructor-arg>元素

               bean对象注入:ref属性,通过引用bean的id,实现构造器注入

               字面值注入:value属性:通过直接设置字符串,实现字符串构造器注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="compactDisc" class="com.mzj.springframework.ioc._03_XmlConfig.SgtPeppers" />

    <bean id="mediaPlayer" class="com.mzj.springframework.ioc._03_XmlConfig.CDPlayer">
        <constructor-arg ref="compactDisc" />
        <constructor-arg value="abc" />
    </bean>

</beans>

             涉及的类、接口均没有spring相关注解 

          方式2:使用Spring 3.0所引入的c-命名空间:作为<constructor-arg>元素的替代方案

                bean对象注入:【c:cd-ref】,其中cd是构造函数参数名

               字面值注入:【c:str="abcd"】,其中str是构造函数参数名

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

    <bean id="compactDisc" class="com.mzj.springframework.ioc._03_XmlConfig.SgtPeppers"/>

    <bean id="mediaPlayer" class="com.mzj.springframework.ioc._03_XmlConfig.CDPlayer"
          c:cd-ref="compactDisc" c:str="abcd">
    </bean>

</beans>

其中【c:cd-ref】还有其他表达方式:

属性名以 c: 开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“ -ref ,这是一个命名的约定,它会告诉Spring,正在装配的是一个 bean 的引用。
注意:上面列子中,构造器参数名可以通过构造器参数列表序号替换,以便修改参数列表变量名后,导致程序无法执行:
如果构造器只有一个参数,则可以省略序号:
 

字面值注入也具有通过构造器参数列表顺序号、省略顺序号的使用特性。

以上两种构造器注入的方式<constructor-arg>缺点:冗长,c-命名空间缺点:支持的功能要比前者少:装配集合上:在装配集合方面,<constructor-arg>c-命名空间的属性更有优势。目前,使用c-命名空间的属性无法实现装配集合的功能。

      装配集合

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

    <bean id="sgtPeppers" class="com.mzj.springframework.ioc._03_XmlConfig.collection.SgtPeppers"/>

    <bean id="mediaPlayer" class="com.mzj.springframework.ioc._03_XmlConfig.collection.CDPlayer4Collection">
        <constructor-arg value="abcde"/>
        <constructor-arg>
            <list>
                <value>12</value>
                <value>34</value>
                <value>56</value>
            </list>
        </constructor-arg>
        <constructor-arg>
            <list>
                <ref bean="sgtPeppers" />
                <ref bean="sgtPeppers" />
                <ref bean="sgtPeppers" />
            </list>
        </constructor-arg>
    </bean>

</beans>
package com.mzj.springframework.ioc._03_XmlConfig.collection;

import com.mzj.springframework.ioc._03_XmlConfig.MediaPlayer;

import java.util.List;

public class CDPlayer4Collection implements MediaPlayer {

  private String str;

  private List<String> stringList;

  private List<SgtPeppers> sgtPeppersList;

  public CDPlayer4Collection(String str, List<String> stringList, List<SgtPeppers> sgtPeppersList) {
    this.stringList = stringList;
    this.str = str;
    this.sgtPeppersList = sgtPeppersList;
  }

  public void play() {
    System.out.println(stringList);
    System.out.println(sgtPeppersList);
  }

}
package com.mzj.springframework.ioc._03_XmlConfig.collection;

public class SgtPeppers {

  private String title = "title for collection";
  private String artist = "The Beatles";
  
  public String toString() {
    return "Playing " + title + " by " + artist + this.hashCode();
  }

}

测试代码:

package com.mzj.springframework.ioc._03_XmlConfig;

import com.mzj.springframework.ioc._03_XmlConfig.collection.CDPlayer4Collection;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/10 16:08
 * @Version: 1.0
 */
public class ConstructorArgCollectionMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("com/mzj/springframework/ioc/_03_XmlConfig/cdplayer-config4Collection.xml");
        CDPlayer4Collection cdPlayer4Collection = (CDPlayer4Collection) classPathXmlApplicationContext.getBean("mediaPlayer");
        cdPlayer4Collection.play();
    }
}

输出结果

可见,装配集合如果是对象引用,则默认对象是单例的。

除了<List>,也可以使用<set>

②设置注入(通过setter方法):
           方式1:<property>元素

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="compactDisc" class="com.mzj.springframework.ioc._03_XmlConfig.setter.SgtPeppers"/>

    <bean id="mediaPlayer" class="com.mzj.springframework.ioc._03_XmlConfig.setter.CDPlayer">
<!--        其中name值对应CDPlayer类中有set方法的属性名称        -->
        <property name="cd" ref="compactDisc"/>
    </bean>

</beans>

测试代码:

package com.mzj.springframework.ioc._03_XmlConfig;

import com.mzj.springframework.ioc._03_XmlConfig.setter.CDPlayer;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/10 16:08
 * @Version: 1.0
 */
public class SetterMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("com/mzj/springframework/ioc/_03_XmlConfig/setter/cdplayer-config.xml");
//        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("soundsystem/ConstructorArgReferenceTest-context.xml");
        CDPlayer cdPlayer = (CDPlayer) classPathXmlApplicationContext.getBean("mediaPlayer");
        cdPlayer.play();
    }
}

涉及到的bean:

package com.mzj.springframework.ioc._03_XmlConfig.setter;

public class CDPlayer {

    private SgtPeppers cd;

    public void setCd(SgtPeppers cd) {
        this.cd = cd;
    }

    public void play() {
        cd.play();
    }

}
package com.mzj.springframework.ioc._03_XmlConfig.setter;

public class SgtPeppers {

    private String title = "Sgt. Pepper's Lonely Hearts Club Band";
    private String artist = "The Beatles";

    public void play() {
        System.out.println("Playing " + title + " by " + artist);
    }

}

测试结果:

     方式2:p-命名空间,作为<property>元素的替代方案

          与构造器注入替代方案类似,语法如下:

设置注入也可以注入字面量、list、set等特性,这与构造器参数非常类似(以上property元素、p-命名空间两种方式都支持,其中注入集合命名空间不支持),这里略,方式为:

 <property name="str" value="abc"/>
 <property name="list1">
    <list>
        <value>12</value>
        <value>34</value>
        <value>56</value>
    </list>
 </property>

扩展:虽然,不能使用命令空间注入集合,但是可以使用Spring util-命名空间中的一些功能来简化:util-命名空间所提供的功能之一就是<util:list>元素,它会创建一个列表的bean

示例略。

1.1.4 混合配置

1、最佳实践:实际项目中,经常同时将【自动化配置】和【显示配置】混合在一起使用,即便你更喜欢通过JavaConfig实现显式配置,但有时候XML却是最佳方案。

2、混合配置过程中需要了解的知识点:

  • spring中,这些配置都不是互斥的。同时至少需要通过一个显示配置来启动组件扫描和自动装配。
  • 自动装配(Autowired)、JavaConfig中通过@Bean注解的方法接受的参数(JavaConfig方式的依赖注入bean的创建),并不在意要装配的bean的发现/创建方式:
    • ①通过组件扫描获得的:XML<context:component-scan>或者JavaConfig的@ComponentScan注解
    • ②通过JavaConfig配置的——@Bean注解
    • ③通过XML中配置的——<bean>标签
  • JavaConfig中使用@Import和@ImportResource来拆分JavaConfig类(当一个JavaConfig类过于庞大)
  • JavaConfig中引用JavaConfig配置:
    • 在@Configuration类中通过@Import(XXX.class,YYY.class)注解引入其他JavaConfig类
      • 方式1:在Configuration类B中import依赖的Configuration类A
        • 示例代码:com.mzj.springframework.ioc._04_MixConfig.JavaConfigImportJavaConfig._1
      • 方式2:创建一个更高级别的Configuration类C,在类C中importA与B
        • 示例代码:com.mzj.springframework.ioc._04_MixConfig.JavaConfigImportJavaConfig._2
  • JavaConfig中引用XML配置:
    • 在@Configuration类中通过@ImportResource(“classpath:XXX.xml”)注解引入其他XML
      • 代码示例:com.mzj.springframework.ioc._04_MixConfig.JavaConfigImportXml
  • XML配置中可以使用<import>元素来拆分XML配置(当一个XML过于庞大)
  • XML配置中引用JavaConfig配置
    • 通过<import>标签只能引入其他XML配置
    • 通过<bean>标签可以引入其他JavaConfig配置
      • 代码示例:com.mzj.springframework.ioc._04_MixConfig.XmlImportJavaConfig

3、混合配置最佳实践:

不管使用 JavaConfig 还是使用 XML 进行装配,我通常都会创建一个根配置(root configuration )用来汇总其他的JavaConfig装配类和 其他 XML配置文件 。我也会在根配置中启用组件扫描(通过<context:component-scan>或@ComponentScan

1.2 依赖包

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${springframe.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${springframe.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${springframe.version}</version>
    </dependency>

1.3 Spring配置文件加载方式

方式1:本地文件

FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("E:\\workspace\\springframeworkbeginner\\src\\main\\resources\\spring-ioc.xml");	

方式2:放在classpath下

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-context.xml");

方式3:web应用中依赖servlet或者listener进行加载

1.4 bean的高级装配

1.4.1 环境与profile

本小节示例代码:com.mzj.springframework.ioc._05_advance.profile

1、解决的问题在不同的环境中某个bean会有所不同,采用不同实现:生产环境使用数据源为通过jndi注入的数据源,而开发环境使用H2数据源。

解决了Spring bean要跨各种部署环境的通用问题,在运行时,通过将环境相关的bean与当前激活的 profile进行匹配,Spring能够让相同的部署单元跨多种环境运行,而 不需要进行重新构建。

2、解决方案:配置profile bean

3、原理:spring3.1版本开始支持bean profile的功能,这里的bean profile是逻辑上的概念,不同的bean profile代表不同环境,如生产、开发,要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。没有指定profilebean始终都会被创建,与激活哪个profile没有关系;

4、激活profile

  • 如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile 是激活的。
  • 但如果没有设置spring.profiles.active属性的话,那Spring 将会查找 spring.profiles.default 的值
  •  
    如果 spring.profiles.active spring.profiles.default均没有设置的话,那就没有激活的profile ,因此只会创建那些没有定义在profile 中的 bean
  • 设置上述属性方式如下:
    • 作为DispatcherServlet的初始化参数;
    • 作为Web应用的上下文参数;
    • 作为JNDI条目;
    • 作为环境变量;
    • 作为JVM的系统属性;
    • 在集成测试类上,使用@ActiveProfiles注解设置

5、使用(JavaConfig):

  • JavaConfig进行配置时通过在Configuration增加类级别的@Profile("XXX")注解,标识出这个配置中创建的bean只有在XXX profile激活时才会创建,其中XXX为dev、prod等代表不同环境的字符串;然后通过上述激活方式(其中之一)在运行时设置(激活)当前环境。
package com.mzj.springframework.ioc._05_advance.profile;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import javax.sql.DataSource;

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {

    @Bean
    public DataSource dataSource(){
        EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:com/mzj/springframework/ioc/_05_advance/schema.sql")
                .addScript("classpath:com/mzj/springframework/ioc/_05_advance/test-data.sql")
                .build();
        return embeddedDatabase;
    }
}
package com.mzj.springframework.ioc._05_advance.profile;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

import javax.sql.DataSource;

@Configuration
@Profile("prod")
public class ProductionProfileConfig {

    @Bean
    public DataSource dataSource() {
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
}
package com.mzj.springframework.ioc._05_advance.profile;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({DevelopmentProfileConfig.class,ProductionProfileConfig.class})
public class AllInOneConfig {

}

测试代码:

package com.mzj.springframework.ioc._05_advance.profile;

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;

import javax.sql.DataSource;

public class DataSourceConfigTest {

  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes=AllInOneConfig.class)

  @ActiveProfiles("dev")
  public static class DevDataSourceTest {

    @Autowired
    private DataSource dataSource;
    
    @Test
    public void shouldBeEmbeddedDatasource() {
      System.out.println(dataSource);
    }
  }

}

测试结果:

  • spring3.1只能在类上使用@Profile注解,从spring3.2开始,可以在方法级别使用@Profile注解:
package com.myapp;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
public class DataSourceConfig {
  
  @Bean
  @Profile("dev")
  public DataSource embeddedDataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.H2)
        .addScript("classpath:schema.sql")
        .addScript("classpath:test-data.sql")
        .build();
  }

  @Bean
  @Profile("prod")
  public DataSource jndiDataSource() {
    JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/myDS");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
    return (DataSource) jndiObjectFactoryBean.getObject();
  }

}
6、使用( XML方式 ):
  • 通过<beans>元素的profile属性,注意,这里时beans元素的属性,而不是bean
  • 还可以在根 <beans> 元素中嵌套定义 <beans> 元素,而不是为每个环境都创建一个profile XML 文件。这能够将所有的 profile bean 定义放到同一个XML 文件中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

  <beans profile="dev">
    <jdbc:embedded-database id="dataSource" type="H2">
      <jdbc:script location="classpath:schema.sql" />
      <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>
  </beans>
  
  <beans profile="prod">
    <jee:jndi-lookup id="dataSource"
      lazy-init="true"
      jndi-name="jdbc/myDatabase"
      resource-ref="true"
      proxy-interface="javax.sql.DataSource" />
  </beans>
</beans>
7、部署时使用:
  • 所有的配置文件都会放到部署单元之中(如WAR件),但是只有profile属性与当前激活profile相匹配的配置文件才会被用到

8、扩展:配置多个生效的profile

  • spring.profiles.active和spring.profiles.default设置多个profile名称时,以逗号分隔。
  • 当然,同时启用dev和prod profile可能也没有太大的意义,不过你可以同时设置多个彼此不相关的profile

1.4.2 条件化的bean

1、解决问题

更多、更细粒度的控制一个bean的创建条件

Profile bean是在运行时条件化创建bean的一种方式,但是Spring 4提供了一种更为通用的方式,通过这种方式能够声明某些bean的创建与否要依赖于给定条件的输出结果。结合使用@Conditional注解和 Spring Condition接口的实现,能够为开发人员提供一种强大和灵活的机制,实现条件化地创建bean。

2、Spring版本:4.0以上

3、使用方式

@Conditional注解:可以用到带有@Bean注解的方法上使用,可以给@Conditional注解设置一个实现Condition接口的类的class参数,Condition接口唯一的方法为:

boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
  • 如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建这些bean
  • 通过matches()方法参数获取到的信息可以用来实现该方法的逻辑
  • ConditionContext接口:条件上下文,可以获取如下信息
package org.springframework.context.annotation;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;

/**
 * Context information for use by {@link Condition}s.
 *
 * @author Phillip Webb
 * @author Juergen Hoeller
 * @since 4.0
 */
public interface ConditionContext {

	/**
	 * Return the {@link BeanDefinitionRegistry} that will hold the bean definition
	 * should the condition match, or {@code null} if the registry is not available.
	 */
	BeanDefinitionRegistry getRegistry();

	/**
	 * Return the {@link ConfigurableListableBeanFactory} that will hold the bean
	 * definition should the condition match, or {@code null} if the bean factory
	 * is not available.
	 */
	ConfigurableListableBeanFactory getBeanFactory();

	/**
	 * Return the {@link Environment} for which the current application is running,
	 * or {@code null} if no environment is available.
	 */
	Environment getEnvironment();

	/**
	 * Return the {@link ResourceLoader} currently being used, or {@code null} if
	 * the resource loader cannot be obtained.
	 */
	ResourceLoader getResourceLoader();

	/**
	 * Return the {@link ClassLoader} that should be used to load additional classes,
	 * or {@code null} if the default classloader should be used.
	 */
	ClassLoader getClassLoader();

}

接口方法说明:

  • 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
  • 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
  • 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
  • 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
  • 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
  • AnnotatedTypeMetadata接口能够让我们检查带有 @Bean 注解的方法上还有什么其他的注解
public interface AnnotatedTypeMetadata {

	boolean isAnnotated(String annotationName);

	Map<String, Object> getAnnotationAttributes(String annotationName);

	Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);

	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);

	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);

}

接口方法说明:

  • 借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。
  • 借助其他的那些方法,我们能够检查@Bean注解的方法上其他注解的属性。

4、扩展

之前提到的@Profile注解,Spring4进行了重构,新的实现就是通过@Conditional注解实现的:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();

}

其中的ProfileCondition:

/*
 * Copyright 2002-2013 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;

/**
 * {@link Condition} that matches based on the value of a {@link Profile @Profile}
 * annotation.
 *
 * @author Chris Beams
 * @author Phillip Webb
 * @author Juergen Hoeller
 * @since 4.0
 */
class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		if (context.getEnvironment() != null) {
			MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
			if (attrs != null) {
				for (Object value : attrs.get("value")) {
					if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
						return true;
					}
				}
				return false;
			}
		}
		return true;
	}

}
可以看到, ProfileCondition 通过AnnotatedTypeMetadata得到了用于 @Profile 注解的所有属性。借助该信息,它会明确地检查value 属性,该属性包含了 bean 的profile名称。然后,它根据通过 ConditionContext 得到的Environment来检查[借助 acceptsProfiles() 方法]该 profile是否处于激活状态。

1.4.3 处理自动装配的歧义性

1、解决问题

使用spring自动装配时,如果不仅有一个bean能够匹配结果的话Spring会抛出NoUniqueBeanDefinitionException

2、其他说明

更常见的情况是给定的类型只有一个实现类,因此自动装配能够很好地运行

3、原理

当确实发生歧义性的时候,Spring提供了多种可选方案来解决这样的问题。你可以将可选bean中的某一个设为首选(primary)的

bean ,或者使用限定符( qualifier )来帮助 Spring 将可选的 bean 的范围缩小到只有一个bean
 
4、使用方式
 
1)标示首选的bean(有局限:设置多个 @Primary 无法保证唯一)
 
方式1:通过@Primary注解:与@Component组合用在组件扫描的bean(自动装配+组件扫描bean使用)
方式2: 通过@Primary注解:与@Bean组合用在JavaConfig的@Bean方法上(自动装配+JavaConfig配置bean使用)

方式3:通过<bean>元素的primary属性进行设置( 自动装配+XML配bean置使用
点评:不管你采用什么方式来标示首选bean,效果都是一样的,都是告诉Spring在遇到歧义性的时候要选择首选的bean。
但是,如果你标示了两个或更多的首选 bean ,那么它就无法正常工作了
 
2) 使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean(更强大:可以保证唯一)

方式:@Qualifier注解:与@Autowired和@Inject协同使用

这是使用限定符的最简单的例子。为 @Qualifier 注解所设置的参数就是想要注入的bean ID
 
在bean通过@Service等注解纳入IOC容器时,可以通过@Service(value="tcp")的方式设置bean的ID,然后在 @Qualifier注解中使用,@Service 不设置value属性时,ID默认为类名首字母小写;
 
如果需要将@Qualifier注解的参数设置成读取外部properties配置文件(即与属性占位符一起使用,比如下面这种用法:
@Autowired
@Qualifier("${beanName}")
private MyBean myBean;

),但是这种设置是不生效的,可以使用@Resources注解,并使用name属性替代@Autowired+@Qualifier:

1)通过@Resources注解自动注入,并设置name属性指定唯一实现,属性值读取配置文件的transport.type属性

@Resource(name="${transport.type}")
private GDITransport gdiTransport;

2)引入外部配置文件

方式一:单独一个配置类读取配置项 

@Component
@PropertySource({"classpath:gdiAttribute.properties"})
public class GdiAttribute {

    @Value("${transport.type}")
    private String transportType;

     public String getTransportType() {
        return transportType;
    }
}

方式二:在JavaConfig类中读取配置项

@Configuration
@PropertySource(value= {"classpath:/gdiAttribute.properties"})
public class JavaConfig {

...
}

 3)在不同实现类中设置bean的唯一ID

@Service(value = "tcp")
@Slf4j
public class TcpTransport extends AbstractGDITransport {
    ...
}

4)在gdiAttribute.properties配置文件中设置配置项值:

#传输类型
transport.type=http

这样,就可以通过修改外部配置文件,来在不同环境中应用不同的Service实现了。。

说明:其实@Qualifier注解的参数是一个复杂的限定符:

 
限定符规则:
  • 如果没有指定其他的限定符的话,所有的bean都会给定一个默认的限定符,这个限定符与 bean ID 相同
    • 缺点:如果类改名,会导致致限定符失效
  • 使用自定义的限定符:可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。在这里所需要做的就是在bean声明上添加@Qualifier注解:

cold 限定符分配给了 IceCream bean 。因为它没有耦合类名,因此你可以随意重构IceCream 的类名,而不必担心会破坏自动装配。在注入的地方,只要引用cold 限定符就可以了:
 
自定义限定符命名最佳实践:为bean选择特征性或描述性的术语,而不是使用随意的名字
  • 使用自定义的限定符注解:原理:一个自定义注解,使用@Qualifier注解进行修饰使之成为一个自定义的限定符注解,可以根据bean的特征,分别定义不同的自定义的限定符注解,然后用这些注解对bean进行修饰,得到bean修饰与注入时的唯一性
    • 使用自定义的限定符存在问题:如果多个bean都具备相同特性的话会自定义相同名称的限定符,又会造成歧义了

此时,增加新的特性使之可以区分,并将特性定义成自定义的限定符注解:

这样可以用多个自定义的限定符注解,对某一个bean进行修饰:
 
在注入点,我们使用必要的限定符注解进行任意组合,从而将可选范围缩小到只有一个bean 满足需求。以保证得到bean的唯一性
评价:
观察一下 setDessert() 方法以及它的注解,这里并没有在任何地方明确指定要将IceCream 自动装配到该方法中。相反,我们使用所需bean 的特性来进行指定,即 @Cold @Creamy 。因此,setDessert() 方法依然能够与特定的 Dessert实现保持解耦。任意满足这些特征的bean都是可以的。在当前选择Dessert实现时,恰好如此,IceCream是唯一能够与之匹配的bean。
 

1.4.4 bean的作用域(生命周期)

本小节示例代码: com.mzj.springframework.ioc._05_advance.scope

1、解决问题

    一个bean对象,使用(可见)范围

2、作用域种类

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean例。
  • 请求(Rquest):在Web应用中,为每个请求创建一个bean例。

3、其他说明

  • bean默认:单例,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例
  • 类如果保持一些状态,重用是不安全的,就要定义为其他作用域类型

4、使用方式

默认是singleton,如果要选择其他作用域,要使用@Scope注解,可以在使用@Component或@Bean的地方一起使用@Scope注解:

1)发现bean的方式为:组件扫描时

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.mzj.springframework.ioc._05_advance.scope" />

</beans>
bean 的类上使用@Scope 注解,将其声明为原型 bean
@Component(value = "myBean1234")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyBeanPrototype {
}
默认为单例bean:
package com.mzj.springframework.ioc._05_advance.scope;

import org.springframework.stereotype.Component;

@Component(value = "myBean123")
public class MyBean {
}

也可以使用@Scope("prototype"),但是使用SCOPE_PROTOTYPE常量更 加安全并且不易出错

测试代码:
package com.mzj.springframework.ioc._05_advance.scope;

import org.springframework.context.support.ClassPathXmlApplicationContext;

//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = { "classpath*:com/mzj/springframework/ioc/_05_advance/scope/scope.xml" })
public class ScopeTest {

//    @Autowired
//    private MyBean myBean;
//
//    @Test
//    public void testScope(){
//
//    }

    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("com/mzj/springframework/ioc/_05_advance/scope/scope.xml");
        MyBean myBean1 = (MyBean) classPathXmlApplicationContext.getBean("myBean123");
        System.out.println(myBean1);
        MyBean myBean2 = (MyBean) classPathXmlApplicationContext.getBean("myBean123");
        System.out.println(myBean2);

        MyBeanPrototype myBeanPrototype1 = (MyBeanPrototype) classPathXmlApplicationContext.getBean("myBean1234");
        System.out.println(myBeanPrototype1);
        MyBeanPrototype myBeanPrototype2 = (MyBeanPrototype) classPathXmlApplicationContext.getBean("myBean1234");
        System.out.println(myBeanPrototype2);;
    }

}

输出:

com.mzj.springframework.ioc._05_advance.scope.MyBean@192d3247
com.mzj.springframework.ioc._05_advance.scope.MyBean@192d3247
com.mzj.springframework.ioc._05_advance.scope.MyBeanPrototype@3ecd23d9
com.mzj.springframework.ioc._05_advance.scope.MyBeanPrototype@569cfc36

2)发现bean的方式为:JavaConfig时

在Configuration类的@Bean方法增加@Scope注解:

3)发现bean的方式为:XML时

使用<bean>元素的scope属性来设置作用域:

5、扩展

1)Web应用的两个作用域:使用会话和请求作用域

=====下面以session进行说明,请求作用域(request)也类似=====

Web 应用中,如果能够实例化在会话和请求范围内共享的 bean ,那将是非常有价值的事情。
使用方式:
其中WebApplicationContext.SCOPE_SESSION的值是session,这个常量是位于spring-web包,所以需要增加新的依赖:
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>${springframe.version}</version>
</dependency>

proxyMode属性:

proxyMode=ScopedProxyMode.INTERFACES,这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题:

问题:比如一个singleten级别的beanA依赖这个session级别的ShoppingCart类,在spring容器启动时会创建类A的实例,此时还不能创建ShoppingCart类,因为当前还没有用户登陆,没有session,此种场景下,spring会根据proxyMode属性的设置(①依据接口创建代理还是②使用cglib依据类创建代理)先创建一个ShoppingCart对象的代理,当类A调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。

  • proxyMode=ScopedProxyMode.INTERFACES:表明这个代理要实现ShoppingCart接口(如果ShoppingCart是接口而不是类的话,这个设置是可以的
  • proxyMode=ScopedProxyMode.TARGET_CLASS:如果ShoppingCart是类的话,就要设置这个值,以此来表明要生成目标
    类扩展的方式创建代理(通过cglib

在XML中声明作用域、及代理 

声明作用域方式:通过<bean>元素的scope属性设置bean的作用域

声明代理方式:使用spring aop命名空间的<aop:scoped-proxy>元素,默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:

使用这个元素,需要增加如下命名空间(IDE会自动添加的):

1.4.5 运行时注入

本小节示例代码:com.mzj.springframework.ioc._05_advance.runtimeInject

1、解决问题

   避免硬编码方式注入(无论是JavaConfig还是XML方式),如下面两种硬编码注入:

2、说明

    spring提供两种运行时注入的机制:

 ①Environment动态获取外部属性

 ②属性占位符(Property placeholder

 ③Spring表达式语言(SpEL

3、使用方式

    1)Environment动态获取外部属性

使用@PropertySource注解和Environment:

下面例子展现了一个基本的Spring配置类,它使用外部的属性来装配Mybean

package com.mzj.springframework.ioc._05_advance.runtimeInject;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/16 12:48
 * @Version: 1.0
 */
@Configuration
@PropertySource("classpath:com/mzj/springframework/ioc/_05_advance/app.properties")
public class Config4RuntimeInject {

    @Autowired
    Environment environment;

    @Bean
    public MyBean createMyBean(){
        return new MyBean(environment.getProperty("abc"));
    }

}
@PropertySource引用了类路径中一个名为 app.properties的文件:
abc=1234567
这个属性文件会通过@PropertySource注解的使用加载到Spring Environment 中,稍后可以从这里检索属性。
测试类:
package com.mzj.springframework.ioc._05_advance.runtimeInject;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=Config4RuntimeInject.class)
public class RuntimeInjectTest {

  @Autowired
  MyBean myBean;

  @Test
  public void play() {
    System.out.println(myBean);
  }

}

输出:

Environment详解:
 
①其中getProperty() 方法有四个重载的变种形式:
第1种:上例
第2种:在指定属性不存 在的时候,会使用一个默认值
 
第3、4种:剩下的两种getProperty()方法与前面的两种非常类似,但是它们不会将所有的值都视为String类型,比如:

②其他方法1:(属性相关方法)

  • getRequiredProperty()方法:如果disc.titledisc.artist属性没有定义的话,将会抛出IllegalStateException异常
  • containsProperty()方法:检查一下某个属性是否存在
  • getPropertyAsClass()方法:想将属性解析为类
③其他方法2:除了属性相关的功能以外,Environment还提供了一些方法来检查哪些profile处于激活状态
  • String[] getActiveProfiles() :返回激活 profile 名称的数组;
  • String[] getDefaultProfiles():返回默认 profile 名称的数组;
  • boolean acceptsProfiles(String... profiles):如果environment 支持给定 profile 的话,就返回 true

2)属性占位符(Property placeholder

为了使用占位符,需要进行如下配置:

i)使用JavaConfig时:在Configuration类中增加一个返回PropertySourcesPlaceholderConfigurer的bean:

使它能够基于Spring Environment及其属性源来解析占位符(未验证:还需要使用@PropertySource注解指定外部配置文件)。

ii)使用xml配置时:在xml配置文件中增加如下元素(引入特点配置文件):

<context:property-placeholder
            location="com/mzj/springframework/ioc/_05_advance/app.properties" />

使用方式1:XML中使用属性占位符${XXXX}

按照这种方式,XML配置没有使用任何硬编码的值,它的值是从配置文件以外的一个源中解析得到的。

xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--    <context:component-scan base-package="com.mzj.springframework.ioc._05_advance.scope" />-->

    <!--1、spring xml配置文件中 引入外部配置文件-->
    <context:property-placeholder
            location="com/mzj/springframework/ioc/_05_advance/app.properties" />

    <!--2、通过属性占位符(Property placeholder):${XXX}方式运行时注入配置文件中属性-->
    <bean class="com.mzj.springframework.ioc._05_advance.runtimeInject.propertyPlaceholder.MyBean">
        <constructor-arg value="${abc}"/>
    </bean>
</beans>

bean:

package com.mzj.springframework.ioc._05_advance.runtimeInject.propertyPlaceholder;

import org.springframework.stereotype.Component;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/16 12:51
 * @Version: 1.0
 */
@Component
public class MyBean {

    private String message;

    public MyBean(String message){
        this.message = message;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("MyBean{");
        sb.append("message='").append(message).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

测试类:

package com.mzj.springframework.ioc._05_advance.runtimeInject;

import com.mzj.springframework.ioc._05_advance.runtimeInject.propertyPlaceholder.MyBean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/16 14:02
 * @Version: 1.0
 */
@RunWith(SpringJUnit4ClassRunner.class)
//这种方式也可以:@ContextConfiguration("classpath:com/mzj/springframework/ioc/_05_advance/runtimeInject/propertyPlaceholde/propertyPlaceholde.xml")C
@ContextConfiguration(locations = { "classpath*:com/mzj/springframework/ioc/_05_advance/runtimeInject/propertyPlaceholde/propertyPlaceholde.xml" })
public class PropertyPlaceholderTest {

    @Autowired
    MyBean myBean;

    @Test
    public void test(){
        System.out.println(myBean);
    }
}

运行结果:

使用方式2:如果我们依赖于【组件扫描】和【自动装配】来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以在bean上使用【@Value注解】,它的使用方式与@Autowired注解非常相似。

xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <!--1、spring xml配置文件中 引入外部配置文件-->
    <context:property-placeholder
            location="com/mzj/springframework/ioc/_05_advance/app.properties" />

    <!--2、如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话, 那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以 使用@Value注解,它的使用方式与@Autowired注解非常相似。-->
    <context:component-scan base-package="com.mzj.springframework.ioc._05_advance.runtimeInject.propertyPlaceholder._4Auto" />

</beans>

bean:

package com.mzj.springframework.ioc._05_advance.runtimeInject.propertyPlaceholder._4Auto;

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

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/16 12:51
 * @Version: 1.0
 */
@Component
public class MyBean {

    private String message;

    //3、使用@Value注解注入外部配置文件中属性
    public MyBean(@Value("${abc}") String message){
        this.message = message;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("MyBean{");
        sb.append("message='").append(message).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

测试类:

package com.mzj.springframework.ioc._05_advance.runtimeInject;

import com.mzj.springframework.ioc._05_advance.runtimeInject.propertyPlaceholder._4Auto.MyBean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/16 14:02
 * @Version: 1.0
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:com/mzj/springframework/ioc/_05_advance/runtimeInject/propertyPlaceholde/propertyPlaceholde4Auto.xml" })
public class PropertyPlaceholder4AutoTest {

    @Autowired
    MyBean myBean;

    @Test
    public void test(){
        System.out.println(myBean);
    }
}

运行结果:

总结:解析外部属性能够将值的处理推迟到运行时,但是它的关注点在于根据名称解析来自于Spring Environment和属性源的属性。而Spring表达式语言提供了一种更通用的方式在运行时计算所要注入的值。

3)使用spring表达式语言进行装配

见附录:spring表达式(SpEL)

附录:SpEL

1、作用

它能够在运行时计算要注入到bean属性中的值:

它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值,SpEL能够用在依赖注入以外的其他地方。例如:

  • Spring Security支持使用SpEL表达式定义安全限制规则。
  • Spring MVC应用中使用Thymeleaf模板作为视图的话,那么这些模板可以使用SpEL表达式引用模型数据。

2、特性

  • 使用beanID来引用bean
  • 调用方法和访问对象的属性;
  • 对值进行算术、关系和逻辑运算;
  • 正则表达式匹配;
  • 集合操作。

3、语法

SpEL 表达式要放到 #{ ... } 之中,这与属性占位符有些类似,属性占位符需要放到“ ${ ... } 之中

4、用法

定义SpEL表达式

1)表示字面值

SpEL 中使用字面值其实没有太大的意思,毕竟将整型属性设置为1,或者将 Boolean 属性设置为 false 时,我们并不需要使用 SpEL 。我承认在SpEL 表达式中,只包含字面值情况并没有太大的用处。
#{1}

这个表达式的计算结果就是数字1

#{3.1415}
#{9.87E4}

表示浮点值,第二个是表示:98,700

#{'mazhongjia'}

表示string字面值

#{true}

表示布尔类型值

2)在表达式中使用Java中类型(调用Java中代码)

#{T(System).currentTimeMillis()}

它的最终结果是计算表达式的那一刻当前时间的毫秒数。T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用 其static修饰的currentTimeMillis()方法。

如果要在 SpEL 中访问类作用域的方法和常量的话,要依赖 T() 这个关键的运算符。例如,为了在SpEL 中表达 Java Math 类,需要按照如下的方式使用T() 运算符:
T(java,lang.Math)
这里所示的 T() 运算符的结果会是一个 Class 对象,代表了java.lang.Math。如果需要的话,我们甚至可以将其装配到一个Class 类型的 bean 属性中。但是 T()运算符的真正价值在于它能够访问目标类型的静态方法和常量。
 

3)引用bean、属性和方法

#{myBean}
#{myBean.attr1}
#{myBean.method1()}
#{myBean.method1().method2()}
#{myBean.method1()?.method2()}

以下每一行对应上面示例说明:

  • 引用myBean:此功能可以使用SpEL将一个bean装配到另外一个bean的属性中
  • 会计算得到ID为myBeanbeanattr1属性
  • 调用myBean的method1方法
  • 对于被调用方法的返回值来说,我们同样可以调用它的方法
  • 为了避免method1返回null出现的空指针,可以使用类型安全的运算符:【?.】这个运算符能够在访问它右边的内容之前,确保它所对应的元素不是null。所以,如果method1()的返回值是null的话,那么SpEL将不会调用method2()方法。表达式的返回值会是null。
4)引用系统属性
#{systemProperties['mySystemAttr1']}

引用系统中mySystemAttr1属性

5)SpEL运算符

说明:SpEL 提供了多个运算符,这些运算符可以用在 SpEL表达式的值上
                                     运算符类型
                                  运 算 符
算术运算
+ - * / % ^
比较运算
< > == <= >= lt gt eq le ge
逻辑运算
and or not
条件运算
?: (ternary) ?: (Elvis)
正则表达式
matches

例子1:

#{2 * T(java.lang.Math).PI * mybean.attr1}

id为mybean的bean的attr1属性。

其他spEL运算符的组合使用略,这里只罗列出功能点。

  • 当使用 String 类型的值时, “+” 运算符执行的是连接操作,与在 Java 中是一样的
  • 比较运算符有两种形式:符号形式和文本形式。在大 多数情况下,符号运算符与对应的文本运算符作用是相同的
  • 正则表达式:当处理文本时,有时检查文本是否匹配某种模式是非常有用的
  • SpEL中最令人惊奇的一些技巧是与集合和数组相关的。最简单的事 情可能就是引用列表中的一个元素了

6)SpEL表达式引用外部配置文件

方式1:

配置文件:

abc=123

spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <context:component-scan base-package="com.mzj.springframework.ioc._05_advance.runtimeInject.spEL" />

    <!-- 1、定义一个bean  专门用于获取properties中的值 -->
    <bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:com/mzj/springframework/ioc/_05_advance/app.properties</value>
            </list>
        </property>
    </bean>

    <!-- 2、Spring的动态变量,能在bean中直接调用 -->
    <bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="properties" ref="configProperties" />
    </bean>
</beans>

注入:

package com.mzj.springframework.ioc._05_advance.runtimeInject.spEL;

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

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/16 12:51
 * @Version: 1.0
 */
@Component
public class MyBean {

    private String message;

    /**
     * spEL引用外部properties配置文件
     *
     * 这里的configProperties对应spEL.xml的bean的id
     * @param message
     */
    public MyBean(@Value("#{configProperties['abc']}") String message){
        this.message = message;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("MyBean{");
        sb.append("message='").append(message).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

测试类:

package com.mzj.springframework.ioc._05_advance.runtimeInject;

import com.mzj.springframework.ioc._05_advance.runtimeInject.spEL.MyBean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Auther: mazhongjia
 * @Date: 2020/3/16 14:02
 * @Version: 1.0
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:com/mzj/springframework/ioc/_05_advance/spEL/spEL.xml" })
public class SpELTest {

    @Autowired
    MyBean myBean;

    @Test
    public void test(){
        System.out.println(myBean);
    }
}

运行结果:


方式2:

配置文件:

index.version=v1

添加一个java bean用于读取properties文件中的配置:

@Component("config")
public class Config {
 
    @Value("${index.version}")
    public String indexVersion;
 
    public String getIndexVersion() {
        return indexVersion;
    }
 
    public void setIndexVersion(String indexVersion) {
        this.indexVersion = indexVersion;
    }
}

spring配置文件添加注解扫描:

<context:annotation-config />
<context:component-scan base-package="com.***.config"/>

spring配置加载properties:

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

spEL中实现注入

@Value("#{config.indexVersion}")
public String version;

使用SpEL表达式

1)组件扫描方式

如果通过组件扫描创建 bean,在@Component类中 注入属性和构造器参数时,我们可以使用@Value注解,这与之前看到的属性占位符非常类似。

 

2)XML配置方式
 
  • 可以将 SpEL 表达式传入 <property>(XML方式的设值注入) 
  • 或<constructor-arg> value 属性中(XML方式的构造器注入)
  • 或者将其作为 p- 命名空间或c-命名空间条目的值(XML方式的设值注入、构造器注入的替代方式) 

spEL使用建议

在动态注入值到 Spring bean 时, SpEL 是一种很便利和强大的方式。我们有时会忍不住编写很复杂的表达式。但需要注意的是,不要
让你的表达式太智能。你的表达式越智能,对它的测试就越重要。SpEL毕竟只是 String 类型的值,可能测试起来很困难。鉴于这一点,我建议尽可能让表达式保持简洁,这样测试不会是什么大问题。

到此,springIOC的讲解就到此结束了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值