《spring 2.0 in action》点滴 (Chapter 03)

 
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,该属性的typejava.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,见下表:
 
下面讲解如何扩展 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
 
 
下面通过一个例子来讲解如何把 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>
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值