After the official release, I summarized it in What happened to the typical changes of Apache Wicket 8.
This article is for 12/23 of Java EE Advent Calendar 2016.
Yesterday, @ HirofumiIwasaki's "Future image of Java EE 8 (revised) through JavaOne 2016" -2016-java-ee-advent-calendar /). Tomorrow is @kikutaro.
By the way, Java component-oriented web frameworks, which are rarely hot topics these days, are typical ones that have been continuously developed since the latter half of the 2000s, such as JSF, Apache Wicket, and Vaadin. Can be mentioned.
Most of the Apache Wicket use cases published on Twitter accounts, etc. are from overseas, but in Japan, [Tagajo City Archive Site](http:: There are reports that it is also used in //tagajo.irides.tohoku.ac.jp/index) and National Police Agency site opinion box. I will. Wicket itself is also being continuously developed by the community, and development versions of Wicket 8.0 for Java 8 are beginning to be released.
Wicket 8.0 makes Java 8 Lambda expressions available in Wicket components and models. For Wicket users who have fallen in love with the stance of a web framework that is easy for Java programmers to develop, this is a long-awaited improvement while wondering "when will it come?"
Against this background, in this article, Wicket 8 Migration Guide: Migration to Wicket 8.0 and [ I would like to summarize the changes in the way code is written from the upcoming Wicket 8.0 release, with reference to Documents by the committer at ApacheCon EU 2016.
The content of the article is based on Wicket 8.0.0-M2 at the time of writing, and is subject to change in the future.
Wicket 8.0 requires at least Java 8 Wicket 8.0 requires Servlet 3.1 (Jetty 9.2+, Apache Tomcat 8+, JBoss WildFly 10+)
(From Migration to Wicket 8.0)
With Wicket 8.0, Java 8 is a required environment. Along with this, the base Servlet has also been upgraded to 3.1, so it is necessary to pay attention to the supported version of the Servlet container [^ 1].
[^ 1]: But web.xml still remains ...
LambdaModel
A major change is the addition of the Lambda Model.
As the name implies, this is a ** Model that allows you to specify getter / setter processing for ModelObject in a Java 8 Lambda expression or method reference.
// Wicket 8.Example of Lambda Model from 0
Order order = new Order();
IModel<String> model = LambdaModel.of(order::getUserName, order::setUserName);
In this way, the method reference of the first argument is treated as getModelObject ()
, and the method reference of the second argument is treated as the processing inside the setModelObject (String)
process.
This Model seems to have the intent to be used as an alternative to ** PropertyModel **.
//Examples of Property Models so far
Order order = new Order();
IModel<String> model = new PropertyModel(order, "userName");
PropertyModel is convenient because it can be linked with instance fields and setters / getters, but on the other hand, it is troublesome to create and modify it because the referenced field variable must be specified as a character string. If you use LambdaModel, you can solve this trouble and do the same thing as it is [^ 2].
[^ 2]: Personally, I use CompoundPropertyModel anyway ...
Components such as Label work by passing a Model (or an object inside it). ** Lambda expressions and method references can also be used when passing a component's Model or Object **.
Order order = new Order();
//Until now (even if you pass getUserName directly, it will wrap in Model internally)
add(new Label("foo", Model.of(order.getUserName())));
add(new Label("foo", order.getUserName()));
// Wicket 8.From 0, you can pass Model or Object with method reference or Lambda expression
add(new Label("foo", order::getUserName));
add(new Label("foo", () -> {...}));
** Model now has a factory method (of) as standard **. You can use Lambda expressions and method references in of.
IService service = new Service();
//So far
IModel<List<Dish>> model = new LoadableDetachableModel<List<Dish>>() {
private static final long serialVersionUID = 1380883852804223557L;
@Override
protected List<Dish> load() {
return service.fetchDishes();
}
};
// Wicket 8.How to write from 0. I am very happy that the LoadableDetachableModel is on one line.
IModel<List<Dish>> model = LoadableDetachableModel.of(service::fetchDishes);
In this way, you can write a LoadableDetachableModel etc. in one line, which is convenient depending on the usage, but you had to make it an anonymous class every time **. This is very nice. Personally, I felt that this was the biggest advantage among the changes in Model.
However, on the other hand, ** AbstractReadOnlyModel has been deprecated, and it is now recommended to use IModel's anonymous class instead **. When migrating to Wicket 8.0, it seems that it will be necessary to search and replace all at once.
IService service = new Service();
//So far
IModel<Integer> model = new AbstractReadOnlyModel<Integer>() {
private static final long serialVersionUID = 2631631544271526736L;
@Override
public Integer getObject() {
return new Random().nextInt();
}
};
// Wicket 8.From 0, AbstractReadOnlyModel is deprecated. Switch to an anonymous class in IModel.
IModel<Integer> model = new IModel<Integer>() {
private static final long serialVersionUID = -2636918853427797619L;
@Override
public Integer getObject() {
return new Random().nextInt();
}
};
For Link that moves between pages and Button component that posts a value with Foam, it was common to make it an anonymous class in order to override the onClick () and onSubmit () methods that control the click event.
** In Wicket8, by using Lambda expression, you can create Links and Buttons without making anonymous classes **.
//So far
Link<Void> link = new Link<Void>("toHogePage") {
private static final long serialVersionUID = 3391006051307639762L;
@Override
public void onClick() {
//Various processing
setResponsePage(new HogePage(model));
}
};
// Wicket 8.Link component from 0
add(Link.onClick("toHogePage", (l) -> {
//Various processing
setResponsePage(new toHogePage(model));
}));
//If you only move pages, it will be one line
add(Link.onClick("toHogePage", (l) -> setResponsePage(new toHogePage(model))));
//So far
Button button = new Button("toHogePage") {
private static final long serialVersionUID = 7447947126462635005L;
@Override
public void onSubmit() {
//Various processing enters around here
setResponsePage(new HogePage(model));
}
};
// Wicket 8.Button component from 0
Button button = Button.onSubmit("toHogePage", (b) -> {
//Various processing enters around here
setResponsePage(new HogePage(model));
}));
//This is also one line if only page movement
Button button = Button.onSubmit("toHogePage", (b) -> setResponsePage(new CompletionPage(orderModel)));
The arguments l
and b
in the lambda expression are references to the Link and button instances themselves. It can be reused in a Lambda expression in case you want to operate a Link or Button (or a Model or Form that can be obtained from it) after clicking.
** The same applies to Ajax-based Links and Buttons **. As an example, if you had a button that updates a component named wmc
with Ajax when you submit a form,
//So far
AjaxButton button = new AjaxButton("button") {
private static final long serialVersionUID = 796871957135885247L;
@Override
protected void onSubmit(AjaxRequestTarget target) {
target.add(getPage().get("wmc"));
}
@Override
protected void onError(AjaxRequestTarget target) {
target.add(getForm().get("feeedback"));
}
};
// Wicket 8.From 0, Ajax Link,Button can also be written concisely.
//The second argument Lambda expression is onSubmit,The Lambda expression of the third argument is onError.
AjaxButton button = AjaxButton.onSubmit("onSubmit",
(b, t) -> t.add(getPage().get("wmc")),
(b, t) -> t.add(b.getForm().get("feedback")));
It will be like. The Wicket8 Lambda expression argument b
is the button itself, and the argument t
is AjaxRequestTarget.
I often use these components, but they also caused mass production of anonymous classes, so I'd be very happy if I could write them in one line (at the shortest).
Behavior
Behavior, which adds behavior and functions to components with Ajax etc., also has a part that can be written simply by factory methods and lambda.
Below is an example of a Label component with Behavior that updates the component itself every second (?).
// Wicket 8.AbstractAjaxTimerBehavior from 0. The variable t is AjaxRequestTarget.
add(new Label("timer", LoadableDetachableModel.of(LocalDateTime::now))
.setOutputMarkupId(true)
.add(AbstractAjaxTimerBehavior.onTimer(ONE_SECOND, (t) -> t.add(getPage().get("timer")))));
In the past, it was necessary to create anonymous classes and subclasses when preparing such Behaviors. Wicket 8.0 has been devised to avoid these anonymous classes and make it easier to write with less code.
Based on the examples introduced so far, I made an example of a simple submission form (import is omitted). If you've used Wicket before, you'll find that the Java code is shorter.
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="utf-8"/>
</head>
<body>
<h1>Order form</h1>
<form wicket:id="form">
<dl>
<div wicket:id="feedback"></div>
<dt>name</dt>
<dd><input type="text" wicket:id="userName"></dd>
<dt>meal</dt>
<dd>below<span wicket:id="numberOfDish"></span>Please choose one from the items.</dd>
<dd>
<div wicket:id="dish"></div>
</dd>
</dl>
<div>
<button type="submit" wicket:id="onSubmit">order</button>
</div>
</form>
</body>
</html>
public class OrderPage01 extends WebPage {
private static final long serialVersionUID = -8412714440456444538L;
public OrderPage01() {
IService service = new Service();
IModel<Order> orderModel = CompoundPropertyModel.of(Model.of(new Order()));
IModel<List<Dish>> dishes = LoadableDetachableModel.of(service::fetchDishes);
//I'm using queue because the hierarchy is troublesome
queue(new Form<>("form", orderModel));
queue(new FeedbackPanel("feedback"));
queue(new TextField<>("userName").setRequired(true));
queue(new Label("numberOfDish", dishes.getObject()::size));
queue(new RadioChoice<>("dish", dishes).setSuffix("<br/>").setRequired(true));
queue(Button.onSubmit("onSubmit", (b) -> setResponsePage(new CompletionPage(orderModel))));
}
}
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="utf-8"/>
</head>
<body>
<h1>confirmation screen</h1>
<p><span wicket:id="userName"></span>Is<span wicket:id="dish"></span>I ordered</p>
<div><a wicket:id="toIndexPage">Return</a></div>
</body>
</html>
public class CompletionPage extends WebPage {
private static final long serialVersionUID = 3057979294465321296L;
public CompletionPage(IModel<Order> orderModel) {
setDefaultModel(CompoundPropertyModel.of(orderModel));
add(new Label("userName"));
add(new Label("dish"));
add(Link.onClick("toIndexPage", (link) -> setResponsePage(IndexPage.class)));
}
}
Unfortunately, not all Models, components, and Behaviors have a way around anonymous classes.
** ListView etc. are also typical components that are used as anonymous classes, but no particular improvement has been made, and it is necessary to use them as anonymous classes as before **.
Also, even if it is a Link or Button component, the methods that initialize the anonymous class component such as ** ʻonInitialize () and ʻonConfigure ()
are the same as before **. Therefore, if you want to generate a component that is set as an Ajax update target (that is, set setOutputMarkupId (boolean)
) or a component that requires Validation and Visibility settings, it will continue as before. Must be anonymous class [^ 3].
[^ 3]: It is possible to connect setting methods such as setOutputMarkupId (boolean)
in the form of a method chain, but the return value of these methods must be set to Component type. Yes, casting becomes indispensable when adding child components. You can use the queue well and allow the cast to proceed.
However, I think that it is comfortable to use just by simply writing Links, Buttons, and Models that are likely to be on any page that do not require such detailed control.
To achieve what we've introduced so far, Wicket 8.0 provides its own Functional Interface. They are WicketBiConsumer
, WicketBiFunction
, WicketConsumer
, WicketFunction
, WicketSupplier
. However, these are just inheriting the Java standard Functional Interface and making it serializable.
In addition, IModel is also a FunctionalInterface, and default methods such as processing at the time of detach and default methods for intermediate processing for generating a Model from Model such as filter and map are prepared.
It seems that these can be used when creating or improving Models and components by yourself.
There are many other minor changes towards Wicket 7.x → 8.0, but in this article, the most interesting part of programming on a daily basis, the simplification of the description of Models and components, and I summarized the update of the part that can be avoided by anonymous class using lambda.
We hope that it will be useful for migrating products that have already been developed with Wicket, and for those who will develop with Wicket 8.0 or later in some way in the future.
A code example can be found on github.
Recommended Posts