3 авг. 2008 г.

Useful spring tips

Про то, насколько хорош spring framework можно рассказывать долго, отмечу только некоторые, которые меня приятно удивили давеча:
  • util-схема и константы
    Было:
    <bean id="..." class="...">
    <property name="isolation">
    <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
    class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
    </bean>
    С использованием util:constant
    <bean id="..." class="...">
    <property name="isolation">
    <util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
    </property>
    </bean>

    Для этого нужен spring 2.x и включить util-схему
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd 
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
  • The p-namespace
    вместо кода типа
    <bean name="classic" class="com.example.ExampleBean">
    <property name="email" value="foo@bar.com"/>
    </bean>
    можно использовать короткую запись установки свойств через т.н. p-namespace:
    <bean name="p-namespace" class="com.example.ExampleBean"
    p:email="foo@bar.com"/>
    Для этого нужен spring 2.5 и включить p-namespace
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
  • использование внешних значений свойств PropertyPlaceholderConfigurer
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
    <value>classpath:jdbc.properties</value>
    </property>
    </bean>
    
    <bean id="dataSource" destroy-method="close"
    class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>
    свойства загружаются из jdbc.properties, который находится в classpath, если же необходимо загрузить свойства из properties-файла, не из classpath, то достаточно указать его путь (относительный или абсолютный):
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
    <value>./conf/jdbc.properties</value>
    </property>
    </bean>
    Замечание: можно определить порядок разрешения свойств используя systemPropertiesModeName у PropertyPlaceholderConfigurer:
    • SYSTEM_PROPERTIES_MODE_NEVER никогда не проверять системные свойства
    • SYSTEM_PROPERTIES_MODE_FALLBACK проверять системные свойства, если не найдено в указанных locations (поведение по-умолчанию)
    • SYSTEM_PROPERTIES_MODE_OVERRIDE использовать значения системных свойств в первую очередь и только после искать в locations


    Начиная с spring 2.5 существует более краткая запись:
    <context:property-placeholder location="classpath:jdbc.properties"/>
    требуется наличие context схемы :
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    Замечание: Вообще, для загрузки spring контекста следует использовать ApplicationContext, а не BeanFactory:
    final ApplicationContext context = 
    new FileSystemXmlApplicationContext(fileName);
    Почему стоит использовать ApplicationContext, а не BeanFactory ?
    Если кратко: следует использовать ApplicationContext пока нет действительно весомой причины не использовать его.
    Если всё же возникает вопрос «а почему бы и нет ?» стоит читать более подробно и развёрнуто о использовании BeanFactory и ApplicationContext.

    Замечание 2: подстановка значений свойств с использованием PropertyPlaceholderConfigurer относится к BeanPostProcessor, и в случае использования BeanFactory подстановка значений свойств не будет произведена.
  • Spring: Remoting
    RMI: Если необходимо выставить pojo объект, например foo.bar.impl.ServiceImpl, по RMI
    <bean id="service" class="foo.bar.impl.ServiceImpl">
    <!-- установить все необходимые свойства объекта -->
    </bean>
    необходимо, чтобы объект описывал некоторый интерфейс (java интерфейс), в данном случае foo.bar.Service, и выставить его через RmiServiceExporter:
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
    <!-- имя сервиса в реестре RMI, может отличаться от имени bean'а -->
    <property name="serviceName" value="Service"/>
    <property name="service" ref="service"/>
    <property name="serviceInterface" value="foo.bar.Service"/>
    <!-- порт по-умолчанию: 1099 -->
    <property name="registryPort" value="1199"/>
    </bean>

    если необходимо выставить несколько интерфейсов, то лучше определить RMI-реестр отдельно:
    <bean id="rmiRegistry" 
    class="org.springframework.remoting.rmi.RmiRegistryFactoryBean"
    p:port="${rmi.port}" />
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
    <property name="serviceName" value="Service"/>
    <property name="service" ref="service"/>
    <property name="serviceInterface" value="foo.bar.Service"/>
    <property name="registry" ref="rmiRegistry"/>
    </bean>
    клиентская часть
    <bean id="service"
    class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
    p:serviceUrl="rmi://HOST:1199/Service"
    p:serviceInterface="foo.bar.Service"
    p:refreshStubOnConnectFailure="true"
    p:lookupStubOnStartup="false" />
    вот и всё, что нужно для обращения к объекту выставленному по RMI, все вызовы прозрачно происходят через spring proxy.

    Spring's HTTP invoker
    server side:
    <util:map id="serviceEndpoints">
    <entry key="/Service">
    <bean class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"
    p:serviceInterface="foo.bar.service"
    p:service-ref="service" />
    </entry>
    </util:map>
    
    <bean id="httpServer" 
    class="org.springframework.remoting.support.SimpleHttpServerFactoryBean"
    p:port="${server.port}" 
    p:contexts-ref="serviceEndpoints"/>
    client side:
    <bean id="service"
    class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"
    p:serviceUrl="${http.server.url}/Service"
    p:serviceInterface="foo.bar.Service">
    <qualifier value="remoted" />
    </bean>
  • Spring & JUnit
    Используя JUnit 4.4 и Sprint 2.x можно перенести загрузку контекста spring в annotations: хорошая статья о том как тестировать Spring-приложение с помощью JUnit

    Замечание: Требуется JUnit 4.4, spring-test.jar, тесты должны быть в стиле JUnit4 (с использованием annotations)

    дополнено:
    По-умолчанию @Autowire работает по autowire-by-type (связывание по типу), всё хорошо, когда с spring контексте только один bean данного типа, когда появляется ещё один bean такого же типа - появляется неоднозначность выбора необходимого bean'а - для этого используется @Qualifier с указанием имени bean'а
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"classpath:test-spring-config.xml"})
    public class SomeTest {
    @Autowired
    private Foo foo;
    
    @Autowired
    @Qualifier(value="bar")
    private Bar bar;
    
    @Test
    public void testingSmth(){
    // ...
    }
    }


    когда же появляются типизированные коллекции или типизированные объекты Autowire буквально сносит мосх в виде неразрешённых зависимостей...

    Поэтому, рекомендуется использовать @Resource вместо @Autowired.
    @Resource входит в JSR 250 и входит в состав java 6, для java 5 придётся подключать jsr250-api-1.0.jar.

    Замечание: В @Resource также можно указывать и имя ресурса, тем самым нет необходимости использовать @Qualifier
    @Resource(name="smthBeanName")


    Источник: @Autowire does not work as expected for Collections based Repository implementation
  • Вызов статического метода
    <bean id="ui"
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"
    p:staticMethod="javax.swing.UIManager.setLookAndFeel">
    <property name="arguments">
    <list>
    <value>com.jgoodies.plaf.plastic.PlasticXPLookAndFeel</value>
    </list>
    </property>
    </bean>

4 комментария:

Andrey ``Bass'' Shcheglov комментирует...

Очень полезно, спасибо.

К слову о <context:property-placeholder/>, стоит упомянуть необходимость импорта соотвествующей схемы:

xmlns:context = "http://www.springframework.org/schema/context"

Andrey ``Bass'' Shcheglov комментирует...

Володя, замечание к твоему замечанию =)

"подстановка значений свойств с использованием PropertyPlaceholderConfigurer относится к BeanPostProcessor, и в случае использования BeanFactory подстановка значений свойств не будет произведена."

Стоит добавить, что подстановка значений не будет произведена по умолчанию. При этом никто не мешает сделать пост-конфигурацию фабрики приямо из Java, хотя это и менее удобно:

final Resource resource = new ClassPathResource("jdbc.properties");
final PropertyResourceConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setLocation(resource);
configurer.postProcessBeanFactory(factory);

Vladimir Dolzhenko комментирует...

@Andrew ``Bass'' Shcheglov:

спасибо, уместные замечания.

Анонимный комментирует...

@Resource вместо @Autowired не получится использовать под Jboss ~5.1 - он считает их JEB-аннотациями (требует указать mapping-name, а если дать - не находит его в JNDI)