Spring实战(第4版)Spring In Action - 第3章 高级装配

15 篇文章 0 订阅

第3章 高级装配

3.1 环境与profile

3.1.1 配置profile bean

在Java中配置

分开配置

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {
	
	@Bean(destroyMethod="shutdown")
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
				.addScript("classpath:schema.sql")
				.addScript("classpath:test-data.sql")
				.build();
	}
}

and

@Configuration
@Profile("prod")
public class ProductionProfileConfig {
	
	@Bean
	public DataSource dataSource() {
		JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
		jndiObjectFactoryBean.setJndiName("jdbc/myDS");
		jndiObjectFactoryBean.setResourceRef(true);
		jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
		
		return (DataSource)jndiObjectFactoryBean.getObject();
	}
}

一同配置

@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中配置profile

方案1:beans 中添加属性

<?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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd"
		profile="dev">

	<jdbc:embedded-database id="dataSource">
		<jdbc:script location="classpath:schema.sql" />
		<jdbc:script location="classpath:test-data.sql" />
	</jdbc:embedded-database>

</beans>

方案2:bean中添加属性

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd">

	<beans profile="dev">
		<jdbc:embedded-database id="dataSource">
			<jdbc:script location="classpath:schema.sql" />
			<jdbc:script location="classpath:test-data.sql" />
		</jdbc:embedded-database>
	</beans>
	
	<beans profile="qa">
		<bean id="dataSource" 
		class="org.apache.commons.dbcp2.BasicDataSource" 
		destroy-method="close" 
		p:url="jdbc:h2:tcp://dbserver/~test" 
		p:driverClassName="org.h2.Driver"
		p:username="sa"
		p:password="password"
		p:initialSize="20" />
	</beans>
	
	<beans profile="prod">
		<jee:jndi-lookup id="dataSource"
			jndi-name="jdbc/myDatabase"
			resource-ref="true"
			proxy-interface="javax.sql.DataSource" />
	</beans>

</beans>

3.1.2 激活profile

依赖两个独立属性:

  • spring.profiles.default(推荐开发环境使用此参数,设置DispatcherServlet的web.xml文件)
  • spring.profiles.active(QA、生产或者其他环境,根据具体情况使用系统属性、环境变量、或JNDI设置)

激活方式:

  • 作为DispatcherServlet的初始化参数
  • 作为Web应用的上下文参数
  • 作为JNDI条目
  • 作为环境变量
  • 作为JVM的系统属性
  • 再集成测试类上,使用@ActiveProfiles注解设置

使用profile进行测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {PersistenceTestConfig.class, 
		ProductionProfileConfig.class, 
		DevelopmentProfileConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
	
	@Autowired(required=false)
	private DataSource dataSource;

	@Test
	public void testDataSource() {
		assertNotNull(dataSource);
	}
	
}

3.2 条件化的Bean

public class MagicExistsCondition implements Condition {

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

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

ConditionContext接口

public interface ConditionContext {

	BeanDefinitionRegistry getRegistry();

	ConfigurableListableBeanFactory getBeanFactory();

	Environment getEnvironment();

	ResourceLoader getResourceLoader();

	ClassLoader getClassLoader();

}

AnnotatedTypeMetadata接口


public interface AnnotatedTypeMetadata {

	boolean isAnnotated(String annotationType);

	Map<String, Object> getAnnotationAttributes(String annotationType);

	Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);

	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);

	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);

}

Spring4 Profile 源码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();

}

ProfileCondition源码

class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		if (context.getEnvironment() != null) {
			MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
			if (attrs != null) {
				for (Object value : attrs.get("value")) {
					if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
						return true;
					}
				}
				return false;
			}
		}
		return true;
	}

}

3.3 处理自动装配的歧义性

下面的代码自动装配就会产生歧义

@Component
public class Cake implements Dessert {

}

@Component
public class Cookies implements Dessert {

}

@Component
public class IceCream implements Dessert {
	
}

@Configuration
@ComponentScan
public class DessertConfig {
	
	Dessert dessert;
	
	@Autowired
	public void setDessert(Dessert dessert) {
		this.dessert = dessert;
	}
}

3.3.1 标示首选的bean

@Primary

@Component
@Primary
public class IceCream implements Dessert {
	
}

但是多个bean被标示@Primary依然会出现歧义性

3.3.2 限定自动装配的bean

@Qualifier 默认beanID作为限定参数

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

创建自定义限定符

@Component
@Qualifier("cold")
public class IceCream implements Dessert {
	
}
    @Autowired
	@Qualifier("cold")
	public void setDessert(Dessert dessert) {
		this.dessert = dessert;
	}

与@bean注解一同使用

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

使用自定义限定符注解

下面的代码有问题,都出现了重复注解

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

@Component
@Qualifier("cold")
@Qualifier("fruity")
public class Popsicle implements Dessert {

}

解决方案自定义注解,继承@Qualifier注解

Cold注解

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

}

Creamy注解

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

}

Fruity注解

@Retention(RUNTIME)
@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR })
@Qualifier
public @interface Fruity {

}

自定义限定符的多重限定的实现方式

@Component
@Cold
@Creamy
public class IceCream implements Dessert {
	
}
@Component
@Cold
@Fruity
public class Popsicle implements Dessert {

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

3.4 bean 的作用域

Spring定义的多种作用域

  • 单例(Singleton)
  • 原型(Prototype)
  • WEB会话(Session)
  • WEB请求(Request)
  •  
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {

}

bean中

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

XML中

<bean id="notepad" class="com.C3_4.Notepad" scope="prototype" />

3.4.1 使用会话Session和请求Request作用域

@Component
@Scope(value=ConfigurableBeanFactory.SCOPE_SESSION, 
	proxyMode=ScopedProxyMode.INTERFACES)
public interface ShoppingCart {

}

注意代理:StoreService是单例bean,ShoppingCart是会话(Session)bean,StoreService在Spring的应用上下文加载的时候创建,但这个时候ShoppingCart不存在,因为他属于会话(Session)作用域。Spring并不会将实际的ShoppingCart bean注入,而是注入代理bean,这个代理与ShoppingCart有相同的方法。当StoreService真正调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给真正的ShoppingCart的bean。

@Component
public class StoreService {
	
	ShoppingCart shoppingCart;
	
	@Autowired
	public void setShoppingCart(ShoppingCart shoppingCart) {
		this.shoppingCart = shoppingCart;
	}
}

proxyMode属性:

  • 接口-ScopedProxyMode.INTERFACE
  • 类(基于CGLIB)-ScopedProxyMode.TARGET_CLASS
public enum ScopedProxyMode {

	DEFAULT,

	NO,

	INTERFACES,

	TARGET_CLASS;

}

3.4.2 在XML中声明作用域代理

默认是基于CGLIB创建目标类的代理

    <bean id="cart" class="com.C3_4.ShoppingCart" scope="session">
		<aop:scoped-proxy/>
	</bean>

基于接口代理

    <bean id="cart" class="com.C3_4.ShoppingCart" scope="session">
		<aop:scoped-proxy proxy-target-class="false" />
	</bean>

3.5 运行时值注入

3.5.1 注入外部的值

硬编码方式:

    @Bean
	public CompactDisc sgtPeppers() {
		return new BlankDisc("sgt. pepper's Lonely Hearts Club Band", "The Beatles");
	}
<bean id="sgtPeppers" class="soundsystem.BlankDisc"
		p:title="sgt. pepper's Lonely Hearts Club Band"
		p:artist="The Beatles" />

避免硬编码,Spring提供两种运行时求值方式:

  • 属性占位符(Property placeholder)
  • Spring表达式语言(SpEL)

3.5.1 注入外部的值

app.properties

disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles
@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
	
	@Autowired
	Environment env;
	
	@Bean
	public BlankDisc disc() {
		return new BlankDisc(
				env.getProperty("disc.title"),
				env.getProperty("disc.artist")
				);
	}
}

深入学习Spring的Environment

public interface Environment extends PropertyResolver {

	String[] getActiveProfiles();

	String[] getDefaultProfiles();

	boolean acceptsProfiles(String... profiles);

}
public interface PropertyResolver {

	boolean containsProperty(String key);

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

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

	String getRequiredProperty(String key) throws IllegalStateException;

	<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

	String resolvePlaceholders(String text);

	String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

}

解析属性占位符

${}

!!!@Value不能用在构造方法,测试不好使

    public void set(
			@Value("${disc.title}") String title, 
			@Value("${disc.artist}") String artist) {
		this.title = title;
		this.artist = artist;
	}

使用占位符需要配置PropertySourcesPlaceholderConfigurer() bean,通过基于Srping的Environment及其属性源来解析占位符

    // 这个推荐使用
    @Bean
	public static PropertySourcesPlaceholderConfigurer palceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}
	
	// 这个不推荐使用
	@Bean
	public static PropertyPlaceholderConfigurer palceholderConfigurer() {
		return new PropertyPlaceholderConfigurer();
	}

XML配置

<context:property-placeholder />

3.5.2 使用Spring表达式语言进行装配

SpEL(Spring Expression Language)特性:

  • 使用Bean的Id来引用bean
  • 调用方法和访问对象的属性
  • 对值进行算术、关系和逻辑运算
  • 正则表达式匹配
  • 集合操作

SpEL样例:

#{1}
#{T(System).currentTimeMillis()}
#{sgtPeppers.artist}
${systemProperties['disc.title']}
    public BlankDisc(
			@Value("#{systemProperties['disc.title']}") String title, 
			@Value("#{systemProperties['disc.artist']}") String artist) {
		this.title = title;
		this.artist = artist;
	}

XML

<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_title="#{systemProperties['disc.title']}"
c:_artist="#{systemProperties['disc.artist']}" />

表示字面值

#{3.14159}
#{9.87E4}
#{'hello'}
#{false}

引用bean、属性和方法

#{sgtPeppers}
#{sgtPeppers.artist}
#{artistSelector.selectArtist()}
#{artistSelector.selectArtist().toUpperCase()}

"?."运算符

#{artistSelector.selectArtist()?.toUpperCase()}

在表达式中使用类型

Class对象 T(java.lang.Math) 常量 T(java.lang.Math).PI 静态方法 T(java.lang.Math).random()

SpEL运算符

#{2 * T(java.lang.Math).PI * circle.radius}

#{2 * T(java.lang.Math).PI * circle.radius ^ 2}

#{disc.title + ' by ' + disc.artist}


#{counter.total == 100}

#{counter.total eq 100}

#{scoreboard.score > 1000 ? "winner!" : "Loser"}

// Elvis运算符
#{disc.title ?: 'Rattle and Hum'}

正则表达式

#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}

计算集合

"[]"获取元素

#{jukebox.songs[4].title}

#{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title}

#{'This is a test'[3]}

".?[]"集合过滤 ".^ []"集合第一个 ".$[]"集合最后一个

#{jukebox.songs.?[artist eq 'Aerosmith']}

".![]"投影运算符

//返回title列表
${jukebox.songs.![title]}
//返回title列表
${jukebox.songs.?[artist eq 'Aerosmith'].![title]}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值