spring从头开始(三)----bean织入进阶

部署环境

在软件开发中,部署环境的改变对软件本身的影响是一件很棘手的事情。如开发环境和生成环境等等。

spring的策略是在运行时做出决定,而不是编译时。
使用@Profile注解去指明该bean属于哪一个环境。

@Profile("dev")

在spring3.1版本中,@Profile还只能用在class级别上,从3.2版本开始@Profile可以用在方法级别上了,控制粒度更细了。

在xml配置文件中使用profile属性配置,

<beans profile="dev">

是beans级别的属性

问题:如何使某个profile处于active状态

spring提供了两个参数控制Profile的状态:
spring.profiles.active 和 spring.profiles.default

spring.profiles.active 这个值设置了谁,谁就是active的。
如果spring.profiles.active没有设置,那么spring.profiles.default设置了谁,谁就是active的。
如果两个都没有设置,那么全都不是active的。所有被@Profile注解的都会无效。

问题:这两个参数的设置方法

  • DispatcherServlet的初始化参数
  • web application的context 参数
  • jndi
  • 环境变量
  • jvm环境变量
  • 在单元测试方法中使用@ActiveProfiles注解

如,在web.xml中配置:

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

或者servlet中配置初始化参数

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>spring.profiles.default</param-name>
      <param-value>dev</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

如单元测试中:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
... }

Spring4提供的更强大的@Conditional注解

应用在bean级别的注解,粒度更加细化。只有@Conditional注解的条件判断为true时,bean才会被创建。
如:

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

如何使用呢?@Conditional和Condition接口是成对出现的。

public interface Condition {
    boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata);
}

我们要做的就是实现matches方法,它判断为true就创建,判断为false就不创建bean。

public class MagicExistsCondition implements Condition {
  public boolean matches(
          ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
return env.containsProperty("magic"); 
 }
}

注意到matches方法有两个参数,其中ConditionContext是一个接口

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

从方法名来看,我们应该可以得到这些信息:
- 得到bean注册信息,getRegistry
- 得到bean工厂
- 得到环境变量
- 得到资源加载器
- 得到类加载器

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

解决自动注入的ambiguity问题

前面讲到,在自动注入的时候,当发现有多于1个的合适的bean可以注入时,spring是无法做决定的,报ambiguity错误。
1. 使用@Primary注解
在类上使用@Component注解为bean时,同时使用@Primary注解,表明这是最优秀的bean。
在bean上使用@Bean注解时,加上@Primary。多个时选择最优先的bean注入。
xml中配置:bean标签中设置属性primary=”true”
2. 使用限定注解@Qualifier
可以和@Autowired连用
如:

@Autowired
@Qualifier("iceCream")

意思是注入beanId是iceCream的bean,准确的说是beanId中有这个字符串的bean。
spring在创建bean是,默认使用它的小写类名作为beanId。
这样会导致的一个问题是,当改变类名时,beanId会改变,注入的地方的@Qualifier如果忘记改了,就会出错。
有个办法是,在声明bean时,也加上@Qualifier注解,此时的作用是定义自己的限定条件。如:

@Component
@Qualifier("cold")

这样定义了自己的限定,使用时就可以

@Autowired
@Qualifier("cold")

@Qualifier和@Bean这种确定性注解放在一起是不起作用的。
@Qualifier可以同时使用多个。
但是,注意,java是不允许同时使用多个相同的注解的。java8允许,但该注解必须被@Repeatable注解,but我们的@Qualifier并没有。
所有有个变通的方式是,自定义注解。使用@Qualifier注解。如:

@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 Happy { }

这样就可以同时用多个了。

Scope

  • Singleton 。整个application范围单例。默认。
  • Prototype。每次注入都创建新的。
  • Session。在web应用中,每个session创建一个新的。
  • Request。在web应用中,每个request创建一个新的。
    配置scope:
    在javaConfig中使用@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)注解
    在xml中为<bean>使用 scope="prototype"

session的scope:

@Scope(
    value=WebApplicationContext.SCOPE_SESSION,
    proxyMode=ScopedProxyMode.INTERFACES)

注意proxyMode是用来解决将session或者request级别的bean注入singleton的bean时会遇到的问题。因为一个singleton的bean是在spring的application context 加载的时候创建的,而session或者request级别的bean是在session或者request创建时才创建出来的,所以此时还不存在。spring的解决办法是为这个session或者request级别的bean创建一个代理,将其注入singleton的bean。
注意上面的例子中我们使用的是接口ScopedProxyMode.INTERFACES。
也就是说此时注解是使用在一个接口上面。
如果是class类,要用ScopedProxyMode.TARGET_CLASS。spring会使用CGLib去创建基于类的代理。
这里写图片描述

在xml中配置代理:
使用

<aop:scoped-proxy />

默认情况下是使用CGLIB生成基于类的代理。如果要使用接口,用

<aop:scoped-proxy proxy-target-class="false" />

spring的EL表达式

spring的el表达式主要是为了运行时动态的注入数据,而不是写死。
在看spel之前,还有另一种方法也可以达到这个目的:属性占位符(property placeholders)。
例子:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
  @Autowired
  Environment env;
  @Bean
  Declare a property source

  public BlankDisc disc() {
    return new BlankDisc(
        env.getProperty("disc.title"),
         env.getProperty("disc.artist"));
} }

Environment还有很多有用的方法,具体看api。

在xml中使用,${xxx}占位

spel提供的增强能力:
  • 通过id访问bean
  • 在对象上执行方法或者获取属性
  • 数学,关系,逻辑运算
  • 正则表达
  • 集合操作

使用 #{ … },里面的就是spel表达式
#{T(System).currentTimeMillis()}得到系统时间。T()操作符,返回的是class。所以只能去访问静态方法和常量。
#{sgtPeppers.artist}得到id是sgtPeppers的artist属性值
#{systemProperties['disc.title']}得到系统参数值
#{artistSelector.selectArtist()?.toUpperCase()}这个例子selectArtist返回string,然后对其Upper,?是为了解决空指针问题。

操作符:
这里写图片描述

选择操作符
#{jukebox.songs.?[artist eq 'Aerosmith']}jukebox.songs返回list,.?[]操作符根据后面的表达式进行选取,true的都会返回。
.^[]返回第一个匹配的。
.$[] 返回最后一个匹配的。

投射操作符.![]
#{jukebox.songs.![title]}jukebox.songs.返回的是一个Song类型的list,对其使用.![]作用是取每一个的title属性值,组成新的String的list返回。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值