[Article] WSO2 Microservices Framework for Java the Spring Way - Part 1
- Sagara Gunathunga
- Head of IAM DevRel - WSO2
Table of contents
- Introduction
- Features supported by WSO2 MSF4J Spring Module
- Developing MSF4J services using Spring
- Developing and configuring MSF4J interceptors using Spring
- Developing MSF4J ExceptionMappers using Spring
- Configuring MSF4J transports
- Injecting environment-specific properties
- Conclusion
Introduction
A key new feature that came with the WSO2 Microservices Framework for Java 2.0 (MSF4J) was its support for the Spring framework. Before we get into the details, let’s first try to understand the motivation and design goal that resulted in the addition of this feature.
- Spring is a very popular framework among Java developers. Those who are familiar with the Spring framework tend to follow the same practice wherever possible and generally seek Spring integration support from other Java frameworks as well. The ability to use the Spring framework to develop MSF4J services and MSF4J extensions was one of the main objectives of the MSF4J 2.0.0 release.
- The Spring framework itself and its supportive projects provide significant integration support to third-party frameworks. MSF4J Spring module enables service developers to use such Spring integration projects to integrate MSF4J services with third-party frameworks. For example, Spring ORM (object-relational mapping) modules, such as Spring-Hibernate, can be used to integrate the Hibernate ORM framework with MSF4J services.
- In the Java world, the Spring framework is a very popular option to configure and assemble the internals of an application or a framework. A good example is Apache CXF that uses the Spring framework to define web services and also to configure internals of the framework. Similarly, it’s possible to use the MSF4J Spring module to configure the internals of the MSF4J framework.
Features supported by WSO2 MSF4J Spring Module
The MSF4J Spring module has the ability to
- Configure and register MSF4J services in the form of Spring beans
- Use Spring framework features, such as Dependency Injection (DI), JDBC support, JPA support, and AOP within MSF4J services
- Configure and register MSF4J interceptors in the form of Spring beans
- Register MSF4J ExceptionMapper extensions using the Spring framework
- Configure MSF4J framework internals using the Spring framework, such as HTTP port, SSL configurations, in-built Interceptor configurations, etc.
- Provide environment specific values to MSF4J services based on Spring framework
Developing MSF4J services using Spring
To develop a basic MSF4J service using Spring, all you need to do is register a web resource class that’s annotated with MSF4J annotations as a Spring bean. Once the class or instance of the class is registered with the Spring framework, it will be exposed as an MSF4J service. To achieve this you can use one of the following options;
- Add @Component annotation to the resources class so that the Spring framework can detect the web resource class as an MSF4J service by executing its component auto-scan mechanism. Example 1 below, illustrates such a resource class annotated with the @Component annotation.
@Component @Path("/hello") public class Hello { @Autowired private HelloService helloService; @GET @Path("/{name}") public String hello(@PathParam("name") String name) { return helloService.hello(name); } }
Example 1
In the above code segment, the existence of the @Component annotation in the Hello class allows Spring to identify the Hello class as a Spring-managed component, then the existence of the @Path annotation in the Hello class allows the MSF4J Spring module to further identify this Hello class as a possible MSF4J service. This auto-detection process facilitates deploying the above Hello resource class as a Spring-managed MSF4J service.
Additionally, you will notice an instance of ‘HelloService” class is provided to the Hello resource by adding Spring @Autowired annotation. During startup, the Spring framework is responsible for injecting these dependencies into service instances before being deployed into the MSF4J runtime.
- Instead of annotating resource classes using the @Component annotation, it is possible to use the Spring Application Context to register MSF4J services directly. MSF4J Spring module support both Spring Java Configurations and traditional XML-based configurations.
First, let’s look at the following Example 2 which uses Spring Java Configuration to register MSF4J services.
@Configuration public class SpringConfiguration { @Bean public Hello hello() { return new Hello(); } @Bean public HelloService helloService() { return new HelloService(); } }
@Path("/hello") public class Hello { @Autowired private HelloService helloService; @GET @Path("/{name}") public String hello(@PathParam("name") String name) { return helloService.hello(name); } }
Example 2
In the above example, we have introduced a new class called SpringConfiguration to define Spring configuration explicitly. Instances of both Hello and HelloServices classes are defined as Spring beans in the SpringConfiguration by adding the @Bean annotation. One flexibility of this approach is that developers have the freedom to instantiate and configure Spring beans as they want instead of letting the Spring framework to decide how to instantiate and configure bean instances. Additionally, in contrast to Example 1, here Hello resource class is annotated using the @Path annotation only.
Traditionally the Spring framework has supported XML as a bean configuration option. However, this option is not quite popular these days but it’s still possible to use XML to register MSF4J resource class with the Spring ApplicationContex. Example 3 below illustrates this approach.
<beans xmlns="https://www.springframework.org/schema/beans" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hello" class="org.wso2.msf4j.samples.spring.sample3.Hello"> <property name="helloService" ref="helloService"/> </bean> <bean id="helloService" class="org.wso2.msf4j.samples.spring.sample3.HelloService" /> </beans>
@Configuration @ImportResource("classpath:application-context.xml") public class SpringConfiguration { }
Example 3
In the above XML code segment we have defined helloService and Hello classes as Spring beans. Additionally, we instructed the Spring framework to inject an instance of HelloService class as a property of Hello resource during the startup.
In the above example, in addition to the XML configuration file, we have to provide SpringConfiguration class as well; this configuration class just indicates that the Spring framework will import specified XML configuration files from the Classpath; in this case application-context.xml file. This was achieved by adding the @ImportResource annotation into the SpringConfiguration class.
As stated earlier, MSF4J Spring support enables you to use any core feature of the Spring framework. Example 4 demonstrates how you could use Spring lifecycle methods using the @PostConstruct and @PreDestroy annotations.
@Component @Path("/hello") public class Hello { @Autowired private HelloService helloService; @GET @Path("/{name}") public String hello(@PathParam("name") String name) { return helloService.hello(name); } @PostConstruct public void init() { System.out.println("Method init() invoked..."); } @PreDestroy public void destroy() { System.out.println("Method destroy() invoked..."); } }
Example 4
As you can see in the above code segment we have used @PostConstruct annotation on init() method to instruct the Spring framework to invoke init() method after the bean has been constructed using default constructor and just before it’s instance is returned to requesting object.
Similarly, we have used @PreDestroy annotation on destroy() method to instruct the Spring framework to invoke destroy() method just before the bean is destroyed inside the bean container.
In some practical cases, it is required that the Spring beans get additional information about the Spring container and its related infrastructure, e.g. a Spring bean may need to access the current application context so that it can perform some operations like loading specific beans from the container in a programmatic way. To support such requirements, the Spring framework provides a lot of Aware interfaces. Each interface requires you to implement a method to inject the dependency in bean and ApplicationContextAware, BeanFactoryAware and BeanNameAware are some you can use with the MSF4J services.
Developing and configuring MSF4J interceptors using Spring
Interceptor is an MSF4J concept of intercepting incoming messages to perform certain actions before and after a request arrives to resource methods. Checking security headers of incoming HTTP messages, logging incoming and outgoing messages are among possible interceptor use cases. It should be noted that any MSF4J interceptor should implement the org.wso2.msf4j.Interceptor interface.
Using MSF4J Spring you can either configure in-built interceptors or even register and configure your own custom interceptors. Let’s see how you can configure an inbuilt interceptor using the Spring Java configuration approach.
@Configuration public class SpringConfiguration { @Bean public MetricsInterceptor metricsInterceptor() { MetricsInterceptor metricsInterceptor = new MetricsInterceptor(); return metricsInterceptor; } }
Example 5
In the above example, we have defined MetricsInterceptor as a Spring bean that collects and publishes HTTP metrics. This allows the Spring framework to detect this bean as interceptor implementation and add it as an interceptor to the MSF4J runtime with provided configuration.
Now let’s look at how to write a custom interceptor and register it with Spring ApplicationContext. As discussed in the service development section, it’s possible to use one of the following three approaches to register custom MSF4J interceptors with the Spring framework:
- Annotate custom MSF4J interceptor class using @Component annotation
- Explicitly define custom MSF4J interceptor with Spring ApplicationContext using Spring Java configuration
- Explicitly define custom MSF4J interceptor with Spring ApplicationContext using Spring XML configuration
Example 6 uses the Spring @Component annotation to register HeaderLogInterceptor class with Spring ApplicationContext. HeaderLogInterceptor prints all the HTTP headers from incoming messages to the server console.
@Component public class HeaderLogInterceptor implements Interceptor { @Override public boolean preCall(Request request, Response response, ServiceMethodInfo serviceMethodInfo) throws Exception { Iterator> itr = request.getHeaders().entrySet().iterator(); while (itr.hasNext()) { Map.Entry entry = itr.next(); System.out.println("Header Name: " + entry.getKey() + " value : " + entry.getValue()); } return true; } @Override public void postCall(Request request, int i, ServiceMethodInfo serviceMethodInfo) throws Exception { } }
Example 6
Here @Component annotation enables the Spring framework to identify HeaderLogInterceptor as a Spring-managed component first and then as an MSF4J interceptor implementation in order to deploy it as an interceptor to MSF4J runtime.
Example 7 is exactly the same example, but instead of @Component annotation, here we use Spring Java configuration to register HeaderLogInterceptor class with Spring ApplicationContext.
public class HeaderLogInterceptor implements Interceptor { @Override public boolean preCall(Request request, Response response, ServiceMethodInfo serviceMethodInfo) throws Exception { Iterator> itr = request.getHeaders().entrySet().iterator(); while (itr.hasNext()) { Map.Entry entry = itr.next(); System.out.println("Header Name: " + entry.getKey() + " value : " + entry.getValue()); } return true; } @Override public void postCall(Request request, int i, ServiceMethodInfo serviceMethodInfo) throws Exception { } }
@Configuration public class SampleConfiguration { @Bean public HeaderLogInterceptor headerLogInterceptor() { HeaderLogInterceptor headerLogInterceptor = new HeaderLogInterceptor(); return headerLogInterceptor; } }
Example 7
Developing MSF4J ExceptionMappers using Spring
The ExceptionMapper concept enables a centralized mechanism to handle Java Exceptions without scattering Exception handling code all over your codebase. If this concept is not familiar to you, this is a good resource to follow.
You can use one of the following three options to register MSF4J ExceptionMappers with the Spring framework:
- Annotate ExceptionMappers implementation classes using @Component annotation
- Explicitly define MSF4J ExceptionMappers implementations classes with Spring ApplicationContext by using Spring Java configuration
- Explicitly define ExceptionMappers implementations with Spring ApplicationContext by using Spring XML configuration
@Component @Path("/hello") public class Hello { @Autowired private HelloService helloService; @GET @Path("/{name}") public String hello(@PathParam("name") String name) throws InvalidNameException { return helloService.hello(name); } }
@Component public class InvalidNameExceptionMapper implements ExceptionMapper{ @Override public Response toResponse(InvalidNameException exception) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception.getMessage()).build(); } }
Example 8
In the above example, @Component annotation allows the Spring framework to detect and register InvalidNameExceptionMapper as an ExceptionMapper for MSF4J runtime.
Configuring MSF4J transports
As mentioned in the introduction section, the ability to use the Spring framework to configure the internals of MSF4J is an important feature. Let’s try to understand this feature with a few transport-related examples.
In the following example, we try to change the default HTTP port of the Netty transport connector through a Spring configuration. Either Spring Java configuration or XML configuration approaches can be used for this purpose, but we’re using Java configuration here.
@Configuration public class SpringConfiguration { @Bean public HTTPTransportConfig http(){ return new HTTPTransportConfig(6060); } }
Example 9.1
Adding HTTPTransportConfig bean with custom configuration to Spring ApplicationContext will prevent automatic injection of default HTTPTransportConfig bean. In the above example, we configured the Netty transport to start on port number 6060 instead of default 8080 port number. In our next example we use both HTTPTransportConfig and HTTPSTransportConfig beans.
In case if you want to start multiple HTTP transports on different ports, as shown in the following example, you can add additional HTTPTransportConfig ( or HTTPSTransportConfig) beans into the HTTPTransportConfig class.
@Configuration public class SpringConfiguration { @Bean public HTTPTransportConfig http(){ return new HTTPTransportConfig(6060); } @Bean public HTTPTransportConfig httpInternal() { return new HTTPTransportConfig(7070); } }
Example 9.2
Configuring SSL for HTTPS transport is a very important requirement for any real-world service. Example 10 illustrates how you can configure SSL using Spring Java configuration.
@Configuration public class TransportConfiguration { @Bean public HTTPTransportConfig http() { return new HTTPTransportConfig().enabled(false); } @Bean public HTTPSTransportConfig https() { return new HTTPSTransportConfig().port(7070).keyStore("wso2carbon.jks") .keyStorePass("wso2carbon").certPass("wso2carbon").enabled(); } }
Example 10
In the above example, we have provided custom configurations for both HTTPTransportConfig and HTTPSTransportConfig beans; this will override their default configurations. First, we have disabled HTTP transport by providing a custom configuration. Then, we have added HTTPSTransportConfig bean to provide SSL properties. In this case, it’s required to provide the following 3 properties:
- Keystore file location
- Keystore password
- Certificate password
NOTE - In the above example we have hard coded keystore file location and passwords, but in real-world cases, you should not hard code these values and instead pass these values during the startup as environment specific values. Since we haven’t discussed the environment property injection yet, we just used hard-coded values for this example.
Once you run this sample, the HTTPS transport connector will be started on port number 7070 and no HTTP connector will be started.
Here it is important to note that the bean-name “http” is reserved for default HTTPTransportConfig bean; this means if you define your own HTTPTransportConfig bean, the Spring framework won’t add another HTTPTransportConfig bean with default configuration. Similarly, the bean-name “https” is reserved for default HTTPSTransportConfig bean.
Both HTTPTransportConfig and HTTPSTransportConfig support a widely used subset of transport properties only and hide complex properties defined in Netty transport from MSF4J developers. But for advanced use cases, it is possible to configure Netty transport connectors directly by adding ListenerConfiguration beans into Spring ApplicationContext. The following example demonstrates such a use case.
@Configuration public class TransportConfiguration { @Bean public ListenerConfiguration http() { ListenerConfiguration listenerConfiguration = new ListenerConfiguration("netty", "0.0.0.0", 7070); listenerConfiguration.setEnableDisruptor(false); listenerConfiguration.setParameters(getDefaultTransportParams()); return listenerConfiguration; } private ListgetDefaultTransportParams() { Parameter param1 = new Parameter(); param1.setName(Constants.EXECUTOR_WORKER_POOL_SIZE); param1.setValue("1024"); return Collections.singletonList(param1); } }
Example 11
Injecting environment-specific properties
Now let’s look at how you can provide environment specific values into MSF4J services through the Spring framework. As discussed already, we should inject environment-specific configuration details such as SSL properties, database access details, and JMS broker configurations into the Spring framework during startup instead of hard coding or packaging such configurations along with application codes.
There are a set of predefined configuration properties available with the MSF4J Spring module, especially for HTTP and HTTPS transport where you can inject real values using one of the following three approaches:
- Provide the value as a Spring command line argument
As an example port for HTTP transport can be changed by providing a following Spring command line argument.
java -jar target/sample1-1.0.0.jar --http.port=9090
- Provide the value as a Java system variable
java -Dhttp.port=9090 -jar target/sample1-1.0.0.jar
- Adding file called application.properties into the Classpath and provide envirmant specific values as a key-value pairs.
http.port=7070
Other than these inbuilt environment properties you can also define your own environment specific properties. Let’s look at the following example for such a use case.
@Configuration public class GreetingConfiguration { @Value("${msg.greeting:Hello}") private String greeting; @Bean public HelloService helloService() { return new HelloService(greeting); } }
Example 12.1
In the above code segment, we have annotated ‘greeting’ field using Spring @Value annotation. This annotation performs expression-driven dependency injection in order to find the actual value; real processing of the @Value annotation is performed by one of the configured BeanPostProcessor of the current Spring runtime.
In the above sample we can pass the actual value in the greeting field using one of the following three approaches:
- Passing as Spring command-line argument
--msg.greeting=Hi
- Passing as a Java system variable
-Dmsg.greeting=Hi
- Adding application.properties into the Classpath with “msg.greeting=Hi” value.
msg.greeting=Hi
Here is the HelloService class implementation in order to further understand this example.
public class HelloService { private String greeting; public HelloService(String greeting) { this.greeting = greeting; } public String hello(String name) { return greeting + " " + name; } }
Example 12.2
Conclusion
This article discussed a number of topics ranging from developing a simple HelloWorld MSF4J service to developing MSF4J extensions, such as Interceptors, ExceptionMapper, etc. It also explained how you can configure the internals of MSF4J through Spring. Part 2 of this article will discuss the complete application where we will use most of the above topics together with database access using Spring-Hibernate and Spring-JPA.
You can access the complete samples of this article here.