Skip to main content

15 posts tagged with "Backend"

View All Tags

Build Spring Boot Starter Project

· One min read

Open in Notion

Two Java Files

@ConfigurationProperties(prefix = "spring.ftsi")
public class IndexServiceAutoConfigurationProperties {

}

@Configuration
@EnableConfigurationProperties(IndexServiceAutoConfigurationProperties.class)
@ConditionalOnClass(IndexService.class)
@ConditionalOnProperty(prefix = "spring.ftsi", name = "enabled", matchIfMissing = true)
public class IndexServiceAutoConfiguration {

@Autowired
private IndexServiceAutoConfigurationProperties properties;

@Bean
@ConditionalOnMissingBean(IndexService.class)
public IndexService indexService() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
IndexService service = new IndexService();
return service;
}
}

File: /src/main/resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.bndy.ftsi.starter.IndexServiceAutoConfiguration

Usage

application.yml

spring:
ftsi:
property1: ...
property2: ...

java

@SpringBootApplication
public class Application {

@Autowried
IndexService indexService;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Enable https in Spring Boot

· One min read

Open in Notion

  1. Create Self Signed SSL Certificate

    keytool -genkeypair -alias filenameAlias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore filename.p12 -validity 3650

    And use below code to view certificate details.

    keytool -list -keystore filename.p12
  2. Enabling HTTPS in Spring Boot application.propreties

    # The format used for the keystore. for JKS, set it as JKS
    server.ssl.key-store-type=PKCS12
    # The path to the keystore containing the certificate
    server.ssl.key-store=classpath:keystore/javadevjournal.p12
    # The password used to generate the certificate
    server.ssl.key-store-password=use the same password which we added during certificate creation
    # The alias mapped to the certificate
    server.ssl.key-alias=javadevjournal
    # Run Spring Boot on HTTPS only, default 443 like no https 80 port
    server.port=8443

Note that you need copy filename.p12 to src/main/resources/keystore folder

Error Handling in Spring Boot

· 3 min read

Open in Notion

Disabling the Whitelabel Error Page

S1: In application.properties server.error.whitelabel.enabled=false

S2: Excluding the ErrorMvcAutoConfiguration bean

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
#for Spring Boot 2.0
#spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration

Or by adding this annotation to the main class:

@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})

Custom ErrorController

@Controller
public class MyErrorController implements ErrorController {
@RequestMapping("/error")
public String handleError() {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
// get the errors
Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());

if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "error-404";
} else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "error-500";
}
}
return "error";
}

@Override
public String getErrorPath() {
return "/error";
}
}

在 Spring 中常见的全局异常处理,主要有三种:

(1)注解 ExceptionHandler

(2)继承 HandlerExceptionResolver 接口

(3)注解 ControllerAdvice

@Controller
@RequestMapping("c")
public class Controller1 {
// **/c/query -> response 400
@ResponseBody
@ResponseMapping(value = "/query", produces = "appliation/json;charset=UTF-8")
public String query(@RequestParam("id") Long id) {
return id;
}

// **/c/calc -> response 500
@ResponseBody
@ResponseMapping(value = "/calc", produces = "application/json;charset=UTF-8")
public String calc() {
int a = 2/0;
return "":
}
}

注解 ExceptionHandler

注解 ExceptionHandler 作用对象为方法,最简单的使用方法就是放在 controller 文件中,详细的注解定义不再介绍。如果项目中有多个 controller 文件,通常可以在 baseController 中实现 ExceptionHandler 的异常处理,而各个 contoller 继承 basecontroller 从而达到统一异常处理的目的。因为比较常见,简单代码如下:

@ExceptionHandler(Exception.class)
@ResponseBody
public String exception(Exception ex) {
return this.getClass().getSimpleName() + ": " + ex.getMessage();
}

优点:ExceptionHandler 简单易懂,并且对于异常处理没有限定方法格式; 缺点:由于 ExceptionHandler 仅作用于方法,对于多个 controller 的情况,仅为了一个方法,所有需要异常处理的 controller 都继承这个类,明明不相关的东西,强行给他们找个爹,不太好。

注解 ControllerAdvice

这里虽说是 ControllerAdvice 注解,其实是其与 ExceptionHandler 的组合使用。在上文中可以看到,单独使用 @ExceptionHandler 时,其必须在一个 Controller 中,然而当其与 ControllerAdvice 组合使用时就完全没有了这个限制。换句话说,二者的组合达到的全局的异常捕获处理。 这种方法将所有的异常处理整合到一处,去除了 Controller 中的继承关系,并且达到了全局捕获的效果,推荐使用此类方式; Controller 中单独 @ExceptionHandle 异常处理排在了首位,@ControllerAdvice 排在了第二位。

@ControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public String exceptionHandler(Exception ex) {
return "";
}
}

实现 HandlerExceptionResolver 接口

HandlerExceptionResolver 本身 SpringMVC 内部的接口,其内部只有 resolveException 一个方法,通过实现该接口我们可以达到全局异常处理的目的。

@Component
public class MyHandlerExcptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
PrintWriter writer = response.getWriter();
writer.write(ex.getMessage());
writer.flush();
writer.close();

return new ModelAndView();
}
}

可以看到 500 的异常处理已经生效了,但是 400 的异常处理却没有生效,并且根没有异常前的返回结果一样。这是怎么回事呢?不是说可以做到全局异常处理的么?

经过DefaultHandlerExceptionResolver异常处理源码分析,可以看到我们的自定义类 MyHandlerExceptionResolver 确实可以做到全局处理异常,只不过对于 query 请求的异常,中间被 DefaultHandlerExceptionResolver 插了一脚,所以就跳过了 MyHandlerExceptionResolver 类的处理,从而出现 400 的返回结果。

Publishing Repo to Maven Central

· 2 min read

Open in Notion

Create an account on oss.sonatype.org

Create an account and create an ISSUE about Repo. And waiting approved.

Generate and share a PGP signature

Installing GnuPG

gpg --version

Generating a Key Pair and Sharing Key

gpg --generate-key
gpg --list-keys --keyid-format short
gpg --list-secret-keys --keyid-format short
gpg --keyserver hkp://pool.sks-keyservers.net --send-keys <pubkeyid>
// gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys <pubkeyid>

Delete Key or Sub Key

gpg2 –edit-key A6BAB25
gpg> 1
gpg> save
gpg> key 2
gpg> delkey

Local GPG

Add ~/.gradle/gradle.properties with following content:

signing.keyId=<pubkeyid>
signing.password=<passwordgpg>
// For new version GPG, secring.gpg does not be used.
// But you can still use `gpg --export-secret-keys > ~/.gnupg/secring.gpg` to generate
// On windows, you can use `C:/Users/<yourname>/.gnupg/secring.gpg` instead.
signing.secretKeyRingFile=/Users/<yourname>/.gnupg/secring.gpg

ossrhUsername=<name>
ossrhPassword=<password>

Project Configuration

build.gradle example:

group ‘net.bndy’
version ‘1.0’

apply plugin: ‘java’
apply plugin: ‘maven’
apply plugin: ‘signing’

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
compile group: ‘com.fasterxml.jackson.core’, name: ‘jackson-core’, version: ‘2.9.3’
compile group: ‘com.fasterxml.jackson.core’, name: ‘jackson-databind’, version: ‘2.9.3’
compile group: ‘commons-codec’, name: ‘commons-codec’, version: ‘1.11’
compile group: ‘javax.mail’, name: ‘javax.mail-api’, version: ‘1.6.0’
compile group: ‘org.apache.directory.studio’, name: ‘org.apache.commons.io’, version: ‘2.4’
compile group: ‘javax.servlet’, name: ‘servlet-api’, version: ‘2.5’
}

task javadocJar(type: Jar) {
classifier = ‘javadoc’
from javadoc
}

task sourcesJar(type: Jar) {
classifier = ‘sources’
from sourceSets.main.allSource
}

artifacts {
archives javadocJar, sourcesJar
}

signing {
sign configurations.archives
}

uploadArchives {
repositories {
mavenDeployer {
beforeDeployment {
MavenDeployment deployment -> signing.signPom(deployment)
}

repository(url: “https://oss.sonatype.org/service/local/staging/deploy/maven2/”) {
authentication(userName: ossrhUsername, password: ossrhPassword)
}

snapshotRepository(url: “https://oss.sonatype.org/content/repositories/snapshots/”) {
authentication(userName: ossrhUsername, password: ossrhPassword)
}

pom.project {
name ‘Jlib’
packaging ‘jar’

// optionally artifactId can be defined here
description ‘A Java Library’
url ‘https://github.com/bndynet/Jlib’

scm {
url ‘https://github.com/bndynet/Jlib’
connection ‘scm:git:git://github.com/bndynet/Jlib.git’
developerConnection ‘scm:git:git@github.com:bndynet/Jlib.git’
}

licenses {
license {
name ‘The Apache License, Version 2.0’
url ‘http://www.apache.org/licenses/LICENSE-2.0.txt’
}
}

developers {
developer {
id ‘bndynet’
name ‘Bendy Zhang’
email ‘zb@bndy.net’
url ‘http://bndy.net’
}
}
}
}
}

Deployment

  1. gradle uploadArchives
  2. Go to https://oss.sonatype.org/#stagingRepositories to close your repo. If closed successfully, you can release it. Otherwise, drop it and regradle it again.

TimeZone in Spring Boot

· One min read

Open in Notion

@SpringBootApplication
public class Application {
@PostConstruct
public void init(){
// Setting Spring Boot SetTimeZone
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

In application.properties

spring.jackson.time-zone= # Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10".