javascript
JSR-308和Checker框架为jOOQ 3.9添加了更多类型安全性
Java 8引入了JSR-308,它為Java語言添加了新的注釋功能。 最重要的是:鍵入注釋。 現(xiàn)在可以像下面這樣設(shè)計(jì)怪物了:
比注解更瘋狂的是類型注解。 在數(shù)組上。 誰認(rèn)為這是有效的Java代碼? pic.twitter.com/M9fSRRerAD
— Lukas Eder(@lukaseder) 2016年3月20日
該推文中顯示的代碼確實(shí)可以編譯。 現(xiàn)在可以注釋每種類型,以便以任何自定義方式增強(qiáng)類型系統(tǒng)。 為什么,你可能會(huì)問? 這種語言增強(qiáng)的主要驅(qū)動(dòng)用例之一是checker框架 ,這是一個(gè)開放源代碼庫,可讓您輕松實(shí)現(xiàn)任意編譯器插件以進(jìn)行復(fù)雜的類型檢查。 最無聊和瑣碎的例子是可空性。 考慮以下代碼:
import org.checkerframework.checker.nullness.qual.Nullable;class YourClassNameHere {void foo(Object nn, @Nullable Object nbl) {nn.toString(); // OKnbl.toString(); // Failif (nbl != null)nbl.toString(); // OK again} }上面的示例可以直接在checker框架實(shí)時(shí)演示控制臺(tái)中運(yùn)行 。 使用以下注釋處理器編譯以上代碼:
javac -processor org.checkerframework.checker.nullness.NullnessChecker afile.java
產(chǎn)量:
錯(cuò)誤:[dereference.of.nullable]取消引用可能為空的引用nbl:5:9
太棒了! 例如,它的工作方式與在Ceylon或Kotlin中 實(shí)現(xiàn)的流敏感類型非常相似,不同之處在于它更為冗長。 但是它也要強(qiáng)大得多,因?yàn)榭梢允褂米⑨屘幚砥髦苯釉贘ava中實(shí)現(xiàn)實(shí)現(xiàn)增強(qiáng)的和帶注釋的Java類型系統(tǒng)的規(guī)則! 通過某種方式使注解圖靈完整。��
這對(duì)jOOQ有什么幫助?
jOOQ已經(jīng)提供了兩種類型的API文檔注釋。 這些注釋是:
- @PlainSQL –表示DSL方法接受“純SQL”字符串,這可能會(huì)帶來SQL注入風(fēng)險(xiǎn)
- @Support –表示DSL方法可以本機(jī)工作,或者可以針對(duì)給定的SQLDialect集進(jìn)行仿真
這種方法的一個(gè)示例是CONNECT BY子句 ,該子句得到Cubrid,Informix和Oracle的支持,為了方便起見,它也被重載為也接受“普通SQL”謂詞:
@Support({ CUBRID, INFORMIX, ORACLE }) @PlainSQL SelectConnectByConditionStep<R> connectBy(String sql);到目前為止,這些注釋僅用于文檔目的。 使用jOOQ 3.9后,不再可用。 現(xiàn)在,我們向jOOQ API引入了兩個(gè)新的注釋:
- org.jooq.Allow –允許在給定范圍內(nèi)使用一組方言(或@PlainSQL批注)
- org.jooq.Require –在給定范圍內(nèi)要求通過@Support注釋支持一組方言
最好通過示例來解釋。 讓我們先看看@PlainSQL
限制對(duì)
使用jOOQ API的最大優(yōu)點(diǎn)之一就是SQL注入已經(jīng)成為過去。 由于jOOQ是內(nèi)部特定于域的語言,因此用戶確實(shí)可以直接在Java代碼中直接定義SQL表達(dá)式樹,而不是像JDBC那樣使用聲明的字符串化版本。 表達(dá)式樹是用Java編譯的,因此不可能通過用戶輸入注入任何不需要的或無法預(yù)見的表達(dá)式。
但是有一個(gè)例外。 jOOQ并不支持每個(gè)數(shù)據(jù)庫中的所有SQL功能。 這就是jOOQ附帶豐富的“普通SQL” API的原因,在該API中,可以將自定義SQL字符串嵌入SQL表達(dá)式樹中的任何位置。 例如,上面的CONNECT BY子句:
DSL.using(configuration).select(level()).connectBy("level < ?", bindValue).fetch();上面的jOOQ查詢轉(zhuǎn)換為以下SQL查詢:
SELECT level FROM dual CONNECT BY level < ?如您所見,完全有可能“做錯(cuò)”并產(chǎn)生SQL注入風(fēng)險(xiǎn),就像在JDBC中一樣:
DSL.using(configuration).select(level()).connectBy("level < " + bindValue).fetch();區(qū)別非常細(xì)微。 使用jOOQ 3.9和checker框架,現(xiàn)在可以指定以下Maven編譯器配置:
<plugin><artifactId>maven-compiler-plugin</artifactId><version>3.3</version><configuration><source>1.8</source><target>1.8</target><fork>true</fork><annotationProcessors><annotationProcessor>org.jooq.checker.PlainSQLChecker</annotationProcessor></annotationProcessors><compilerArgs><arg>-Xbootclasspath/p:1.8</arg></compilerArgs></configuration> </plugin>org.jooq.checker.PlainSQLChecker將確保不會(huì)編譯使用帶有@PlainSQL注釋的API的客戶端代碼。 我們收到的錯(cuò)誤消息是這樣的:
C:\ Users \ lukas \ workspace \ jOOQ \ jOOQ-examples \ jOOQ-checker-framework-example \ src \ main \ java \ org \ jooq \ example \ checker \ PlainSQLCheckerTests.java:[17,17]錯(cuò)誤:[普通]當(dāng)前范圍不允許使用SQL。 使用@ Allow.PlainSQL。]
如果您知道自己在做什么,并且絕對(duì)必須在非常特定的位置(范圍)使用jOOQ的@PlainSQL API,則可以使用@Allow.PlainSQL對(duì)該位置(范圍)進(jìn)行注釋,并且代碼可以再次正常編譯:
// Scope: Single method. @Allow.PlainSQL public List<Integer> iKnowWhatImDoing() {return DSL.using(configuration).select(level()).connectBy("level < ?", bindValue).fetch(0, int.class); }甚至:
// Scope: Entire class. @Allow.PlainSQL public class IKnowWhatImDoing {public List<Integer> iKnowWhatImDoing() {return DSL.using(configuration).select(level()).connectBy("level < ?", bindValue).fetch(0, int.class);} }甚至(但是您可能只是關(guān)閉檢查器):
// Scope: entire package (put in package-info.java) @Allow.PlainSQL package org.jooq.example.checker;好處是顯而易見的。 如果安全性對(duì)您非常重要(應(yīng)該如此),則只需在每個(gè)開發(fā)人員版本或至少在CI版本中啟用org.jooq.checker.PlainSQLChecker ,并在“偶然”使用@PlainSQL API時(shí)獲得編譯錯(cuò)誤遇到。
限制對(duì)
現(xiàn)在,對(duì)于大多數(shù)用戶而言,更有趣的是能夠檢查客戶端代碼中使用的jOOQ API是否確實(shí)支持您的數(shù)據(jù)庫。 例如,上面的CONNECT BY子句僅在Oracle中受支持(如果我們忽略不太流行的Cubrid和Informix數(shù)據(jù)庫)。 假設(shè)您僅使用Oracle。 您要確保您使用的所有jOOQ API都與Oracle兼容。 現(xiàn)在,您可以將以下注釋添加到所有使用jOOQ API的軟件包中:
// Scope: entire package (put in package-info.java) @Allow(ORACLE) package org.jooq.example.checker;現(xiàn)在,只需激活org.jooq.checker.SQLDialectChecker來鍵入代碼以檢查@Allow符合性,即可完成:
<plugin><artifactId>maven-compiler-plugin</artifactId><version>3.3</version><configuration><source>1.8</source><target>1.8</target><fork>true</fork><annotationProcessors><annotationProcessor>org.jooq.checker.SQLDialectChecker</annotationProcessor></annotationProcessors><compilerArgs><arg>-Xbootclasspath/p:1.8</arg></compilerArgs></configuration> </plugin>從現(xiàn)在開始,每當(dāng)您使用任何jOOQ API時(shí),上述檢查器都將驗(yàn)證以下三個(gè)值是否為true:
- 正在使用的jOOQ API未使用@Support注釋
- 使用的jOOQ API帶有@Support注釋,但沒有任何顯式的SQLDialect (即“可在所有數(shù)據(jù)庫上工作”),例如DSLContext.select()
- 使用的jOOQ API帶有@Support注釋,并帶有SQLDialects引用的至少一個(gè)@Allow
因此,在這樣標(biāo)注的包裝中……
// Scope: entire package (put in package-info.java) @Allow(ORACLE) package org.jooq.example.checker;……使用這樣注釋的方法就可以了:
@Support({ CUBRID, INFORMIX, ORACLE }) @PlainSQL SelectConnectByConditionStep<R> connectBy(String sql);…但是使用這樣注釋的方法不是:
@Support({ MARIADB, MYSQL, POSTGRES }) SelectOptionStep<R> forShare();為了允許使用此方法,例如,客戶端代碼除了可以使用ORACLE語言外,還可以使用MYSQL語言:
// Scope: entire package (put in package-info.java) @Allow({ MYSQL, ORACLE }) package org.jooq.example.checker;從現(xiàn)在開始,此程序包中的所有代碼都可能引用支持MySQL和/或Oracle的方法。
@Allow批注有助于在全局級(jí)別上訪問API。 多個(gè)@Allow注釋(范圍可能不同)創(chuàng)建了允許的方言的@Allow取關(guān)系,如下所示:
// Scope: class @Allow(MYSQL) class MySQLAllowed {@Allow(ORACLE)void mySQLAndOracleAllowed() {DSL.using(configuration).select()// Works, because Oracle is allowed.connectBy("...")// Works, because MySQL is allowed.forShare();} }從上面可以看出,析取兩個(gè)方言不能確保給定的語句在兩個(gè)數(shù)據(jù)庫中都可以使用。 所以…
如果我希望同時(shí)支持兩個(gè)數(shù)據(jù)庫怎么辦?
在這種情況下,我們將使用新的@Require注釋。 多個(gè)@Require注釋(范圍可能不同)創(chuàng)建所需方言的合集,如下所示:
// Scope: class @Allow @Require({ MYSQL, ORACLE }) class MySQLAndOracleRequired {@Require(ORACLE)void onlyOracleRequired() {DSL.using(configuration).select()// Works, because only Oracle is required.connectBy("...")// Doesn't work because Oracle is required.forShare();} }如何使用
假設(shè)您的應(yīng)用程序僅需要與Oracle一起使用。 現(xiàn)在,您可以在軟件包上添加以下注釋,例如,由于在您的代碼中不允許將MySQL作為方言,因此您將無法使用任何僅MySQL的API:
@Allow(ORACLE) package org.jooq.example.checker;現(xiàn)在,隨著需求的變化,您還希望從應(yīng)用程序中也開始支持MySQL。 只需將軟件包規(guī)格更改為以下內(nèi)容,然后開始修復(fù)jOOQ使用中的所有編譯錯(cuò)誤。
// Both dialects are allowed, no others are @Allow({ MYSQL, ORACLE })// Both dialects are also required on each clause @Require({ MYSQL, ORACLE }) package org.jooq.example.checker;默認(rèn)值
默認(rèn)情況下,對(duì)于任何范圍, org.jooq.checker.SQLDialectChecker都采用以下注釋:
- 什么都不允許。 每個(gè)@Allow批注都會(huì)添加到允許的方言集中。
- 一切都是必需的。 每個(gè)@Require批注將從必填方言集中刪除。
實(shí)際觀看
這些功能將是jOOQ 3.9的組成部分。 只需添加以下依賴項(xiàng)即可使用它們:
<dependency><!-- Use org.jooq for the Open Source editionorg.jooq.pro for commercial editions, org.jooq.pro-java-6 for commercial editions with Java 6 support,org.jooq.trial for the free trial edition --><groupId>org.jooq</groupId><artifactId>jooq-checker</artifactId><version>${org.jooq.version}</version> </dependency>…,然后為您的編譯器插件選擇適當(dāng)?shù)淖⑨屘幚砥鳌?
不能等到j(luò)OOQ 3.9嗎? 不用了 只需從GitHub上檢查3.9.0-SNAPSHOT版本,然后按照此處給出的示例項(xiàng)目進(jìn)行操作:
- https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-checker-framework-example
做完了! 從現(xiàn)在開始,使用jOOQ時(shí),您可以確保編寫的任何代碼都可以在計(jì)劃支持的所有數(shù)據(jù)庫上運(yùn)行!
我認(rèn)為,今年的Annotatiomaniac冠軍頭銜應(yīng)該交給檢查框架的制定者:
有關(guān)檢查器框架的更多信息:
- http://types.cs.washington.edu/checker-framework/
- http://eisop.uwaterloo.ca/live#mode=display(實(shí)時(shí)演示)
翻譯自: https://www.javacodegeeks.com/2016/05/jsr-308-checker-framework-add-even-typesafety-jooq-3-9.html
總結(jié)
以上是生活随笔為你收集整理的JSR-308和Checker框架为jOOQ 3.9添加了更多类型安全性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓车载导航软件下载(安卓车载导航软件)
- 下一篇: Thymeleaf 3 – Thymel