bean的作用域
在默认情况下,spring应用上下文中所有的bean都是单例的,也就是说spring容器中,默认只会创建一个类的实例,不管这个bean在哪里调用,事实上都是同一个实例。
但是这样的设置对于某些需要重用的对象是不合理的,所以spring也给出了重用这些bean的机会。
spring就此定义了多种作用域:
1.单例(Singleton):在整个应用中,只创建bean的一个实例
2.原型(Prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean的实例
3.会话(Session):在Web应用中,为每个会话创建一个bean实例
4.请求(Request):在web应用中,为每个请求创建一个bean实例
由于默认情况是singleton,那么如何控制bean的作用域呢?
1.在Java显式配置中,使用@Scope注解,可以与@Component或@Bean一起使用
@Bean
@Scope("prototype")
public IceCream iceCream(){
return new IceCream();
}
//或者和@Component一起使用
@Component
@Scope("prototype")
public class IceCream{
private String name;
......
}
2.在XML配置文件中,使用scope属性
<bean id="iceCream" class="com.lucky.IceCream" scope="prototype"></bean>
会话和请求作用域
在电子商务中,一个账户有购物车,订单,付款等等需求,难道每次我们都要去创建一个账户的实例去处理这些请求或者操作吗?当然不是,都是同一个账户,所以在web应用中,我们希望按照我们的逻辑来设置bean的作用域。
@Component
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){
......
}
这里的ShoppingCart bean是会话作用域的,当spring应用上下文加载的时候它还没有创建,直到某个用户进入系统后,ShoppingCart才会被创建。
当作用域被标记为request的时候,那么在spring web的应用中,每次请求都会创建对应的一个bean实例。
当作用域被标记为session的时候,也是需要在spring web的应用中,每次会话的过程开始的时候,也会创建所有作用域被标记为session的bean实例。
事实上还有一个globalsession,对于bean的作用域,我们在后面的实例中再具体了解。
运行时注入
注入外部的值
处理外部值的最简单方式就是:
1.告诉spring属性源
2.通过spring的Enviroment来获取属性值
示例:
①添加app.properties文件
disc.title= night song
disc.artist = JayZhou
②在配置类上告诉spring属性源
使用@PropertySource注解
@Configuration
@PropertySource("classpath:com/soundsystem/app.properties")
public class ExpressiveConfig{
//③获取spring中的Enviroment
@Autowired
Enviroment env;
@Bean
public BlankDisc disc(){
return new BlankDisc(env.getProperty("disc.title"),
env.getProperty("disc.artist"));
}
}
附上BlankDisc.java
@Component
public class BlankDisc {
private String title;
private String artist;
public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
@Override
public String toString() {
return "BlankDisc{" +
"title='" + title + '\'' +
", artist='" + artist + '\'' +
'}';
}
}
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes={ExpressiveConfig.class})
public class OutTest{
@Test
public void test1(){
ApplicationContext ctx= new AnnotationConfigurationContext(ExpressiveConfig.class);
BlankDisc disc = ctx.getBean("disc");
System.out.println(disc.toString());
}
}
Spring占位符
在spring装配中,我们之前注入字面量的值都是硬编码的方式,但是这种方式不推荐。
一般情况下,我们会把某些固定的值写到xxx.properties这样的外部文件中,然后使用占位符${…}.
比如,还是使用app.properties文件,上面我们使用的是Spring的Enviroment类来获取属性值,但是这里我们直接使用占位符的形式告诉Spring装配这个bean的时候初始化的值。
1.在xml中配置
<bean id="blankDisc" class="com.lucky.BlankDisc"
c:_title="${disc.title}"
c:_artist="{disc.artist}">
</bean>
2.在JavaConfig中配置,就不是直接使用${},而是使用@Value注解:
需要在这个bean的类中添加构造器
public BlankDisc(
@Value("${disc.title}")String title,
@Value("${disc.artist}") String artist){
this.title = title;
this.artist = artist;
}
但是使用占位符是有前提条件的,我们需要进行配置,spring才能解析占位符。
1.在xml中配置
<context:property-placeholder/>
2.在JavaConfig中配置
@Bean
public PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
SpringEL
1.什么是SpringEL?
SpringEL是spring表达式语言,它能够以强大和间接的方式将值装配到bean属性或者构造器参数中。
2.SpringEL能做什么?
- 使用bean的ID来引用bean
- 调用方法和访问对象的属性
- 对值进行算术,关系和逻辑运算
- 正则表达式匹配
- 集合操作
SpringEL表达式是使用#{exp},而不是${…},表达式exp可以是数字常量,可以是引用其他的bean或者属性。
//可以是数字常量
#{1}
//可以是引用其他的bean
#{sgtPeppers}
//可以是引用其他bean的属性
#{sgtPeppers.artist}
//也可以是通过systemProperties对象引用系统属性
#{systemProperties['disc.title']}
SpEL–表示字面值
SpEL可以表示整数字面量,浮点数,String值,以及Boolean值
//表示整数
#{10}
//表示浮点数
#{3.14159}
//表示String值
#{'hello'}
//表示boolean值
#{true}
虽然SpEL可以用于表示字面值,但是其实意义并不大,我们完全可以直接赋值,或者引入外部文件使用属性占位符,但是当组合更为复杂的表达式时,还是以这些为基础的。
SpEL–引用bean、属性、方法
1.可以通过bean的id引用bean
#{sgtPerppers}
2.也可以引用bean的属性
#{sgtPeppers.artist}
3.可以引用某个bean的方法
#{sgtPeppers.selectArtist()}
4.对于被调用方法的返回值来说,可以继续调用方法
假设我们返回一个String值,也可以调用String类的方法
#{artistSelector.selectArtist().toUpperCase()}
考虑到安全问题,如果调用方法可能返回的是null,为了避免出现NullPointerException,我们可以使用类型安全的运算符:
#{sgtPerppers.selectArtist()?.toUpperCase()}
这里的?类似三目运算符中的?,意思就是:如果sgtPerppers.selectArtist()返回值是null,那么就不会调用后面的toUpperCase方法,就直接返回null,如果返回的不是null,就调用toUpperCase方法。
SpEL–在表达式中使用类型
如果SpEL要访问类作用域的方法和常量的话,要依赖T()这个关键的运算符
例如要使用Math类
T(java.lang.Math)
如果要将Math类的PI这个常量装配到某个Bean中,
#{T(java.lang.Math).PI}
SpEL运算符
SpEL可以使用
算术运算符:+,-,*,/,^,%
比较运算符:<,>,==,<=,>=,eq
逻辑运算符:and,or,not,|
条件运算: ?:(ternary),?:(Elvis)
正则表达式:matches
1.算术运算符
示例:
使用SpEL表达式表示圆的周长,有个bean叫circle,该bean有个属性:radius
1.计算圆的周长
#{2*T(java.lang.Math).PI * circle.radius}
2.计算圆的面积
#{T(java.lang.Math).PI.circle.radius^2}
如果是String类型的值,+执行连接操作
#{'hello,'+sgtPerppers.artist}
2.比较运算符
示例:比较连个数是否相等
#{counter.total == 100}
#{counter.total eq 100}
3.三元运算符
#{score>1000 ? "Winner" : "Loser"}
三元运算符还有一个常见的应用,就是检查是否为null,如果是null的话,就给出一个默认值。
#{disc.title ?: 'Night song'}
SpEL计算正则表达式
通关matches关键字匹配:
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
SpEL计算集合
SpEL也可以计算集合或者数组:
#{jukebox.songs[4].title}
//找到jukebox这个bean的songs集合中的title属性
还可以更复杂,比如我们要随机从songs里面挑一首歌
#{jukebox.songs[T(java.lang.Math).random()*jukebox.songs.size()].title}
我们看到[ ]运算符用来从集合或数组中按照索引获取元素,实际上,它还可以从String值中获取字符,当然也是按照索引。
#{'world'[2]}
//这个表达式表示的就是索引为2的字符 'r'
SpEL提供了查询运算符 .?[],它会用来对集合进行过滤,得到集合的一个子集
示例:比如我们要得到所有作者是JayZhou的歌曲
#{jukebox.songs.?[artist eq 'JayZhou']}
还有另外两个查询运算符
//.^[] 查询第一个匹配项 查询歌单列表中周杰伦的第一首歌曲
#{jukebox.songs.^[artist eq '周杰伦']}
//.$[]查询最后一个匹配项,查询歌单列表中蔡依林的最后一首歌曲
#{jukebox.songs.$[artist eq '蔡依林']}
还有一个投影运算符
//.![]投影运算符 作用:把集合中每个成员的某个属性放到另外一个集合中
// 例如,把张学友所有的歌曲名字放到另一个集合中
#{jukebox.songs.?[artist eq '张学友'].![title]}
总结:
1.bean的作用域有5种:singleton,prototype,requet,session,globalsession
2.使用属性占位符注入外部文件的值
3.使用@Value注解注入外部文件的值
4.使用属性占位符的xml配置以及java类的配置
5.SpEL表达式的用法,有5种:
- 对bean的引用,通过bean的id
- 调用方法或者属性
- 各类运算符
- 正则表达式
- 集合操作