最近文章

JUnit 5 @BeforeAll和@AfterAll的用法

JUnit 5的@BeforeAll和@AfterAll注释分为两种情景使用:静态方法和实例方法。静态方法@BeforeAll和@AfterAll默认是添加到静态方法上,@BeforeAll注释的静态方法会在执行所有测试用例之前调用,@AfterAll注释的静态方法会在所有的测试用例完成后调用。它们是在应用级别,应用于所有实例对象。示例:import org.junit.jup
标签:

Java :Observer和Observable废弃原因及解决方案

Observer和Observable在Java 9标记为废弃。废弃原因Observer和Observable有几个原因:1、不能序列化Observable没有实现Serializable接口,它的内部成员变量都是私有的,子类不能通过继承它来对Observable的成员变量处理。所以子类也不能序列化。参考:Why is java.util.Observable&nbs
标签:

解决error: resource android:attr/lStar not found?

用React Native写的项目,构建Android时报错:Execution failed for task ':app:processDevelopmentDebugResources'.> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
标签:

build.gradle转换为maven的pom文件

build.gradle文件示例:repositories { mavenCentral()}group = 'com.example'version = '0.0.1-SNAPSHOT'apply plugin: 'java'dependencies { compile('org.slf4j:slf4j-api') testCompile('junit:junit')} 1
标签:

Spring Security自定义验证失败处理器AuthenticationFailureHandler

Spring Security的AuthenticationManager用来处理验证的请求,处理的结果分两种:验证成功:结果由AuthenticationSuccessHandler处理验证失败:结果由交给AuthenticationFailureHandler处理。在Spring Security内置了几种验证失败处理器:DelegatingAuthenticationFailureHandl

Spirng Security使用注解对方法做权限安全控制

 Spring Security默认情况下是关闭了对方法级的安全控制。可以通过xml或者是在添加了@Configuration注解的bean上添加@EnableGlobalMethodSecurity来开启对方法级别的安全控制。Spring Security 支持三种方法级注解, 分别是 JSR-205/Secured 注解/prePostEnabled,可以在开启对方法级的安全控制时设

Spring MVC获取请求header的方法

在Spring MVC有两种方法可以用来获取请求头Header的值。方法一、通过在方法的参数添加注解@RequestHeader示例如下:@Controllerpublic class RequestHeaderDemoController { @Autowired private HttpServletRequest request; @GetMapping("/prin
标签:

Kotlin使用JUnit 5的@BeforeAll和@AfterAll

Java版本JUnit5 @BeforeAll和@AfterAll的用法可以参考此文。Kotlin的类是没有静态方法的,如果要提供类似于Java的静态方法,可以使用伴生对象(companion object)语法。应用于所有实例的@BeforeAll和@AfterAll把Java版静态方法的JUnit 5 @BeforeAll和@AfterAll使用Kotl
标签:

Maven编译Java10项目报错:LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:testCompile

使用Maven编译一个简单的java 10项目报错,报错的是maven-compiler-plugin:3.7.0插件。插件配置如下:<build>     <plugins>         <plugin>   
标签:

Java判断LocalTime是否在午夜

判断LocalTime是否在午夜,可以判断时间是否在23:59和00:01之间。代码如下:private final LocalTime ONE_MINUTE_BEFORE_MIDNIGHT = LocalTime.of(23, 59, 0); private final LocalTime ONE
标签:

Java 10:类型推断局部变量var

Java 10将新增特性:类型推断的局部变量声明var。java9以及之前的版本,声明一个局部变量需要显式声明它的类型。Java 10引入了新的变量声明关键词var,使用它不需要我们显式声明局部变量的类型,它会自动推断出局部变量的类型。Java 9示例:import java.util.*; public class Java9&nbs
标签:

Java 9的Process和ProcessHandler

Java 9给Process API引入了多种改进,其中新增了ProcessHandler类,它提供了进程相关的信息,如pid,父进程,子进程,进程开始时间以及累计cpu时间等。这里使用Java 9的jshell简单演示下Process的使用:jshell> Process p = new ProcessBuil
标签:

Java 9 Stream新增方法takeWhile的bug

Java 9的Stream新增方法takeWile():允许我们返回Stream里满足条件的前面部分元素。如:String[] arr= {"a", "b", "c","d"}; Arrays.stream(arr)     &n
标签:

Java 9模块声明中requires and requires transitive的区别

可读性(Readability)首先要理解模块的可读性module bar{     requires drink; } bar requires drink意味着:bar模块强制要求存在drink模块,这称为可靠配置。bar模块可以读入drink模块的文件,这称为可读性。bar模块可以访问drink模块的代码,这称为
标签:

理解Java 9的open module(公开模块)

模块化是Java 9新增的一个很重要且影响代码结构的特性。分类根据外部代码在编译时和运行时对模块的访问权限不同分为:常规模块(normal module)和公开模块(open module)。编译时访问比较容易理解,即代码能否显式直接使用模块里的类型,没有权限访问,编译时报错。在运行时访问模块代码是指使用Java里的Core Reflection 
标签:

Java 9:紧凑字符串(Compact String)

目前Java的String实现是把字符串存放在一个char类型的数组里,char占用两个字节(16位)。但是String作为很常用的类,在很多时候它只包含Latin-1里的字符,这些字符只需要一个字节(8位)存储,所以在这种情况下很容易造成空间的浪费。Java 9引进紧凑字符串来解决这个问题。使用UTF-16字符数组,或者是1字节的数组加上一个编码标识符来存储字符串。如果字符串的内容都是
标签:

Java 9:改进的Try-With-Resources

Java 7之前在Java 7之前,在使用一些资源的类时,如BufferedReader,我们要常常提醒自己,必须要在finally块关闭资源。Java 6示例BufferedReader br = null; try {   br = new BufferedReader(ne
标签:

[译]Java 9:一步步迁移项目到Jigsaw(模块化)

Java 9出来了。 我们来试试一个简单的Spring项目。 为了使练习更具挑战性,我们还要尝试使用新的模块系统。 该项目只是一个使用Spring,JDBC和Shedlock的简单示例。1、阅读所有可用的文档和规格说明。 嗯,听起来很无聊。 跳过第一步。2、下载JDK并尝试运行该项目。 我们很幸运,我们所有的依赖只使用公共Jav
标签:

[译]使用JDK 9 Flow API进行响应式编程

什么是响应式编程?响应式编程是关于处理数据项的异步流,也就是应用程序在数据项发生时对其进行响应。 数据流实质上是指随时间发生的数据项序列。与迭代内存数据相比, 这个模型的内存效率更高,因为数据是以流的形式处理的。在响应式编程模型中,有一个Publisher和一个Subscriber。 Publisher发布一个数据流,Subscriber异步订阅。该模型还提供了一种机
标签:

Java 9:List.of()与Arrays.asList()的区别

Java 9新增了List.of的集合工程方法。它与Arrays.asList区别如下:1、Arrays.asList返回的是可变的列表,而List.of返回的是不可变的列表List<Integer> list = Arrays.asList(1, 2, null); list.set(3, 3); //&
标签:

配置JVM查看JIT编译机器码

JVM配置查看JIT编译机器码的选项:-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 查看特定的方法-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*MyClass.myMethod
标签:

JVM调试禁用JIT

JVM禁用JIT:java -Djava.compiler=NONE Main
标签:

JVM 类执行机制:解释执行(interpreter)和编译执行(JIT)

JVM执行字节码有两种方式:解释模式(interpreter)和编译模式(jit)。整个java程序执行过程如下:使用javac把.java源文件编译为字节码,文件一般以.class作为后缀字节码经过JIT环境变量进行判断,是否属于热点代码(多次调用的方法,或循环等)热点代码使用JIT编译为可执行的机器码非热点代码使用解释器解释执行所有字节码解释器将每个Java指令都转译成对等的微处理器指令,并根
标签:

JUnit 5 @BeforeAll和@AfterAll的用法

JUnit 5的@BeforeAll和@AfterAll注释分为两种情景使用:静态方法和实例方法。

静态方法

@BeforeAll和@AfterAll默认是添加到静态方法上,@BeforeAll注释的静态方法会在执行所有测试用例之前调用,@AfterAll注释的静态方法会在所有的测试用例完成后调用。它们是在应用级别,应用于所有实例对象。

示例:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Junit5Test {

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

    @BeforeAll
    static void beforeAll() {
        LOGGER.info("beforeAll called");    
    }

    @Test
    public void aTest1() {
        LOGGER.info("aTest1 called");
        LOGGER.info(this.toString());        
    }

    @Test
    public void aTest2() {
        LOGGER.info("aTest2 called");
        LOGGER.info(this.toString());
    }

    @AfterAll
    static void afterAll() {
        LOGGER.info("afterAll called");        
    }
}

实例方法

@BeforeAll注释的实例方法会在实例所有的测试用例之前调用,@AfterAll注释的实例方法会在所有实例的测试用例之后调用。但如果@BeforeAll或@AfterAll要应用在实例方法上,需要在实例的类上添加注释@TestInstance。

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class Junit5Test {
....
}

Java :Observer和Observable废弃原因及解决方案

Observer和Observable在Java 9标记为废弃。

废弃原因

Observer和Observable有几个原因:

1、不能序列化

Observable没有实现Serializable接口,它的内部成员变量都是私有的,子类不能通过继承它来对Observable的成员变量处理。所以子类也不能序列化。

参考:Why is java.util.Observable class not serializable.

2、不是线程安全

在 java.util.Observable文档里没有强制要求Observable是线程安全的,它允许子类覆盖重写Observable的方法,事件通知无序以及事件通知发生在不同的线程里,这些都是会影响线程安全的问题。

参考:Documentation of java.util.Observable

3、支持事件模型的功能简单

支持事件模型的功能很简单,例如,只是支持事情发生变化的概念,但是不能提供更多哪些内容发生了改变。

参考:deprecate Observer and Observable

解决方案

可以使用java.beans 里的PropertyChangeEvent 和 PropertyChangeListener 来代替目前Observer和Observable的功能。

示例

public class Demo {  
  
  private String name;  
  private PropertyChangeSupport listeners = new PropertyChangeSupport(this);  
    
  public Demo() {  
      this.name= "my name";  
  }  

  public String getName() {  
      return this.name;  
  }  
      
  public void setName(String name) {  
      String oldValue = this.name;  
      this.name= name;  
      //发布监听事件  
      firePropertyChange("name", oldValue, demoName);  
  }  
      
  public void addPropertyChangeListener(PropertyChangeListener listener) {  
      listeners.addPropertyChangeListener(listener);  
  }  
      
  public void removePropertyChangeListener(PropertyChangeListener listener){  
      listeners.removePropertyChangeListener(listener);  
  }  
      
  protected void firePropertyChange(String prop, Object oldValue, Object newValue) {  
      listeners.firePropertyChange(prop, oldValue, newValue);  
  }  
}

public class Main {  
  public static void main(String[] args) {  
    Demo demo= new Demo();  
     demo.addPropertyChangeListener(new PropertyChangeListener(){  
      public void propertyChange(PropertyChangeEvent evt) {  
         System.out.println("OldValue:"+evt.getOldValue());  
        System.out.println("NewValue:"+evt.getNewValue());  
        System.out.println("tPropertyName:"+evt.getPropertyName());  
    }});  
     demo.setName("new Name");  
  }  
}

解决error: resource android:attr/lStar not found?

用React Native写的项目,构建Android时报错:

Execution failed for task ':app:processDevelopmentDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
> Android resource linking failed
.../app/build/intermediates/incremental/mergeDevelopmentDebugResources/merged.dir/values/values.xml:2682: AAPT: error: resource android:attr/lStar not found.

原因时androidx.core:core-ktx导致。解决方案就是把androidx.core:core-ktx指定版本为1.6.0

在应用的build.gradle配置:

configurations.all {
resolutionStrategy {
force 'androidx.core:core-ktx:1.6.0'
}
}

或者在android/build.gradle

implementation 'androidx.core:core:1+

改为

implementation 'androidx.core:core:1.6.0


build.gradle转换为maven的pom文件

build.gradle文件示例:

repositories {
mavenCentral()
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
apply plugin: 'java'
dependencies {
compile('org.slf4j:slf4j-api')
testCompile('junit:junit')
}

1、安装gradle的maven插件

此插件会帮助我们实现build.gradle转换为maven的pom文件。

a、在build.gradle添加如下:

apply plugin: 'maven'

b、执行安装

gradle install

执行完maven插件的安装后,在根目录下默认会生成三个子目录,如下:

  • libs:包含了名为${artifactId}-${version}.jar的jar文件
  • poms: 名为pom-default.xml的pom文件
  • tmp/jar: 包含了manifest信息

其中pom文件内容类似于:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>gradle-to-maven</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

定制maven插件

除了使用默认生成的的jar包名,maven插件也允许对生成的jar名做定制,如

install {
repositories {
mavenInstaller {
pom.version = '0.0.1-maven-SNAPSHOT'
pom.groupId = 'com.example.sample'
pom.artifactId = 'gradle-maven-converter'
}
}
}

生成的结果:

<groupId>com.example.sample</groupId>
<artifactId>gradle-maven-converter</artifactId>
<version>0.0.1-maven-SNAPSHOT</version>


Spring Security自定义验证失败处理器AuthenticationFailureHandler

Spring Security的AuthenticationManager用来处理验证的请求,处理的结果分两种:

  • 验证成功:结果由AuthenticationSuccessHandler处理
  • 验证失败:结果由交给AuthenticationFailureHandler处理。

在Spring Security内置了几种验证失败处理器:

  • DelegatingAuthenticationFailureHandler将AuthenticationException子类委托给不同的AuthenticationFailureHandler,这意味着我们可以为AuthenticationException的不同实例创建不同的行为
  • ExceptionMappingAuthenticationFailureHandler根据AuthenticationException的完整类名将用户重定向到特定的URL
  • SimpleUrlAuthenticationFailureHandler是默认使用的组件,如果指定,它会将用户重定向到failureUrl;否则,它只会返回401响应

自定义AuthenticationFailureHandler

如果想自定义验证失败处理器,需要实现AuthenticationFailureHandler接口。AuthenticationFailureHandler接口源码如下:

package org.springframework.security.web.authentication;
public interface AuthenticationFailureHandler {
/**
* 认证失败时会调用此方法
* @param request 出现认证失败时所处于的请求.
* @param response 对应上面请求的响应对象.
* @param exception 携带认证失败原因的认证失败异常对象
* request.
*/
void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException;
}

实现一个简单的自定义AuthenticationFailureHandler,如下:

public class CustomAuthenticationFailureHandler 
implements AuthenticationFailureHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response,AuthenticationException exception)
throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, Object> data = new HashMap<>();
data.put("exception", exception.getMessage());
response.getOutputStream().println(objectMapper.writeValueAsString(data));
}
}

这里只是简单的返回了一个验证失败的http状态码401,并把异常信息返回。

配置自定义AuthenticationFailureHandler

Spring Security默认是使用SimpleUrlAuthenticationFailureHandler,在配置中修改为自定义的AuthenticationFailureHandler。

@Configuration
@EnableWebSecurity
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("test")
.password("test")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http)
throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.failureHandler(customAuthenticationFailureHandler());
}
@Bean
public AuthenticationFailureHandler customAuthenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
}

可以注意到在HttpSecurity上调用了failureHandler来配置自定义的AuthenticationFailureHandler.

Spirng Security使用注解对方法做权限安全控制

 Spring Security默认情况下是关闭了对方法级的安全控制。可以通过xml或者是在添加了@Configuration注解的bean上添加@EnableGlobalMethodSecurity来开启对方法级别的安全控制。Spring Security 支持三种方法级注解, 分别是 JSR-205/Secured 注解/prePostEnabled,可以在开启对方法级的安全控制时设置。

方法级的安全控制开启

通过xml开启对方法级的安全控制:

<global-method-security secured-annotations="enabled" />
<global-method-security jsr250-annotations="enabled" />
<global-method-security pre-post-annotations="enabled" />

通过注解的方式开启对方法级的安全控制:

//@Secured 注解
@EnableGlobalMethodSecurity(securedEnabled=true)
//JSR-205 注解
@EnableGlobalMethodSecurity(jsr250Enabled=true)
//@PreAuthorize 类型的注解(支持 Spring 表达式)
@EnableGlobalMethodSecurity(prePostEnabled=true)

prePostEnabled支持 Spring EL 表达式,而其他两种注解方式不支持。使用Spring EL表达式可以更灵活地定制一些权限规则。

@Secured注解的使用

只有满足角色的用户才能访问被注解的方法, 否则将会抛出 AccessDenied 异常.

@Secured("ROLE_TELLER","ROLE_ADMIN"), 该方法只允许 ROLE_TELLER 或 ROLE_ADMIN 角色的用户访问.
@Secured("IS_AUTHENTICATED_ANONYMOUSLY"), 该方法允许匿名用户访问.

JSR-205注解的使用

@DenyAll注解, 拒绝所有的访问
@PermitAll 注解, 运行所有访问
@RolesAllowed({"USER","ADMIN"}), 该方法只允许有 ROLE_USER 或 ROLE_ADMIN 角色的用户访问.

PreAuthorize的使用

@PreAuthorize 注解, 在方法调用之前, 基于表达式结果来限制方法的使用.
@PostAuthorize 注解, 允许方法调用, 但是如果表达式结果为 false, 将抛出一个安全性异常.
@PostFilter 注解, 允许方法调用, 但必要按照表达式来过滤方法的结果.
@PreFilter 注解, 允许方法调用, 但必须在进入方法之前过来输入值.

1、使用returnObject对返回值多检查

对于 @PostAuthorize 和 @PostFilter 注解, 可以在表达式中使用 returnObject 保留名, returnObject 代表着被注解方法的返回值, 我们可以使用 returnObject 保留名对注解方法的结果进行验证.

比如:

@PostAuthorize ("returnObject.owner == authentication.name")
public Book getBook();

2、使用#号引用方法参数

在表达式中, 可以使用 #arg 的形式来代表注解方法中的参数 arg.

比如:

@PreAuthorize ("#book.owner == authentication.name")
public void deleteBook(Book book);

使用@P注解或@Param注解给参数设置别名

@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);

3、内置的表达式列表

  • hasRole([role]) :如果有当前角色, 则返回 true(会自动加上 ROLE_ 前缀)
  • hasAnyRole([role1, role2]): 如果有任一角色即可通过校验, 返回true,(会自动加上 ROLE_ 前缀)
  • hasAuthority([authority]) :如果有指定权限, 则返回 true
  • hasAnyAuthority([authority1, authority2]): 如果有任一指定权限, 则返回true
  • principal:获取当前用户的 principal 主体对象
  • authentication:获取当前用户的 authentication 对象,
  • permitAll:总是返回 true, 表示全部允许
  • denyAll:总是返回 false, 代表全部拒绝
  • isAnonymous():如果是匿名访问, 返回true
  • isRememberMe(): 如果是remember-me 自动认证, 则返回 true
  • isAuthenticated(): 如果不是匿名访问, 则返回true
  • isFullAuthenticated(): 如果不是匿名访问或remember-me认证登陆, 则返回true
  • hasPermission(Object target, Object permission)
  • hasPermission(Object target, String targetType, Object permission) 


Spring MVC获取请求header的方法

在Spring MVC有两种方法可以用来获取请求头Header的值。

方法一、通过在方法的参数添加注解@RequestHeader

示例如下:

@Controller
public class RequestHeaderDemoController {
@Autowired
private HttpServletRequest request;
@GetMapping("/printheader")
public String printHeader(@RequestHeader HttpHeaders headers) {
System.out.println("from parameter:" + headers.getFirst("myheader"));
return "demo";
}
}

或者在注解@RequestHeader指定获取的header:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
//..
@Controller
public class DemoController {
@GetMapping(value = "/demo")
public String hello(@RequestHeader(value="myheader") String myheader)
//..
}
}


方法二、从HttpServletRequest获取请求头信息

示例如下:

@Controller
public class RequestHeaderDemoController {
@Autowired
private HttpServletRequest request;
@GetMapping("/printheader")
public String printHeader(HttpServetRequest request) {
System.out.println("from parameter:" + request.getHeader("myheader"));
return "demo";
}
}

Kotlin使用JUnit 5的@BeforeAll和@AfterAll

Java版本JUnit5 @BeforeAll和@AfterAll的用法可以参考此文

Kotlin的类是没有静态方法的,如果要提供类似于Java的静态方法,可以使用伴生对象(companion object)语法。

应用于所有实例的@BeforeAll和@AfterAll

把Java版静态方法的JUnit 5 @BeforeAll和@AfterAll使用Kotlin的伴生对象语法转换如下:

import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.slf4j.LoggerFactory

class Junit5KotlinTest {

    @Test
    fun aTest1() {
        LOGGER.info("aTest1 called")
        LOGGER.info(this.toString())
    }

    @Test
    fun aTest2() {
        LOGGER.info("aTest2 called")
        LOGGER.info(this.toString())
    }

    companion object {
        private val LOGGER = LoggerFactory.getLogger(Junit5KotlinTest::class.java)

        @BeforeAll
        @JvmStatic
        internal fun beforeAll() {
            LOGGER.info("beforeAll called")
        }

        @AfterAll
        @JvmStatic
        internal fun afterAll() {
            LOGGER.info("afterAll called")
        }
    }
}

这里使用了@JvmStatic注释用来标记@BeforeAll和@AfterAll注释的方法是等同于JVM的静态方法。@BeforeAll注释的方法会在所有实例的所有测试用例之前调用。@AfterAll注释的方法会在所有实例的所有测试用例之后调用。

应用于实例的@BeforeAll和@AfterAll

应用在实例方法上那就简单点了,Java版的转换如下:

import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.slf4j.LoggerFactory

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class Junit5KotlinTest {

    private val LOGGER = LoggerFactory.getLogger(Junit5KotlinTest::class.java)

    @BeforeAll
    internal fun beforeAll() {
        LOGGER.info("beforeAll called")
    }

    @Test
    fun aTest1() {
        LOGGER.info("aTest1 called")
        LOGGER.info(this.toString())
    }

    @Test
    fun aTest2() {
        LOGGER.info("aTest2 called")
        LOGGER.info(this.toString())
    }


    @AfterAll
    internal fun afterAll() {
        LOGGER.info("afterAll called")
    }
}

Maven编译Java10项目报错:LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:testCompile

使用Maven编译一个简单的java 10项目报错,报错的是maven-compiler-plugin:3.7.0插件。插件配置如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                <release>10</release>
            </configuration>
        </plugin>
    </plugins>
</build>

错误信息:

org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:testCompile (default-testCompile) on project example: Execution default-testCompile of goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:testCompile failed.
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:213)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:154)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:146)
        at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
        at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
        at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
        at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
        at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:309)
        at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:194)
        at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:107)
        at org.apache.maven.cli.MavenCli.execute(MavenCli.java:993)
        at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:345)
        at org.apache.maven.cli.MavenCli.main(MavenCli.java:191)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
        at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
        at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
        at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
Caused by: org.apache.maven.plugin.PluginExecutionException: Execution default-testCompile of goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:testCompile failed.
        at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:145)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
        ... 20 more
Caused by: java.lang.IllegalArgumentException
        at org.objectweb.asm.ClassReader.<init>(Unknown Source)
        at org.objectweb.asm.ClassReader.<init>(Unknown Source)
        at org.objectweb.asm.ClassReader.<init>(Unknown Source)
        at org.codehaus.plexus.languages.java.jpms.AsmModuleInfoParser.parse(AsmModuleInfoParser.java:80)
        at org.codehaus.plexus.languages.java.jpms.AsmModuleInfoParser.getModuleDescriptor(AsmModuleInfoParser.java:54)
        at org.codehaus.plexus.languages.java.jpms.LocationManager.resolvePaths(LocationManager.java:83)
        at org.apache.maven.plugin.compiler.TestCompilerMojo.preparePaths(TestCompilerMojo.java:281)
        at org.apache.maven.plugin.compiler.AbstractCompilerMojo.execute(AbstractCompilerMojo.java:762)
        at org.apache.maven.plugin.compiler.TestCompilerMojo.execute(TestCompilerMojo.java:176)
        at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:134)
        ... 21 more

原因及解决方法

在错误信息里 org.objectweb.asm.ClassReader.<init>(Unknown Source)的非法参数异常IllegalArgumentException。

原因是maven-compiler-plugin默认依赖了一个旧版本的asm包,它是不支持java 10的。把org.ow2.asm:asm配置最新版(6.1)可以解决,配置如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
        <release>10</release>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>6.1</version>
        </dependency>
    </dependencies>
</plugin>

Java判断LocalTime是否在午夜

判断LocalTime是否在午夜,可以判断时间是否在23:59和00:01之间。

代码如下:

private final LocalTime ONE_MINUTE_BEFORE_MIDNIGHT = LocalTime.of(23, 59, 0);
private final LocalTime ONE_MINUTE_AFTER_MIDNIGHT = LocalTime.of(0, 1, 0);

public boolean isAtMidnight(LocalTime time) {
    return time.isAfter(ONE_MINUTE_BEFORE_MIDNIGHT) || time.isBefore(ONE_MINUTE_AFTER_MIDNIGHT);
}

需要注意的是时间在23:59之后,但在00:01之前这两个条件是或逻辑“||”,而不是与逻辑“&&”这是因为LocalTime.isAfter() 或者 LocalTime.isBefore()都是假设比较的时间是在同一天。

Java 10:类型推断局部变量var

Java 10将新增特性:类型推断的局部变量声明var。

java9以及之前的版本,声明一个局部变量需要显式声明它的类型。Java 10引入了新的变量声明关键词var,使用它不需要我们显式声明局部变量的类型,它会自动推断出局部变量的类型。

Java 9示例:

import java.util.*;

public class Java9 {
  public static void main(String... args) {
    for (String arg : args) {
      System.out.println(arg);
    }

    for (int i = 0; i < args.length; i++) {
      String arg = args[i];
      System.out.println(arg);
    }

    List<String> strings = new ArrayList<>();
    strings.add("hello");
    for (String string : strings) {
    }

    List<String> explicit = new ArrayList<>();

    String name = "Hello";
    name += " World";
    System.out.println("name = " + name);

    int var = 42; // <-- hmmm, wonder what will happen?
  }
}

Java 10 var示例

import java.util.*;

public class Java10 {
  public static void main(String... args) {
    for (var arg : args) {
      System.out.println(arg);
    }

    for (var i = 0; i < args.length; i++) {
      var arg = args[i];
      System.out.println(arg);
    }

    var strings = new ArrayList<String>();
    strings.add("hello");
    for (String string : strings) {
    }

    List<String> explicit = new ArrayList<>();

    var name = "Hello";
    name += " World";
    System.out.println("name = " + name);

    var var = 42; // <-- this works
  }
}

var并不是保留字,在示例里,var var = 42是有效的,这种做法主要是为了兼容旧的版本。

Java 9的Process和ProcessHandler

Java 9给Process API引入了多种改进,其中新增了ProcessHandler类,它提供了进程相关的信息,如pid,父进程,子进程,进程开始时间以及累计cpu时间等。

这里使用Java 9的jshell简单演示下Process的使用:

jshell> Process p = new ProcessBuilder("stress", "--cpu", "4", "--timeout", "5").start();
p ==> Process[pid=5572, exitValue="not exited"]

jshell> p.pid()
$2 ==> 11542

jshell> p.info().user()
$3 ==> Optional[ccs]

jshell> p.info().command()
$4 ==> Optional[/usr/bin/stress]

jshell> p.info().commandLine()
$5 ==> Optional[/usr/bin/stress --cpu 4 --timeout 120]

jshell> Arrays.toString(p.info().arguments().get())
$6 ==> "[--cpu, 4, --timeout, 120]"

jshell> p.info().startInstant()
$7 ==> Optional[2018-02-26T10:31:53.642Z]

jshell> p.info().totalCpuDuration().get().toMillis()
$8 ==> 0

获取所有运行的进程

ProcessHandler提供了一个静态方法allProcesses(),它返回所有进程的一个Stream。输出所有进程示例:

ProcessHandle.allProcesses()
             .map(ProcessHandle::info)
             .map(ProcessHandle.Info::commandLine)
             .flatMap(Optional::stream)
             .forEach(System.out::println)

进程退出触发函数

Process的onExit()方法可以让我们在进程退出时执行一些函数处理:

Process proc = new ProcessBuilder("sleep", "10").start();
proc.onExit()
    .thenAccept(p -> System.out.println("Process " + p.pid() + " exited with " + p.exitValue()));

Java 9 Stream新增方法takeWhile的bug

Java 9的Stream新增方法takeWile():允许我们返回Stream里满足条件的前面部分元素。

如:

String[] arr= {"a", "b", "c","d"};
Arrays.stream(arr)
        .takeWhile(e -> !e.equalsIgnoreCase("c"))
        .forEach(e-> System.out.println(e));

条件为判断字符串和“c”比较,不想等返回true。结果返回a和b,输出:

a
b

bug

但使用flatMap对元素展开返回的元素做takeWhile,返回结果有误:

String[][] ss = {{"a", "b"}, {"c", "d"}}; 
  Stream.of(ss)
    .flatMap(Arrays::stream)
    .takeWhile(e -> e.equalsIgnoreCase("c"))
    .forEachOrdered(System.out::println); 

实际输出:

a
b
d 

与期待输出的a和b不一致。

这个bug会在JDK 10修复:https://bugs.openjdk.java.net/browse/JDK-8193856

Java 9模块声明中requires and requires transitive的区别

可读性(Readability)

首先要理解模块的可读性

module bar{
    requires drink;
}

bar requires drink意味着:

  • bar模块强制要求存在drink模块,这称为可靠配置。
  • bar模块可以读入drink模块的文件,这称为可读性。
  • bar模块可以访问drink模块的代码,这称为可访问性。
module bar{
    requires transitive drink;
}

bar requires transitive drink和bar requires drink 一样,模块bar对模块drink同样具有依赖存在,可读以及可访问的特性。对于模块bar和模块drink来说,这两种写法没有区别。

隐含可读性(Implied readability)

transitive意为传递的意思。如果其他模块如customer依赖于bar模块,bar模块使用requires transitive引用drink模块,那么customer模块没有 直接声明依赖于drink模块,它对drink模块也是具有可读性的,这个称之为隐含可读性。

即bar requires transitive drink,加上customer requires bar,customer对drink也是可读。如果去掉transitive,则customer对drink不可读。

参考:What's the difference between requires and requires transitive statements in Java 9 module declaration

理解Java 9的open module(公开模块)

模块化是Java 9新增的一个很重要且影响代码结构的特性。

分类

根据外部代码在编译时和运行时对模块的访问权限不同分为:常规模块(normal module)和公开模块(open module)。

编译时访问比较容易理解,即代码能否显式直接使用模块里的类型,没有权限访问,编译时报错。在运行时访问模块代码是指使用Java里的Core Reflection API,反射访问。

在一些框架里如Spring Framework,在运行时常常会使用反射机制访问代码。

常规模块

声明常规模块在module前没有关键字open。

module com.example {

}

外部代码访问常规模块,不管是在编译时(compile time)还是在运行时(runtime)都只授权访问模块明确导出的包里的类型。

module com.example {
  exports com.example.service;
}

在这个示例里,外部代码只能访问模块com.example里的com.example.service类型,对于其他没有导出的包不能访问。

另外,即使是模块导出的包,也并不是包里的所有类型都能访问,外部代码只能访问public和protected类型,以及这里类型里的public和protected成员变量和方法。

公开模块

声明公开模块在module前添加关键词open。

open module com.example {

}

外部代码访问公开模块,在编译时只授权访问模块明确导出的包里的类型。而在运行时则能够访问模块里的所有包类型。

open module com.example {
  exports com.example.service;
}

与常规模块一样,在编译时,外部代码只能访问模块导出的com.example.service里的类型,且也只能访问包里的public和protected类型,以及这些类型里的public和protected成员。

在运行时,外部代码则可以访问公开模块的所有包里的类型以及这些类型的成员,不受类型是否为public或protected控制。

opens指令

但有时不需要对模块的所有包都公开,这种情况可以在module的声明体里使用opens指令。

module com.example {
  exports com.example.service;
  opens com.example.service.spi;
}

对于使用opens公开的包,在运行时,外部代码对这些包里的所有类型都是可以使用反射机制访问到的。

注意:opens指令只能在常规模块中使用,不能用在公开模块里。这是比较容易理解的,因为在公开模块里所有的包都是公开的。

Java 9:紧凑字符串(Compact String)

目前Java的String实现是把字符串存放在一个char类型的数组里,char占用两个字节(16位)。但是String作为很常用的类,在很多时候它只包含Latin-1里的字符,这些字符只需要一个字节(8位)存储,所以在这种情况下很容易造成空间的浪费。

Java 9引进紧凑字符串来解决这个问题。

  1. 使用UTF-16字符数组,或者是1字节的数组加上一个编码标识符来存储字符串。如果字符串的内容都是ISO-8859-1/Latin-1字符,则使用ISO-8859-1/Latin-1编码存储字符串,否则使用UTF-16编码存储数组。
  2. 这些只是实现的修改,String的接口没有任何修改。
  3. 紧凑字符串集成在JDK 9里,默认启动。

Java 9:改进的Try-With-Resources

Java 7之前

在Java 7之前,在使用一些资源的类时,如BufferedReader,我们要常常提醒自己,必须要在finally块关闭资源。

Java 6示例

BufferedReader br = null;
try {
  br = new BufferedReader(new FileReader("C:\\test.txt"));
  System.out.println(br.readLine());
} catch (IOException e) {
  e.printStackTrace();
} finally {
  try {
    if (br != null)
      br.close();
  } catch (IOException ex) {
    ex.printStackTrace();
  }
}

在Java 6的这个示例里,这段代码有几点不好:

  1. 代码冗长,不可读
  2. 在finally块了还需要对close()方法添加try-catch
  3. 在调用close()前,总要判断Reader是否为null
  4. 如果忘了在finally关闭资源,容易造成资源泄漏

Java 7

Java 7新增了try-with-resoureces自动管理资源的语法。

Java 7示例

try (BufferedReader br = new BufferedReader(new FileReader("C:\\test.txt"))) {
  System.out.println(br.readLine());
} catch (IOException e) {
  e.printStackTrace();
}

这短短的几行代码很优雅地解决了Java 7之前对资源管理存在的问题。 在try()里声明的资源,用户不需要手动关闭资源,它会在用完后自动关闭。这样不需要在关闭资源时的try-catch以及检查资源是否为null,也很好避免了因为忘记关闭资源可能导致的资源泄漏。

Java 7的try-with-resources语法需要符合以下规则:

  1. 资源类需要实现java.lang.AutoCloseable接口
  2. 资源变量必须在try()声明为本地变量。
  3. 如果资源在Try-With-Resources语句外声明,那么必须在try()里使用本地变量引用外部声明的资源变量

以下例子不正确

BufferedReader br = new BufferedReader(new FileReader("C:\\test.txt"))
try (br) {
  System.out.println(br.readLine());
} catch (IOException e) {
  e.printStackTrace();
}

需要改为

BufferedReader br = new BufferedReader(new FileReader("C:\\test.txt"))
try (BufferedReader localReader = br) {   //重新引用为本地变量
  System.out.println(localReader.readLine());
} catch (IOException e) {
  e.printStackTrace();
}

这也算是Java 7/8的Try-With-Resouces bug。

Java 9

Try-With-Resouces在Java 9的改进就是针对Java7/8里资源必须在try()里声明的问题做的修改。

Java 9改进后的Try-With-Resouces允许资源在Try-With-Resouces外部声明,且不需要在try()里重新引用。

BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
 try (reader) {
   System.out.println(reader.readLine());
 }

[译]Java 9:一步步迁移项目到Jigsaw(模块化)

Java 9出来了。 我们来试试一个简单的Spring项目。 为了使练习更具挑战性,我们还要尝试使用新的模块系统。 该项目只是一个使用Spring,JDBC和Shedlock的简单示例。

1、阅读所有可用的文档和规格说明。 嗯,听起来很无聊。 跳过第一步。

2、下载JDK并尝试运行该项目。 我们很幸运,我们所有的依赖只使用公共Java API,所以它运行有效。 在现实的项目中,你可能不会那么幸运。 一些库使用Java 9中不可用的已移除的内部API。你必须等待库作者来修复它们,这通常是不容易的。 幸运的是,这不是我们的情况。

我们的项目运行在Java 9 JVM上,我们可以在这里停下来。 我们可以利用紧凑的字符串和其他优化,而不再进一步。 但是我们也想使用新的语言功能。

3、切换编译器到Java 9:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
        <source>1.9</source>
        <target>1.9</target>
    </configuration>
</plugin>

仍然有效,很酷。 我们可以在这里使用新的“--release”选项,但不幸的是,IntelliJ还不支持。 再次,我们可以在这里停下来。 现在我们可以使用所有新的语言功能。 但是让我们试用Jigsaw。

4、添加module-info.java,再次运行项目

module shedlock.example {
}

该项目没有编译。 我们收到很多“package XYZ is not visible”的编译错误。 这是正常的 - 我们正在使用外部库,而且我们还没有在module-info中声明这些依赖。 幸运的是,我们有最新的IntelliJ,它会帮我们修复。 这是一些手工工作,我期望它将来能够一键就完成,但现在已经足够好了。 或者,你可以使用jdeps工具

module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
}

请注意,没有库有module-info声明。 我们在这里看到的是从JAR名称自动生成名称的模块。 库作者在发布模块化版本时,某些名称可能会更改。 例如,slf4j.api将很可能被重命名为org.slf4j。 虽然类似这样在项目里不是什么问题。 当我们升级slf4j时,我们也可以改变依赖关系。

但在库文件里,这是不同的。

一旦我们声明依赖,如果没有调整所有的项目就很难改变它,这取决于我们。 这就是为什么Maven警告我们“检测到所需的基于文件名的automodules。请不要将此项目发布到公共仓库!” 现在我们已经声明了所有的依赖项,并且编译了项目! 我们完成了! 对?

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to module spring.core
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:198)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:192)
at spring.core@4.3.7.RELEASE/org.springframework.cglib.core.ReflectUtils$1.run(ReflectUtils.java:54)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at spring.core@4.3.7.RELEASE/org.springframework.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:44)
... 25 more

没那么快。

Java模块在编译期和运行时被强制执行。我已在IntelliJ执行了这个项目,在IntellJ看到module-info.java以及自动使用了模块路径而非类路径,也依次开启了运行时检查。我们得到'module java.base does not "opens java.lang" to module spring.core'。Spring正尝试反射来自模块java.base的类,而模块系统不允许这样做。Spring 5支持Java 9,那么我们更新到最新的Spring 5RC4,但没有用。

我们再次降一下级。

我们可以查阅文档吗?没办法,去Stackoverflow找解答。 我们必须打开java.base模块。 由于我们无法修改,唯一的方法是使用命令行参数。

使用--add-opens java.base/java.lang=spring.core命令行参数。现在spring.core模块可以通过反射访问到了java.base。我们来执行它并转到下一个例外。

Caused by: java.lang.IllegalAccessException: class org.springframework.cglib.proxy.Enhancer (in module spring.core) cannot access class net.javacrumbs.shedlockexample.SpringConfig$EnhancerBySpringCGLIB$81275b04 (in module shedlock.example) because module shedlock.example does not export net.javacrumbs.shedlockexample to module spring.core
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:589)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
at java.base/java.lang.reflect.Field.set(Field.java:778)

Spring正尝试访问我们项目的类,我们得到这个错误'module shedlock.example does not export net.javacrumbs.shedlockexample to module spring.core',我们来做JVM告诉我们的并导出我们唯一的包。

exports net.javacrumbs.shedlockexample to spring.core;

仍然不幸:我们得到'module shedlock.example does not "opens net.javacrumbs.shedlockexample" to module spring.core'。我们导出了编译时的包,但不是用于反射。 到目前为止,模块系统的错误消息一直很好,但是这里有点混乱。我们的代码编译了,但在运行时模块系统却抱怨说我们没有导出包。导出应该只影响编译期,那么我们为什么在运行时得到这错误呢?

原因很简单:Spring使用CGLIB生成我们类的子类,所以编译时和运行时之间的区别有点模糊。 根据规范,“一个普通的模块只能对明确导出或显式公开(或两者)的包进行反射访问。”在运行时生成的类是反射访问吗? 我们试图通过公开来取代导出。 它仍然有效。稍后有一些异常,我们最终得到了一个有效模块声明。

module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
    opens net.javacrumbs.shedlockexample to spring.core, spring.beans, spring.context;
}

酷,但还不完美。 想象一下,你项目中包含更多的包,并且你希望在大多数应用程序中使用Spring,Jackson或其他使用反射的库。 一个接一个地公开它们听起来好像很多工作。 幸运的是,我们可以公开整个模块。

6、使用关键词open公开模块

open module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
}

再次引用规范。“一个公开模块,使用open修饰,在编译时只允许访问输入那些明确导出的包,但在运行时运行访问输入它所有的包,就好像所有的包都导出一样。”这就是 我们需要的。

我们完成了! 这是一个有趣的练习,我们学到了什么呢? 首先,可以将你的项目迁移到模块。 这值得么? 很可能不会。 我建议等到工具和库准备就绪。 在流行的边缘很有趣,但对于一个生产项目,我会给它几个月,直到所有的工具和库都是Java 9准备好,所有的严重错误都是固定的。 还有一点可以弄清楚Jigsaw如何适应整个生态系统。

示例项目里可以看到所有步骤。

[译]使用JDK 9 Flow API进行响应式编程

什么是响应式编程?

响应式编程是关于处理数据项的异步流,也就是应用程序在数据项发生时对其进行响应。 数据流实质上是指随时间发生的数据项序列。与迭代内存数据相比, 这个模型的内存效率更高,因为数据是以流的形式处理的。

在响应式编程模型中,有一个Publisher和一个Subscriber。 Publisher发布一个数据流,Subscriber异步订阅。

该模型还提供了一种机制来引入更高阶的函数,这样就可以使用Processor在流上操作。 Processor转换数据流不需要更改Publisher或Subscriber。 Processor(或一连串的Processor)位于Publisher和Subcriber之间来将一个数据流转换为另一个。 Publisher和Subcriber独立于发生在数据流中的转换。

为什么要响应式编程?

  • 更简洁的代码,使其更具可读性。
  • 从模板代码中抽离出来,专注于业务逻辑。
  • 从底层的线程,同步和并发问题中抽离出来。
  • 流处理意味着高效的内存
  • 该模型几乎可以应用于任何类型的问题。

JDK 9 Flow API

JDK 9中的Flow API与Reactive Streams Specification相对应,这是一个事实上的标准。 Reactive Streams Specification是标准化响应式编程的举措之一。 已经有几个实现支持Reactive Streams Specification。

Flow API(和Reactive Streams API)在某些方面是迭代器模式和观察者模式的组合。 迭代器模式是一个拉式(pull)的模型,应用程序从数据源拉取项目。 观察者模式是一个推送(push)模型,数据源的项目被推送到应用程序。 使用Flow API,应用程序最初请求(拉)N个项目,然后发布者将最多N个项目推送给订阅者。 所以说它是拉和推编程模型的混合。

过一遍Flow API 接口

@FunctionalInterface
public static interface Flow.Publisher<T> {
    public void  subscribe(Flow.Subscriber<? super T> subscriber);
}
  
public static interface Flow.Subscriber<T> {
    public void  onSubscribe(Flow.Subscription subscription);
    public void  onNext(T item);
    public void  onError(Throwable throwable);
    public void  onComplete();
}
  
public static interface Flow.Subscription {
    public void  request(long n);
    public void  cancel();
}
  
public static interface Flow.Processor<T,R>  extends Flow.Subscriber<T>, Flow.Publisher<R> {
}

订阅者Subscriber

Subscriber订阅Publisher的回调。 除非有请求,数据项目是不会被推送到订阅者,但可能会请求多个项目。 对于给定订阅(Subscription),调用Subscriber的方法是严格按顺序的。 应用程序可以响应订阅者上的以下回调。

onSubscribe

对于给定的订阅,在调用任何其他Subcriber方法之前调用此方法。

onNext

订阅下一个项目调用此方法

onError

在Publisher或Subcriber遇到不可恢复的错误时调用此方法,之后Subscription不会再调用Subscriber其他的方法。

如果Publisher遇到不允许将项目发送给Subscriber的错误,则Subscriber会收到onError消息,然后不会再收到其他消息。

onComplete

当已知不会再额外调用Subscriber的方法,且没有发生有错误而导致终止订阅,调用此方法。之后Subscription不会调用其它Subscriber的方法。

当知道没有更多的消息发送给它时,订阅者收到onComplete。

Subscriber示例

import java.util.concurrent.Flow.*;
...

public class MySubscriber<T> implements Subscriber<T> {
    private Subscription subscription;

    @Override
    public void onSubscribe(Subscription subscription) {
        this.subscription = subscription;
        subscription.request(1); //a value of  Long.MAX_VALUE may be considered as effectively unbounded  
    }

    @Override
    public void onNext(T item) {
        System.out.println("Got : " + item);
        subscription.request(1); //a value of  Long.MAX_VALUE may be considered as effectively unbounded  
    }

    @Override
    public void onError(Throwable t) {
        t.printStackTrace();
    }

    @Override
    public void onComplete() {
        System.out.println("Done");
    }
} 

 

发布者Publisher

发布者将数据流发布给注册的订阅者。 它通常使用Excutor异步发布项目给订阅者。 Publisher确保每个订阅的Subcriber方法严格按顺序调用。

使用JDK的SubmissionPublisher将数据流发布给订阅者的示例

import java.util.concurrent.SubmissionPublisher;
...

//Create Publisher
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();

//Register Subscriber
MySubscriber<String> subscriber = new MySubscriber<>();
publisher.subscribe(subscriber);

//Publish items
System.out.println("Publishing Items...");
String[] items = {"1", "x", "2", "x", "3", "x"};
Arrays.asList(items).stream().forEach(i -> publisher.submit(i));
publisher.close(); 

订阅Subscription

连接Flow.Publisher和Flow.Subscriber。 Subscriber只有在请求时才会收到项目,并可能随时通过Subscription取消订阅。

方法

  • request:将给定数量的n个项目添加到当前未完成的此订阅需求中。
  • cancel:导致Subscriber(最终)停止接收消息。

处理器Processor

充当Subscriber和Publisher的组件。 处理器位于Publisher和Subscriber之间,它把一个流转换为另一个。 可能有一个或多个链接在一起的处理器,链中最后处理器的结果由Subscriber处理。 JDK没有提供任何具体的处理器,因此需要单独编写任何需要的处理器。

将字符串转换为整数的处理器示例

import java.util.concurrent.Flow.*;
import java.util.concurrent.SubmissionPublisher;
...

public class MyTransformProcessor<T,R> extends SubmissionPublisher<R> implements Processor<T, R> {

    private Function function;
    private Subscription subscription;

    public MyTransformProcessor(Function<? super T, ? extends R> function) {
        super();
        this.function = function;
    }

    @Override
    public void onSubscribe(Subscription subscription) {
        this.subscription = subscription;
        subscription.request(1);
    }

    @Override
    public void onNext(T item) {
        submit((R) function.apply(item));
        subscription.request(1);
    }

    @Override
    public void onError(Throwable t) {
        t.printStackTrace();
    }

    @Override
    public void onComplete() {
        close();
    }
}  

使用处理器转换数据流的示例代码

import java.util.concurrent.SubmissionPublisher;
...

//Create Publisher  
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();

//Create Processor and Subscriber  
MyFilterProcessor<String, String> filterProcessor = new MyFilterProcessor<>(s -> s.equals("x"));

MyTransformProcessor<String, Integer> transformProcessor = new MyTransformProcessor<>(s -> Integer.parseInt(s));

MySubscriber<Integer> subscriber = new MySubscriber<>();

//Chain Processor and Subscriber  
publisher.subscribe(filterProcessor);
filterProcessor.subscribe(transformProcessor);
transformProcessor.subscribe(subscriber);

System.out.println("Publishing Items...");
String[] items = {"1", "x", "2", "x", "3", "x"};
Arrays.asList(items).stream().forEach(i -> publisher.submit(i));
publisher.close();

背压

当Publisher以比Subscriber所消费数据快得多的速率产生背压。 未处理项目的缓冲区的大小可能受到限制。 Flow API没有提供任何API来发信号或处理背压,但是可以有多种策略由自己来处理背压。 看看RxJava如何处理背压。

总结

将响应式编程的API添加到JDK 9是一个好的开始。 许多其他产品也开始提供响应式编程的API来访问其功能。 尽管Flow API允许程序员开始编写响应式程序,但是生态系统仍然需要发展。

例如,响应式程序可能仍然使用传统的API来访问数据库,也许是因为并不是所有的数据库都支持用于响应式编程的API。 即响应式程序依赖的API可能还不支持响应式编程模型。

参考

原文:Reactive Programming with JDK 9 Flow API

Java 9:List.of()与Arrays.asList()的区别

Java 9新增了List.of的集合工程方法。它与Arrays.asList区别如下:

1、Arrays.asList返回的是可变的列表,而List.of返回的是不可变的列表

List<Integer> list = Arrays.asList(1, 2, null);
list.set(3, 3); // 允许

List<Integer> list = List.of(1, 2, 3);
list.set(3, 10); // 不允许

2、Arrays.asList运行null元素,而List.of则不允许

List<Integer> list = Arrays.asList(1, 2, null); // 允许
List<Integer> list = List.of(1, 2, null); // 这里会抛出NullPointException

3、Arrays.asList返回的列表使用contains可以来检查null,而List.of不能检查null,会报NullPointException.

List<Integer> list = Arrays.asList(1, 2, 3);
list.contains(null); // 返回false

List<Integer> list = List.of(1, 2, 3);
list.contains(null); // 这里会抛出NullPointException

4、Arrays.asList返回的是原来数组的视图,对原来数组做修改会放映到列表中,而List.of不会。

Integer[] array = {1,2,3};
List<Integer> list = Arrays.asList(array);
array[1] = 10;
System.out.println(list); // 输出[1,10,3],第二个元素已被修改为10

Integer[] array = {1,2,3};
List<Integer> list = List.of(array);
array[1] = 10;
System.out.println(list); // 输出 [1, 2, 3]

配置JVM查看JIT编译机器码

JVM配置查看JIT编译机器码的选项:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

查看特定的方法

-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*MyClass.myMethod

JVM调试禁用JIT

JVM禁用JIT:

java -Djava.compiler=NONE Main

JVM 类执行机制:解释执行(interpreter)和编译执行(JIT)

JVM执行字节码有两种方式:解释模式(interpreter)和编译模式(jit)。

整个java程序执行过程如下:

  1. 使用javac把.java源文件编译为字节码,文件一般以.class作为后缀
  2. 字节码经过JIT环境变量进行判断,是否属于热点代码(多次调用的方法,或循环等)
  3. 热点代码使用JIT编译为可执行的机器码
  4. 非热点代码使用解释器解释执行所有字节码

解释器将每个Java指令都转译成对等的微处理器指令,并根据转译后的指令先后次序依序执行,由于一个Java指令可能被转译成十几或数十几个对等的微处理器指令,这种模式执行的速度相当缓慢。 

Sun公司为了解决解释器的执行慢的问题,引入了JIT技术。JIT针对一个具体的class进行编译,经过编译后的程序,被优化成相当精简的原生型指令码。

示例

public static void main(String[] args) {
    long start = System.nanoTime();

    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++){
    }
    long finish = System.nanoTime();
    long d = (finish - start) / 1000000;

    System.out.println("Used " + d);
}

上面的循环,使用JIT只需要极少的时间,而禁用JIT后执行速度慢了很多。

$ javac Demo.java
$ java Demo
Used 6
$ java -Djava.compiler=NONE Demo
Used 100030

其中添加-Djava.compiler禁用了JIT,速度相对使用JIT大大降低。