spring funcitons

1. java config test

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {

  @Rule
  public final StandardOutputStreamLog log = new StandardOutputStreamLog();

  @Autowired
  private MediaPlayer player;

2. java xml test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:META-INF/spring/soundsystem.xml")
public class CDPlayerXMLConfigTest {

  @Rule
  public final StandardOutputStreamLog log = new StandardOutputStreamLog();

  @Autowired
  private MediaPlayer player;

3. java config ,xml 配置相互引用

@Configition
@ComponentScan(basePackages = "com.young")
public class config{

}
@Configition
@ComponentScan(basePackages = {"com.young","com.youn4j"})
public class config{

}
@Configition
@ComponentScan(basePackageClasses = {CdPlayer.class,DVDPlayer.class})
public class config{

}

<?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="soundsystem" />

</beans>

在xml配置中引用java config配置

<?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 class="soundsystem.CDConfig" />

  <bean id="cdPlayer"
        class="soundsystem.CDPlayer"
        c:cd-ref="compactDisc" />
        
</beans>
@Configuration
public class CDConfig {
  @Bean
  public CompactDisc compactDisc() {
    return new SgtPeppers();
  }
}

在java config配置中应用xml配置

@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {

}

<?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="soundsystem.BlankDisc"
        c:_0="Sgt. Pepper's Lonely Hearts Club Band"
        c:_1="The Beatles">
    <constructor-arg>
      <list>
        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
        <value>With a Little Help from My Friends</value>
        <value>Lucy in the Sky with Diamonds</value>
        <value>Getting Better</value>
        <value>Fixing a Hole</value>
        <!-- ...other tracks omitted for brevity... -->
      </list>
    </constructor-arg>
  </bean>

</beans>
public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;
  private List<String> tracks;

  public BlankDisc(String title, String artist, List<String> tracks) {
    this.title = title;
    this.artist = artist;
    this.tracks = tracks;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }

}

4. 环境与profile

@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();
  }

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

激活profile,在web.xml中配置,需要配置两个地方:为上下文设置profile,为servlet设置profile

<context-param>
    <param-name>spring.profile.default</param-name>
    <param-value>dev</param-value>
</context-param>
<servlet>
    <init-param>
        <param-name>spring.profile.default</param-name>
        <param-value>dev</param-value>
     </init-param>
</servlet>

使用profile进行测试

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));
    }
  }

5. 条件化bean@Conditional,用在带有@Bean注解的方法上.给定的条件为true,创建,否则,忽略这个bean.

@Configuration
public class MagicConfig {

  @Bean
  @Conditional(MagicExistsCondition.class)
  public MagicBean magicBean() {
    return new MagicBean();
  }
  
}
package com.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;

public class MagicExistsCondition implements Condition {

  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return env.containsProperty("magic");
  }
  
}

6.处理自动装配的歧义性

 同一个接口有多个类实现了这个接口,就会造成多种匹配结果,阻碍spring自动装配属性,构成参数,或方法参数。

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

Spring 会抛出NoUniqueBeanDefinitionException异常

Spring提供了多种可选方案来解决这样的问题。你可以将可选bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean。

@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。

@Component
@Primary
public class IceCream implements Dessert{...}

如果你通过Java配置显式地声明IceCream,那么@Bean方法应该如下所示:

@Bean
@Primary
public Dessert iceCream(){
    return new IceCream();
}

使用XML配置bean的话,同样可以实现这样的功能。<bean>元素有一个primary属性用来指定首选的bean:

<bean id="iceCream" class="com.IceCream" primaryKey="true"></bean>

标识首选bean的目的都是为了在发生歧义的情况下告诉spring哪些bean要去首选,但是多个实现同一接口的bean如果都标记了@primary,name就会出错了。

@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean,从而来缩小选择范围。

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

@Qualifier注解所设置的参数就是想要注入的bean的ID。所有使用@Component注解声明的类都会创建为bean,并且bean的ID为首字母变为小写的类名。因此,@Qualifier("iceCream")指向的是组件描时所创建的bean,并且这个bean是IceCream类的实例。如果你重构了IceCream类,将其重命名为Gelato的话,那此时会发生什么情况呢?如果这样的话,bean的ID和默认的限定符会变为gelato,这就无法匹配setDessert()方法中的限定符。自动装配会失败。

为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。

@Component
@Qualifier("cold")
public class IceCream implements Dessert(){...}

@Autowired
@Qualifier("cold")
public Dessert iceCream(){
    return new IceCream()
}

如果同一接口的不同实现同样有上述限定符,同样也会出现问题,为了解决问题就是再次将范围缩小

@Component
@Qualifier("cold")
@Qualifier("fruity")
public class IceCream implements Dessert(){...}

@Autowired
@Qualifier("cold")
@Qualifier("fruity")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

这里只有一个小问题:Java不允许在同一个条目上重复出现相同类型的多个注解。

@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {

}
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy{

}
 
@Component
@Cold 
@Creamy
public class IceCream implements Dessert(){...}
@Autowired
@Cold 
@Creamy
public void setDessert(Dessert dessert){ this.dessert = dessert; }

声明与注入使用相同的注解(基于@Qualifier)来限定,为最佳选择方案。

7.bean的作用域

Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。让对象保持无状态并且在应用中反复重用这些对象可能并不合理。有时候,可能会发现,你所使用的类是易变的(mutable),它们会保持一些状态,因此重用是不安全的。在这种情况下,将class声明为单例的bean就不是什么好主意了,因为对象会被污染,稍后重用的时候会出现意想不到的问题。

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

使用@Scope注解,它可以与@Component或@Bean一起使用

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {
  ...
}
<bean class="com.myapp.Notepad"
        scope="prototype" />
  
  <bean class="com.myapp.UniqueThing" />
@Bean
  @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  public Notepad notepad() {
    return new Notepad();
  }

使用会话和请求作用域

如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的:

@Component
@Scope(value = "sesson",proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){

}

这里,我们将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例的。,@Scope同时还有一个proxyMode属性,它被设置成ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题.因为StoreService是一个单例的bean,会在Spring应用上下文加载
的时候创建。当它创建的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。但是ShoppingCart bean是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话
之后,才会出现ShoppingCart实例。另外,系统中将会有多个ShoppingCart实例:每个用户一个。我们并不想让Spring注入某个固定的ShoppingCart实例到StoreService中。我们希望的是StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,如图3.1所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。现在,我们带着对这个作用域的理解,讨论一下proxyMode属性。如配置所示,proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。如果ShoppingCart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果ShoppingCart是一个具体的类的话,Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。尽管我主要关注了会话作用域,但是请求作用域的bean会面临相同的装配问题。因此,请求作用域的bean应该也以作用域代理的方式进行注入。

运行时注入:

为了避免硬编码,让这些值在运行的时候在确定,spring提供了两种在运行时求值的方法:属性占位符(Propert placeholder),spring表达式(SpEL)

注入外部的值:

注入外部的值最简单的方法就是声明属性源,通过spirng的Environment来检索属性,getPropertiey有四个重载方法

String getProperty(String key);

String getProperty(String key, String defaultValue);

<T> T getProperty(String key, Class<T> targetType);

<T> T getProperty(String key, Class<T> targetType, T defaultValue);

如果希望这个属性必须要定义,可以使用getRequiredProperty(String key),如果没有定义则抛出IllegalStateException异常。

如果要验证这个属性是否存在,可以调用containsProperty(String key)方法。

@Configuration
@PropertySource("classpath:app.properties")
public class ExpressiveConfig {
    @Autowired
    Environment evn;
    public void test(){
        String title = evn.getProperty("desc.title");
        String content = evn.getProperty("desc.content","");
        int money = evn.getProperty("desc.money",Integer.class,0);
    }
    
}

 8. 运行时注入

  将一个值注入到bean属性或者构造器参数中,我们希望避免硬编码值,而是想让这些值在运行时在确认。Spring提供了两种在运行时确认值得方式:属性占位符,Spring表达式语言(SpEL);

  处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。

  

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class EnvironmentConfig {

  @Autowired
  Environment env;
  
  @Bean
  public BlankDisc blankDisc() {
    return new BlankDisc(
        env.getProperty("disc.title"),
        env.getProperty("disc.artist"));
     env.getProperty("disc.title", "Rattle and Hum"),
        env.getProperty("disc.artist", "U2")); } }

这个属性文件会加载到Spring的Environment中,稍后可以从这里检索属性。

 

posted @ 2017-03-23 21:43  yangfei969  阅读(269)  评论(0编辑  收藏  举报