Java Null Pointer Exception

Null Pointer Exception(NPE)是 Java 语言中非常常见的一个问题。


Article.java

public class Article
{
    private Author author;

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }
}

Author.java

public class Author
{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Main.java

public class Main
{
    public static void main(String[] args) {
        Article article = null;

        System.out.println(article.getAuthor().getName());
    }
}

比如我们写了上面这样一段代码,运行时就会报 NullPointerException。

Exception in thread "main" java.lang.NullPointerException
        at Main.main(Main.java:6)

当然解决 NullPointerException 的方法也有很多,比如使用 if-else。

public class Main1
{
    public static void main(String[] args) {
        Article article = null;

        if (article != null && article.getAuthor() != null) {
            System.out.println(article.getAuthor().getName());
        } else {
            System.out.println("Unknown");
        }
    }
}

比如使用 Java 8 的 Optional。

import java.util.Optional;

public class Main2
{
    public static void main(String[] args) {
        Article article = null;

        System.out.println(Optional.ofNullable(article).map(Article::getAuthor).map(Author::getName).orElse("Unknown"));
    }
}

或者使用 Kotlin 的 Safe Calls。

data class Article(var author: Author?);

data class Author(var name: String);

fun findArticle() : Article? {return null}

fun main() {
    val article = findArticle();
    println(article?.author?.name)
}

参考:

https://blog.udemy.com/java-null-pointer-exception/

https://kotlinlang.org/docs/reference/null-safety.html

206 total views, no views today

java.lang.IllegalArgumentException: Invalid character found in the request target.

最近在使用 Spring Boot 开发项目的时候遇到了这样一个错误:

java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

请求中包含无效字符,有效字符的定义请查看 RFC 7230 和 RFC 3986。

查看了一下我的请求参数,应该是 [] 字符被 Tomcat 当作无效字符处理了。

Request Params:
expressNumber[]=111&expressNumber[]=222&expressNumber[]=333

在网上找了一下解决方案,需要在 Tomcat 配置文件中添加 relaxedQueryChars。

Tomcat >= 8.5
relaxedQueryChars = "[]"

由于 Spring Boot 使用的是 Embedded Tomcat ,可以参考以下设置方式。

@Component
public class MyTomcatWebServerCustomizer
        implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.addConnectorCustomizers(connector -> connector.setAttribute("relaxedQueryChars", "[]"));
    }
}

问题已经成功解决了,但是还有另外一种思路。

再来看一下我的请求参数,是要传递一个数组。

Request Params:
expressNumber[]=111&expressNumber[]=222&expressNumber[]=333

Java Code:
@RequestParam(name = "expressNumber[]") String[] expressNumber

有没有其他方式呢?有的,而且还有两种。

Request Params:
expressNumber=111&expressNumber=222&expressNumber=333

Java Code:
@RequestParam String[] expressNumber
Request Params:
expressNumber=111,222,333

Java Code:
@RequestParam String[] expressNumber

参考:

https://stackoverflow.com/questions/48655291/java-lang-illegalargumentexception-invalid-character-found-in-the-request-targe

https://stackoverflow.com/questions/41053653/tomcat-8-is-not-able-to-handle-get-request-with-in-query-parameters/44005213#44005213

https://stackoverflow.com/questions/51703746/setting-relaxedquerychars-for-embedded-tomcat

https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-web-servers.html

879 total views, no views today

Spring Boot Security

开发一个 RESTful 接口的过程中,权限认证是必不可少的一个重要功能。

这里我们以 Spring Boot Security 为例,实现一个自定义的 Token 认证。

大致流程如下:

  1. 用户 -> 登录页面 -> 用户中心 -> 获取token
  2. 用户 -> token-> 当前应用 -> token-> 用户中心 -> 认证

首先我们新建配置文件 SecurityConfig。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilterBean() throws Exception {
        return new TokenAuthenticationFilter();
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();

        http.csrf().disable();

        http.addFilterBefore(tokenAuthenticationFilterBean(), UsernamePasswordAuthenticationFilter.class);
    }
}

然后实现 TokenAuthenticationFilter 拦截器。

public class TokenAuthenticationFilter extends OncePerRequestFilter
{
    @Autowired
    private UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException
    {
        //extract token from header
        final String token = request.getHeader("x-auth-token");

        if (null != token) {
            //get and check whether token is valid ( from DB or file wherever you are storing the token)
            final User user = userService.getTokenUser(token);

            if (null != user) {
                //Populate SecurityContextHolder by fetching relevant information using token
                final UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        filterChain.doFilter(request, response);
    }
}

最后实现用户中心认证的业务逻辑。

@Service
public class UserService {

    @Autowired
    private OrderService orderService;

    public User getTokenUser(String token) {
        //TODO
    }
}

参考:

https://stackoverflow.com/questions/42354138/spring-security-token-based-authentication

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

https://www.lefer.cn/posts/55880

216 total views, no views today

GitLab CI/CD

Workflow

GitLab workflow example

准备工作

在开始使用 GitLab CI/CD 之前,我们需要完成以下准备工作。

  1. 安装 Gitlab 和 Gitlab Runer。
  2. 生成一对 SSH Private Key 和 SSH Public Key。

SSH Private Key 配置在 Setting > CI/CD > Variables 里面。

SSH Public Key 配置在要部署的服务器上的 ~/.ssh/authorized_keys 文件里面。


下面我们用两个例子来体验一下 GitLab CI / CD 配置文件 .gitlab-ci.yml 的语法。

PHP Examples

image: tanghengzhi/gitlab-deploy

before_script:
    # Add the private SSH key to the build environment
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config

stages:
    - test
    - deploy

test:
    stage: test
    script: echo 'PHP is the best language in the world'
    only:
        - master

deploy-dev-and-test:
    stage: deploy
    script:
        # Run git pull
        - ssh ec2-user@54.223.162.119 sudo git -C /opt/case/woof-go-waybill pull
        - ssh ec2-user@54.223.162.119 sudo git -C /opt/case/woof-go-waybill-beta pull
    only:
        - master

deploy-online-1:
    stage: deploy
    script:
        # Run git pull
        - ssh ec2-user@54.222.244.71 sudo git -C /opt/case/waybill-service pull
    when: manual
    only:
        - master

deploy-online-2:
    stage: deploy
    script:
        # Run git pull
        - ssh ec2-user@52.81.109.73 sudo git -C /opt/case/waybill-service pull
    when: manual
    only:
        - master

Java Examples

image: tanghengzhi/gitlab-deploy:java

before_script:
    # Add the private SSH key to the build environment
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config

stages:
    - build
    - deploy

build:
    stage: build
    script:
        - ./gradlew build
    artifacts:
        paths:
          - build/libs/*.jar
    only:
        - master

deploy-dev:
    stage: deploy
    script:
        - rsync build/libs/*.jar ec2-user@54.223.162.119:/opt/case/waybill-service/build/libs/
        - ssh ec2-user@54.223.162.119 sudo service waybill-service restart
    only:
        - master

参考:

https://docs.gitlab.com/ee/ci/introduction/index.html

https://docs.gitlab.com/ee/ci/

https://github.com/tanghengzhi/gitlab-deploy

432 total views, no views today

Learn Java Web(2) – Spring Framework

这是 Learn Java Web 系列的第二篇,前一篇的主要内容是介绍什么是 Java Servlet,以及用 Java Servlet 写一个 Hello World 页面。这一篇的主要内容是介绍什么是 Spring Framework,以及用 Spring Framework 写一个 Hello World 接口。

1. 下载 & 安装 Gradle

brew install gradle

gradle -v
Gradle 4.6

2. 代码来自 Spring Guides

git clone https://github.com/spring-guides/gs-rest-service.git

cd gs-rest-service/complete/

gradle bootRun

3. 访问 http://localhost:8080/greeting

{
    "id": 1,
    "content": "Hello, World!"
}

参考:

https://spring.io/
https://spring.io/guides
https://spring.io/guides/gs/rest-service/

208 total views, no views today

Learn Java Web (1) – Java Servlet

每一天我们都在见证历史 – 2018.2.25


最近打算花一点时间学习一下 Java Web 开发。顾名思义,学习 Java Web 开发的前提是你必须要会一点 Java 语言和 Web 开发的基础知识,如果你已经忘的差不多了可以去稍微补一下相关知识。

Java Web 开发系列应该会有很多篇,今天我们先从 Java Servlet 开始,首先我们先来了解一些概念。

Java EE: 现在 Java 平台一共有三个版本,Java EE(Java Platform, Enterprise Edition),Java SE(Java Platform, Standard Edition),Java ME(Java Platform, Micro Edition)。JAVA ME 已死,我们就不过多的去讨论了,我们主要聊聊 Java SE 和 Java EE。

我们打开 Java SE 的下载页面,Java SE 有 JRE, Server JRE,和 JDK 三个安装包。JRE 和 Server JRE 是用来提供 Java 运行环境,JDK 是用来搭建 Java 开发环境。我们再看一下 Java EE 的下载页面,Java EE 8 Platform SDK,Java EE 8 Web Profile SDK。仔细看一下页面介绍,就会发现是打包了 GlassFish 的两个不同版本,具体的区别我在 Stack Overflow 上面找了一张图。

Java EE 定义了一套企业级开发需要的接口,Java EE 8 Web Profile 是 Java EE 的子集,只包含 Java Web 开发需要用到的接口。相对应地,GlassFish 是 Java EE 定义的接口的具体实现,GlassFish Web Profile 是 Java EE 8 Web Profile 包含的所有接口的具体实现。我们常用的 Tomcat 和 Jetty 也分别是 Apache 和 Eclipse 对 Java EE 部分接口的具体实现。

Java Servlet: Java Servlet API 是 Java EE 定义的一套企业级开发需要的接口中一个非常重要的接口,更是 Java Web 开发必备的接口。今天我们使用 JDK + Tomcat + IntelliJ IDEA 实现一个 Hello World 页面,通过实际案例来了解一下什么是 Java Servlet。

1. 下载 & 安装 JDK, Tomcat, IntelliJ IDEA

使用的版本:

JDK 9.0.4

Tomcat 8.5.28

IntelliJ IDEA 2017.3

2. 代码来自 Tomcat Examples

src/HelloWorld.class

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("");
        out.println("");
        out.println("");
        out.println("");
        out.println("");
        out.println("Hello World!");
        out.println("");
        out.println("");
    }
}

web/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
    <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_3_1.xsd"
           version="3.1">

    <servlet>
        <servlet-name>HelloWorld</servlet-name>
        <servlet-class>HelloWorld</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloWorld</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

3. IntelliJ IDEA 操作


参考:

https://www.oschina.net/question/12_52027

http://www.oracle.com/technetwork/java/javase/downloads/index.html

http://www.oracle.com/technetwork/java/javaee/downloads/index.html

https://stackoverflow.com/questions/24239978/java-ee-web-profile-vs-java-ee-full-platform

https://en.wikipedia.org/wiki/Java_servlet

https://www.jetbrains.com/help/idea/2017.1/enabling-web-application-support.html

281 total views, no views today