Spring in Action
3.4 bean 的作用域
Spring定义了多种作用域:
- 单例(Singleton,
ConfigurableBeanFactory.SCOPE_SINGLETON
): 在整个应用中, 只创建bean的一个实例。 - 原型(Prototype,
ConfigurableBeanFactory.SCOPE_PROTOTYPE
): 每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。 - 会话(Session,
WebApplicationContext.SCOPE__SESSION
): 在Web应用中,为每个创建一个bean实例。 - 请求(Request): 在Web应用中,为每个请求创建一个bean实例。
单例是默认作用域, 要更改作用域需要使用@Scope
注解, 它可以与@Compoent
或@Bean
一起使用。
Session and Request Scope
假设一个购物车 bean:
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){ }
value=WebApplicationContext.SCOPE_SESSION
表示这是一个会话bean.
proxyMode=ScopedProxyMode=INTERFACES
是为了解决将会话或请求作用域的bean注入到单例bean中遇到的问题.
假设我们要将 ShoppingCart
bean 注入到单例 StoreService
bean 的 Setter
方法中:
@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart cart){
//...
}
}
因为StoreService
是一个单例bean, 会在应用上下文家在的时候创建, 当它创建的时候, Spring会试图将ShoppingCart
注入到 setShoppingCart
, 但是 ShoppingCart
只有在创建会话之后才会出现实例, 另外系统将会有多个ShoppingCart
实例, 并不想让某个特定的实例注入到 StoreService
中. 加上ScopedProxyMode.INTERFACES
之后, Spring并不会将实际的 ShoppingCart
bean注入到 StoreService
中, Spring会注入一个到 ShoppingCart
bean的代理. 这个代理会暴露和 ShoppingCart
接口相同的方法, 当 StoreService
调用 ShoppingCart
的方法时, 代理会对其进行懒解析, 并将调用委托给会话作用域中真正的ShoppingCart
bean. 如果ShoppingCart
是接口而不是一个具体类的话, Spring就没有办法创建基于接口的代理了, 此时它必须使用CGLib来生成基于类的代理, 所以 bean类型是具体类的话, 必须将 proxyMode
属性设置为ScopedProxyMode.TARGET_CLASS
.
运行时值注入
package chapter3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
@PropertySource("classpath:/chapter3/app.properties")
public class ResourcesSample {
@Autowired
private Environment env;
public void print() {
String firstName = this.env.getProperty("firstName");
System.out.println("first name: " + firstName);
Integer age = this.env.getProperty("age", Integer.class);
System.out.println("age: " + age);
String notExisted = this.env.getProperty("notExisted", "default");
System.out.println("not existed: " + notExisted);
}
}
firstName=alan
lastName=wei
age=28
String getProperty(String key)
String getProperty(String key, String defaultValue)
T getProperty(String key, Class<T> type)
T getProperty(String key, Class<T> type, T defaultValue)
在使用 getProperty
方法时如果没有指定默认值(defaultValue
), 并且这个属性没有定义的话, 获取到的值是null. 如果你希望这个属性必须要定义, 可以使用 getRequiredProeprty()
方法(如果未定义抛出IllegalStateException
异常).
如果想检查属性是否存在, 可以调用 boolean Environment.containsProperty(String key)
.
如果想将属性解析为类的话, 可以使用 getPropertyAsClass()
方法:
Class<Foo> foo = env.getPropertyAsClass("foo.class", Foo.class);
解析属性占位符
package temporary;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@Configuration
@PropertySource("classpath:/chapter3/app.properties")
public class AppConfig {
//这里执行值注入
@Value("${db.driver}")
private String driver;
//为了使值注入生效, 必须配置一个 PropertySourcesPlaceholderConfigurer,
//因为它能够基于 Spring Environment 及其属性源来解析占位符.
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
3.5.2 使用 Spring 表达式语言进行装配
Spring 3引入了 SpEL(Spring Expression Language, SpEL), 能够将值装配到bean属性和构造器参数中:
- 使用 bean 的ID来饮用bean
- 调用方法和访问对象的属性
- 对值进行算数、关系和逻辑运算
- 正则表达式匹配
- 集合操作
4 面向切面的 Spring
在软件开发中, 散布于应用中多处的功能被称为横切关注点(cross-cutting concern). 通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接切入到应用的业务逻辑中.) 把这些横切关注点与业务逻辑相分离正式面向切面编程(AOP)所要解决的问题.
4.1 什么是面向切面编程
4.1.1 定义AOP术语
通知(Advice) 切面的工作被称为通知. 通知定义了切面是什么以及何时使用. Spring 切面可以应用5种类型的通知:
- 前置通知(Before): 在目标方法被调用之前调用通知功能
- 后置通知(After): 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么
- 返回通知(After-returning): 在目标方法成功执行之后调用通知