J2Cache serializer reflection exception and --illegal-access solution under JDK11

problem phenomenon

When deploying an application online recently, the following exceptions were found:

Failed to instantiate [net.oschina.j2cache.CacheChannel]: Factory method 'cacheChannel' threw exception; nested exception is java.lang.ExceptionInInitializerError
	...
Caused by: java.lang.ExceptionInInitializerError: null
	...
Caused by: java.lang.reflect.InaccessibleObjectException: 
`Unable to make field private final byte[] java.lang.String.value accessible: 
module java.base does not "opens java.lang" to unnamed module @1b70203f`

The online application uses OpenJdk11, and integrates J2cache and serialized json. The relevant configuration is as follows:

Note:
At first, the serialization method used fastjson, but because the myObj.md5 attribute was not serialized during serialization, the attribute was empty when read again, resulting in a program exception. class MyObj { //This attribute does not provide a getter
/    setter Method   private String md5;   . . . . . } After successively trying to switch serialization methods fst and json, I finally found that the json method supports serialization of the myObj.md5 attribute, so the json serialization method was adopted.




j2cache:
  # Cache Serialization Provider
  # values:
  # fst -> using fast-serialization (recommend)
  # kryo -> using kryo serialization
  # json -> using fst's json serialization (testing)
  # fastjson -> using fastjson serialization (embed non-static class not support)
  # java -> java standard
  # fse -> using fse serialization
  # [classname implements Serializer]
  serialization: json

When the program is started locally, the following warning will appear. Since it did not affect the running of the program, it did not pay much attention at first:

WARNING: An illegal reflective access operation has occurred
WARNING: `Illegal reflective access by org.nustaq.serialization.FSTClazzInfo (file:/D:/mvn_repository/de/ruedigermoeller/fst/2.57/fst-2.57.jar) to field java.lang.String.value`
WARNING: Please consider reporting this to the maintainers of org.nustaq.serialization.FSTClazzInfo
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

When the online application starts, the following startup parameters are set:

java -jar myApp.jar
--illegal-access=deny
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED

root of the problem

The reason why the online application fails to start is because of the settings --illegal-access=deny.

The concept introduced after Java9 module模块, and the difference module模块is not allowed to use reflection to access 非public的字段/方法/构造函数(field/method/constructor), unless the access is 目标模块set for reflection open, which allows itself to be non-public reflection access by other modules.

An example of code that uses reflection to access non-public (non-public) is as follows:

package com.module1;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class PropertyUtil {
    
    

  public static Map<String, Object> getProperties(Object object) throws IllegalAccessException {
    
    
      Class<?> theClass = object.getClass();
      Field[] declaredFields = theClass.getDeclaredFields();
      Map<String, Object> fieldsMap = new HashMap<>();
      for (Field declaredField : declaredFields) {
    
    
      
          //此处即为non-public field的反射访问
          //后续在其他模块如module2.app调用此getProperties(objInModule2)即存在跨模块non-public反射访问
          declaredField.setAccessible(true);
              
          Object o = declaredField.get(object);
          fieldsMap.put(declaredField.getName(), o);
      }
      return fieldsMap;
  }
}

An example of open non-public reflective access between modules is as follows:

//模块1
module module1.app {
    
    
  exports com.module1;
}

//模块2
module module2.app {
    
    
    //开放模块module2的com.module2包给模块module1.app,
    //即模块module2.app允许模块module1.app对模块2的com.module2包下对象进行non-public反射访问,
    //再简单点:[模块2]中的[包com.module2]允许被[模块1]进行non-public反射访问
    //等同于: --add-opens module2.app/com.module2=module1.app
    opens com.module2 to module1.app;
    requires module1.app;
}

While the code developed using Jdk8 (without the module concept) runs in Java9+, the Jdk8 code will be automatically classified into 未命名的模块 unnamed module, as in the previous log unnamed module @1b70203f, it can be used to ALL-UNNAMEDrepresent all unnamed modules.

A new JVM option was introduced after Java 9 --illegal-access, which has four optional values:

  • permit : The default value of Jdk9 and Jdk11, which allows non-public reflective access between different modules, and only gives a warning for the first illegal access, and no subsequent warnings. In this mode, the code of Jdk8 (or lower version) will be automatically set to opens, that is, the code of Jdk8 is allowed to be accessed by non-public reflection by other modules, and the code of Jdk8 is also allowed to be accessed by non-public reflection of other modules. In this way, Java programs with mixed versions of high and low versions can be guaranteed to run normally as usual.
  • warn : similar to permit, but every illegal access will warn
  • debug : Similar to warn, but adds a function similar to e.printStackTrace() on the basis of warn
  • deny : The future default value, prohibits all non-public reflective access between different modules, and throws an exception if illegal reflection occurs , except for modules excluded by special command line parameters, such as using –add-opens to exclude certain modules using which can be accessed through illegal reflection

It is used online--illegal-access=deny , so when illegal reflection occurs, the program will throw an exception and fail to start. When running the program in the local development environment
, if the –illegal-access is not explicitly set, the default is used , so it can be started successfully, but the first illegal reflection A warning was given when visiting.--illegal-access=premit

To sum up, when it is set --illegal-access=deny(推荐设置deny,兼容未来Java版本), it needs to be added at the same time --add-opensto enable the corresponding 模块/包illegal 其他模块(non-public) reflective access.

For example, according to the previous log:

Unable to make field private final byte[] java.lang.String.value accessible:      
module java.base does not "opens java.lang" to unnamed module @1b70203f     

After the key prompt log is disassembled, the table is as follows:

The name of the accessed module Accessed package name The name of the module that initiated the illegal access
module java.base does not “opens java.lang” to unnamed module @1b70203f

Specific conversion format:--add-opens 被访问模块名/被访问包名=发起非法访问的模块名

According to the above log, the following --add-openscommand is converted into:

--add-opens java.base/java.long=ALL-UNNAMED

Note: ALL-UNNAMED Indicates all unnamed modules

Restart repeatedly and follow the prompts to finally sort out the complete --add-opensoptions as follows:

java -jar myApp.jar
--illegal-access=deny
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.text=ALL-UNNAMED

After adding the above complete startup options, the online program can start normally.

change your mind

The previous --add-openspass did guarantee the successful start of the online program, but --add-opensis this a bit too much?
Since it is an illegal reflection introduced by the J2Cache serializer, replace it with a custom serializer that does not introduce illegal reflection access.

The custom J2Cache Jackson serializer code is as follows:

package com.myapp.serializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import net.oschina.j2cache.util.Serializer;

import java.io.IOException;

/**
 * J2Cache Jackson序列化器
 *
 * @author luohq
 * @date 2023-03-15 15:48
 */
public class J2cacheJacksonSerializer implements Serializer {
    
    

    private final ObjectMapper om;

    public J2cacheJacksonSerializer() {
    
    
        this.om = new ObjectMapper();
        //设置可见性 - 全部属性、全部权限
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //设置序列化Json中包含对象类型
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        //忽略空Bean转json的错误
        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        //忽略未知属性,防止json字符串中存在,java对象中不存在对应属性的情况出现错误
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        //注册一个时间序列化及反序列化的处理模块,用于解决jdk8中localDateTime等的序列化问题
        om.registerModule(new JavaTimeModule());

    }

    @Override
    public String name() {
    
    
        return "jackson";
    }

    @Override
    public byte[] serialize(Object obj) throws IOException {
    
    
        return om.writeValueAsBytes(obj);
    }

    @Override
    public Object deserialize(byte[] bytes) throws IOException {
    
    
        return om.readValue(bytes, Object.class);
    }
}

Configure J2Cache to use a custom serializer:

j2cache:
  # Cache Serialization Provider
  # values:
  # fst -> using fast-serialization (recommend)
  # kryo -> using kryo serialization
  # json -> using fst's json serialization (testing)
  # fastjson -> using fastjson serialization (embed non-static class not support)
  # java -> java standard
  # fse -> using fse serialization
  # [classname implements Serializer]
  serialization: com.myapp.serializer.J2cacheJacksonSerializer

In summary, the program can be started normally by the following command:

java -jar myApp.jar --illegal-access=deny

References:
https://www.logicbig.com/tutorials/core-java-tutorial/modules/reflective-access.html
https://www.logicbig.com/tutorials/core-java-tutorial/modules/illegal-access -operations.html
https://www.logicbig.com/tutorials/core-java-tutorial/modules/unnamed-modules.html
Solve illegal reflection access warnings above JDK9

Guess you like

Origin blog.csdn.net/luo15242208310/article/details/129560283