Spring boot build version and time access

Spring boot can create the build version info for you automatically and with a default bean to access these information. I found this link is very helpful on this  and i jus tlist main points at here.

https://www.vojtechruzicka.com/spring-boot-version/

  1. build-info goal create the version info
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>

 

2. In java code, just use BuildProperties to get the version info. You cna then display on UI and logs.

@Autowired
BuildProperties buildProperties;

 

3. you can even add more properties in POM to let it saved in build-info, you can refer to the link details for this.

Advertisements

How to let swagger control group display

In Swagger, you normally divide the API into groups and each group will have a Docket.

But many times, you do not want someone or some environment to see all groups . you want to control each group showing or not. This looks like a difficult task after some search. But in fact swagger already has this feature in Docket.

@Bean
public Docket myDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("MYAPI").apiInfo(...).select().paths(....)
.build().useDefaultResponseMessages(false).forCodeGeneration(true).enable(true);
}

So enable(boolean) is part to control enable method for the group. You can use your configuration or role logic to control this flag value to control the group here.

 

How to let your spring boot pick up external property file

You may already see that ./config/application.properties can override the default spring boot application.properties settings. But when you deploy your app to tomcat and with many other web app together, then you need to have unique properties files foe each app and also make external property file be picked up by web app.

So let us do something like this:

  1. Change in your java main code to let your app use a unique properties file instead of application.properties.
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        System.setProperty("spring.config.name", "myapp");
        SpringApplication.run(MyApplication.class, args);
    }
}

For war version of tomcat:

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(MyApplication.class).properties("spring.config.name: myapp");
    }

}

Then name your properties file like: myapp.properties, instead of the default name.

2. Now let us also enable the external property file to override the default myapp.properties.

in Spring boot, it is just ./config/myapp.properties

For tomcat version, we need to add env variable to do this. Set a spring_config_location environment variable pointing to the folder that contains your myapp.properties file. In the case of Tomcat you can do this by adding the following line to your <TOMCAT_HOME>/bin/setenv.sh file (create the file if missing):

export spring_config_location=<TOMCAT_HOME>/conf/

Place the myapp.properties properties file in that folder <TOMCAT_HOME>/conf/.

So every app will looking into the conf folder to look for their unique properties file to load.

 

Refer to:

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

 

 

How to create a Async http call in Spring Boot

Use case in this example:

1. Client makes call to server side, returned immediately, but already start a asynchrony process in server side.

2. When the process is still running, client need to call http call to get the processing status from time to time.

3.  This example shows a processing status shared by the whole container context, as the Bean and controller scope in Spring is Singleton. You can also try to make controller and bean into session, request level if needed.Or use the session/Model to handle shared data.

4.  @Async can not be used in the controller , I test it. Callable maybe it the way for controller method.

 

– Let us take a look code.  Create one service bean and make it expose the status by a getter.

@Service
public class TestService {

private static final Logger logger = LoggerFactory.getLogger(TestService.class);

private String status = "Not start";

@Async("processExecutor")
 public void process() {
 logger.info("Received request to process in ProcessServiceImpl.process()");
 status = "in Process";
 try {
 Thread.sleep(15 * 1000);
 logger.info("Processing complete");
 } catch (InterruptedException ie) {
 logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
 }
 
 status = "Done";
 
 }

public String getStatus() {
 return status;
 }
}

 

Then config the application to make it support the @EnableAsync and define the tread pool for Async calls.

@EnableAsync
public class MyDemoApplication {
...........................................................// for Async test code
 @Bean(name = "processExecutor")
 public TaskExecutor workExecutor() {
 ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
 threadPoolTaskExecutor.setThreadNamePrefix("Async-");
 threadPoolTaskExecutor.setCorePoolSize(3);
 threadPoolTaskExecutor.setMaxPoolSize(3);
 threadPoolTaskExecutor.setQueueCapacity(600);
 threadPoolTaskExecutor.afterPropertiesSet();
 logger.info("ThreadPoolTaskExecutor set");
 return threadPoolTaskExecutor;
 }

 

Then at spring mvc controller create two method for http , start process and check process status:

 

@Autowired
 private TestService processService;
....
@GetMapping("/startAsync")
 public Result startAsync() {
 processService.process();
 statusData = processService.getStatus();
 Result rt = new Result(true, statusData);
 return rt;
 }

@GetMapping("/getAsyncStatus")
 public Result getAsyncStatus() {
 statusData = processService.getStatus();
 Result rt = new Result(true, statusData);
 return rt;
 }

Then server side is done!

Let me create a client code to test it how is works:

 testStartAsyncCall();
 testAsyncStatusCall();
 try {
 TimeUnit.SECONDS.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 testAsyncStatusCall();
 try {
 TimeUnit.SECONDS.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 testAsyncStatusCall();

 

You can see the process take 15 seconds to finish, so client side try to get the status every 10 seconds after call to start the process. so we can see how status is changed in the whole process.

My example is modified from one example post, Please refer to this post:

http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html

 

 

 

Exception Processing @RestControllerAdvice in Spring

A very useful class in the Spring 4 plus is the  @RestControllerAdvice, it combines the @ControllerAdvice inside. By defining a @RestControllerAdvice, when a controller thorw one exception, you can put all the exception handlers code into one class and you then can choose to use different @ExceptionHandler method to deal with different exceptions , or you even can deal all in one method to use the instance check up to decide what response msg you should send out in response;

So you have chance to reply user a friend msg of your runtime exception of your Restful API, and even with customized http status code too with ResponseEntity class.

Here I put a fake sample code to show how it works. you can do more study and use the handler in your way. Result is a class you defined Object your want to reply to client side.


@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(value = { MyCustomizedException.class })
 @ResponseStatus(HttpStatus.BAD_REQUEST)
public Result myCustomizedExceptionHandler(MyCustomizedException ex) {
 // ..... what ever process
 return new Result(500, 0001, ex.getMessage());
 }


@SuppressWarnings("rawtypes")
 @ExceptionHandler(value = Exception.class)
 public ResponseEntity handle(Exception e) {
 e.printStackTrace();
 Result rt = new Result(e.getMessage());

if (e instanceof SoaException) {
 rt = ((SoaException) e).getBodyStatus();
 } else if (e instanceof MissingServletRequestParameterException) {
 rt = DataUtil.rt("0002");
 } else if (e instanceof HttpRequestMethodNotSupportedException) {
 rt = DataUtil.rt("0003");
 } else if (e instanceof HttpMediaTypeNotSupportedException) {
 rt = DataUtil.rt("0004");
 } else if (e instanceof HttpMessageNotReadableException) {
 rt = DataUtil.rt("0005");
 } else if (e instanceof BindException) {
 rt = DataUtil.rt("0006");
 } else if (e instanceof NumberFormatException) {
 rt = DataUtil.rt("0007");
 } else if (e instanceof DataIntegrityViolationException) {
 rt = DataUtil.rt("0008");
 } else {
 rt = DataUtil.rt(Constants.ERROR_CODE);
 }
 LOGGER.error(rt.getMessage() + e);
 return new ResponseEntity<>(rt, HttpStatus.OK);
 }

 

Spring Admin Server and UI for Spring Boot Application – 2

Now , let us add the security login for the spring admin server.

1. Add these into pom.xml

<dependency>
 <groupId>de.codecentric</groupId>
 <artifactId>spring-boot-admin-server-ui-login</artifactId>
 <version>1.5.7</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
 </dependency>

spring-boot-admin-server-ui-login will supply the login and logout page.

 

2. Create a web security config class like this:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
 protected void configure(HttpSecurity http) throws Exception {
 http.formLogin().loginPage("/login.html").loginProcessingUrl("/login").permitAll();
 http.logout().logoutUrl("/logout");
 http.csrf().disable();
 http.authorizeRequests().antMatchers("/login.html", "/**/*.css", "/img/**", "/third-party/**").permitAll();
 http.authorizeRequests().antMatchers("/**").authenticated();
 http.httpBasic();
 }
}

 

3. put this into the application.properties

management.security.enabled=true
security.user.name=admin
security.user.password=*******

Restart and now you need login to access the server UI.

Screenshot from 2018-04-27 15-37-57

 

But now, you will lost your client app in the UI. To let the client can access the server URL to exchange the data to server, we also need to change client configuration:

Just add this into client application.properties, the server can connect with client again:

management.security.enabled = false
spring.boot.admin.username=admin
spring.boot.admin.password=*******

management.security.enabled = false is to tell the server no need the security policy to access the client URL of actuator.   But client will use admin account to login to the server side to submit data…

 

Here I have done the demo for the single client and server admin UI login.  Next step you need to look at in the discovery  and eureka environment, how to link one server with more clients in cluster.

Please refer the doc of admin server for more details.

https://codecentric.github.io/spring-boot-admin/1.5.7/