
- Rating:
- Views: 569
- Author: JLupin
- Skill level: easy
- Comments: 0
Overview
This tutorial will show you how to create servlet (Spring BOOT) microservice for JLupin Platform. What is servlet microservice? It is just a microservice which uses servlet container to serve its logic. JLupin Platform is using Spring Boot to allow users to choose servlet container that they want.
Requirements
To complete this tutorial you will need JLupin Platform version 1.5.0.3. You can download it from here.
Configure project
When you create service which will be available to other users of your system, you probably want to tell them how to use it. For native microservice you will create separate interfaces module. But servlet microservice probably won't be accessed with use of proxy objects but with simple json calls or will serve HTTP pages. To keep microservice structure similar for both types we will create implementation module for servlet microservice, but we won't create interfaces module.
We will use Maven feature called reactor. It is built-in mechanism for handling multi-module project. So we are going to start with standard Maven structure.
+--implementation | | | +--src | | | | | +--main | | | | | +--java | | | | | +--resources | | | +--pom.xml | +--pom.xml
Below pom files with proper configuration are presented.
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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> <artifactId>welcome-servlet-microservice</artifactId> <groupId>com.example.application</groupId> <version>1.0.0</version> <packaging>pom</packaging> <modules> <module>implementation</module> </modules> <properties> <maven.surefire.skipTests>false</maven.surefire.skipTests> <maven.failsafe.plugin.version>2.20</maven.failsafe.plugin.version> <maven.surefire.plugin.version>2.20</maven.surefire.plugin.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven.surefire.plugin.version}</version> <configuration> <skipTests>${maven.surefire.skipTests}</skipTests> </configuration> </plugin> </plugins> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>${maven.failsafe.plugin.version}</version> <executions> <execution> <id>integration-test</id> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> <configuration> <includes> <include>**/Test*.java</include> <include>**/*Test.java</include> <include>**/*Tests.java</include> <include>**/*TestCase.java</include> </includes> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
implementation/pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> </parent> <name>welcome-servlet-microservice</name> <groupId>com.example.application</groupId> <artifactId>welcome-servlet-microservice-implementation</artifactId> <packaging>war</packaging> <version>1.0.0</version> <repositories> <!-- Repository is also accessible using https connection: --> <!-- https://support.jlupin.com/maven2/ --> <repository> <id>jlupin-central</id> <name>jlupin</name> <url>http://support.jlupin.com/maven2/</url> </repository> </repositories> <pluginRepositories> <!-- Repository is also accessible using https connection: --> <!-- https://support.jlupin.com/maven2/ --> <pluginRepository> <id>jlupin-central</id> <name>jlupin</name> <url>http://support.jlupin.com/maven2/</url> </pluginRepository> </pluginRepositories> <properties> <jlupin.platform.client.version>1.5.0.3</jlupin.platform.client.version> <jlupin.platform.maven.plugin.version>1.5.0.0</jlupin.platform.maven.plugin.version> <jlupin.servlet.monitor.version>1.5.0.0</jlupin.servlet.monitor.version> </properties> <dependencies> <!-- JLupin dependencies --> <dependency> <groupId>com.jlupin</groupId> <artifactId>jlupin-client-assembly</artifactId> <version>${jlupin.platform.client.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.jlupin</groupId> <artifactId>jlupin-spring-boot-2-servlet-monitor</artifactId> <version>${jlupin.servlet.monitor.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> </dependencies> <build> <finalName>${project.name}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.jlupin</groupId> <artifactId>jlupin-platform-maven-plugin</artifactId> <version>${jlupin.platform.maven.plugin.version}</version> <executions> <execution> <id>jlupin-zip</id> <goals> <goal>zip</goal> </goals> <configuration> <additionalFilesDirectories> <param>../additional-files</param> </additionalFilesDirectories> <includeDependenciesInZip>false</includeDependenciesInZip> </configuration> </execution> <execution> <id>jlupin-deploy</id> <phase>pre-integration-test</phase> <goals> <goal>deploy</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
JLupin Maven repository is configured to get access to required artifacts. Also dependencies required for this tutorial and development are added. JLupin Platform Maven Plugin is configured to zip microservice and deploy it. Also content of additional-files is added to created zip file. You need to create this directory on same level as implementation directory. Configuration files will be placed here. You should end up with structure:
+--additional-files | +--implementation | | | +--src | | | | | +--main | | | | | +--java | | | | | +--resources | | | +--pom.xml | +--pom.xml
Microservice implementation
At first create suitable package structure. In this tutorial com.example.application will be used as main package. Recommended structure (also used by this tutorial) is presented below.
+--additional-files | +--implementation | | | +--src | | | | | +--main | | | | | +--java | | | | | | | +--com | | | | | | | +--example | | | | | | | +--application | | | | | | | +--configuration | | | | | | | +--controller | | | | | +--resources | | | +--pom.xml | +--pom.xml
When you done with package structure create basic Spring configuration file under configuration package.
package com.example.application.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.List; @Configuration @ComponentScan({ "com.example.application", "com.jlupin.servlet.monitor.configuration" }) public class SpringConfiguration { }
Ok, we have Spring configuration file created. But our microservice does nothing. Create simple REST controller which accepts name as request body and returns welcome message.
package com.example.application.controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.Collections; import java.util.Map; @RestController public class WelcomeController { @CrossOrigin @PostMapping(value = "/getWelcomeMessage") public Map<String, String> getWelcomeMessage(@RequestBody Map<String, String> map) { return Collections.singletonMap("message", "Hello " + map.get("name") + "!"); } }
Controller is ready. Now we need to create starter class for Spring Boot:
package com.example.application; import com.example.application.configuration.SpringConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootApplicationStarter { public static void main(String[] args) throws Exception { SpringApplication.run(SpringConfiguration.class, args); } }
We used annotations to configure Spring container. But you can configure starter class the way you want. Now create servlet_configuration.yml file and setup your microservice - example below. Put this file inside additional-files directory.
SERVERS: HTTP: type: spring_boot httpPrimaryPort: -1 httpSecondaryPort: -1 waitForFinishExecuteAllRequests: true waitToShutdownThreadsOnStop: 5000 springBootLoaderClassName: org.springframework.boot.loader.WarLauncher contextName: /welcome-servlet-microservice isStartOnMainServerInitialize: true httpStickySession: false TRANSMISSION: readTimeout: 480000 isWaitForFinishExecuteAllRequests: false waitToShutdownThreadsOnStop: 60000 backlog: 0 receiveBufferSize: 0 isReuseAddress: false threadPoolSize: 8 isLogPeriodicOnDebug: true isDestroyThreadOnTimeout: false threadExecutingTimeOut: 3600000 isStartOnMainServerInitialize: true PROPERTIES: platformVersion: '1.5.0.3' #jvmOptions1: '-Xms128M -Xmx512M -agentlib:jdwp=transport=dt_socket,address=12998,server=y,suspend=n' jvmOptions1: '-Xms128M -Xmx512M' #jvmOptions_2 - default the same as jvmOptions_1 #jvmOptions2: '-Xms128M -Xmx512M' externalPort: '8000' version: '1.0.0' switchDelayTime: 1000 connectionSocketTimeoutInMillis: 1000 readTimeoutInMillis: 30000 isKeepAlive: false isOOBInline: false isTcpNoDelay: false isReuseAddress: false sendBufferSize: 0 receiveBufferSize: 0 soLinger: 0 trafficClass: 0 #javaExecutablePath: 'c:\\jvm\\bin\\java.exe' #additionalClassPath: 'c:\\temp\\*' isStartOnMainServerInitialize: true priorityStartOnMainServerInitialize: 5 waitForProcessInitResponseTimeInMillis: 90000 waitForProcessStartResponseTimeInMillis: 90000 waitForProcessDestroyResponseTimeInMillis: 30000 isAllFilesToJVMAppClassLoader: true #isStackDumping: true isArchiveOnStart: false startLogMode: INFO isInitErrorCauseWithNetworkInformation: true isJmxEnabled: true jmxOptions: '-Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false' jmxPrimaryPort: -1 jmxSecondaryPort: -1 isExternalHealthcheck: false externalHealthcheckURI: '/sampleURI/' httpStickySessionCookieOptions: 'option1=value1' checkAvailableScript: 'function isAvailable(checkResponseTimeInMillis, jrmcActiveThreads, jrmcMaxThreads, queueActiveThreads, queueMaxThreads, servletActiveThreads, servletMaxThreads, jvmMaxMemoryInBytes, jvmTotalMemoryInBytes, jvmFreeMemoryInBytes, jvmProcessCpuLoadInPercentage, userAvailableFlag) { var isAvailableByUser = Boolean(userAvailableFlag); if(checkResponseTimeInMillis > 20000 || !isAvailableByUser) { return false; } return true; }' INITIALIZING_LOGGER: #directoryPath: '/logs/server' #fileName: 'file_name' fileExtension: 'log' fileSizeInMB: 20 maxFiles: 10 MEMORY_ERRORS: isRestartOnError: true howManyTimes: 4 percentageGrowth: 15 isHeapDump: true THREAD_POOLS: THREAD_POOL_1: size: 8 waitingTimeForTasksCompletionInMillis: 10000 #THREAD_POOL_2: # size: 8 # waitingTimeForTasksCompletionInMillis: 10000
You should also configure logging system (Log4j2 for JLupin and Spring container). Example configuration with asynchronous appender below.
Put this into log4j2.xml file inside additional-files directory and inside resources directory.
<?xml version="1.0" encoding="UTF-8"?> <!-- ===================================================================== --> <!-- --> <!-- Log4j2 Configuration --> <!-- --> <!-- ===================================================================== --> <!-- | For more configuration information and examples see the Apache Log4j2 | website: https://logging.apache.org/log4j/2.x/index.html --> <Configuration status="WARN" dest="errors/welcome-servlet-microservice_log4j2_status.log"> <!-- Extract log directory and file name into variables --> <Properties> <Property name="logDirectory">../logs/microservice/welcome-servlet-microservice</Property> <Property name="logFileName">microservice</Property> </Properties> <Appenders> <!-- RollingFileAppender configured to role every day --> <RollingFile name="FILE"> <FileName>\${logDirectory}/\${logFileName}.log</FileName> <FilePattern>\${logDirectory}/\${logFileName}.%d{yyyy-MM-dd}.log</FilePattern> <!-- Compress log files to gzip --> <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy --> <!-- <FilePattern>${logDirectory}/${logFileName}.%d{yyyy-MM-dd}.log.gz</FilePattern> --> <!-- Do not truncate file --> <Append>true</Append> <!-- The default pattern: Date Priority [Category] (Thread) Message\n --> <PatternLayout pattern="%d %-5p [%c] (%t) %m%n" /> <Policies> <!-- Rollover every microservice start - very useful for debugging --> <!-- <OnStartupTriggeringPolicy /> --> <!-- Rollover at the top of each day --> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <!-- Rollover if file size is greater than 200 MB --> <!-- <SizeBasedTriggeringPolicy size="200 MB"/> --> </Policies> <CreateOnDemand>true</CreateOnDemand> <!-- Keep last 10 log files --> <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy --> <!-- <DefaultRolloverStrategy max="10" /> --> </RollingFile> <!-- AsyncAppender for high performance --> <Async name="ASYNC_FILE"> <BufferSize>1000</BufferSize> <AppenderRef ref="FILE" /> </Async> </Appenders> <Loggers> <!-- Setup for root logger with AsyncAppender --> <Root level="info"> <AppenderRef ref="ASYNC_FILE" /> </Root> </Loggers> </Configuration>
That's all. Now your microservice is ready to deploy.
Deploying microservice
Run server (start/start.sh or start/start.cmd in main server directory). Then run maven command to deploy your microservice:
mvn clean package jlupin-platform:deploy@jlupin-deploy --projects implementation --also-make
Check microservice's list to see your microservice (start/control.sh):
./control.sh microservice list
Zone Node Microservice Type Version #Services default NODE_1 queueMicroservice native 1.5.0.3 1 default NODE_1 exchange-rates native 1.5.0.3 1 default NODE_1 channelMicroservice native 1.5.0.3 1 default NODE_1 currency-converter-gbp native 1.5.0.3 1 default NODE_1 exchange servlet 1.5.0.3 4 default NODE_1 http-session-repository native 1.5.0.2 2 default NODE_1 currency-converter-chf native 1.5.0.3 1 default NODE_1 currency-converter-eur native 1.5.0.3 1 default NODE_1 welcome-servlet-microservice servlet 1.0.0 1
You can see your microservice on the list. You can check its status to see if it is running.
./control.sh microservice status welcome-servlet-microservice
Zone Node Microservice ProcessID Status Available Activated default NODE_1 welcome-servlet-microservice 17892 RUNNING yes yes
Integration tests
Now it is time to add some integration tests and check our microservice. We will put them into separate module called integration test. Let's add it with proper test package structure:
+--additional-files | +--implementation | | | +--[...] | +--integration-test | | | +--src | | | | | +--test | | | | | +--java | | | | | | | +--com | | | | | | | +--example | | | | | | | +--application | | | | | +--resources | | | +--pom.xml | +--pom.xml
integration-test/pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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> <parent> <artifactId>welcome-servlet-microservice</artifactId> <groupId>com.example.application</groupId> <version>1.0.0</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>welcome-servlet-microservice-integration-test</artifactId> <properties> <maven.surefire.skipTests>true</maven.surefire.skipTests> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>com.example.application</groupId> <artifactId>welcome-servlet-microservice-implementation</artifactId> <version>1.0.0</version> <classifier>classes</classifier> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>2.0.2.RELEASE</version> <scope>test</scope> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> <version>2.0.2.RELEASE</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> </plugin> </plugins> </build> </project>
Add module into modules section inside top level pom.xml file:
[...] <modules> <module>implementation</module> <module>integration-test</module> </modules> [...]
Because we are creating war for application and we want to test our microservice in seperate module we need to generate classes also for dependency purposes. Add this to implementation/pom.xml:
<plugin> [...] <build> [...] <plugins> [...] <plugin> <artifactId>maven-war-plugin</artifactId> <configuration> <attachClasses>true</attachClasses> <classesClassifier>classes</classesClassifier> </configuration> </plugin> </plugins> </build> </project>
Now create test class.
package com.example.application; import com.example.application.controller.WelcomeController; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @WebMvcTest(WelcomeController.class) public class IntegrationTest { @Autowired private MockMvc mvc; @Test public void test() throws Exception { mvc.perform(post("/getWelcomeMessage").content("{\"name\":\"Piotr\"}").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().json("{\"message\": \"Hello Piotr!\"}")); } }
It is simple test for returned json content. The last thing is to configure logger (Log4j) for this tests. Add log4j2.xml file to test resources.
<?xml version="1.0" encoding="UTF-8"?> <!-- ===================================================================== --> <!-- --> <!-- Log4j2 Configuration --> <!-- --> <!-- ===================================================================== --> <!-- | For more configuration information and examples see the Apache Log4j2 | website: https://logging.apache.org/log4j/2.x/index.html --> <Configuration status="WARN"> <Appenders> <Console name="STDOUT" target="SYSTEM_OUT"> <!-- The default pattern: Date Priority [Category] (Thread) Message\n --> <PatternLayout pattern="%d %-5p [%c] (%t) %m%n" /> </Console> <!-- AsyncAppender for high performance --> <Async name="ASYNC_STDOUT"> <BufferSize>1000</BufferSize> <AppenderRef ref="STDOUT" /> </Async> </Appenders> <Loggers> <!-- Setup for root logger with AsyncAppender --> <Root level="info"> <AppenderRef ref="ASYNC_STDOUT" /> </Root> </Loggers> </Configuration>
It is configured to write logs to standard out. Now you're ready to run tests.
mvn clean integration-test --projects integration-test --also-make
[INFO] Scanning for projects... [...] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.example.application.IntegrationTest [...] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] welcome-servlet-microservice ....................... SUCCESS [ 20.122 s] [INFO] welcome-servlet-microservice ....................... SUCCESS [ 0.056 s] [INFO] welcome-servlet-microservice-integration-test ...... SUCCESS [ 5.594 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 26.322 s [INFO] Finished at: 2018-10-11T10:41:41+02:00 [INFO] Final Memory: 50M/544M [INFO] ------------------------------------------------------------------------
cURL
If you want you can call endpoint with cURL to check if everything works:
curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"Piotr\"}" localhost:8000/welcome-servlet-microservice/getWelcomeMessage
As a result you should see:
{"message":"Hello Piotr!"}
Done tutorial
You can download project which is result of making this tutorial from GitHub: https://github.com/jlupin/creating-servlet-microservice-jlp-1503
RATE & DISCUSS (0)