摘要:但是模塊化當中,無法掃描只有模塊中可以使用有兩種解決方案定義一個專門的資源模塊,并使用提供的接口,實現它,并將這個實現注冊為服務。有兩種方式使用或包名,包名模塊名使用運行時動態。
模塊設計的原則:
1、防止出現編譯時循環依賴(主要是編譯器不支持),但運行時是允許循環依賴的,比如GUI應用
2、明確模塊的邊界
幾種模塊設計:
API模塊,聚合模塊(比如java.base)
兩種方式:
1、可選的編譯時依賴(類似于maven的provided scope)聲明: requires static , requires transitive static
2、使用services模式,缺點就是需要使用侵入性的ServiceLoader API
module framework { requires static fastjsonlib; }
public static void main(String... args) { try { Class> clazz = Class.forName("javamodularity.fastjsonlib.FastJson"); FastJson instance = (FastJson) clazz.getConstructor().newInstance(); System.out.println("Using FastJson"); } catch (ReflectiveOperationException e) { System.out.println("Oops, we need a fallback!"); } }
注意,通過requires static聲明后,運行時,即使fastjsonlib模塊在模塊路徑中,仍然會跑到異常塊中,因為requies static聲明的模塊不會出現在模塊解析路徑上。除非你通過jlink打包時,加入--add-modules fastjsonlib選項來顯式將其添加到模塊解析路徑(通過--add-modules也是作為一個root module).
使用Services模式的可選依賴請參考之前的對于Services的探討
Versioned Modulesjar命令打包時可以通過 --module-version=
所以如果需要版本化,還是得借助于Maven,Gradle之類的打包工具。
分模塊內資源訪問、模塊間資源訪問
模塊內資源訪問firstresourcemodule/
├── javamodularity
│?? └── firstresourcemodule
│?? ├── ResourcesInModule.java
│?? ├── ResourcesOtherModule.java
│?? └── resource_in_package.txt 包內資源
├── module-info.java
└── top_level_resource.txt 與module-info.java平級的資源
訪問方式有幾種,見下面代碼:
public class ResourcesInModule { public static void main(String... args) throws Exception { Class clazz = ResourcesInModule.class; InputStream cz_pkg = clazz.getResourceAsStream("resource_in_package.txt"); //<1> URL cz_tl = clazz.getResource("/top_level_resource.txt"); //<2> Module m = clazz.getModule(); //<3> InputStream m_pkg = m.getResourceAsStream( "javamodularity/firstresourcemodule/resource_in_package.txt"); //<4> InputStream m_tl = m.getResourceAsStream("top_level_resource.txt"); //<5> assert Stream.of(cz_pkg, cz_tl, m_pkg, m_tl) .noneMatch(Objects::isNull); } }
在模塊化中,不推薦使用ClassLoder::getResource*
注意上面代碼中用到了Module API
.
├── firstresourcemodule
│?? ├── javamodularity
│?? │?? └── firstresourcemodule
│?? │?? ├── ResourcesInModule.java
│?? │?? ├── ResourcesOtherModule.java
│?? │?? └── resource_in_package.txt
│?? ├── module-info.java
│?? └── top_level_resource.txt
└── secondresourcemodule
├── META-INF │?? └── resource_in_metainf.txt ├── foo │?? └── foo.txt ├── javamodularity │?? └── secondresourcemodule │?? ├── A.java │?? └── resource_in_package2.txt ├── module-info.java └── top_level_resource2.txt
注意,下面代碼的前提是兩個模塊的包都沒暴露給對方
public class ResourcesOtherModule { public static void main(String... args) throws Exception { OptionalotherModule = ModuleLayer.boot().findModule("secondresourcemodule"); //<1> otherModule.ifPresent(other -> { try { InputStream m_tl = other.getResourceAsStream("top_level_resource2.txt"); //<2> InputStream m_pkg = other.getResourceAsStream( "javamodularity/secondresourcemodule/resource_in_package2.txt"); //<3> InputStream m_class = other.getResourceAsStream( "javamodularity/secondresourcemodule/A.class"); //<4> InputStream m_meta = other.getResourceAsStream("META-INF/resource_in_metainf.txt"); //<5> InputStream cz_pkg = Class.forName("javamodularity.secondresourcemodule.A") .getResourceAsStream("resource_in_package2.txt"); //<6> assert Stream.of(m_tl, m_class, m_meta) .noneMatch(Objects::isNull); assert Stream.of(m_pkg, cz_pkg) .allMatch(Objects::isNull); } catch (Exception e) { throw new RuntimeException(e); } }); } }
請注意<1>中的ModuleLayer.boot() API
<2>說明了模塊中的top-level資源總是可以被其他模塊訪問的
<3>將得到null,因為模塊2的包沒有開放給模塊1,模塊包中的資源訪問遵循模塊的封裝原則
<4>將返回結果,上面提到資源訪問遵循模塊封裝原則,但對于.class文件除外。(想想也是,因為是允許運行時獲取到別的模塊封裝的Class對象,只是不允許反射調用相關方法)
<5>由于META-INF不是一個包,所以其不會遵循模塊封裝原則,換言之,也像top-level資源一樣,是可以被其他模塊訪問的。
<6>Class.forName會正常調用,不過接著調用的.getResourceAsStream會返回null,就像<3>說明的一樣。
記住一個原則:資源封裝只針對包下的(除.class外,包下的.class文件也可以被其他模塊訪問),其余的不會有封裝。
那么問題來了,如果我真的很想公開包下的資源給其他模塊呢?
使用open module或者opens 包名,比如:
open module aaa{ ... } module aaa{ opens a.b.c }ResourceBundle
我們知道jdk有個i18n資源加載API: ResourceBundle。它的行為是掃描classpath中的所有資源,只要符合baseName和Local即可加載到。
但是java9模塊化當中,無法掃描classpath,只有模塊中可以使用ResourceBundle::getBundle
有兩種解決方案:
1、定義一個專門的i18n資源模塊,并open module
2、使用java9提供的ResourceBundleProvider接口,實現它,并將這個實現注冊為服務。
深度反射與淺反射的區別:淺反射只是獲取基本的類信息,比如字段名,方法上的注解等,而深度反射會進行字段賦值,方法調用等。
模塊化強封裝帶來的問題就是,我們沒法使用深度反射,比如對一個exports包中的某個公開類的private域進行反射調用,field.setAccessible(true)之類的就會出現異常;對非exports包中的類進行任何深度反射都是非法的。
那么我們熟悉的ORM框架,IOC框架等都廣泛地使用了深度反射。這就會導致問題。如何解決?使用Services肯定是不行的,因為框架本身改動成本就會很大,沒幾個愿意這么改。
有兩種方式: 1、使用open module或opens 包名, opens 包名 to 模塊名;2、使用Module::addOpens運行時動態open。
java9還為反射類添加了canAccess方法、trySetAccessible方法
open允許對open的模塊或包進行深度反射
還有個問題,假如我們想對三方提供的模塊進行深度反射,那該怎么辦呢,總不能去拿到別人的代碼改module-info.java聲明吧。這個時候就要用到java命令行參數 --add-opens
java9基于JEP193提供了反射的替代方案用于訪問非public元素MethodHandles (始于java7),VarHandles(始于java9)
示例:
src
├── application │ ├── javamodularity │ │ └── application │ │ ├── Book.java │ │ └── Main.java │ └── module-info.java └── ormframework ├── javamodularity │ └── ormframework │ └── OrmFramework.java └── module-info.java
Book是一個POJO,里面有個private title字段
OrmFramework是一個模擬orm行為的demo,內容如下:
ublic class OrmFramework { private Lookup lookup; public OrmFramework(Lookup lookup) { this.lookup = lookup; } publicT loadfromDatabase(String query, Class clazz) { try { MethodHandle ctor = lookup.findConstructor(clazz, MethodType.methodType(void.class)); T entity = (T) ctor.invoke(); Lookup privateLookup = MethodHandles.privateLookupIn?(clazz, lookup); VarHandle title = privateLookup.findVarHandle(clazz, "title", String.class); // Name/type presumably found in some orm mapping config title.set(entity, "Loaded from database!"); return entity; } catch(Throwable e) { throw new RuntimeException(e); } }
Main類內容如下:
public static void main(String... args) { Lookup lookup = MethodHandles.lookup(); OrmFramework ormFramework = new OrmFramework(lookup); Book book = ormFramework.loadfromDatabase("/* query */", Book.class); System.out.println(book.getTitle()); }
你可能要問,為什么OrmFramework需要傳入Lookup,因為只有application模塊的Lookup才能有權限訪問那個模塊的非public元素,而OrmFramework模塊自己生成的Lookup是沒有權限訪問的。
所以使用MethodHandles與VarHandles時需要注意Lookup的權限
java.lang.module提供了三種類型的能力:1、查詢模塊屬性(主要基于module-info.java的內容);2、運行時動態修改模塊的行為;3、模塊內資源訪問
類圖:
1、查詢模塊屬性(主要基于module-info.java的內容)
public class Introspection { public static void main(String... args) { Module module = String.class.getModule(); String name1 = module.getName(); // Name as defined in module-info.java System.out.println("Module name: " + name1); Setpackages1 = module.getPackages(); // Lists all packages in the module System.out.println("Packages in module: " + packages1); // The methods above are convenience methods that return // information from the Module"s ModuleDescriptor: ModuleDescriptor descriptor = module.getDescriptor(); String name2 = descriptor.name(); // Same as module.getName(); System.out.println("Module name from descriptor: " + name2); Set packages2 = descriptor.packages(); // Same as module.getPackages(); System.out.println("Packages from descriptor: " + packages2); // Through ModuleDescriptor, all information from module-info.java is exposed: Set exports = descriptor.exports(); // All exports, possibly qualified System.out.println("Exports: " + exports); Set uses = descriptor.uses(); // All services used by this module System.out.println("Uses: " + uses); } }
2、運行時動態修改模塊的行為
比如動態exports
Module target=... Module current=getClass().getModule(); current.addExports("com.test.in.Hello",target);
看了這段代碼,你可能要問,第二行,假如我是在別的模塊中調用,那么是不是任何模塊都可以修改其他模塊的exports,opens等屬性呢,非也,JVM運行時會判斷Module對象的調用上下文,如果檢測到調用時非當前模塊,那么就會出現異常。這種行為叫做Caller Sensitive
Caller Sensitive
jdk定義了很多caller sensitive的方法,只要是caller sensitive的方法都會被注解@CallerSensitive標注,比如剛剛提到的Module::addExports,Field::setAccessible
Module API中可修改運行時行為的幾個方法:
addExports(String pkgName, Module other)
addOpens(String pkgName, Module other)
addReads(Module other)
模塊上也可以加注解
@Deprecated module m{ }
你也可以自定義模塊注解
注意:@Target(value={PACKAGE, MODULE})
@Retention(RetentionPolicy.RUNTIME) @Target(value={PACKAGE, MODULE}) public @interface CustomAnnotation { }容器應用模式 Layers And Configurations
ModuleLayer API、boot layer、layer的父子關系、一個layer可以有多個父layer
一個layer包含了當前root模塊的解析圖(module resolution graph),一個應用中可以有多個layer,但是只有一個boot layer,啟動時的boot layer是java給你自動創建的,你也可以手動創建layer,那么這個創建的layer的parent就是boot layer。 只有boot layer才能解析platform module,但children layer可以共享boot layer中的Platform module,但是如果boot layer中沒有加載到的platform module,children module是無法使用的。
public static void main(String... args) { Driver driver = null; // We reference java.sql.Driver to see "java.sql" gets resolved ModuleLayer.boot().modules().forEach(m -> System.out.println(m.getName() + ", loader: " + m.getClassLoader())); System.out.println("System classloader: " + ClassLoader.getSystemClassLoader()); }
創建ModuleLayer的示例:
ModuleFinder finder=ModuleFinder.of(Paths.get("../modules")); ModuleLayer bootLayer=ModuleLayer.boot(); //第二個Finder參數是在第一個finder中找不到模塊時才會去第二個finder中找,還有個resolveAndBind方法,區別在于,后者還會解析services provides/uses Configuration config=bootLayer.configuration().resolve(finder,ModuleFinder.of(), Set.of("rootmodule")); ClassLoader cl=ClassLoader.getSystemClassLoader(); ModuleLayer newLayer=bootLayer.defineModulesWithOneLoader(config,cl);
上面的Configuration除了resolve方法外,還有個resolveAndBind方法,區別在于,后者還會解析services provides/uses
ClassLoaders in Layer
引入模塊化以后,去掉了之前的ExtClassLoader,引入了PlatformClassLoader
如果我們為每個layer都傳入不同的ClassLoader,那么則允許不同layer中存在相同的全限定類,這樣可以做到隔離與相互不干擾。
Plug-in 架構比如Eclipse,IDEA都是基于插件的應用
在Java9中,我們有兩種方式來實現插件化:1、仍然利用以前的Services能力;2、結合ModuleLayer+Services實現封裝性更強的插件
public class PluginHostMain { public static void main(String... args) { if (args.length < 1) { System.out.println("Please provide plugin directories"); return; } System.out.println("Loading plugins from " + Arrays.toString(args)); StreamContainer架構pluginLayers = Stream .of(args) .map(dir -> createPluginLayer(dir)); //<1> pluginLayers .flatMap(layer -> toStream(ServiceLoader.load(layer, Plugin.class))) // <2> .forEach(plugin -> { System.out.println("Invoking " + plugin.getName()); plugin.doWork(); // <3> }); } static ModuleLayer createPluginLayer(String dir) { ModuleFinder finder = ModuleFinder.of(Paths.get(dir)); Set pluginModuleRefs = finder.findAll(); Set pluginRoots = pluginModuleRefs.stream() .map(ref -> ref.descriptor().name()) .filter(name -> name.startsWith("plugin")) // <1> .collect(Collectors.toSet()); ModuleLayer parent = ModuleLayer.boot(); Configuration cf = parent.configuration() .resolve(finder, ModuleFinder.of(), pluginRoots); // <2> ClassLoader scl = ClassLoader.getSystemClassLoader(); ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl); // <3> return layer; } static Stream toStream(Iterable iterable) { return StreamSupport.stream(iterable.spliterator(), false); } }
比如tomcat,Jetty就是基于Container的應用,支持運行時動態depoy和undeploy應用。
與Plugin-in架構的區別:1、Container支持運行時deploy和undeploy;2、Plugin-in是用的是Services思路,而Container模式不應該使用Services。這種情況下,就需要使用模塊的open功能,但是我們又不應該強制應用open,那么這就需要用到ModuleLayer.Controller::addOpens了,與Module::addOpens是Caller Sensitive不同,它可以實現跨模塊調用來修改模塊屬性。然后利用Deep reflection來實例化應用類
private static void deployApp(int appNo) { AppDescriptor appDescr = apps[appNo];//AppDescriptor是自定義的類 System.out.println("Deploying " + appDescr); ModuleLayer.Controller appLayerCtrl = createAppLayer(appDescr); Module appModule = appLayerCtrl.layer() .findModule(appDescr.rootmodule) .orElseThrow(() -> new IllegalStateException(appDescr.rootmodule + " missing")); appLayerCtrl.addOpens(appModule, appDescr.appClassPkg, Launcher.class.getModule()); ContainerApplication app = instantiateApp(appModule, appDescr.appClass); deployedApps[appNo] = app; app.startApp(); } private static ModuleLayer.Controller createAppLayer(AppDescriptor appDescr) { ModuleFinder finder = ModuleFinder.of(Paths.get(appDescr.appDir)); ModuleLayer parent = ModuleLayer.boot(); Configuration cf = parent.configuration() .resolve(finder, ModuleFinder.of(), Set.of(appDescr.rootmodule)); ClassLoader scl = ClassLoader.getSystemClassLoader(); ModuleLayer.Controller layerCtrl = ModuleLayer.defineModulesWithOneLoader(cf, List.of(parent), scl); return layerCtrl; } private static ContainerApplication instantiateApp(Module appModule, String appClassName) { try { ClassLoader cl = appModule.getClassLoader(); Class> appClass = cl.loadClass(appClassName); if(ContainerApplication.class.isAssignableFrom(appClass)) { return ((Class) appClass).getConstructor().newInstance(); } else { System.out.println("WARNING: " + appClassName + " doesn"t implement ContainerApplication, cannot be started"); } } catch (ReflectiveOperationException roe) { System.out.println("Could not start " + appClassName); roe.printStackTrace(); }
注意點:只有jvm啟動時的boot layer才能解析platform module,在這里就是Container的root layer,但children layer可以共享boot layer中的Platform module,但是如果boot layer中沒有加載到的platform module,children module是無法使用的。所以Container啟動時可以指定參數--add-modules ALL-SYSTEM這樣便可以解析所有的platform module到layer module graph中
總之:不管是Plugin-in還是Container模式,我們都需要適應新的ModuleLayer API就像以前的ClassLoader API一樣
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71464.html
摘要:命令行參數文件鑒于遷移到后可能需要很長的命令行參數,有些會限制命令行長度,支持定義一個命令行參數文件。已有三分庫可以自動轉成模塊,只要在啟動時將放在指定路徑中,便會自動變成。 java[c]命令行參數文件 鑒于遷移到java9后可能需要很長的命令行參數,有些os會限制命令行長度,java9支持定義一個命令行參數文件。使用方式: java @arguments.txt arguments...
摘要:如果你想查看運行時模塊的加載過程輸出結果表示為模塊,由于我限制了不再往下輸出了,而我們模塊又沒有別的額外依賴,所以僅有這行輸出。 jdk9模塊快速入門 列出自帶模塊:java --list-modulesmac多版本jdk共存:http://adolphor.com/blog/2016...模塊規則示意圖:showImg(https://segmentfault.com/img/bVb...
摘要:本書概括以軟件系統為例,重點講解了應用架構中的物理設計問題,即如何將軟件系統拆分為模塊化系統。容器獨立模塊不依賴于具體容器,采用輕量級容器,如獨立部署模塊可獨立部署可用性模式發布接口暴露外部配置使用獨立的配置文件用于不同的上下文。 本文為讀書筆記,對書中內容進行重點概括,并將書中的模塊化結合微服務、Java9 Jigsaw談談理解。 本書概括 以Java軟件系統為例,重點講解了應用架構...
摘要:新特性概述系列一安裝及使用系列二運行系列三模塊系統精要系列四更新系列五系列六系列七系列八系列九與的區別遷移注意事項參數遷移相關選項解析使用構建實例使用示例帶你提前了解中的新特性 Java語言特性系列 Java5的新特性 Java6的新特性 Java7的新特性 Java8的新特性 Java9的新特性 Java10的新特性 Java11的新特性 Java12的新特性 Java13的新特性...
閱讀 477·2021-11-22 12:05
閱讀 1541·2021-11-17 09:33
閱讀 3586·2021-11-11 16:54
閱讀 2673·2021-10-14 09:49
閱讀 4046·2021-09-06 15:01
閱讀 1828·2019-08-29 17:23
閱讀 701·2019-08-29 14:09
閱讀 720·2019-08-29 12:28