资讯专栏INFORMATION COLUMN

第三章 高级装配

only_do / 2169人阅读

摘要:注解只可以装配只有一个实现类的例如下面的有三个实现类,自动装配时,就会不知道选哪一个,因而会报错误。使用表达式语言进行装配使用的来引用待补充实例调用方法和访问对象的属性对峙进行算数,关系和逻辑运算正则表达式匹配集合操作

完整代码请见:https://github.com/codercuixi...

第一部分 @Profile注解的使用

环境与profile 是否启用某个bean,常用于数据库bean
通过profile启用不同的bean,特别是对于各种不同的数据库(开发线,测试线,正式线),尤其管用。
1.1第一步 配置profile bean。通过@Profile修饰类或者方法名,来表明这个Bean是可以动态启动与否的

package com.myapp;

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;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean(destroyMethod = "shutdown")
    @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();
    }
}

1.2.第二步,激活profile。
通过@ActiveProfile来激活指定的Profile,启用指定的数据库Bean。

package profiles;

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 results = jdbc.query("select id, name from Things", new RowMapper() {
        @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(classes=DataSourceConfig.class)
  @ActiveProfiles("prod")
  public static class ProductionDataSourceTest {
    @Autowired
    private DataSource dataSource;
    
    @Test
    public void shouldBeEmbeddedDatasource() {
      // should be null, because there isn"t a datasource configured in JNDI
      assertNull(dataSource);
    }
  }
}

1.3.通过两个参数激活profile
spring.profiles.active和spring.profiles.default,优先使用前者的配置。
设置这两个参数的方式有如下几种:(待补充完整)

作为DIspatcherServlet的初始化参数

作为Web应用的上下文参数

作为JNDI条目

作为环境变量

作为JVM的系统属性

在集成测试类上,使用@ActiveProfile注解设置。(也就是上面第二步演示的)

第二部分 条件化的bean

通过@Conditional, 可以用到Bean上,当条件为true,则创建该Bean;否则则不创建。
主要分为一下三步
1.像往常一样定义Bean的POJO类
2.编写org.springframework.context.annotation.Condition接口的类MagicExistsCondition,用来创建是否创建该Bean
3.将@Conditional(MagicExistsCondition.class)应用到Bean的JavaConfig上。

package conditional.habuma.restfun;

public class MagicBean {
}
package conditional.habuma.restfun;

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

/**
 * * @Author: cuixin
 * * @Date: 2018/8/30 18:32
 */
public class MagicExistsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        return environment.containsProperty("magic");
    }
}
package conditional.habuma.restfun;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

/**
 * * @Author: cuixin
 * * @Date: 2018/8/30 18:32
 */
@Configuration
public class MagicConfig {

    @Bean
    @Conditional(MagicExistsCondition.class)
    public MagicBean magicBean(){
        return new MagicBean();
    }
}

由于实现了match方法中带有两个参数,我们可以通过这两个参数做到什么呢?
ConditionContext接口的方法

public interface ConditionContext {

    /**
     * 返回BeanDefinitionRegistry,可用来判断Bean是否定义
     */
    BeanDefinitionRegistry getRegistry();

    /**
     * 返回ConfigurableListableBeanFactory,可用来检查Bean是否存在,甚至探查Bean的属性
     */
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();

    /**
     * 返回Environment,可用来判断环境变量是否存在,且获取环境变量的值
     */
    Environment getEnvironment();

    /**
     *返回ResourceLoader,可用来读取或探查已经加载的资源
     */
    ResourceLoader getResourceLoader();

    /**
     * 返回ClassLoader,可用来加载类或判断类是否存在
     */
    @Nullable
    ClassLoader getClassLoader();

}

AnnotatedTypeMetadata 用来获取注解相关信息

public interface AnnotatedTypeMetadata {

    
    boolean isAnnotated(String annotationName);
    
    @Nullable
    Map getAnnotationAttributes(String annotationName);
    
    @Nullable
    Map getAnnotationAttributes(String annotationName, boolean classValuesAsString);
    
    @Nullable
    MultiValueMap getAllAnnotationAttributes(String annotationName);
    
    @Nullable
    MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);

第三部分 处理自动装配的歧义性。

3.1@AutoWired 注解只可以装配只有一个实现类的Bean
例如下面的Dessert有三个实现类,自动装配时,Spring就会不知道选哪一个,因而会报NoUniqueBeanDefinitionException错误。

public interface Dessert {
}
@Component
public class Cake implements Dessert {
}
@Component
public class Cookies implements Dessert {
}
@Component
public class IceCream implements Dessert {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CakeConfig.class)
public class CakeTest {
    @Autowired
    private Dessert dessert;//Spring: emmm.... I don"t which one to choose
    @Test
    public void getDessert(){
        assertNotNull(dessert);
    }
}

3.2 @Primary 可以指定某个实现类作为优先Bean创建
给蛋糕加个@Primary,表明首选蛋糕作为首选项。然后在执行Test,发现就不抱错了。
@Primary可以配合@Component, @Bean, @Autowired使用。

@Component
@Primary
public class Cake implements Dessert {
}

3.3 @Qualifie将使用的Bean限定到具体的实现类
由于@Qualifier是基于字符串去匹配Bean id的,所以你修改了类名就可能导致找不到对应的Bean了。但我尝试了一下,如果使用IDEA的Refactor->Rename,会帮我们自动更改多处的。
@Qualifie可以配合@Component, @Bean, @Autowired使用。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CakeConfig.class)
public class CakeTest {
    @Autowired
    @Qualifier("cookies")
    private Dessert dessert;
    @Test
    public void getDessert(){
        assertNotNull(dessert);
    }
}

Spring实战中,为了解决@Qualifier“不够用”,拼命地建立自定义注解,我感觉是没有必要,有点画蛇添足的感觉。

四. bean的作用域

四种不同的作用域
单例(Singleton默认):在整个应用中,只创建bean的一个实例。
比如

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public Notepad notepad() {
        return new Notepad();
    }

原型(Prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean实例。
比如:

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Notepad notepad() {
        return new Notepad();
    }

会话(Session):在web应用中,为每个会话创建一个bean实例, 举个例子:

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public Notepad notepad() {
        return new Notepad();
    }

请求(Request):在web应用中,为每个请求创建一个bean实例,举个栗子:

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public Notepad notepad() {
        return new Notepad();
    }

这里需要注意的是proxyMode这个属性。(TODO:通过例子加深理解)
当值为ScopedProxyMode.TARGET_CLASS时,表示的该bean类型是具体类,只能使用CGLib来生成基于类的代理。
当值为ScopedProxyMode.INTERFACES时。

五.运行时注入

1.使用@PropertySource, @Environment注入外部值

app.properties的内容
disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles

public class BlankDisc {
    private final String title;
    private final String artist;
    public BlankDisc(String title, String artist){
        this.title = title;
        this.artist = artist;
    }
    public String getArtist() {
        return artist;
    }

    public String getTitle() {
        return title;
    }
}

@Configuration
@PropertySource("classpath:/externals/com/soundsystem/app.properties")
public class EnvironmentConfig {
    @Autowired
    private Environment env;

    @Bean
    public BlankDisc blankDisc(){
        return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));
    }

}

另外Environment的getProperty有4个重载方式可以选择

String getProperty(String key); //获取指定key的内容;如果找不到key就返回null
String getProperty(String key, String defaultValue);//获取指定key的内容;如果找不到key,就返回默认值
 T getProperty(String key, Class targetType);//targetType用于说明该key的值类型
 T getProperty(String key, Class targetType, T defaultValue);


2.属性占位符
${...}表示属性占位符,常配合@Value使用,举个栗子。

    @Bean
    public BlankDisc blankDisc2(@Value("${disc.title}") String title, @Value("${disc.artist}")String artist){
        return new BlankDisc(title, artist);

    }
3.使用Spring表达式语言进行装配

1.使用bean的id来引用Bean TODO 待补充实例
2.调用方法和访问对象的属性
3.对峙进行算数,关系和逻辑运算
4.正则表达式匹配
5.集合操作

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76963.html

相关文章

  • Spring核心 Bean的高级装配

    摘要:在集成测试时,通常想要激活的是开发环境的。因为没有耦合类名,因此可以随意重构的类名,不必担心破坏自动装配。在装配中,占位符的形式为使用包装的属性名称。参数装配的是名为的属性值。 环境与profile 配置profile bean 在3.1版本中,Spring引入了bean profile的功能。使用profile,首先将所有不同的bean定义整理到一个或多个profile之中,再将应用...

    forrest23 评论0 收藏0
  • Spring入门看这一篇就够了

    摘要:甲乙交易活动不需要双方见面,避免了双方的互不信任造成交易失败的问题。这就是的核心思想。统一配置,便于修改。带参数的构造函数创建对象首先,就要提供带参数的构造函数接下来,关键是怎么配置文件了。 前言 前面已经学习了Struts2和Hibernate框架了。接下来学习的是Spring框架...本博文主要是引入Spring框架... Spring介绍 Spring诞生: 创建Spring的...

    superw 评论0 收藏0
  • Spring - 高级装配

    摘要:高级装配条件化的自动装配与歧义性的作用域表达式语言环境与可以为不同的环境提供不同的数据库配置加密算法等注解可以在类级别和方法级别,没有指定的始终都会被创建的方式配置不同环境所需要的数据库配置会搭建一个嵌入式的数据库模式定义在测试数据通过加 高级装配 Spring profile 条件化的bean 自动装配与歧义性 bean的作用域 Spring表达式语言 环境与profile p...

    binta 评论0 收藏0
  • Spring高级装配之运行时注入

    摘要:原文地址运行时注入与硬编码注入是相对的。硬编码注入在编译时就已经确定了,运行时注入则可能需要一些外部的参数来解决。提供的两种在运行时求值的方式属性占位符表达式语言注入外部的值使用注解可以引入文件,使用其中的值。 原文地址:http://blog.gaoyuexiang.cn/Sp... 运行时注入与硬编码注入是相对的。硬编码注入在编译时就已经确定了,运行时注入则可能需要一些外部的参数来...

    ZweiZhao 评论0 收藏0
  • Spring - 装配Bean

    摘要:装配任何一个成功的应用都是由多个为了实现某个业务目标而相互协作的组件构成的创建应用对象之间协作关系的行为通常称为装配,这也是依赖注入配置的可选方案在中进行显示配置在中进行显示配置隐式的发现机制和自动装配自动化装配组件扫描会自动发现应用上下文 装配Bean 任何一个成功的应用都是由多个为了实现某个业务目标而相互协作的组件构成的 创建应用对象之间协作关系的行为通常称为装配(wiring)...

    CNZPH 评论0 收藏0

发表评论

0条评论

only_do

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<