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中,稍后可以从这里检索属性。