Spring的高级装配

环境与profile


1. 在JavaConfig中配置profile,使用@profile注解即可。

    Spring3.0之前只可以在类上定义@profile注解,3.2之后可以在方法上定义@profile注解。

2.  在XML中配置profile,定义beans的属性即可。

<beans profile="dev">
</beans>

    可以将所有的beans定义到一个XML,beans可以嵌套beans;也可以定义到不同的XML里。

3. 激活profile

    Spring确定哪个profile处于激活,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。

    设置了active,则根据active设置profile,否则查看default,都没有设置,则没有激活的profile,那么将仅创建没有在profile下的bean。

    有多种方式设置属性:

        1.    作为DispatcherServlet的初始化参数;

        2.    作为Web应用的上下文参数;

        3.    作为JNDI条目;

        4.    作为环境变量;

        5.    作为JVM的系统变量;

        6.    在集成测试类上,使用@ActiveProfiles注解设置。

在web.xml中配置profile

    1.为上下文设置默认的profile:

  <context-param>
  	<param-name>spring.profiles.default</param-name>
  	<param-value>prov</param-value>
  </context-param>

    2.为DisptcherServlet设置初始化参数

  	<init-param>
  		<param-name>spring.profiles.default</param-name>
  		<param-value>prov</param-value>
  	</init-param>

    在测试环境下可以使用@AvctiveProfiles注解激活不同的环境。


条件化的bean

    实现只有在某个特定环境变量设置后,才会创建某个bean。

    Spring4之前,难以实现,Spring4之后,通过@Conditional注解,可以用在带有@Bean的注解上,如果给定的条件为true,就创建这个bean,否则忽略。

条件注释:

@Conditional(PersonCondition.class)

    @Conditional可以是任意实现了Condition接口的类,只需提供matches()方法即可。

matches()方法中存在两个参数ConditionContext、AnnotatedTypeMetadata。

    ConditionContext是一个接口,大致如下:    

public interface ConditionContext{
  BeanDefinitionRegistry getRegistry();
  ConfigurableListableBeanFactory getBeanFactory();
  Enviroment getEnviroment();
  ResourceLoader getResourceLoader();
  ClassLoader getClassLoader();
}


    通过ConditionContext接口,我们可以做到:

    1.    借助getRegisttry()返回值检查bean的定义;

    2.    借助getBeanFactory返回值检查bean存在或者其属性;

    3.    借助getEnviroment()返回值检查环境变量;

    4.    借助getResourceLoader返回值获取加载的资源;

    5.    借助getClassLoader返回值加载并检查类是否存在。

AnnotatedTypeMetadata也是一个接口,可以检查带有@Bean注解的方法上还有什么其他的注解:

public interface AnnotatedTypeMetadata{
	boolean isAnnotated(String annotationType);
	Map<String,Object> getAnnotationAttributes(String annotationType);
	Map<String,Object> getAnnotationAttributes(String annotationType,boolean classValuesAsString);
	MultiValueMap<String,Object> getAnnotationAttributes(String annotationType);
	MultiValueMap<String,Object> getAnnotationAttributes(String annotationType,boolean classValuesAsString);
	
}

    借助isAnnotated()方法,我们能够判断带有@Bean的方法是否还有其他特定的注解;借助其他方法获取其他注解的属性。

    从Spring4开始,@Profile重构了,使其基于@Conditional和Condition实现的。

    @Profile注解如下所示:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profle{
	String[] value();
}

    ProflieCondition检查某个bean是否可用:

    

public class ProfileCondition implements Condition {

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

}

处理自动装配的歧义性

    当Spring中多个bean满足自动装配时,将抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常。

    发生歧义时,Spring提供了多种方案,可以使用首选(primary)或者限定符(qualifier)。

    1.标志首选的bean:

    

@Primary
@Component
public class IceCream implements Dessert {

}
@Primary
@Bean
public Dessert iceCream(){
  return new IceCream();
}
<bean class="com.lcf.spring.different.IceCream" primary="true"/>

    但是当存在两个或以上的首选时,依然无法运行。

    2.限定自动装配的bean

    @Qualifier注解是使用限定符的主要方式。

    限定bean的id:

	@Qualifier("iceCream")
	@Autowired
	private Dessert dessert;

    创建自定义的限定符:

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

} 
	@Qualifier("cold")
	@Autowired
	private Dessert dessert;

 同样的当使用JavaConfig配置时,@Qualifier可以与@Bean注解一起使用。

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

    但是当多个bean都拥有cold这个特性之后,依然会出问题。

    Java不允许在同一个条目上重复出现相同类型的多个注解,所以不能使用@Qualitier注解多个特性。

    所以只能创建自定义的限定符注解:

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

}


bean的作用域

    Spring定义了多种作用域,可以基于这些作用域创建bean(默认单例):

    1.    单例(Singleton):在整个应用中,只创建一个bean的实例;

    2.    原型(Prototype):每次注入或者获取的时候,都会创建一个新的bean;

    3.    会话(Session):在Web应用中为每个会话创建一个bean实例;

    4.    请求(Request):在Web应用中为每个请求创建一个bean实例。

    声明方式(原型):

   

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class Cake implements Dessert {

}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public Dessert cake{
 return new Ckae();
}
<bean class="com.lcf.spring.different.Cake" scope="prototype"/>

    以上都可以实现原型。


    使用会话和请求作用域:

    定义会话域的bean:

	@Bean
	@Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES)
	public ShoppingCart shoppingCart(){
		return new ShoppingCart();
	}

    @Scope的value属性容易理解,设置当前bean的作用域为会话;

    @Scope的proxyMode属性解决了将会话或者请求作用域的bean注入到单例bean的问题。

    我们先来看一下proxyMode所解决问题的场景:

    将会话作用域的bean通过setter注入到单例bean中时,因为单例bean在Spring应用上下文加载的时候创建。Spring会试图注入会话bean到单例bean,单此时会话bean不存在,直到某个用户进入系统,创建会话之后才会出现实例;另外,系统中会存在多个会话bean,我们希望单例bean处理逻辑时,使用的正好是当前会话所对应的会话bean.

    Spring并不是将真正的会话bean注入到单例bean中的,而是注入的一个会话bean的代理,当真正调用单例bean的逻辑时,代理对其进行懒加载,并调用委托给会话bean。

    现在,proxyMode被设置成了ScopedProxyMode.INTERFACES,这表明代理要实现ShoppingCart接口,并将调用委托给实现bean。

    如果ShoppingCart是接口的话,这样没有问题,但当它是一个类的时候,必须使用CGLib代理了,设置属性为:ScopedProxyMode.TARGET_CALSS。

    请求作用域面临同样的问题,同上。


在XML中声明作用域:

    首先要声明aop命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c" xmlns:p="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util" 
       xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd
     http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
    声明会话作用域的bean:
    <!-- 会话作用域 -->
    <bean id="shoppingcart" 
    	class="com.lcf.spring.scope.ShoppingCart"
    	scope="session">
    	<aop:scoped-proxy/>

    <aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的XML属性。默认使用CGLib创建代理。

    可以要求生成基于接口的代理:

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


运行时值注入

    以上所有的依赖注入都是硬编码注入,如果我们想实现值在运行时再确定,spring提供了两种方式:

    1.    属性占位符

    2.    Spring表达式语言(SpEL)

    

    注入外部的值:

    在Spring中,处理外部值最简单的方法就是声明属性源并通过Spring的Enviroment来检索属性

    首先声明一个app.properties文件:

disc.title=wuxinghongqi
disc.artist=lcf

    然后配置文件中引用:

@Configuration
@PropertySource("classpath:app.properties")
public class ExpressiveConfig {
	@Autowired
	Environment env;
	
	@Bean
	public BlankDisc disc(){
		return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));
	}
}

    深入学习Spring的enviroment:

       getProperty有四个重载:

        String getProperty(String key);

        String getProperty(String key,String defaultValue);

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

        String getProperty(String key,Class<T> type,T defalutValue);

        这四个重载很好理解,获取不到值时,给定默认值,转换成相应的对象,给定默认的对象。

        containsProperty()方法:判断某个属性是否存在;

        getPropertyAsClass()方法:将属性解析为类;

        String[] getActiveProfiles()方法:返回激活的profile数组;

        String[] getDefaultProfiles()方法:返回默认的profile数组;

        boolean acceptsProfiles(String...profiles)方法:如果enviroment支持给定的profile,返回true。

解析属性占位符

    在XML中解析BlankDisc构造参数:

    <!-- 属性占位符 -->
    <bean id="disc" class="com.lcf.spring.fitoutBean.BlankDisc"
    	c:_title="$(disc.title)"
    	c:_artist="$(disc.artist)"/>

    依赖组件扫描、自动装配:

    

@Configuration
@PropertySource("classpath:app.properties")
public class ExpressiveConfig {
	
	@Bean
	public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
		return new PropertySourcesPlaceholderConfigurer();
	}
	@Bean
	public BlankDisc disc(@Value("${disc.title}") String title,@Value("${disc.artist}")String artist){
		return new BlankDisc(title,artist);
	}
}

    为了使用占位符,我们必须配置PropertySourcesPlaceholderConfigurer bean.

	@Bean
	public PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
		return new PropertySourcesPlaceholderConfigurer();
	}

    XML配置的话需要声明:

<context:property-placeholder/>

    这里有个问题不解,Spring自动扫描默认使用的无参构造,可以修改吗?


使用Spring表达式进行装配

    SpEL拥有很多特性:

    1.    使用bean的ID引用bean;

    2.    调用方法和访问对象的属性;

    3.    对值进行算数、关系、逻辑运算;

    4.    正则表达式匹配;

    5.    集合操作。

    

    SpEL例子:

    #{1}

    #{T(System).currentTimeMillis()}

    #{sgtPeppers.artist}

    #{systemProperties['disc.title']}

    #{false}

    #{'hello'}

    #{sgtPeppers}

    #{sgtPeppers.getArtist()}

    #{sgtPeppers.getArtist().getLength()}

    当调用方法返回null时;可以这样写:

    #{sgtPeppers.getArtist()?.getLength()}

    “?.”运算符能够在访问它右边的内容之前,确定所对应的元素不为null。

    在表达式中使用类型:

    T(java.lang.Math)

    T(java.lang.Math).PI

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

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

    #{count==100}

    #{count eq 100}

    #{count==100?"a":"b"}

    #{email matches '[/d]'}

    #{list[4]}

    #{'hello'[0]}

    查询运算符:“.?[]”对集合进行过滤,查找指定的子集。

    #{list.?[artist eq 'Aer']}

    “.^[]”查找第一个匹配项“.$[]”查找最后一个匹配项。

    “.![]”从集合成员中选择某个属性放到另一个集合

    #{list.![title]}

    这些SpEL的运用知识简单的使用。

    

        

    




        













    

    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值