§Play 2.6 新增功能
此頁面重點介紹 Play 2.6 的新功能。如果您想了解在遷移到 Play 2.6 時需要進行的變更,請查看 Play 2.6 遷移指南。
§支援 Scala 2.12
Play 2.6 是第一個針對 Scala 2.12 和 2.11 交叉建置的 Play 發行版本。我們更新了許多相依性,以便支援這兩個版本。
您可以透過在 build.sbt
中設定 scalaVersion
設定值來選擇要使用的 Scala 版本。
針對 Scala 2.12
scalaVersion := "2.12.19"
針對 Scala 2.11
scalaVersion := "2.11.12"
§PlayService sbt 外掛程式 (實驗性)
從 Play 2.6.8 開始,Play 也提供 PlayService
外掛程式。這是一個更精簡的 Play 設定,針對微服務而設計。它使用標準 Maven 配置,而非傳統的 Play 配置,且不包含 twirl 範本或 sbt-web 功能。例如
lazy val root = (project in file("."))
.enablePlugins(PlayService)
.enablePlugins(RoutesCompiler) // place routes in src/main/resources, or remove if using SIRD/RoutingDsl
.settings(
scalaVersion := "2.12.19",
libraryDependencies ++= Seq(
guice, // remove if not using Play's Guice loader
akkaHttpServer, // or use nettyServer for Netty
logback // add Play logging support
)
)
注意:此外掛程式被視為實驗性,表示 API 可能會變更。我們預期它會在 Play 2.7.0 中穩定下來。
§「無全域狀態的」應用程式
最大的底層變更在於 Play 不再依賴全域狀態。您仍然可以在 Play 2.6 中透過 play.api.Play.current
/ play.Play.application()
存取全域應用程式,但已標示為不建議使用。這為 Play 3.0 奠定了基礎,在 Play 3.0 中完全沒有全域狀態。
您可以透過設定下列設定值,完全停用存取全域應用程式的功能
play.allowGlobalApplication=false
上述設定會在每次呼叫 Play.current
時引發例外狀況。
§Akka HTTP 伺服器後端
Play 現在使用 Akka-HTTP 伺服器引擎作為預設後端。有關 Play 與 Akka-HTTP 整合的更多詳細資料,請參閱 Akka HTTP 伺服器頁面。還有另一個頁面說明 設定 Akka HTTP。
Netty 後端仍然可用,且已升級為使用 Netty 4.1。您可以明確設定專案,以在 NettyServer 頁面 上使用 Netty。
§HTTP/2 支援 (實驗性)
Play 現在在 Akka HTTP 伺服器上支援 HTTP/2,使用 PlayAkkaHttp2Support
模組
lazy val root = (project in file("."))
.enablePlugins(PlayJava, PlayAkkaHttp2Support)
這會自動化設定 HTTP/2 的大部分程序。不過,它預設不適用於 run
指令。請參閱 Akka HTTP 伺服器頁面 以取得更多詳細資料。
§要求屬性
Play 2.6 中的要求現在包含屬性。屬性讓您可以將額外資訊儲存在要求物件內。例如,您可以撰寫一個會在要求中設定屬性的篩選器,然後稍後在您的動作中存取屬性值。
屬性儲存在附加至每個要求的 TypedMap
中。TypedMap
是不可變的對應,儲存類型安全的鍵和值。屬性以鍵為索引,而鍵的類型會指出屬性的類型。
Java
// Create a TypedKey to store a User object
class Attrs {
public static final TypedKey<User> USER = TypedKey.<User>create("user");
}
// Get the User object from the request
User user = req.attrs().get(Attrs.USER);
// Put a User object into the request
Request newReq = req.addAttr(Attrs.USER, newUser);
Scala
// Create a TypedKey to store a User object
object Attrs {
val User: TypedKey[User] = TypedKey.apply[User]("user")
}
// Get the User object from the request
val user: User = req.attrs(Attrs.User)
// Put a User object into the request
val newReq = req.addAttr(Attrs.User, newUser)
屬性儲存在 TypedMap
中。您可以在 TypedMap
文件中進一步了解屬性:Javadoc、Scaladoc。
要求標籤現在已不建議使用,您應該改為使用屬性。請參閱遷移文件中的 標籤區段 以取得更多資訊。
§路由修改器標籤
路由檔案語法現在讓您可以在每個路由中加入「修改器」,提供自訂行為。我們已在 CSRF 篩選器中實作一個這樣的標籤,即「nocsrf」標籤。預設情況下,下列路由不會套用 CSRF 篩選器。
+ nocsrf # Don't CSRF protect this route
POST /api/foo/bar ApiController.foobar
您也可以建立自己的修改器:+
符號後可以接續任意數量的空白分隔標籤。
這些在 HandlerDef
要求屬性中提供(其中也包含路由檔案中處理常式的其他元資料)
Java
import java.util.List;
import play.routing.HandlerDef;
import play.routing.Router;
HandlerDef handler = req.attrs().get(Router.Attrs.HANDLER_DEF);
List<String> modifiers = handler.getModifiers();
Scala
import play.api.routing.{ HandlerDef, Router }
import play.api.mvc.RequestHeader
val handler = request.attrs.get(Router.Attrs.HandlerDef)
val modifiers = handler.map(_.modifiers).getOrElse(List.empty)
請注意,只有在使用 Play 從 routes
檔案產生路由器時,HandlerDef
要求屬性才會存在。
當路由在程式碼中定義時,例如使用 Scala SIRD 或 Java RoutingDsl
,此屬性不會被加入。在此情況下,request.attrs.get(HandlerDef)
會在 Scala 中傳回 None
或在 Java 中傳回 null
。在建立篩選器時,請記住這一點。
§可注入的 Twirl 範本
現在可以使用 @this
的建構函式註解來建立 Twirl 範本。建構函式註解表示 Twirl 範本可以直接注入到範本中,並可以管理自己的相依性,而不是控制器必須管理相依性,不僅是控制器本身,還有它必須呈現的範本。
舉例來說,假設一個範本相依於組件 TemplateRenderingComponent
,而控制器並未使用此組件。
首先,使用建構函式的 @this
語法建立檔案 IndexTemplate.scala.html
。請注意,建構函式必須置於 apply
方法範本參數所使用的 @()
語法之前
@this(trc: TemplateRenderingComponent)
@(item: Item)
@{trc.render(item)}
預設情況下,Twirl 在 Play 中使用 @this
語法建立的所有已產生 Scala 範本類別,都會自動加上 @javax.inject.Inject()
註解。如果您願意,可以在 build.sbt
中變更此行為
// Add one or more annotation(s):
TwirlKeys.constructorAnnotations += "@java.lang.Deprecated()"
// Or completely replace the default one with your own annotation(s):
TwirlKeys.constructorAnnotations := Seq("@com.google.inject.Inject()")
現在,透過在建構函式中注入範本,來定義控制器
Java
public class MyController extends Controller {
private final views.html.indexTemplate template;
@Inject
public MyController(views.html.indexTemplate template) {
this.template = template;
}
public Result index() {
return ok(template.render());
}
}
Scala
class MyController @Inject()(indexTemplate: views.html.IndexTemplate,
cc: ControllerComponents)
extends AbstractController(cc) {
def index = Action { implicit request =>
Ok(indexTemplate())
}
}
一旦範本已定義其相依性,控制器就可以將範本注入控制器中,但控制器不會看到 TemplateRenderingComponent
。
§篩選器強化
Play 現在附帶一組已啟用的篩選器,透過組態定義。這為新的 Play 應用程式提供「預設安全」的體驗,並加強現有 Play 應用程式的安全性。
下列過濾器預設啟用
play.filters.csrf.CSRFFilter
可防止 CSRF 攻擊,請參閱 ScalaCsrf / JavaCsrfplay.filters.headers.SecurityHeadersFilter
可防止 XSS 和 frame 來源攻擊,請參閱 SecurityHeadersplay.filters.hosts.AllowedHostsFilter
可防止 DNS 重新繫結攻擊,請參閱 AllowedHostsFilter
此外,現在可透過 application.conf
設定過濾器。如要附加至預設清單,請使用 +=
play.filters.enabled+=MyFilter
如果您想要特別停用過濾器以進行測試,您也可以從設定中執行此操作
play.filters.disabled+=MyFilter
請參閱 過濾器頁面 以取得更多詳細資料。
注意:如果您從不使用 CSRF 表單輔助工具(例如
CSRF.formField
)的現有專案進行移轉,您可能會在 CSRF 過濾器的 PUT 和 POST 要求中看到「403 禁止」。如要檢查此行為,請將<logger name="play.filters.csrf" value="TRACE"/>
加入您的logback.xml
。同樣地,如果您在 localhost 以外的地方執行 Play 應用程式,您必須設定 AllowedHostsFilter 以特別允許您連線的 Hostname/IP。
§gzip 過濾器
如果您啟用 gzip 過濾器,您現在也可以透過 application.conf
(而非撰寫您自己的 Filters
類別)根據其內容類型控制哪些回應會或不會進行 gzip 壓縮
play.filters.gzip {
contentType {
# If non empty, then a response will only be compressed if its content type is in this list.
whiteList = [ "text/*", "application/javascript", "application/json" ]
# The black list is only used if the white list is empty.
# Compress all responses except the ones whose content type is in this list.
blackList = []
}
}
請參閱 gzip 過濾器頁面 以取得更多詳細資料。
§JWT Cookies
Play 現在使用 JSON Web Token (JWT) 格式作為會話和快閃 Cookies。這允許使用標準化的簽署 Cookie 資料格式、Cookie 過期(讓重播攻擊更困難)以及在簽署 Cookie 時有更大的彈性。
§記錄標記 API
已將 SLF4J 標記支援新增至 play.Logger
和 play.api.Logger
。
在 Java API 中,這是 SLF4J Logger API 的直接傳輸。這很有用,但您可能會發現類似 Godaddy Logger 的 SLF4J 封裝器,以獲得更豐富的記錄體驗。
在 Scala API 中,標記是透過 MarkerContext 特質新增的,此特質會新增為記錄器方法的隱式參數,即
import play.api._
logger.info("some info message")(MarkerContext(someMarker))
這開啟了隱式標記的大門,以便在多個陳述式中傳遞記錄,這使得在不使用 MDC 的情況下,更輕鬆地將內容新增至記錄。例如,使用 Logstash Logback Encoder 和 隱式轉換鏈,可以自動將要求資訊編碼至記錄陳述式
trait RequestMarkerContext {
// Adding 'implicit request' enables implicit conversion chaining
// See https://scala-docs.dev.org.tw/tutorials/FAQ/chaining-implicits.html
implicit def requestHeaderToMarkerContext(implicit request: RequestHeader): MarkerContext = {
import net.logstash.logback.marker.LogstashMarker
import net.logstash.logback.marker.Markers._
val requestMarkers: LogstashMarker = append("host", request.host)
.and(append("path", request.path))
MarkerContext(requestMarkers)
}
}
然後在控制器中使用,並透過可能使用不同執行內容的 Future
進行傳遞
def asyncIndex = Action.async { implicit request =>
Future {
methodInOtherExecutionContext() // implicit conversion here
}(otherExecutionContext)
}
def methodInOtherExecutionContext()(implicit mc: MarkerContext): Result = {
logger.debug("index: ") // same as above
Ok("testing")
}
請注意,標記內容對於「追蹤子彈」風格的記錄也非常有用,您可以在特定要求中記錄,而不用明確變更記錄層級。例如,您可以在符合特定條件時新增標記
trait TracerMarker {
import TracerMarker._
implicit def requestHeaderToMarkerContext(implicit request: RequestHeader): MarkerContext = {
val marker = org.slf4j.MarkerFactory.getDetachedMarker("dynamic") // base do-nothing marker...
if (request.getQueryString("trace").nonEmpty) {
marker.add(tracerMarker)
}
marker
}
}
object TracerMarker {
private val tracerMarker = org.slf4j.MarkerFactory.getMarker("TRACER")
}
class TracerBulletController @Inject() (cc: ControllerComponents) extends AbstractController(cc) with TracerMarker {
private val logger = play.api.Logger("application")
def index = Action { implicit request: Request[AnyContent] =>
logger.trace("Only logged if queryString contains trace=true")
Ok("hello world")
}
}
然後在 logback.xml
中使用下列 TurboFilter 觸發記錄
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
<Name>TRACER_FILTER</Name>
<Marker>TRACER</Marker>
<OnMatch>ACCEPT</OnMatch>
</turboFilter>
如需更多資訊,請參閱 ScalaLogging 或 JavaLogging。
如需更多關於在記錄中使用標記的資訊,請參閱 Logback 手冊中的 TurboFilters 和 基於標記的觸發 區段。
§組態改進
在 Java API 中,我們已從 Lightbend 的 Config 函式庫移至標準 Config
物件,而不是 play.Configuration
。這使行為與標準組態行為一致,因為方法現在預期所有金鑰都存在。請參閱 Java 組態移轉指南 以取得移轉詳細資料。
在 Scala API 中,我們已為 play.api.Configuration
類別引入了新方法,以簡化 API 並允許載入自訂類型。現在您可以使用隱含的 ConfigLoader
載入任何您想要的自訂類型。與 Config
API 相同,新的 Configuration#get[T]
預設會預期金鑰存在,並傳回類型為 T
的值,但也有 ConfigLoader[Option[T]]
允許 null
設定值。有關更多詳細資訊,請參閱 Scala 設定文件。
§安全性記錄
已針對 Play 中與安全性相關的操作新增安全性標記,而失敗的安全性檢查現在會以 WARN 層級記錄,並設定安全性標記。這可確保開發人員始終知道特定要求失敗的原因,這在 Play 中預設啟用安全性篩選器後非常重要。
安全性標記也允許觸發或篩選安全性失敗,與一般記錄不同。例如,若要停用所有設定有 SECURITY 標記的記錄,請將下列行新增至 logback.xml
檔案
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
<Marker>SECURITY</Marker>
<OnMatch>DENY</OnMatch>
</turboFilter>
此外,使用安全性標記的記錄事件也可以觸發訊息傳送至 Security Information & Event Management (SIEM) 引擎,以進行進一步處理。
§在 Java 中設定自訂記錄架構
先前,如果您想要使用自訂記錄架構,即使您的專案是 Java,您也必須使用 Scala 來設定它。現在,可以在 Java 和 Scala 中建立自訂的 LoggerConfigurator
。要在 Java 中建立 LoggerConfigurator
,您需要實作所提供的介面,例如,設定 Log4J
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.slf4j.ILoggerFactory;
import play.Environment;
import play.LoggerConfigurator;
import play.Mode;
import play.api.PlayException;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.config.Configurator;
public class JavaLog4JLoggerConfigurator implements LoggerConfigurator {
private ILoggerFactory factory;
@Override
public void init(File rootPath, Mode mode) {
Map<String, String> properties = new HashMap<>();
properties.put("application.home", rootPath.getAbsolutePath());
String resourceName = "log4j2.xml";
URL resourceUrl = this.getClass().getClassLoader().getResource(resourceName);
configure(properties, Optional.ofNullable(resourceUrl));
}
@Override
public void configure(Environment env) {
Map<String, String> properties = LoggerConfigurator.generateProperties(env, ConfigFactory.empty(), Collections.emptyMap());
URL resourceUrl = env.resource("log4j2.xml");
configure(properties, Optional.ofNullable(resourceUrl));
}
@Override
public void configure(Environment env, Config configuration, Map<String, String> optionalProperties) {
// LoggerConfigurator.generateProperties enables play.logger.includeConfigProperties=true
Map<String, String> properties = LoggerConfigurator.generateProperties(env, configuration, optionalProperties);
URL resourceUrl = env.resource("log4j2.xml");
configure(properties, Optional.ofNullable(resourceUrl));
}
@Override
public void configure(Map<String, String> properties, Optional<URL> config) {
try {
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
loggerContext.setConfigLocation(config.get().toURI());
factory = org.slf4j.impl.StaticLoggerBinder.getSingleton().getLoggerFactory();
} catch (URISyntaxException ex) {
throw new PlayException(
"log4j2.xml resource was not found",
"Could not parse the location for log4j2.xml resource",
ex
);
}
}
@Override
public ILoggerFactory loggerFactory() {
return factory;
}
@Override
public void shutdown() {
LoggerContext loggerContext = (LoggerContext) LogManager.getContext();
Configurator.shutdown(loggerContext);
}
}
注意:此實作與 Scala 版本的
LoggerConfigurator
完全相容,必要時甚至可以在 Scala 專案中使用,這表示模組建立者可以提供 LoggerConfigurator 的 Java 或 Scala 實作,而且它們都可以在 Java 和 Scala 專案中使用。
§分離 Java Forms 模組和 PlayMinimalJava 外掛程式
Java 表單功能已拆分到一個獨立的模組中。表單功能依賴於幾個 Spring 模組和 Hibernate 驗證程式,因此如果您沒有使用表單,您可能希望移除 Java 表單模組,以避免這些不必要的依賴關係。
此模組會由 PlayJava
外掛程式自動包含,但可以使用 PlayMinimalJava
外掛程式來停用
lazy val root = (project in file("."))
.enablePlugins(PlayMinimalJava)
§Java 編譯時間元件
就像在 Scala 中一樣,Play 現在有元件可以啟用 Java 編譯時期依賴注入。這些元件是您應該 implements
的介面,而且它們提供預設實作。對於使用 執行時期依賴注入 時可以注入的所有類型,都有元件。若要使用編譯時期依賴注入建立應用程式,您只需要提供使用 play.BuiltInComponents
自訂實作的 play.ApplicationLoader
實作,例如
import play.routing.Router;
import play.ApplicationLoader;
import play.BuiltInComponentsFromContext;
import play.filters.components.HttpFiltersComponents;
public class MyComponents extends BuiltInComponentsFromContext
implements HttpFiltersComponents {
public MyComponents(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
return Router.empty();
}
}
play.ApplicationLoader
import play.ApplicationLoader;
public class MyApplicationLoader implements ApplicationLoader {
@Override
public Application load(Context context) {
return new MyComponents(context).application();
}
}
並根據 Java 編譯時期依賴注入文件 中的說明,設定 MyApplicationLoader
。
§改善表單處理 I18N 支援
MessagesApi
和 Lang
類別用於 Play 中的國際化,而且需要顯示表單中的錯誤訊息。
過去,在 Play 中組合表單需要 多個步驟,而且沒有在表單處理的內容中討論從要求建立 Messages
執行個體。
此外,當需要表單處理時,將 Messages
執行個體傳遞到所有範本片段中並不方便,而且 Messages
內含支援是直接透過控制器特質提供的。I18N API 已透過新增 MessagesProvider
特質、直接連結到要求的內含以及改善表單文件而加以精緻化。
已新增 MessagesActionBuilder
。此動作建構器提供 MessagesRequest
,它是 WrappedRequest
,它會延伸 MessagesProvider
,只需要將單一內含參數提供給範本,而且您不需要使用 I18nSupport
延伸 Controller
。這也很有用,因為若要將 CSRF 與表單一起使用,範本中必須提供 Request
(技術上是 RequestHeader
)和 Messages
物件。
class FormController @Inject()(messagesAction: MessagesActionBuilder, components: ControllerComponents)
extends AbstractController(components) {
import play.api.data.Form
import play.api.data.Forms._
val userForm = Form(
mapping(
"name" -> text,
"age" -> number
)(UserData.apply)(UserData.unapply)
)
def index = messagesAction { implicit request: MessagesRequest[AnyContent] =>
Ok(views.html.displayForm(userForm))
}
def post = ...
}
其中 displayForm.scala.html
定義如下
@(userForm: Form[UserData])(implicit request: MessagesRequestHeader)
@import helper._
@helper.form(action = routes.FormController.post()) {
@CSRF.formField @* <- takes a RequestHeader *@
@helper.inputText(userForm("name")) @* <- takes a MessagesProvider *@
@helper.inputText(userForm("age")) @* <- takes a MessagesProvider *@
}
如需更多資訊,請參閱 ScalaI18N。
§測試支援
建立 MessagesApi
執行個體的支援已獲得改善。現在,當您要建立 MessagesApi
執行個體時,您可以使用預設引數建立 DefaultMessagesApi()
或 DefaultLangs()
。如果您要從組態或其他來源指定測試訊息,您可以傳入這些值
val messagesApi: MessagesApi = {
val env = new Environment(new File("."), this.getClass.getClassLoader, Mode.Dev)
val config = Configuration.reference ++ Configuration.from(Map("play.i18n.langs" -> Seq("en", "fr", "fr-CH")))
val langs = new DefaultLangsProvider(config).get
new DefaultMessagesApi(testMessages, langs)
}
§Future Timeout 和 Delayed 支援
Play 使用 Futures
特質改善了非同步作業中對 future 的支援。
您可以使用 play.libs.concurrent.Futures
介面將 CompletionStage
包裝在非封鎖 timeout 中
class MyClass {
@Inject
public MyClass(Futures futures) {
this.futures = futures;
}
CompletionStage<Double> callWithOneSecondTimeout() {
return futures.timeout(computePIAsynchronously(), Duration.ofSeconds(1));
}
}
或在 Scala API 中使用 play.api.libs.concurrent.Futures
特質
import play.api.libs.concurrent.Futures._
class MyController @Inject()(cc: ControllerComponents)(implicit futures: Futures) extends AbstractController(cc) {
def index = Action.async {
// withTimeout is an implicit type enrichment provided by importing Futures._
intensiveComputation().withTimeout(1.seconds).map { i =>
Ok("Got result: " + i)
}.recover {
case e: TimeoutException =>
InternalServerError("timeout")
}
}
}
還有一個 delayed
方法,它只會在指定延遲後執行 Future
,其運作方式類似於 timeout。
如需更多資訊,請參閱 ScalaAsync 或 JavaAsync。
§CustomExecutionContext 和執行緒池大小
這個類別定義委派給 akka.actor.ActorSystem
的自訂執行緒內容。在不應使用預設執行緒內容的情況下,例如使用資料庫或封鎖 I/O 時,這非常有用。詳細資訊可以在 ThreadPools 頁面中找到,但 Play 2.6.x 新增一個處理基礎 Akka 調度器查詢的 CustomExecutionContext
類別。
§使用預先組態的 CustomExecutionContexts 更新範本
在 Play 下載頁面 上所有使用封鎖 API(例如 Anorm、JPA)的範例範本都已更新為在適當的地方使用自訂執行緒環境。例如,前往 https://github.com/playframework/play-java-jpa-example/ 會顯示 JPAPersonRepository 類別會使用包覆所有資料庫操作的 DatabaseExecutionContext
。
對於包含 JDBC 連線池的執行緒池大小調整,您會希望使用執行緒池執行器,將固定執行緒池大小設定為與連線池相符。依照 HikariCP 的池大小調整頁面 中的建議,您應該將 JDBC 連線池設定為實體核心數量的兩倍,加上磁碟主軸數量。
此處使用的傳送器設定來自 Akka 傳送器
# db connections = ((physical_core_count * 2) + effective_spindle_count)
fixedConnectionPool = 9
database.dispatcher {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = ${fixedConnectionPool}
}
}
§在 Scala 中定義自訂執行緒環境
若要定義自訂執行緒環境,請使用傳送器名稱對 CustomExecutionContext
進行子類別化
@Singleton
class DatabaseExecutionContext @Inject()(system: ActorSystem)
extends CustomExecutionContext(system, "database.dispatcher")
然後將執行緒環境傳遞為隱含參數
class DatabaseService @Inject()(implicit executionContext: DatabaseExecutionContext) {
...
}
§在 Java 中定義自訂執行緒環境
若要定義自訂執行緒環境,請使用傳送器名稱對 CustomExecutionContext
進行子類別化
import akka.actor.ActorSystem;
import play.libs.concurrent.CustomExecutionContext;
public class DatabaseExecutionContext
extends CustomExecutionContext {
@javax.inject.Inject
public DatabaseExecutionContext(ActorSystem actorSystem) {
// uses a custom thread pool defined in application.conf
super(actorSystem, "database.dispatcher");
}
}
然後明確傳遞 JPA 環境。
public class JPAPersonRepository implements PersonRepository {
private final JPAApi jpaApi;
private final DatabaseExecutionContext executionContext;
@Inject
public JPAPersonRepository(JPAApi jpaApi, DatabaseExecutionContext executionContext) {
this.jpaApi = jpaApi;
this.executionContext = executionContext;
}
...
}
§Play WSClient
改進
Play WSClient
有大幅的改善。Play WSClient
現在是獨立的 play-ws 實作的包裝器,可以在 Play 外部使用。此外,play-ws 中所涉及的底層程式庫已經 遮蔽,因此其中使用的 Netty 實作不會與 Spark、Play 或任何使用不同版本的 Netty 的其他程式庫衝突。
最後,如果存在快取實作,現在支援 HTTP 快取。使用 HTTP 快取表示重複請求後端 REST 服務時可以節省資源,特別是在與復原功能(例如 stale-on-error
和 stale-while-revalidate
)結合使用時。
如需更多詳細資訊,請參閱 WsCache 和 WS 遷移指南。
§Play JSON 改進
這個版本的 JSON 程式庫包含許多改進。
§序列化的元組功能
現在,play-json 可以序列化元組,而且隱式範圍中還有 Reads
和 Writes
實作。元組會序列化為陣列,因此 ("foo", 2, "bar")
會在 JSON 中呈現為 ["foo", 2, "bar"]
。
§Scala.js 支援
Play JSON 2.6.0 現在支援 Scala.js。您可以使用以下方式新增相依項
libraryDependencies += "com.typesafe.play" %%% "play-json" % version
其中 version
是您想要使用的版本。這個程式庫應該可以有效地與在 JVM 上執行的方式相同,但沒有支援 JVM 類型。
§自動化 JSON 對應的客製化命名策略
可以自訂由 Json
巨集 (reads
、writes
或 format
) 所產生的處理常式。因此,可以定義命名策略,以依所需方式對應 JSON 欄位。
若要使用自訂命名策略,您需要為 JsonConfiguration
和 JsonNaming
定義隱式執行個體。
提供兩種命名策略:預設策略,使用類別屬性的名稱,以及 JsonNaming.SnakeCase
情況。
可以如下使用非預設策略
import play.api.libs.json._
implicit val config = JsonConfiguration(SnakeCase)
implicit val userFormat: OFormat[PlayUser] = Json.format[PlayUser]
此外,可以透過提供 JsonNaming
執行個體,來實作自訂命名策略。
§測試改進
已將一些公用程式類別新增至 2.6.x 中的 play.api.test
套件,以透過注入相依性元件,讓功能測試更為容易。
§注入
有許多功能測試直接透過隱式 app
使用注入器
"test" in new WithApplication() {
val executionContext = app.injector.instanceOf[ExecutionContext]
...
}
現在透過 Injecting
特質,您可以省略此步驟
"test" in new WithApplication() with Injecting {
val executionContext = inject[ExecutionContext]
...
}
§StubControllerComponents
StubControllerComponentsFactory
會建立一個 Stub ControllerComponents
,可用於單元測試控制器
val controller = new MyController(stubControllerComponents())
§StubBodyParser
StubBodyParserFactory
會建立一個 Stub BodyParser
,可用於單元測試內容
val stubParser = stubBodyParser(AnyContent("hello"))
§檔案上傳改進
上傳檔案使用 TemporaryFile
API,該 API 依賴於將檔案儲存在暫時檔案系統中,如 ScalaFileUpload / JavaFileUpload 中所指定的,可透過 ref
屬性存取。
上傳檔案本質上是一個危險的作業,因為無限制的上傳檔案可能會導致檔案系統容量滿載,因此 TemporaryFile
背後的想法是,它只在完成時有作用範圍,並且應盡快將其移出暫時檔案系統。任何未移動的暫時檔案都會被刪除。
在 2.5.x 中,TemporaryFile 會在檔案參考被垃圾回收時刪除,使用 finalize
。然而,在 特定條件 下,垃圾回收並未及時發生。背景清理已移至使用 FinalizableReferenceQueue 和 PhantomReferences,而不是使用 finalize
。
TemporaryFile
的 Java 和 Scala API 已重新製作,以便所有 TemporaryFile
參考都來自 TemporaryFileCreator
特徵,並且可以根據需要交換實作,現在有一個 atomicMoveWithFallback
方法,如果可用,則使用 StandardCopyOption.ATOMIC_MOVE
。
§TemporaryFileReaper
現在還有一個 play.api.libs.Files.TemporaryFileReaper
,可以啟用它來使用 Akka 排程器在排程的基礎上刪除暫時檔案,這與垃圾回收方法不同。
預設情況下,Reaper 已停用,並透過 application.conf
啟用
play.temporaryFile {
reaper {
enabled = true
initialDelay = "5 minutes"
interval = "30 seconds"
olderThan = "30 minutes"
}
}
上述設定會使用「olderThan」屬性刪除超過 30 分鐘的檔案。它會在應用程式啟動後 5 分鐘啟動 Reaper,並在那之後每 30 秒檢查一次檔案系統。Reaper 不會感知任何現有的檔案上傳,因此如果系統未仔細設定,則長期的檔案上傳可能會遇到 Reaper。
下一步:移轉指南
在此文件發現錯誤?此頁面的原始程式碼可在此處找到。在閱讀文件指南後,請隨時提交拉取請求。有問題或建議要分享嗎?前往我們的社群論壇與社群展開討論。