博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第三章 高级装配
阅读量:6850 次
发布时间:2019-06-26

本文共 9560 字,大约阅读时间需要 31 分钟。

完整代码请见:

第一部分 @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;@Configurationpublic 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 */@Configurationpublic 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 {}@Componentpublic class Cake implements Dessert {}@Componentpublic class Cookies implements Dessert {}@Componentpublic 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@Primarypublic 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时。
Spring作用域代理

五.运行时注入

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

app.properties的内容disc.title=Sgt. Peppers Lonely Hearts Club Banddisc.artist=The Beatlespublic 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就返回nullString 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.集合操作

转载地址:http://iugul.baihongyu.com/

你可能感兴趣的文章