Spring实战(第4版)Spring In Action - 第3章 高级装配
作者:互联网
第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]}
标签:装配,String,title,Spring,public,bean,Dessert,Action,class 来源: https://blog.csdn.net/weixin_40677588/article/details/100105617