Declaring parent and child beans
对于
同一个
class,如果你需要
定义多个
bean,那么你
可以尝试使用
parent and child bean
。
对于parent and child bean,spring为
<bean>
element
提供了
2
个属性:
Parent
—
指定
the
id
of a
<bean> which is
作为
parent bean
。
Abstract
—
如果设置为
true
,表示该
<bean>
的定义是
abstract
的,
spring
将不会实例化该
bean
。
例子
1
:
旧的代码:
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="Jingle Bells" />
<property name="instrument" ref="saxophone" />
</bean>
<bean id="david" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="Jingle Bells" />
<property name="instrument" ref="saxophone" />
</bean>
上面的代码可以看到对于
Instrumentalist class
定义了两个
bean
,这两个
bean
除了
id
不同外,其他都一样
。如果我们要定义
50
个
bean
,可以想象下。这时我们可以使用
parent bean
:
<bean id="baseSaxophonist"
class="com.springinaction.springidol.Instrumentalist"
abstract="true"
>
<property name="instrument" ref="saxophone" />
<property name="song" value="Jingle Bells" />
</bean>
<bean id="kenny"
parent="baseSaxophonist"
/>
<bean id="david"
parent="baseSaxophonist"
/
>
上面的代码把
baseSaxophonist
bean
的
abstract
属性设置为
true
,则
spring
不会对其实例化。
Kenny and david bean
则继承
baseSaxophonist
bean
。
请注意:
parent bean
并不一定要定义成
abstract
!
Overriding inherited properties
child bean
还可以覆盖继承于
parent bean
的
properties,如:
例子2:
<bean id="baseSaxophonist"
class="com.springinaction.springidol.Instrumentalist"
abstract="true"
>
<property name="instrument" ref="saxophone" />
<property name="song" value="Jingle Bells" />
</bean>
<bean id="frank" parent="baseSaxophonist">
<property name="song" value="Mary had a little lamb" />
</bean>
Abstracting common properties
这个功能
通常用在不同
class
的
bean
使用同一个
property
的情况下,
这样就可以对这个
property
专门定义一个
bean
例子3:
旧的代码:
<bean id="taylor"
class="com.springinaction.springidol.Vocalist">
<property name="song" value="Somewhere Over the Rainbow" />
</bean>
<bean id="stevie"
class="com.springinaction.springidol.Instrumentalist">
<property name="instrument" ref="guitar" />
<property name="song" value="Somewhere Over the Rainbow" />
</bean>
上面的代码定义了
不同的
class
的两个
bean
,但它们都使用了同一个
property: song。那么我们就
可以把共同的property抽出来作为一个bean定义(注意:这个bean并不对应任何的class,即没有class属性):
<bean id="basePerformer" abstract="true">
<property name="song" value="Somewhere Over the Rainbow" />
</bean>
<bean id="taylor"
class="com.springinaction.springidol.Vocalist"
parent="basePerformer" />
<bean id="stevie"
class="com.springinaction.springidol.Instrumentalist"
parent="basePerformer">
<property name="instrument" ref="guitar" />
</bean>
Applying method injection
Java
并没有提供下列功能:
add new methods to any class at runtime, without changing the class’s definition
。但
Ruby
则提供了这个功能
。举个例子,你想在
ruby string class
里添加一个
new method
,那么你只需要象下面这样定义(而不需要修改本身的
string class
):
class String
def print_length
puts "This string is #{self.length} characters long"
end
end
定义好后,你就可以使用了,如:
message = "Hello"
message.print_length
但
Java
里没有这功能。
Spring
提供了
method injection
功能,使你能够在
runtime
期间把
method
注入到
class
里
Spring
提供了
2
种方式的
method injection
:
1
.
Method replacement
—Enables existing methods (abstract or concrete) to be replaced at runtime with new implementations
(这种方式是
for
“返回值不是在
spring
里有
bean
定义类型”的方法
)
.
2
.
Getter injection
—Enables existing methods (abstract or concrete) to be replaced at runtime with a new implementation that returns a specific bean from the Spring context.
(这种方式是
for
“使用
get
开头的,而且返回值是在
spring
里有
bean
定义类型”
的方法
)
method replacement
旧的代码
public class MagicBoxImpl implements MagicBox {
public MagicBoxImpl() {}
public String getContents() {
return "A beautiful assistant";
}
}
<bean id="magicBox" class="com.springinaction.springidol.MagicBoxImpl" />
如果你想在
runtime
期间修改
getContents
方法的话,就可以使用
spring method replacement:
<bean id="magicBox"
class="com.springinaction.springidol.MagicBoxImpl">
<replaced-method name="getContents" replacer="tigerReplacer" />
</bean>
<bean id="tigerReplacer" class="com.springinaction.springidol.TigerReplacer" />
import org.springframework.beans.factory.support.MethodReplacer;
public class TigerReplacer implements MethodReplacer {
public Object reimplement(Object target, Method method,
Object[] args) throws Throwable {
return "A ferocious tiger";
}
}
注意:被替代的
method
也可以是一个抽象方法!
Getter injection
public abstract class Instrumentalist implements Performer {
public void perform() throws PerformanceException {
getInstrument()
.play();
}
public abstract Instrument getInstrument();
}
上面的
getInstrument
方法就是一个符合
getter injection
的典型。那么我们要如何使用呢:
假设有一个实现了
Instrument
的
Guitar class
,你希望在
runtime
期间上面的
getInstrument
方法返回的是一个
Guitar
对象,那么你就可以使用下列配置:
<bean id="stevie"
class="com.springinaction.springidol.Instrumentalist">
<lookup-method name="getInstrument" bean="guitar" />
</bean>
当然还需要有guitar bean的定义:
<bean id="guitar" class="com.springinaction.springidol.Guitar"
scope="prototype"
/>
注意我们把
scope
设置为
prototype
是为了使每次调用
getInstrument
方法时都返回不同的
Guitar
对象
Injecting non-Spring beans
我们知道spring对configure到spring container的所有bean进行创建、管理。但是,
对于在spring里有定义bean,但又不是通过spring来创建的class,又应该如何使用spring呢?这种情况是可能的:例如,一个由ORM创建的object则不是spring创建的。
例子:
Instrumentalist class
public abstract class Instrumentalist implements Performer {
public Instrumentalist() {}
public void perform() throws PerformanceException {
instrument
.play();
}
private
Instrumentinstrument
;
public void set
Instrument
(
Instrumentinstrument
) {
this.
instrument
=
instrument
;
}
}
假设
Instrumentalist class
在
spring
里定义了一个
bean
,
但我们
create
对象
outside of spring
:
Instrumentalist pianist = new Instrumentalist();
pianist.perform();
在执行perform方法时,会抛出
NullPointerException,因为instrument变量为null。
当然,我们可以用代码为对象注入property:
Piano piano = new Piano();
pianist.setInstrument(piano)
;
pianist.perform();
这是可行的,但有些情况下,并不允许你通过代码对其进行注入。例如:
一个
ORM
创建了一个
Instrumentalist
对象,那么我们可能根本没有机会来为其
configure its properties.
对于“
在
spring
里有定义
bean
,但又不是通过
spring
来创建的
class
”这种情况,我们应该把这些在
spring container
外创建的对象纳入到
spring
里来完成
DI
呢?
Spring 2.0
提供了解决方案!
第一步:把
bean
定义成
abstract
:
<bean id="pianist"
class="com.springinaction.springidol.Instrumentalist"
abstract="true"
>
<property name="instrument">
<bean class="com.springinaction.springidol.Piano" />
</property>
</bean>
上面的
bean
定义为
abstract=true
,并不是为了被其他
bean
继承,而纯粹是为了告诉
spring
不要被
spring
实例化!
第二步:如果把上面的定义(注意上面设置的
id
)和
class
联系起来
@Configurable("pianist")
public class Instrumentalist implements Performer {
…
}
@Configurable
的作用有
2
个:
1>
它表明
Instrumentalist
的实例会被纳入和配置到
spring container
里,即使它是在
outside of spring
创建。
2>
它把
Instrumentalist
class
与
id
为
pianist
联系在一起
,当
spring
在配置一个实例时,它会寻找
pianist
bean
作为模板来进行配置(包括
DI
)
第三步:在
spring configure file
里添加下列代码
<aop:spring-configured />
它表示有一些在外部创建的
beans
,需要被配置和纳入进
spring container
。
请注意:
<aop:spring-configured/>
是用到
aspectJ
,这意味着你的
APP
必须在一个
AspectJ-enabled
JVM
里运行。
The best way to AspectJ-enable a Java 5
JVM
is to start it with the following
JVM
argument:
-javaagent:/path/to/aspectjweaver.jar
经过这三步,当你执行下列代码时就不会抛出异常,因为
spring container
会纳入对象,并进行配置(包括注入)
Instrumentalist pianist = new Instrumentalist();
pianist.perform();
Registering custom property editors
有时候我们看到
有些bean的定义只需要通过赋予一个string value就可以设置一个complex property。例如,在spring 2.0 in action第九章,你会看到如何使用JaxRpcPortProxyFactoryBean定义一个web service,对它你需要设置“wsdlDocumentUrl” property,该属性的type是java.net.URL,但你对它进行设置时赋予的是string:
<property name="wsdlDocumentUrl"
value="http://www.xmethods.net/sd/BabelFishService.wsdl" />
spring
会自动帮你把
string value
转换成
URL object,它是怎么做到的呢?就是使用
JavaBeans
API。其中的
java.beans.PropertyEditor提供了把string value转化成non string type的接口。
java.beans.PropertyEditorSupport实现了该接口,它提供了2个方法:
getAsText
()
returns the
String
representation of a property’s value.
setAsText
(String value)
sets a bean property value from the
String
value passed in.
spring
基于
PropertyEditorSupport
上,提供了一些更为具体的
custom editor,见下表:
![](https://p-blog.csdn.net/images/p_blog_csdn_net/totogogo/1.jpg)
下面讲解如何扩展
PropertyEditorSupport class
来自定义一个你自己的
custom property editor:假设你有一个
Contact bean,它包含了一个PhoneNumber type的property
public Contact {
private PhoneNumber phoneNumber;
public void setPhoneNumber(PhoneNumber phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
public PhoneNumber {
private String areaCode;
private String prefix;
private String number;
public PhoneNumber() { }
public PhoneNumber(String areaCode, String prefix,
String number) {
this.areaCode = areaCode;
this.prefix = prefix;
this.number = number;
}
…
}
如果按普通的bean定义,应该是:
<bean id="infoPhone"
class="com.springinaction.chapter03.propeditor.PhoneNumber">
<constructor-arg value="888" />
<constructor-arg value="555" />
<constructor-arg value="1212" />
</bean>
<bean id="contact"
class="com.springinaction.chapter03.propeditor.Contact">
<property name="phoneNumber" ref="infoPhone" />
</bean>
上面的定义中,你必须要定义
phonenumber bean
才能够定义
contact bean
,否则无法
contact bean
无法得到
phonenumber property
注入
。
如果我们使用自定义的
custom PhoneEditor
的话,那么就不用定义
phonenumber bean
啦!
public class PhoneEditor
extends java.beans.PropertyEditorSupport {
//text value example “111-111-111”
public void setAsText(String textValue) {
String areaCode = textValue.substring(0,3);
String prefix = textValue.substring(4,7);
String number = textValue.substring(8);
PhoneNumber phone = new PhoneNumber(areaCode, prefix, number);
setValue(phone);
}
}
那么在configure file里你应该这样定义:
<bean
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.springinaction.chapter03.propeditor.PhoneNumber">
<bean id="phoneEditor"
class="com.springinaction.chapter03.propeditor.PhoneEditor">
</bean>
</entry>
</map>
</property>
</bean>
<bean id="contact"
class="com.springinaction.chapter03.propeditor.Contact">
<property name="phoneNumber" value="888-555-1212" />
</bean>
这样就会使得
contact bean
会自动把
"888-555-1212"
转化成
phoneNumber type property。
CustomEditorConfigurer
实质上
是一个
BeanFactoryPostProcessor
,因此它会在
bean factory
初始化之后调用
registerCustomEditor()
方法来把
custom editors load
进
BeanFactory
里
。
(
你也可以通过代码来调用
registerCustomEditor()
方法来实现它
)
请注意:
spring
自带的
custom editors
(例如
URLEditor and LocaleEditor
)
default
已经被
register
进
spring container
啦,不需要用
CustomEditorConfigurer
.
来
register
它们。
举个例子
,
bean A
有一个
url property
是
java.net.URL type
的,如果你的设置为:
<property name="url" value="http://www.xxx.com" />
那么
spring
会自动帮你把
string value
转化为
url type!
Working with Spring’s special beans
Spring
自身提供了一些特殊的
beans
,通过这些
be
ans
,有下列作用
:
1)
可以在
bean’s creation
和
bean factory
的生命周期里进行
postprocessing bean configuration
2)
从
external property files
来
Load configuration information
3)
从
property files
来
Load resource messages
(如
locale message
)
4)
可以对由其他
beans
或
spring container
发出的
event
进行监听(
listen
)和响应
(response)
5
)
Be aware of their identity within the Spring container
下面逐个逐个介绍这些特殊的
Beans
Postprocessing beans
Spring
提供了
2
个
opportunities
使开发者能够
cut into a bean’s lifecycle
来
review or alter its configuration
。这就叫做
postprocessing
。
postprocessing
操作是在一个
bean
进行
instantiation
and configuration event
发生时执行的。
例如
BeanPostProcessor
接口给开发者提供了
two opportunities to alter a bean after it has been created and wired
:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(
Object bean, String name) throws BeansException;
Object postProcessAfterInitialization(
Object bean, String name) throws BeansException;
}
postProcessBeforeInitialization()
方法是在
bean
初始化之前被调用
(即在
bean
定义中设置的“
init-method
”方法执行之前或实现
InitializingBean
接口
的
Bean
的
afterPropertiesSet()
方法执行之前
)
postProcessAfterInitialization()
方法是在初始化之后被调用
(即在
bean
定义中设置的“
init-method
”方法执行之后或实现
InitializingBean
接口
的
Bean
的
afterPropertiesSet()
方法执行之后
)
PostProcessor
有
2
种:
1)
Bean PostProcessor
2)
Bean Factory PostProcessor
bean postprocessor (
注意:
BeanPostProcessor
是
for all beans
的,
not for one bean)
举个例子,你希望你的
APP
的
所有
Bean
的
String property
在
bean
创建时都要在后面加上一个“!”
,那么就可以通过实现
BeanPostProcessor
接口来实现这个功能。
public class MyProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(
Object bean, String name) throws BeansException {
Field[] fields = bean.getClass().getDeclaredFields();
try {
for(int i=0; i < fields.length; i++) {
//check property
是否是
string type
if(fields[i].getType().equals(
java.lang.String.class)) {
fields[i].setAccessible(true);
String original = (String) fields[i].get(bean);
fields[i].set(bean, original + “!”);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return bean;
}
public Object postProcessBeforeInitialization(
Object bean, String name) throws BeansException {
//Do nothing before initialation
return bean;
}
}
上面的
postProcessAfterInitialization
方法会
for all beans’ property
,它会
all beans
的每一个
string property
都加一个“!”符号
创建了上面的
Bean PostProcessor
之后,我们需要把它注册到
Spring
里
,分
2
种情况:
第一种情况,如果是使用
BeanFactory
的话,那么就必须通过代码来注册
:
BeanPostProcessor processor = new MyProcessor();
factory.addBeanPostProcessor(processor);
第二种情况,也是最最常用到的情况,如果你使用
Application context
的话
,你只需要在
configure file
里定义该
Processor
即可
:
<bean
class="com.springinaction.chapter03.postprocessor.MyProcessor"/>
bean factory postprocessor
BeanFactoryPostProcessor
performs postprocessing on the entire Spring container
BeanFactoryPostProcessor
接口的定义如下:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory)
throws BeansException;
}
postProcessBeanFactory()
方法会在
all beans loaded
之后,任何
bean
(包括
BeanPostProcessor
beans
)被初始化(
instantiated
)之前,被
spring container
所调用。
例如,下面是一个实现了
BeanFactoryPostProcessor
接口的类:
public class BeanCounter implements BeanFactoryPostProcessor {
private Logger LOGGER = Logger.getLogger(BeanCounter.class);
public void postProcessBeanFactory(
ConfigurableListableBeanFactory factory)
throws BeansException {
LOGGER.debug("BEAN COUNT: " +
factory.getBeanDefinitionCount());
}
}
上面的
BeanCounter class
是用来
log
有多少个
bean
被
loaded
进
spring container
。
如果你使用的是
application context
,那么直接通过下面的定义来把
BeanCounter BeanFactoryPostProcessor
注册到
spring container
:
<bean id="beanCounter"
class="com.springinaction.chapter03.postprocessor.BeanCounter"/>
如果你是直接使用
Bean Factory
,则无法使用
BeanFactoryPostProcessor
,因为它无法象
BeanPostProcessor
那样通过
addBeanPostProcessor
方法来注册
上面的
BeanCounter
是自定义的
BeanFactoryPostProcessor
,实际上
Spring
本身就自带了几个不需要你显式注册的
BeanFactoryPostProcessors
。例如上一节提到的
CustomerEditorConfigurer
,它就实现了
BeanFactoryPostProcessor
接口。另一个有用的
BeanFactoryPostProcessor
实现类是
PropertyPlaceholderConfigurer
。它是用来从
external property file
里
load property
然后赋值给
configure xml file
里的变量
,下一节将详细讲解。
Externalizing configuration properties
有时候你喜欢把一些
property
的设置从
spring configure xml file
里分离出来
,例如通常会把
data source
的设置从
spring configure xml file
中分离出来,用一个
property file
来保存。
Spring support this function
:它通过
PropertyPlaceholderConfigurer
来告诉
spring and configure xml file
去
load
来自外部
property file
里的设置
。因此,你的
bean configure xml file
里需要定义:
<bean id="
propertyConfigurer
"
class="org.springframework.beans.factory.config.
PropertyPlaceholderConfigurer
">
<property name="
location" value="jdbc.properties"
/>
</bean>
其中的“
location
”属性是指定
property file
的
path
。
例如,上面指定的
jdbc.properties file’s content is:
database.url=jdbc:hsqldb:Training
database.driver=org.hsqldb.jdbcDriver
database.user=appUser
database.password=password
上面的
property file
定义了
data source
的配置,那么在
spring bean configure file
里如何使用它呢?见下列代码:
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="
${database.url}
" />
<property name="driverClassName" value="
${database.driver}
" />
<property name="username" value="
${database.user}
" />
<property name="password" value="
${database.password}
" />
</bean>
如果你的
bean configure xml file
要使用多个
external property file
,那么就要使用
PropertyPlaceholderConfigurer
的
locations
属性
:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config. PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>jdbc.properties</value>
<value>security.properties</value>
<value>application.properties</value>
</list>
</property>
</bean>
Resolving resource messages
Property file
除了用来作为
external configure file
,还可以用来存储
resource message
(如
Locale resource message
)
假设有
2
个
property file
myresource.properties
—Default resource message
myresource_en_US.properties
—resource message for en US
Spring
提供了一个实现了
MessageSource
接口的
ResourceBundleMessageSource
,你需要通过下列定义来把
message resource
绑定到
spring context
上:
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>trainingtext</value>
</property>
</bean>
请注意:上面的
id
属性值必须为“
messageSource
”
,这样的话你就不需要把
messageSource bean inject
到需要它的
bean
,而只需要调用
ApplicationContext
的
getMessage()
方法即可!
例如:
Locale locale = … ; //determine locale
String text = context.getMessage("computer", locale);
而如果是在
JSP
里使用
message resource
,则使用
tag:
<spring:message code="computer"/>
在你的
bean
里,是通过
ApplicationContext
来获得
message resource
的,你的
Bean
如何获取
ApplicationContext
呢??答案当然是把
ApplicationContext inject
到每一个使用它的
bean
里。问题来了:这样的话不是要在
bean config xml
里为每个使用
ApplicationContext
的
bean
都设置
context
?
Spring
提供了一种
Aware
功能,实现了某个
AwareXXX
接口的
Bean
,则可以自动获取一些
spring
相关的资源(),而不需要为
bean
通过在
bean config xml
里显式设置来获取
。
Spring
提供了一些
Aware
接口,如
BeanFactoryAware
、
ApplicationContextAware
、
ResourceLoaderAware
、
ServletContextAware
等等。比如,实现了
BeanFactoryAware
接口的
bean
,
Spring
容器会自动把
BeanFactory
对象注入该
bean
(当然,前提是该
bean
有一个
BeanFactory
变量),而
实现了
ApplicationContextAware
接口的
bean
,会自动把
ApplicationContext
对象注入该
bean
(当然,前提是该
bean
有一个
ApplicationContext
变量)
因此下面的代码是通过
Aware
来
inject applicationContext
,从而调用
getMessage
:
public class AwareBean implements ApplicationContextAware{
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext context) {
this.applicationContext = context;
}
public String sayHello () {
return applicationContext.getMessage(“hello”);
}
}
上面的
class
的
bean
定义不需要的
applicationContext
设置,会自动注入!
使用
Aware
相关接口的缺点是:你的
bean class
与
spring
发生了严重的依赖关系。
使用
Aware
接口和不使用
Aware
接口的
bean
本身没什么区别(除了是否实现
Aware
接口),但在创建了
bean
实例后,如何把
context
传递给
bean
的途径就有区别了:
*
前者是在创建
bean
实例时就会自动
(
不知不觉的
:))
把
context
传递给该
bean
*
后者则是在创建
bean
实例后,自己手动写代码
bean.setContext(context)
来传递
context
Decoupling with application events
DI
是
spring
采用的使对象之间松耦合的最主要方式,但不是唯一方式。另外一个使对象之间松耦合,但又能够有互动的方式是:
publish and listen for application event
。
在
spring
里,一个
bean
可以用来做一个
event publisher
,也可以用来做一个
event listener
,甚至可以做
both
。
下面通过一个例子来讲解
publish and listen event
机制
第一步:定义一个
application event
,它扩展了
ApplicationEvent
抽象类
public class CourseFullEvent extends ApplicationEvent {
private Course course;
public CourseFullEvent(Object source, Course course) {
super(source);
this.course = course;
}
public Course getCourse() {
return course;
}
}
第二步:定义一个
Application listener
,它实现
org.springframework.context.ApplicationListener
接口
。
public class RefreshListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
…
}
}
你需要注意的是:除了由你自定义的
Bean publish
的
event
之外,
spring
自身还自带了一些扩展了
ApplicationEvent
抽象类的
event
,包括下列几个:
* ContextClosedEvent
—Published when the application context is closed
* ContextRefreshedEvent
—Published when the application context is initialized or refreshed
* RequestHandledEvent
—Published within a web application context when a request is handled
虽然你的大多数
Beans
都不关心这些自带的
event
,但你定义的
Application listener
则不会区分哪些是这些自带的
event
,哪些是你自定义的、需要的
event
,只要有
event
发生就调用
onApplicationEvent()
方法,因此你的
onApplicationEvent()
方法的代码必须用来对
event
进行判断。
第三步:在
spring
里注册该
listener
。
很简单,只需要当作普通
bean
定义即可
<bean id="refreshListener"
class="com.springinaction.foo.RefreshListener"/>
第四步
:
用来
publish event
的
bean
。
请注意:因为
publish event
需要用到
applicationcontext
,因此
bean
里要
DI applicationContext
public class BeanA implements ApplicationContextAware{
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext context) {
this.applicationContext = context;
}
public void testPublishEvent () {
applicationContext
.publishEvent
(new CourseFullEvent(this, new Course()))
;
}
}
Scripting beans
对于
JAVA
来说,你的代码如果改动之后,需要
recompile
成
class
后才有效。有些
user
希望对于一些小的改动,直接修改
code
,不需要
recompile
即可使用。有什么解决方案
呢?方案就是
JAVA
和
Script Language
(如
Ruby
、
Groovy
、
BeansShell
)相结合
。
Spring
支持这种结合!
Spring
可以把
Ruby
、
Groovy
、
BeansShell scripts
定义成
Bean
:
![](https://p-blog.csdn.net/images/p_blog_csdn_net/totogogo/2.jpg)
下面通过一个例子来讲解如何把
script bean
注入到一个
java bean
里
假设有一个
Coconut
的
Bean
,它会被注入一个
Lime interface
的
bean
:
public class Coconut {
public Coconut() {}
public void drinkLime() {
lime.drink();
}
private Lime lime;
public void setLime(Lime lime) {
this.lime = lime;
}
}
Lime interface
:
public interface Lime {
void drink();
}
在
spring bean xml file
里,
Coconut bean
的定义是:
<bean id="coconut" class="com.springinaction.scripting.Coconut">
<property name="lime" ref="lime" />
</bean>
请注意:
上面我们只定义了
coconut bean
,它需要引用一个
id=”lime”
的
bean
,但我们并没有实现
Lime
接口的
class
,因此没有这个
bean。
下面我们打算分别用
ruby, groovy, beansShell scripts
来创建这个
Bean。
但在这之前,你首先要确保你的
spring bean configure xml file
的
<beans>
的声明为:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/
spring-lang-2.0.xsd
">
…
</beans>
Scripting the Lime in Ruby
下面的
ruby scripts
实现了
Lime
接口和
drink
方法
class Lime
def drink
puts "Called the doctor woke him up!"
end
end
Lime.new
最后一句不能省!没有它,就没有
Lime
实例对象被创建并注入到
Coconut bean
里。
重点来了:在
spring xml file
里对
Ruby lime script
的
bean
定义为
:
<lang:jruby id="lime"
script-source="classpath:com/springinaction/scripting/Lime.rb"
script-interfaces="com.springinaction.scripting.Lime" />
Scripting a Groovy Lime
相对于
Ruby
来说,
Groovy
更适合
JAVA
开发者上手。
下面的
groovy scripts
实现了
Lime
接口和
drink
方法
class Lime implements com.springinaction.scripting.Lime {
void drink() {
print "Called the doctor woke him up!"
}
}
在
spring xml file
里对
groovy lime script
的
bean
定义为:
<lang:groovy
id="lime"
script-source
="classpath:com/springinaction/scripting/Lime.groovy" />
Scripting a BeanShell Lime
Ruby and Groovy
都有自己的语法规则,但
BeanShell
就是使用
Java
的语法。这是最吸引
JAVA
开发者的地方。
下面的
bean scripts
实现了
Lime
接口的
drink
方法
void drink() {
System.out.println("Called the doctor woke him up!");
}
从上面代码你可以看到它并不是一个
class definition
,而只定义了
drink
方法。对于
BeanShell
来说,你只需要定义接口所需要的方法,而不需要定义
class
。
在
spring xml file
里对
beanshell lime script
的
bean
定义为:
<lang:bsh id="lime"
script-source
="classpath:com/springinaction/scripting/Lime.bsh"
script-interfaces
="com.springinaction.scripting.Lime" />
上面学习了如何把
script bean
注入到
java class bean
,下面讲解如何把
java class bean
注入到
script bean
(只讲解
groovy
)
我们把上面的例子反过来:
Lime
用
Java class
来实现,
Coconut
用
script
来实现
。
首先创建
Lime
接口及实现类
public interface Lime {
void drink();
}
public class LimeImpl implements Lime {
public LimeImpl() {}
public void drink() {
System.out.println("Called the doctor woke him up!");
}
}
<bean id="lime" class="com.springinaction.scripting.LimeImpl" />
Groovy Coconut Scripts:
class Coconut implements com.springinaction.scripting.ICoconut {
public void drinkLime() {
lime.drink()
}
com.springinaction.scripting.Lime lime;
}
Groovy coconut bean definition:
<lang:groovy
id="coconut"
script-source
="classpath:com/springinaction/scripting/Coconut.groovy">
<lang:property name="lime" ref="lime" />
</lang:groovy>
Refreshing scripted beans
使用
script language
的优势在于不需要
recompile
,但不是说直接覆盖原来的文件
spring
就会自动
detect
到你的
script file
发生变化,而是需要你在
bean configure xml file
里通过
refresh-check-delay
属性来设置
spring
隔多久来
check
你的
script
是否发生变化,即
refreshing.
<lang:jruby id="lime"
script-source="classpath:com/springinaction/scripting/Lime.rb"
script-interfaces="com.springinaction.scripting.Lime"
refresh-check-delay="5000"/>
上面的代码设置隔
5
秒就
check
一次该
bean
所对应的
script file
。
refresh-check-delay
属性值缺省为
-1
,即
disable refreshing
。
Writing scripted beans inline
通常情况下,你都是把
scripts bean
定义在一个
script file
里,然后在
configure xml file
里设置“
script-source
”属性。但有时
如果是想写一些很简单的
script
,你可以利用
<lang:inline-script> element
直接把
script
写在
configure xml file
里。
例:
<lang:bsh id="lime"
script-interfaces="com.springinaction.scripting.Lime">
<lang:inline-script><![CDATA[
void drink() {
System.out.println("Called the doctor woke him up!");
}
]]>
</lang:inline-script>
</lang:bsh>
注意:强烈建议你的
script
写在
<![CDATA[...]]>
里
<lang:jruby>, <lang:groovy> and <lang:bsh>
都
support <lang:inline-script>