这个是接着上一篇写的,这章内容较多,分开来记录一下。。。
三、处理自动装配的歧义性
自动装配让spring完全负责bean引用注入到构造参数和属性中,不过,仅有一个bean匹配所需的结果时,自动装配才是有效的。如果不仅有一个bean能够匹配的话,这种歧义性会阻碍spring自动装配属性、构造参数或方法参数。虽然这种歧义性十分罕见,但是我看了spring解决方法后,感觉spring提供的是一种这类问题的解决办法,显然在这里主要学习的是这种解决此类问题的思想。
//这里是提供了这种特殊情况的demo,有一个方法,需要自动装配Dessert接口 @Autowired public void setDessert(Dessert dessert){ this.dessert = dessert; } //但是这个Dessert接口却有三个实现类,这就有点尴尬了,spring自动装配的时候到底要装配哪个实现类呢..... spring会报NoUniqueBeanDefinitionException错误的 @Component public class Cake implements Dessert{......} @Component public class Cookies implements Dessert{......} @Component public class IceCream implements Dessert{......} //使用@Primary 注解标注首选bean @Primary @Component public class IceCream implements Dessert{......} <bean id="iceCream" class="com.desserteater.IceCream" primary="true" /> //但是两个同时加上@Primary注解呢!!!spring中会有@Qualifier注解 @Autowired @Qualifier("iceCream") public void setDessert(Dessert dessert){ this.dessert = dessert; } //但是这种方式使用的是bean ID,限制符与要注入的bean是紧耦合的,对类名称的改动都会导致限定符失效,但是spring中允许为bean设置自己的限定符 @Component @Qualifier("cold") public class IceCream implements Dessert{......} @Autowired @Qualifier("cold") public void setDessert(Dessert dessert){ this.dessert = dessert; } //但是如果是两个实现类具有相同特点的限制符呢!!!这个考虑的也太全面了吧,程序员就是要有这种精神的,哈哈哈,就是下面这种情况呢..... @Component @Qualifier("cold") @Qualifier("creamy") public class IceCream implements Dessert{......} @Component @Qualifier("cold") @Qualifier("fruity") public class Popsicle implements Dessert{......} //Java 中不允许出现在同一个条目上出现相同类型的多个注解的,唉,终极办法:自定义限制符注解,这个好牛叉牛叉... //这个对应着就是@Qualifier("cold") @Target({ElementType.CONSTRUCAOR, ElementType.FILELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier Public @interface Cold{} //这个对应着就是@Qualifier("creamy") @Target({ElementType.CONSTRUCAOR, ElementType.FILELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier Public @interface Creamy{} //这个对应着就是@Qualifier("fruity") @Target({ElementType.CONSTRUCAOR, ElementType.FILELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier Public @interface Fruity{} //大功告成,这就彻底解决那个问题了,你在有多少的我也不怕了,嘿嘿嘿,完美的解决这个问题了,spring中太美妙了,这种思想真的让人受益匪浅 @Component @Cold @Creamy public class IceCream implements Dessert{......} @Component @Cold @Fruity public class Popsicle implements Dessert{......} @Autowired @Cold @Creamy public void setDessert(Dessert dessert){ this.dessert = dessert; }
四、bean的作用域
在默认情况下,spring应用上下文中所有的bean都是以单例的形式创建的,也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对线实例所带来的成本只留给一些小规模的任务,在这些任务中,让对象保持无状态并且在应用中反复用这些对象可能并不合理。
spring中为解决这个问题,定义了多种作用域,可以基于这些作用域创建bean:
1)单例(Singleton):在整个应用中,只创建bean的一个实例
2)原型(Prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean的实例
3)会话(Session):在web应用中,为每个会话创建一个bean实例
4)请求(Request):在web应用中,为每个请求创建一个bean实例
//Prototype 原型作用域的配置方式 @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Notepad{......} <bean id="notepad" class="com.myapp.Notepad" scope="prototype" /> //Session 会话和 Request 请求作用域 //在电商领域的 处理购物车bean 会话作用域是最合适的 @Component @Scope( value=WebApplicationContext.Scope.SESSION, ProxyMode=ScopedProxyMode.INTERFACES) Public ShoppingCart cart(){......} @Component Public class StoreService(){ //StoreService是一个单例bean,会在spring应用上下文加载的时候创建,当它创建的时候,会试图将ShoppingCart bean注入到set方法中,但是ShoppingCart bean是会话作用域,此时并不存在,直到某个用户进入系统,创建会话之后才会出现ShoppingCart 的实例 @Autowired public void setShoppingCart(ShoppingCart shoppingCart){ this.shoppingCart = shoppingCart; } } /* 这里需要详细讲解一下ProxyMode 系统中,会有多个ShoppingCart 的实例,每个用户一个,我们希望当StoreService处理购物车的时候,它所使用的ShoppingCart 实例恰好是当前会话所对应的那一个 spring并不会将实际的ShoppingCart bean注入到StoreService中,spring会注入一个到ShoppingCart bean的代理,这个代理会暴露于ShoppingCart 相同的方法,StoreService会认为它就是一个购物车 当StoreService 调用 ShoppingCart 的方法时,代理会对其进行懒解析并调用委托给会话作用域内真正的ShoppingCart bean 这个东西好牛气的样子,这里涉及到了代理模式 */ //注意:ScopedProxyMode.INTERFACES 表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean //注意:ScopedProxyMode.TARGET_CLASS 实现的是一个具体的类的话,spring必须使用CGLib 来生成基于类的代理 //XML中实现作用域的配置 这里用到了AOP Spring中面向切面 <bean id="cart" class="com.myapp.ShoppingCart"> <aop:scoped=proxy /> //spring会默认CGLib创建目标类的代理 </bean> //这种 proxy-target-class="false" spring会创建基于接口的代理 <bean id="cart" class="com.myapp.ShoppingCart"> <aop:scoped=proxy proxy-target-class="false" /> </bean>
五、运行时值注入
这节主要讲的就是spring中的表达式语言SpEL,其他的基本上没有特别重要的,值得思考的知识点。
spring提供了两种在运行时求值的方式:
(1)属性占位符(Property placeholder)
(2)Spring 表达式语言(SpEL)
属性占位符的语法是:${.....}
主要来说一下Spring 表达式语言(SpEL)
1、SpEL拥有很多特性,主要包括:
1)使用bean的ID来应用bean
2)调用方法和访问对象的属性
3)对值进行算术、关系和逻辑运算
4)正则表达式匹配
5)集合操作
2、SpEL样例
1)表示String值、浮点数、Boolean值
#{3.14159}
#{9.87E4} 对应着 98700
#{“hello”}
#{true}
2)引用bean、属性和方法
#{sgtPeppers}
#{sgtPeppers.artist}
#{sgtPeppers.selectArtist()}
#{sgtPeppers.selectArtist().toUpperCase()}
#{sgtPeppers.selectArtist()?.toUpperCase()} 避免出现空值,出现?避免出现空值
3)在表达式中使用类型
T{java.lang.Math}
T{java.lang.Math}.PI
T{java.lang.Math}.random()
4)SpEL运算符
#{2*T{java.lang.Math}.PI*circle.radius}
#{T(java.lang.Math).PI*circle.radius^2}
#{disc.title + 'by' + disc.artist}
#{counter.total == 100}
#{counter.total eq 100}
#{score > 100 ? "winner" : "loser"}
5)计算正则表达式
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9._]+\.com'}
6)计算集合
#{jukebox.songs[4].title}
#{jukebox.songs[T(java.lang.Math).random * jukebox.songs.size()].title}
#{jukebox.songs.?[artist eq 'Aerosmith']} 过滤歌曲,得到所有属性为Aerosmith的歌曲的小的集合
#{jukebox.songs.^[artist eq 'Aerosmith']} 查询集合中第一个属性为Aerosmith的歌曲
#{jukebox.songs.![title]} 投影运算符,将集合中的特定的属性放到一个新集合中去
#{jukebox.songs.?[artist eq 'Aerosmith']}.![title] 混合使用,把作者是Aerosmith的title属性放到一个集合中去