Showing posts with label Spring. Show all posts
Showing posts with label Spring. Show all posts

Tuesday, 1 December 2020

SpringBoot repackaging and maven.failsafe.plugin

There is an interesting issue with the interaction between springboot and the maven failsafe plugin.  Normally both of these plugins have no problems but often in springboot we want to repackage into an executable springboot jar.  The normal way to do this is,

        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring.boot.version}</version>
            <executions>
                <execution>
                    <goals>
                       <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>


However, this causes problems with the failsafe plugin not finding some of the classes it needs for testing.  There are two possible solutions to this issue, either is fine.

Change the spring repackage to include a classifier


      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <classifier>exec</classifier>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

Manually include the classes in the failsafe plugin


      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>2.22.2</version>
          <configuration>
              <additionalClasspathElements>
                  <additionalClasspathElement>${basedir}/target/classes</additionalClasspathElement>
              </additionalClasspathElements>
          </configuration>
          <executions>
              <execution>
                  <id>integration</id>
                  <phase>integration-test</phase>
                  <goals>
                      <goal>integration-test</goal>
                  </goals>
              </execution>
          </executions>
      </plugin>

Cucumber set up for SpringBoot and JUnit 4 or JUnit 5

Cucumber has worked well with pure JUnit 4 projects for quite some time.  The transition to JUnit 5 does change things slightly but the change isn't particularly bad once things are set up. Here are the versions that I've been using for these tests

SpringBoot: 2.4.0
Cucumber: 6.9.0
maven.surefire.plugin: 2.22.2
maven.failsafe.plugin: 2.22.2

JUnit4

pom.xml

The Pom must include the dependencies as follows (versions have been dropped off here)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

CucumberIT.java

The CucumberIT class is the bootstrap part of the cucumber testing.  We see here the JUnit4 @RunWith.  The feature files are set here to be in the src/tests/resources/features directory.

import io.cucumber.junit.CucumberOptions;
import io.cucumber.junit.Cucumber;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    tags = "",
    plugin = {"pretty", "json:target/cucumber.json"})
public class CucumberIT {}

CucumberSpringContext.java

This is where the spring wiring gets done to make sure that SpringBoot starts.  I've also included @AutoConfigureMockMvc for making rest calls to the spring boot service from the Step definitions but you don't have to include that.  If you have additional spring configuration you can add @Beans into this class but you need to include the @ContextConfiguration spring annotation too.  In previous versions of cucumber before the @CucumberContextConfiguration annotation was available you needed to have a blank cucumber @Before in this class to make sure it was found.

In fact once this is set up it stays the same for JUnit 4 and 5 because it is primarily a spring configuration not a Cucumber one!

import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;

@CucumberContextConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class CucumberContextTestConfiguration {}

Feature file and properties

As previously mentioned the feature files now go into the src/test/resources/features directory which is referenced in the CucumberIT.  No other properties are necessary for JUnit4


Running with JUnit5

The transition to JUnit5 can happen in two stages.  Firstly just running with JUnit5 can be backwards compatible with all the current JUnit4 annotations and setup with minimal changes.  Here are the changes that need to be made.

Also check out details about the surefire and failsafe plugins which have had problems with JUnit5 before versions 2.22.0 because they can't find JUnit5 tests.

pom.xml

The only differences here are that the junit:junit:4 dependency comes out and the JUnit5 dependencies come in.  Make sure you include the junit-vintage-engine which is what provides the backwards compatibility for JUnit4 annotations, imports etc


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>

CucumberIT.java

This stays the same

Feature file and properties

The feature file stays as was because it is still directly referenced by the CucumberIT class.  However, there is now a warning from Cucumber about the cucumber report.  This can be removed by including a new file cucumber.properties in src/test/resources

cucumber.properties

cucumber.publish.enabled=false
cucumber.publish.quiet=true


Full JUnit5

Now for fully transitioning to run cucumber with JUnit 5.  The test discovery mechanism has changed between JUnit4 and JUnit5 which is the reason for much of the following change. There are no longer any @CucumberOptions so these have to be specified in property files instead.

pom.xml

The junit-vantage-engine dependency has gone and the cucumber-java dependency is replaced with a JUnit5 specific one.  Again the specific versions have been ignored here



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit-platform-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>

CucumberIT.java

The JUnit4 annotations are no longer available so we use the new JUnit5 annotation - note the different import path.  

import io.cucumber.junit.platform.engine.Cucumber;

@Cucumber
public class CucumberIT {}

Feature file and properties

By default the feature files need to be in the same package as the CucumberIT class (the class annotated with @Cucumber) so they are moved.  The cucumber.properties file that we introduced earlier now has to be renamed to junit-platform.properties (but it stays in src/test/resources).  Also, because the @CucumberOptions no longer exists we can include the plugin options here too

junit-platform.properties

cucumber.plugin=pretty,json:target/cucumber.json
cucumber.publish.enabled=false
cucumber.publish.quiet=true

Friday, 1 March 2019

AWS, Spring, Localstack

Using the AWS Java client is very straightforward.  Unit testing is also quite simple by just mocking the AWS classes.  However, integration testing is more complicated.  Here is an example of using LocalStack, TestContainers and Spring to wire AWS objects to point to the LocalStack instance.

Localstack: An implementation of AWS which runs locally with natively or in a docker container
TestContainers: A java library that lets a docker container be run locally for testing

Here TestContainers is used to start the localstack docker image so that the AWS calls can be made against it.


Maven dependencies

Using the v2 dependencies for the AWS library requires bringing in the AWS bom (bill of materials) so that any dependency can just be declared and the bom takes care of getting the correct versions of each dependency.  In the example below the S3 and SQS dependencies are configured.

Dependency management & dependencies

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>bom</artifactId>
        <version>2.4.11</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>s3</artifactId>
    </dependency>
    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>sqs</artifactId>
    </dependency>

Test dependencies:

    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>testcontainers</artifactId>
      <version>1.10.6</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>localstack</artifactId>
      <version>1.10.6</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>cloud.localstack</groupId>
      <artifactId>localstack-utils</artifactId>
      <version>0.1.18</version>
      <scope>test</scope>
    </dependency>


AWS Configuration

The normal spring configuration for aws clients is very straight forward.  Here is an example of an S3Client and an SqsClient using the AWS v2 objects.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.sqs.SqsClient;

@Configuration
public class AwsConfiguration {

  @Bean
  public S3Client s3Client(){
    return S3Client.builder().region(Region.EU_WEST_1).build();
  }

  @Bean
  public SqsClient sqsClient(){
    return SqsClient.builder().region(Region.EU_WEST_1).build();
  }
}

These objects will use the default AwsCredentialProvider but this can be overridden here.


TestConfiguration

To create the test configuration we need to start Localstack using TestContainers.  This test configuration starts the Localstack and then uses it to configure the S3Client and SqsClient to point to localstack

import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;

import java.net.URI;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.wait.strategy.DockerHealthcheckWaitStrategy;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider;
import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.CreateQueueRequest;

@TestConfiguration
public class AwsConfigurationTest {

  @Bean
  public LocalStackContainer localStackContainer() {
    LocalStackContainer localStackContainer = new LocalStackContainer().withServices(SQS, S3);
    localStackContainer.start();
    return localStackContainer;
  }

  @Bean
  public S3Client s3Client() {

    final S3Client client = S3Client.builder()
        .endpointOverride(URI.create(localStackContainer().getEndpointConfiguration(S3).getServiceEndpoint()))
        .build();

    client.createBucket(CreateBucketRequest.builder().bucket("test_bucket").build());

    return client;
  }

  @Bean
  public SqsClient sqsClient() {

    final SqsClient sqs = SqsClient.builder()
        .endpointOverride(URI.create(localStackContainer().getEndpointConfiguration(SQS).getServiceEndpoint()))
        .build();

    sqs.createQueue(CreateQueueRequest.builder().queueName("test_queue").build());

    return sqs;
  }
}




Tuesday, 6 June 2017

Spring Sleuth

Sleuth is used to trace calls in a microservices environment. It creates a trace-id over the whole interactions and a span-id between each call.  For example, there is a call from a client to a microservice to load information for a customer id.  First is the call to the customer service, then the recent orders and accounts services.  All these calls would share the same trace-id but between each one is a different span-id.  To turn on sleuth just add the following dependencies into the pom.  You'll also need to add an application name.

spring.application.name=My Server

Maven


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    <version>1.2.0.RELEASE</version>
</dependency>

The trace and span ids will now be created and can be seen in the headers.

To log this and make it useful though is one thing but there is a graphical tool which makes this very easy.

Zipkin

Zipkin can be configured so that all Sleuth output is sent there and it allows a view of the interactions so that the times and services called can be seen.  To configure and use zipkin just add another dependency,

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    <version>1.2.0.RELEASE</version>
</dependency>

By default everything is logged to localhost:9411 but this can be changed by adding a property

spring.zipkin.baseurl=http://zipkin:9411/

Docker

If you are running with docker you'll need to add a zipkin image into the compose file,

      
  zipkin:
    image: openzipkin/zipkin
    networks:
      - my-network
    hostname: zipkin
    ports:
      - "9411:9411"




Wednesday, 13 August 2014

@Transactional

When connecting to a database a transaction is often required.  There are a number of ways of doing this but the most common is to use Spring transactions.

pom.xml

This dependency is needed to allow transactions

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<scope>compile</scope>
</dependency>


Spring Context

In the spring configuration an entity manager is necessary (see Entity Manager blog entry)

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

<!-- Define a transaction manager so that the @TransactionConfiguration and @Transactional can be used -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

Proxy Class

The above combination will allow spring to create proxy classes which create a transaction before passing into the @Transactional method.  This means that because it is a proxy @Transactional doesn't work for any 'internal' call within the your class.  Eg even if a method is public if it is called from within the same class it won't pass through the proxy and therefore won't have a transaction.
If this type of behaviour is required then the transactions can be wired using AOP and the transaction code is created at compile time rather than a runtime proxy.

Exception Handling

Normal exceptions can be caught using the normal try - catch but if the database isn't there at all then there is an exception generated in the proxy before the method code is called.  The exception is a 
    org.springframework.transaction.CannotCreateTransactionException

To catch the exception within the same method as the actual database errors the transaction can be created a different way.

Programmatic Transactions

A programmatic transaction can be created using the TransactionTemplate in which case the TransactionException (CannotCreateTransactionException) can be caught in the same place.

    try
    {
        transactionTemplate.execute(new TransactionCallbackWithoutResult()
        {
            @Override
            protected void doInTransactionWithoutResult(final TransactionStatus status)
            {
                 // Do something to the database here
            }
        });
    }
    catch (PersistenceException | TransactionException e)
    {
        LOG.error("Database Error", e);
    }

There is also a TransactionCallback() which returns the object from the database whereas the one above doesn't do that.

The transactionTemplate can be created in spring configuration,

    <property name="transactionTemplate">
        <bean class="org.springframework.transaction.support.TransactionTemplate">
            <constructor-arg ref="transactionManager" />
</bean>
    </property>






Friday, 20 June 2014

Spring Web App Template

I don't create new web apps that often and therefore can easily forget the necessary bits!  This is my memory jogger for how do to these.

Folders
Notes: In eclipse create a new java project with the correct maven folder structure.  This is the sample folder structure

    project_home/src
    project_home/src/main
    project_home/src/main/java
    project_home/src/main/resources
    project_home/src/main/webapp
    project_home/src/main/webapp/WEB-INF
    project_home/src/main/webapp/WEB-INF/jsp
    project_home/src/test
    project_home/src/test/java
    project_home/src/test/resources

pom.xml
Location: project_home/pom.xml
Notes: The basic maven pom which has the correct values for the spring classes although the spring versions can be updated. 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.me.app</groupId>
    <artifactId>me-web</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>Sample Web App</name>
    <description />
    <properties>
        <spring.version>3.1.2.RELEASE</spring.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

Eclipse
Notes: After putting this pom in place convert to a maven project.  In eclipse right click on the project and go to Configure - Convert to Maven Project.  This should create a folder called Maven Dependencies which contains all the libraries defined by the pom.
Right click on the project and go to properties.  
 - Select 'Project Facets' and make sure it is a faceted form with Dynamic Web Module, Java and JavaScript are all checked.
 - Click on Deployment Assembly and make sure that Source /src/main/webapp is mapped to Deploy Path / and also that Source: Maven Dependencies are mapped to Deploy Path /WEB-INF/lib.
 - Click on Web Project Settings and check the context-root value
 - Click on Java Build Path and add main/java, main/resources, test/java and test/resources as source folders.

web.xml
Location: project_home/src/main/webapp/WEB-INF/web.xml
Notes: This is the file that is read by the application server to determine details about the servlet it needs to run.  It contains the main servlet class and mappings of which requests get sent to this servlet.  In this case the servlet is the spring MessageDispatchServlet as this is a spring app.
   
 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   
          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
    version="2.5">
  <display-name>Web App</display-name>
  
  <servlet>  
        <servlet-name>webapp</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <load-on-startup>1</load-on-startup>  
    </servlet>  
  
    <servlet-mapping>  
        <servlet-name>webapp</servlet-name>  
        <url-pattern>/app/*</url-pattern>  
    </servlet-mapping>  
  
</web-app>


servlet.xml
Location: project_home/src/mainwebapp/WEB-INF/servlet.xml
Notes: The servlet xml is the file that is used by the spring MessageDispatcherServlet and is the root of the spring configuration for the web application.

<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="  
        http://www.springframework.org/schema/beans       
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
        http://www.springframework.org/schema/context   
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">  
   
    <context:component-scan base-package="com.me.app.web" />  
   
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
        <property name="prefix">  
            <value>/WEB-INF/jsp/</value>  
        </property>  
        <property name="suffix">  
            <value>.jsp</value>  
        </property>  
    </bean>  
   
</beans> 


Controller
Location: project_home/src/main/java/com/me/app/web/controller/Greeting.java
Notes: This is a sample controller that maps to a jsp.

package com.me.app.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
 * Sample controller.
 */
@Controller
@RequestMapping("/greeting")
public class Greeting
{
    /**
     * Method to say hello.
     */
    @RequestMapping("/hello")
    public ModelAndView hello()
    {
        final ModelAndView mav = new ModelAndView("hello");
     return mav;
    }
}

jsp
Location: project_home/src/webapp/WEB-INF/jsp/hello.jsp
Notes: This is a very simple jsp file that is used to just smoke test that the application is running.

<html>  
  <head>
    <title>Sample Web App</title>
  </head>  
  <body>
    <center>  
      <h2>Sample Web App</h2>  
      <h4>Hello</h4>  
    </center>  
  </body>  
</html>  

Wednesday, 30 April 2014

Spring Context Unit Testing

It is always a good idea to unit test the spring contexts that define an application.  This can be as simple as a unit test

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:com/me/my_context.xml" })
    public class SpringContextTest implements ApplicationContextAware
    {
        /**
         * The spring context.
         */
        private ApplicationContext applicationContext;

        /**
         * Test the spring wiring.
         */
        @Test
        public void testSpringWiring()
        {
            final Object obj = applicationContext.getBean("myBean");
            assertTrue(obj instanceof MyBean);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void setApplicationContext(final ApplicationContext applicationContext)
        {
            this.applicationContext = applicationContext;
        }
    }


However, where there is interaction with a JNDI resource as part of the spring start up this needs to be modified to make the JNDI resource available.  Adding this @Before method will set up a JNDI resource for a MySql database.

    /**
     * Set up the jndi data source so that the wiring tests still work.
     */
    @BeforeClass
    public static void setUpClass()
    {
        // Setup the jndi context and the datasource
        try
        {
            // Create a database connection to satisfy the data loading requirements.
            final DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://server:port");
            dataSource.setUsername("username");
            dataSource.setPassword("password");

            final SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
            builder.bind("jdbc/MY_JNDI_NAME", dataSource);
            builder.activate();
        }
        catch (final NamingException ex)
        {
            LOG.error("Error creating JNDI resource", ex);
        }
    }



Monday, 20 January 2014

Dynamic Spring Message Driven Beans

Creating MDBs from spring is really easy particularly if all the information is known up front.  As usual it is just some configuration in the spring context and is shown here.

For dynamic beans things can still be put into the spring configuration but need the destination setting at runtime.  This can be done using some spring context as prototype beans so that a new instance of them is created each time and a small bit of wiring in the code.

Spring Context


<!-- MQ / JMS Connection factory -->
<bean id="jmsConnectionFactory" class="com.ibm.mq.jms.MQTopicConnectionFactory">
<property name="hostName" value="${jms.mq.ip}" />
<property name="port" value="${jms.mq.port}" />
<property name="queueManager" value="${jms.mq.queueManager}" />
<property name="transportType" value="1" />
</bean>


<!-- Spring Connection Factory -->
<bean id="springConnectionFactory" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
<property name="targetConnectionFactory" ref="jmsConnectionFactory" />
<property name="username" value="${jms.mq.username}" />
<property name="password" value="${jms.mq.password}" />
</bean>

<!-- This bean is a prototype bean which is loaded dynamically-->
<!-- it is loaded using a getBean("jmsDestination", requiredTopic") and as such the HOLDING_VALUE is -->
<!-- replaced with whatever is required by the class in question                                    -->
<bean id="jmsDestination" class="com.ibm.mq.jms.MQTopic" scope="prototype">
<constructor-arg value="HOLDING_VALUE" />
</bean>

<!-- Generic MDB Container -->
<bean id="springMdbContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" scope="prototype">
<property name="connectionFactory" ref="springConnectionFactory" />
<!-- The destination is dynamically set in the factory method but a default is provided here-->
<property name="destination" ref="jmsDestination" />
<!-- The normal MessageListener which is put here is added dynamically in the factory -->
</bean>

Java

In the java code a new destination can be obtained using the bean above and the code,

    /**
     * Create a Destination object.
     *
     * @param topic The topic to construct the destination with
     * @return the Destination created.
     */
    public Destination createDestination(final String topic)
    {
        // Create the destination with the topic string.
        return (Destination) applicationContext.getBean("jmsDestination", topic);
    }

This creates a new Destination object based on the spring bean.  This allows the MQ provided to be changed in the spring configuration without having to change code.

To create a new MDB create a destination as above and then use the code,
   
    /**
     * Create a MDB wired by spring.
     *
     * @param destination The destination topic to listen to.
     * @param messageListener The message listener (MDB) to wire
     */
    private void wireMdb(final Destination destination, final MessageListener messageListener)
    {
        // Use spring to wire up the MDB with this destination and a ConnectionFactory 
        // which is already provided in the spring context. 
        // The MDB and Destination are set here and then the container is manually started.
        final DefaultMessageListenerContainer container = 
                (DefaultMessageListenerContainer) applicationContext.getBean("springMdbContainer");
        container.setDestination(destination);
        container.setMessageListener(messageListener);
        container.start();
    }

It is also possible to change the destination that a container is listening to once it has started.  Using the setDestination(...) method does this according to the spring documentation.