Spring从入门到精通—Spring IOC高级依赖注入配置使用实例

1、环境与 Profile

    在开发中我们测试用一套数据库,开发用一套数据库,而且要将应用程序从一个环境迁移到另一个环境,Spring 允许我们定义多套配置,可以配置声明应用哪套配置的 Bean

1.1 Profile
  • Spring中的Profile是什么?
        Spring中的Profile功能其实早在Spring 3.1的版本就已经出来,它可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,我们以前所定义的Bean,当Spring容器一启动的时候,就会一股脑的全部加载这些信息完成对Bean的创建;而使用了Profile之后,它会将Bean的定义进行更细粒度的划分,将这些定义的Bean划分为几个不同的组,当Spring容器加载配置信息的时候,首先查找激活的Profile,然后只会去加载被激活的组中所定义的Bean信息,而不被激活的Profile中所定义的Bean定义信息是不会加载用于创建Bean的。

  • Profile有什么用?
        由于我们平时在开发中,通常会出现在开发的时候使用一个开发数据库,测试的时候使用一个测试的数据库,而实际部署的时候需要一个数据库。以前的做法是将这些信息写在一个配置文件中,当我把代码部署到测试的环境中,将配置文件改成测试环境;当测试完成,项目需要部署到现网了,又要将配置信息改成现网的,真的好烦。。。而使用了Profile之后,我们就可以分别定义3个配置文件,一个用于开发、一个用户测试、一个用户生产,其分别对应于3个Profile。当在实际运行的时候,只需给定一个参数来激活对应的Profile即可,那么容器就会只加载激活后的配置文件,这样就可以大大省去我们修改配置信息而带来的烦恼。

下面我将以两个例子介绍一下目前常用的两种配置方式下如何配置Spring profile
方式一:用xml配置profile

Application.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:dev-data.sql" />
    </jdbc:embedded-database>
  </beans>

  <beans profile="prod">
    <jdbc:embedded-database id="dataSource" type="H2">
      <jdbc:script location="classpath:schema.sql" />
      <jdbc:script location="classpath:prod-data.sql" />
    </jdbc:embedded-database>
  </beans>
</beans>

方式二:用Annotation配置profile,这种方式配置和用xml配置是等价的
在同一个类的不同方法上使用@Profile注解与@Bean一起使用配置出不同的profile实例

@Configuration
public class DataSourceConfig {

  //Spring 引入@Profile 制定某个bean属于哪个profile
  //在方法级别上使用@Profile注解
  @Bean
  @Profile("dev")   //开发环境
  public DataSource embeddedDataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.H2)
        .addScript("classpath:schema.sql")
        .addScript("classpath:dev-data.sql")
        .build();
  }

  @Bean
  @Profile("prod")    //生产环境
  public DataSource embeddedDataSourceDev() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:prod-data.sql")
            .build();
  }
}

激活profile
        Spring在确定哪个profile处于激活状态时,需要依赖两个独立属性:sping.profiles.activespring.profiles.default。Spring提供了@ActiveProfiles用来指定运行测试时要激活哪个profile,如果没有指定sping.profiles.active,会采用spring.profiles.default的默认值。

测试代码:

package com.xzy;

import static org.junit.Assert.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.myapp.DataSourceConfig;

public class DataSourceConfigTest {

  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes=DataSourceConfig.class)
  @ActiveProfiles("dev")
  public static class DevDataSourceTest {
    @Autowired
    private DataSource dataSource;

    @Test
    public void shouldBeEmbeddedDatasource() {
      assertNotNull(dataSource);
      JdbcTemplate jdbc = new JdbcTemplate(dataSource);
      List<String> results = jdbc.query("select id, name from Things", new RowMapper<String>() {
        @Override
        public String mapRow(ResultSet rs, int rowNum) throws SQLException {
          return rs.getLong("id") + ":" + rs.getString("name");
        }
      });

      assertEquals(1, results.size());
      assertEquals("1:A", results.get(0));
    }
  }

  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration("classpath:datasource-config.xml")
  @ActiveProfiles("prod")
  public static class ProductionDataSourceTest_XMLConfig {
    @Autowired
    private DataSource dataSource;

    @Test
    public void shouldBeEmbeddedDatasource() {
      assertNotNull(dataSource);
      JdbcTemplate jdbc = new JdbcTemplate(dataSource);
      List<String> results = jdbc.query("select id, name from Things", new RowMapper<String>() {
        @Override
        public String mapRow(ResultSet rs, int rowNum) throws SQLException {
          return rs.getLong("id") + ":" + rs.getString("name");
        }
      });

      assertEquals(1, results.size());
      assertEquals("1:B", results.get(0));
    }
  }
}

2、条件化Bean

    Spring 4 引入了一个新的@Conditional 注解,它可以用到带@Bean 注解的方法上,如果条件计算结果为 true,就会创建个 Bean设置给@Conditional 的类可以是任意实现了 Condition 接口的类型,如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。若返回false,将不会创建这些bean。
其中:

    ConditionContext :

  • getRegistry():返回的 BeanDefinitionRegistry 检查 Bean 定义:
  • getBeanFactory():返回 ConfigurableListableBeanFactory 检查 Bean 是否存在
  • getEnvironments():返回 Environment 检查环境变量是否存在以及它的值是什么
  • getResourceLoader():返回 ResourceLoader 所加载的瓷源
  • getClassLoader():返回 ClassLoder 加载并检查是否存在
  • AnnotatedTypeMetadata :可以让我们检查带@Bean 注解的方法上还有什么其它注解,它也是一个接口

举个栗子:
写个条件类,实现Condition接口的matches方法,简单的判断一下当前的系统是不是Windows 7的,如果是返回true,否则返回false

package com.xzy.utils;

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

public class StudentCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment env = context.getEnvironment();
        System.out.println(env.toString());
        if("Windows 7".equals(env.getProperty("os.name"))){
            return true;
        }else{
            return false;
        }
    }
}

测试代码:

package com.xzy.utils;

import com.xzy.bean.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;


@Configuration
public class ConditionTest {

    //只用这个条件为true才能产生Student,否则spring压根就不会理他
    @Bean
    @Conditional(StudentCondition.class) 
    public Student appConfig(){
        Student student=new Student();
        student.setName("晓明");
        student.setAge(34);
        return student;
    }
}

测试代码:

程序运行结果:

如果把“Windows 7”改成“Windows 10”就是条件Bean就会返回false,由于无法正常的注入就会出现以下的异常:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.xzy.AppTest': Unsatisfied dependency expressed through field 'student'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xzy.bean.Student' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=appConfig)}

留言区

还能输入500个字符