3.1、环境与profile
如果在不同的环境某个bean会不同(例如DataSource),可以通过配置profile bean,将不同的bean定义整理到多个profile中,部署的时候确保对应的profile处于激活状态。
在Java配置类中进行profile配置
·使用@Profile注解指定某个bean属于哪个profile,注解在@Configuration类(3.1版本)或@Bean方法下面(3.2版本),告诉Spring这个配置类只在某个profile激活时才会创建,否则会被忽略。
在XML中配置profile
通过元素的profile属性
标签可以嵌套,所以不需要多个XML进行配置。
激活profile
激活profile时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。前者可以设置那个profile为激活状态,如果没设置active,则会去寻找default的值。如果都没设置的话,那就没有激活的profile,只会创建没有定义再profile中的bean。
有多种方式设置这两个属性:
·作为DispatcherServlet的初始化参数
·作为Web应用的上下文参数
·作为JNDI条目
·作为环境变量
·作为JVM的系统属性
·在集成测试类上,使用@ActiveProfiles注解设置
3.2条件化的bean(4.0版本新增)
如果希望某个bean在另外的一个bean声明后才创建,否则不创建,可以使用@Conditional注解,用在带@Bean的方法上。当@Conditional中的值计算为true时才创建这个bean。目标类需要实现Condition。
如下,Car类仍是会根据profile创建相应的bean,但Boss这个bean则会根据Car类中的matches方法的返回值判断是否创建。
此时Car类的代码如下:
可见,创建Boss bean的时候会进入matches方法中会对车的价格做判断,如果 > 10000,boss就买不起车了,所以就不会创建boss这个bean。现在只需要对profile进行切换即可进行测试。
matches()方法中,形参会得到ConditionContext和AnnotatedTypeMetadata对象来做出决策。
ConditionContext对象可以获得
·BeanFactory,检查某个bean是否存在,探测该bean的属性(car bean的price属性)
·Registry,检查bean的定义
·Environment检查环境变量是否存在,判断该值(profile)
·ResourceLoader,检查加载的资源
·ClassLoader,加载并检查类是否存在
AnnotatedTypeMetadata对象则可以获得该@Bean方法是否还有其他注解以及这些注解的属性。
Spring4.0以后的版本中@Profile本身也使用了@Conditional注解。
3.3 处理自动装配的歧义性
如果有多个bean匹配结果的话,spring的自动装配就会出现歧义性,提供多种可选方案来解决这样的问题。可以将某个bean设置为首选(primary)或使用限定符(qualifier)使得bean的范围缩小为一个。如下,此时声明多个Car的子类Bean,则会报多Bean异常。
·通过使用@Primary设置bean的首选项
此时虽然有多个Car bean,但是首选项是taxi类,在boss类中装配就会自动选择这个bean。如果设置多个@Primary同样导致歧义性,也会报错
@Primary可以与@Component结合注解在类上,也可以与@Bean结合注解在方法上,使用XML配置时如下:
·通过限定符
因为设置@Primary也不一定能将范围限定到唯一一个,但是限定符可以将范围缩小到唯一。
@Qualifier注解是使用限定符的主要方式,与@Autowired或@Inject协同使用,在注入时明确指出注入的是那个bean的id。
例如在这里明确指出这个Car 要的是car_sedan(因为默认名就是这个),所以会注入Car类型下的car_sedan这个限定符(默认情况下ID作为其限定符)的对象。
也可以自定义一个bean的限定符,在bean声明上加上注解@Qualifier,使其限定符为自定义字符串。java中不允许多次出现相同的注解,所以如果需要设置多重限定符,可以采用编写自定义注解类的方式实现。
3.4bean的作用域
默认情况下,Spring应用上下文所有bean都说作为单例(singleton)的形式创建。但是Spring也定义了多种作用域,可以基于这些作用域创建bean。
·单例(Singleton):整个应用中只创建bean的一个实例
·原型(Prototype):每次注入或通过Spring应用上下文获取的时候都创建一个新的实例
·会话(Session):在Web应用中,为每个会话创建一个bean实例
·请求(Request):在Web应用中,为每个请求创建一个bean实例
如果要使用其他作用域,需要使用@Scope注解,与@Component/@Bean一起使用。
单例/多例的配置:
ConfigurableBeanFactory中封装了单例和多例的值,直接取出即可。在多例中,每次取出的都是一个全新的bean。
request/session配置
在Web应用中,可能需要实例化在会话和请求范围内的bean。例如有一个bean代表购物车(ShoppingCart),需要实现每次会话共享一个购物车,将购物车注入到服务(ShoppingService)中。
WebApplicationContext中封装了这几个属性。告知Spring为每个session创建一个bean。而在同一次会话中这个bean相对于是单例的。
这里有一个购物车和购物服务的注入例子,想要实现一个会话拥有自己的购物车,但购物服务是单例的:
注意,如果是当前这样SCOPE_SESSION的注入方式,因为ShoppingService为默认的单例模式,所以应用上下文会在加载的时候就把Service bean创建出来,而此时没有session会话,不会创建ShoppingCart,导出抛出异常No Scope registered for scope name ‘session’。
正确的做法是在@Scope这个注解内还有一个属性是proxyMode 可以使用ScopedProxyMode中的属性给这个bean做代理。
如果ShoppingCart是接口而不是类的时候,使用INTERFACES,表示为其创建一个代理,这个代理实现ShoppingCart接口,并注入到Service中。当用户真正调用Service的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的Bean(等到用户调用的时候,真正的bean已经创建出来)
如果此时ShoppingCart为一个类,需要使用TARGET_CLASS,表示为ShoppingCart这个类创建一个使用CGLib的基于类的代理,表明要以生成目标类扩展的方式创建代理。
同理,request域的bean也会遇到一样的问题,也需要先生成代理先注入。
使用XML声明session和request的作用域,使用元素的scope属性并用aop:scoped-proxy代替proxyMode,默认情况下该标签使用CGLib创建目标类的代理,也可以将其属性proxy-target-class设置为false要求其生成基于接口的代理。
3.5运行时注入值
当前的注入属性都是硬编码,例如car对象的name被设成了“奔驰”和“宝马”。Spring为实现在运行时注入值,提供了两种方案:
·属性占位符(Property placeholder)
·Spring表达式语言(SpEL)
1、注入外部的值
可以将值写在properties文件中,通过Spring加载并赋值(例如数据库配置)。
新疆一个car.properties并把值写在配置文件中。
使用@PropertySource告知Spring读取哪个配置文件,这个文件则会加载到Spring的Environment中,通过Environment获取后设置值。
Environment获得properties有多种获得方式:
getProperty直接获取(无值为null),获取不到时设置默认值,转型等。
getRequiredProperty,必须有值,否则抛出异常。
使用env.containsProperty()可以检测某个属性是否存在。也可以通过Environment检测哪个profile处于激活/默认/是否被支持等。
2、使用Spring表达式语言进行装配
Spring3引入了SpEL。拥有很多特性包括:
·使用bean ID来应用bean;
·调用方法和访问对象的属性
·对值进行算术,关系,逻辑运算
·正则表达式匹配
·集合操作
SpEL表达式要放到#{…}中