Skip to content

Commit

Permalink
Automatically exposes jdk modules (#1340)
Browse files Browse the repository at this point in the history
* dynamic export

Signed-off-by: JermaineHua <[email protected]>

* Add test for export all

Signed-off-by: JermaineHua <[email protected]>

* Support auto module switch and uts

Signed-off-by: JermaineHua <[email protected]>

* Auto module export with reentrant protection

Signed-off-by: JermaineHua <[email protected]>

---------

Signed-off-by: JermaineHua <[email protected]>
  • Loading branch information
CrazyHZM authored Sep 23, 2024
1 parent 6b504ca commit 5ca7e69
Show file tree
Hide file tree
Showing 7 changed files with 496 additions and 1 deletion.
6 changes: 6 additions & 0 deletions sofa-boot-project/sofa-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alipay.sofa.boot.Initializer;

import com.alipay.sofa.boot.util.ModuleUtil;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

/**
* @author huazhongming
* @since 4.4.0
*/
public class AutoModuleExportApplicationContextInitializer
implements
ApplicationContextInitializer<ConfigurableApplicationContext> {

private static final String AUTO_MODULE_JDK_ENABLE_KEY = "sofa.boot.auto.module.export.jdk.enable";
private static final String AUTO_MODULE_ALL_ENABLE_KEY = "sofa.boot.auto.module.export.all.enable";

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
if (isEnable(applicationContext, AUTO_MODULE_ALL_ENABLE_KEY, "false")) {
ModuleUtil.exportAllModulePackageToAll();
} else if (isEnable(applicationContext, AUTO_MODULE_JDK_ENABLE_KEY, "true")) {
ModuleUtil.exportAllJDKModulePackageToAll();
}
}

protected boolean isEnable(ConfigurableApplicationContext applicationContext, String key,
String defaultValue) {
String switchStr = applicationContext.getEnvironment().getProperty(key, defaultValue);
return Boolean.parseBoolean(switchStr);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alipay.sofa.boot.util;

import com.alipay.sofa.boot.log.SofaBootLoggerFactory;
import org.slf4j.Logger;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* @author huazhongming
* @since 4.4.0
*/
public class ModuleUtil {

private static final Logger LOGGER = SofaBootLoggerFactory
.getLogger(ModuleUtil.class);

private static final MethodHandle implAddOpensToAllUnnamed;

private static final MethodHandle implAddOpens;

private static final MethodHandle implAddExportsToAllUnnamed;

private static final MethodHandle implAddExports;

private static final Map<String, Module> nameToModules;

private static final AtomicBoolean isExported = new AtomicBoolean(false);

static {
implAddOpensToAllUnnamed = createModuleMethodHandle("implAddOpensToAllUnnamed",
String.class);
implAddOpens = createModuleMethodHandle("implAddOpens", String.class);
implAddExportsToAllUnnamed = createModuleMethodHandle("implAddExportsToAllUnnamed",
String.class);
implAddExports = createModuleMethodHandle("implAddExports", String.class);
nameToModules = getNameToModule();
}

/**
* Export all JDK module packages to all.
*/
public static void exportAllJDKModulePackageToAll() {
try {
if (isExported.compareAndSet(false,true) && nameToModules != null) {
nameToModules.forEach((name, module) -> module.getPackages().forEach(pkgName -> {
if (isJDKModulePackage(pkgName)) {
addOpensToAll(module, pkgName);
addExportsToAll(module, pkgName);
}
}));
}
} catch (Throwable t) {
LOGGER.error("Failed to export all JDK module package to all", t);
}
}

private static boolean isJDKModulePackage(String modulePackageName) {
return modulePackageName.startsWith("java.") || modulePackageName.startsWith("jdk.");
}

/**
* Export all module packages to all.
*/
public static void exportAllModulePackageToAll() {
try {
if (isExported.compareAndSet(false,true) && nameToModules != null) {
nameToModules.forEach((name, module) -> module.getPackages().forEach(pkgName -> {
addOpensToAll(module, pkgName);
addExportsToAll(module, pkgName);
}));
}
} catch (Throwable t) {
LOGGER.error("Failed to export all module package to all", t);
}
}

/**
* Updates this module to open a package to all unnamed modules.
*
* @param moduleName
* @param packageName
*/
public static boolean addOpensToAllUnnamed(String moduleName, String packageName) {
return invokeModuleMethod(implAddOpensToAllUnnamed, moduleName, packageName);
}

/**
* Updates this module to open a package to all unnamed modules.
*
* @param module
* @param packageName
*/
public static boolean addOpensToAllUnnamed(Module module, String packageName) {
return invokeModuleMethod(implAddOpensToAllUnnamed, module, packageName);
}

/**
* Updates this module to export a package to all unnamed modules.
*
* @param moduleName
* @param packageName
*/
public static boolean addExportsToAllUnnamed(String moduleName, String packageName) {
return invokeModuleMethod(implAddExportsToAllUnnamed, moduleName, packageName);
}

/**
* Updates this module to export a package to all unnamed modules.
*
* @param module
* @param packageName
*/
public static boolean addExportsToAllUnnamed(Module module, String packageName) {
return invokeModuleMethod(implAddExportsToAllUnnamed, module, packageName);
}

/**
* Updates this module to open a package to another module.
*
* @param moduleName
* @param packageName
*/
public static boolean addOpensToAll(String moduleName, String packageName) {

return invokeModuleMethod(implAddOpens, moduleName, packageName);
}

/**
* Updates this module to open a package to another module.
*
* @param module
* @param packageName
*/
public static boolean addOpensToAll(Module module, String packageName) {

return invokeModuleMethod(implAddOpens, module, packageName);
}

/**
* Updates this module to export a package unconditionally.
* @param moduleName
* @param packageName
*/
public static boolean addExportsToAll(String moduleName, String packageName) {
return invokeModuleMethod(implAddExports, moduleName, packageName);
}

/**
* Updates this module to export a package unconditionally.
* @param module
* @param packageName
*/
public static boolean addExportsToAll(Module module, String packageName) {
return invokeModuleMethod(implAddExports, module, packageName);
}

/**
* invoke ModuleLayer method
*
* @param method
* @param moduleName
* @param packageName
* @return
*/
public static boolean invokeModuleMethod(MethodHandle method, String moduleName,
String packageName) {
Optional<Module> findModule = ModuleLayer.boot().findModule(moduleName);
if (findModule.isPresent()) {
try {
return invokeModuleMethod(method, findModule.get(), packageName);
} catch (Throwable t) {
LOGGER.error("Failed to invoke ModuleLayer method: {}", method, t);
}
}
return false;
}

public static boolean invokeModuleMethod(MethodHandle method, Module module, String packageName) {
try {
method.invoke(module, packageName);
return true;
} catch (Throwable t) {
LOGGER.error("Failed to invoke Module method: {}", method, t);
}
return false;
}

/**
* create MethodHandle from Module
*
* @param methodName
* @param parameterTypes
* @return MethodHandle
*/
private static MethodHandle createModuleMethodHandle(String methodName,
Class<?>... parameterTypes) {
try {
return UnsafeUtil.implLookup().unreflect(
Module.class.getDeclaredMethod(methodName, parameterTypes));
} catch (Throwable t) {
LOGGER.error("Failed to create Module method handle: {}", methodName, t);
}
return null;
}

/**
* Get ModuleLayer.bootLayer field value
*
* @param fieldName
* @return field value
*/
private static Object getModuleLayerFieldsValue(String fieldName) {
ModuleLayer moduleLayer = ModuleLayer.boot();
try {
Class<ModuleLayer> moduleLayerClass = ModuleLayer.class;
Field field = moduleLayerClass.getDeclaredField(fieldName);
return UnsafeUtil.implLookup().unreflectVarHandle(field).get(moduleLayer);
} catch (Throwable t) {
LOGGER.error("Failed to get ModuleLayer field value: {}", fieldName, t);
}
return null;
}

/**
* Get all modules from System.bootLayer
*
* @return modules
*/
@SuppressWarnings("unchecked")
public static Map<String, Module> getNameToModule() {
return (Map<String, Module>) getModuleLayerFieldsValue("nameToModule");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alipay.sofa.boot.util;

import sun.misc.Unsafe;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;

/**
* @author huazhongming
* @since 4.4.0
*/
public class UnsafeUtil {
private static Unsafe UNSAFE;
private static MethodHandles.Lookup IMPL_LOOKUP;

public static Unsafe unsafe() {
if (UNSAFE == null) {
Unsafe unsafe = null;
try {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
unsafe = (Unsafe) theUnsafeField.get(null);
} catch (Throwable ignored) {
}
UNSAFE = unsafe;
}

return UNSAFE;
}

public static MethodHandles.Lookup implLookup() {
if (IMPL_LOOKUP == null) {
Class<MethodHandles.Lookup> lookupClass = MethodHandles.Lookup.class;

try {
Field implLookupField = lookupClass.getDeclaredField("IMPL_LOOKUP");
long offset = unsafe().staticFieldOffset(implLookupField);
IMPL_LOOKUP = (MethodHandles.Lookup) unsafe().getObject(
unsafe().staticFieldBase(implLookupField), offset);
} catch (Throwable ignored) {
}
}
return IMPL_LOOKUP;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ org.springframework.boot.SpringApplicationRunListener=\

# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.alipay.sofa.boot.compatibility.CompatibilityVerifierApplicationContextInitializer
com.alipay.sofa.boot.compatibility.CompatibilityVerifierApplicationContextInitializer,\
com.alipay.sofa.boot.Initializer.AutoModuleExportApplicationContextInitializer
Loading

0 comments on commit 5ca7e69

Please sign in to comment.