1. Introduction
1.绪论
Model View Controller (MVC) is a popular design pattern to build web applications. For several years, it has been a de-facto design principle for building modern web-based applications.
模型-视图-控制器(MVC)是一种流行的设计模式,用于构建Web应用程序。几年来,它一直是构建现代基于网络的应用程序的事实设计原则。
In this tutorial, let’s learn about building a web application using Jakarta EE MVC 2.0 with a web page and a REST API.
在本教程中,让我们学习如何使用Jakarta EE MVC 2.0构建一个带有网页和REST API的Web应用程序。
2. JSR-371
2. JSR-371
Jakarta MVC 2.0 (Formerly JSR 371 MVC 1.0) is an action-based web framework built on Jakarta RESTful Web Services or JAX-RS (formerly Java API for RESTful web services). JSR-371 compliments the JAX-RS with additional annotations that make building web applications more convenient.
Jakarta MVC 2.0(原JSR 371 MVC 1.0)是一个基于动作的 Web 框架,它建立在Jakarta RESTful Web Services或 JAX-RS(原Java API for RESTful Web services)之上。 JSR-371 通过附加注释对 JAX-RS 进行补充,使构建 Web 应用程序更加便利。
JSR 371 or Jakarta MVC standardizes how we develop web applications in Java. Also, the main objective is to leverage the existing CDI (Contexts and Dependency Injections) and Bean Validation and support JSPs and Facelets as view technologies.
JSR 371或Jakarta MVC规范了我们在Java中开发Web应用程序的方式。另外,主要目标是利用现有的CDI(上下文和依赖注入)和Bean验证,并支持JSPs和Facelets作为视图技术。
Presently, Jakarta MVC 2.1 specification work is in progress and probably will release with the Jakarta EE 10 release.
目前,Jakarta MVC 2.1规范工作正在进行中,可能会与Jakarta EE 10版本一起发布。
3. JSR-371 Annotations
3.JSR-371注释
JSR-371 defines a few more annotations apart from the JAX-RS annotations. All these annotations are part of the jakarta.mvc.* package.
JSR-371除了JAX-RS注解外,还定义了一些其他注解。A所有这些注解都是jakarta.mvc.*包的一部分。
3.1. jakarta.mvc.Controller
3.1.jakarta.mvc.控制器
The @Controller annotation marks a resource as an MVC controller. When used for a class, all the resource methods in the class become controllers. Similarly, using this annotation on a resource method makes that method a controller. Typically, defining @Controller on a method is helpful if we want to define MVC Controllers and REST APIs in the same class.
@Controller注解将一个资源标记为一个MVC控制器。当用于一个类时,该类中的所有资源方法都成为控制器。同样地,在一个资源方法上使用这个注解会使该方法成为控制器。通常情况下,如果我们想在同一个类中定义MVC控制器和REST API,在一个方法上定义@Controller是很有帮助的。
For instance, let’s define a controller:
例如,让我们定义一个控制器。
@Path("user")
public class UserController {
@GET
@Produces("text/html")
@Controller
public String showUserForm(){
return "user.jsp";
}
@GET
@Produces("application/json")
public String getUserDetails(){
return getUserDetails();
}
}
This class has a @Controller that renders a user form (showUserForm) and a REST API that returns user details JSON (getUserDetails).
该类有一个@Controller,用于渲染用户表单(showUserForm)和一个REST API,用于返回用户的JSON详细信息(getUserDetails)。
3.2. jakarta.mvc.View
3.2.jakarta.mvc.View
Like @Controller, we can mark a resource class or a resource method with @View annotation. Typically, a resource method that returns a void should have a @View. A class with @View represents the default view for the controllers in the class with a void type.
和@Controller一样,我们可以用@View注解标记一个资源类或一个资源方法。通常,一个返回void的资源方法应该有一个@View。一个带有@View的类代表了该类中控制器的默认视图,其类型为void。
For instance, let’s define a controller with @View:
例如,让我们用@View定义一个控制器。
@Controller
@Path("user")
@View("defaultModal.jsp")
public class UserController {
@GET
@Path("void")
@View("userForm.jsp")
@Produces("text/html")
public void showForm() {
getInitFormData();
}
@GET
@Path("string")
@Produces("text/html")
public void showModal() {
getModalData();
}
}
Here, both resource class and resource method have @View annotation. The controller showForm renders the view userForm.jsp. Similarly, the showModal controller renders defaultModal.jsp, defined on the resource class.
这里,资源类和资源方法都有@View 注释。控制器showForm渲染了视图userForm.jsp。类似地,showModal控制器渲染defaultModal.jsp,定义在资源类上。
3.3. jakarta.mvc.binding.MvcBinding
3.3.jakarta.mvc.binding.MvcBinding
Jakarta RESTful Webservices reject a request that has binding and validation errors. A similar setup may not hold good for users interacting with a web page. Fortunately, Jakarta MVC invokes the controller even when binding and validation errors occur. Typically, the user should be well-informed about the data binding errors.
雅加达RESTful Webservices拒绝有绑定和验证错误的请求。类似的设置对于与网页交互的用户来说可能并不适用。幸运的是,即使发生绑定和验证错误,Jakarta MVC也会调用控制器。通常情况下,用户应该对数据绑定错误有充分的了解。
The controllers inject a BindingResult to present human-readable validation and binding error messages to the user. For example, let’s define a controller with a @MvcBinding:
控制器注入一个BindingResult,向用户展示人类可读的验证和绑定错误信息。例如,让我们定义一个带有@MvcBinding的控制器。
@Controller
@Path("user")
public class UserController {
@MvcBinding
@FormParam("age")
@Min(18)
private int age;
@Inject
private BindingResult bindingResult;
@Inject
private Models models;
@POST
public String processForm() {
if (bindingResult.isFailed()) {
models.put("errors", bindingResult.getAllMessages());
return "user.jsp";
}
}
}
Here, if a user enters an age lesser than 18, the user will be sent back to the same page with the binding errors. The user.jsp page, using an Expression Language (EL), can retrieve the request attribute errors and display them on the page.
在这里,如果用户输入的年龄小于18岁,用户将被送回带有绑定错误的同一页面。user.jsp 页,使用表达式语言(EL),可以检索请求属性错误并在页面上显示。
3.4. jakarta.mvc.RedirectScoped
3.4.jakarta.mvc.RedirectScoped
Consider a form where a user fills and submits the data (HTTP POST). The server processes the data and redirects the user to a success page (HTTP GET). This pattern is widely known as PRG (Post-Redirect-Get) pattern. There are a few scenarios where we like to hold the data between the POST and GET. In these scenarios, the scope of the models/beans is beyond a single request.
考虑一个表单,用户填写并提交数据(HTTP POST)。服务器处理数据并将用户重定向到一个成功页面(HTTP GET)。这种模式被广泛称为PRG(Post-Redirect-Get)模式>。在一些情况下,我们喜欢在POST和GET之间保留数据。在这些场景中,模型/Bean的范围超出了单个请求。
When a bean is annotated with @RedirectScoped, the bean’s state goes beyond a single request. Nevertheless, the state is destroyed after a POST, redirect, and Get completes. The bean demarcated with @RedirectScoped gets destroyed after a POST, Redirect, and a GET is complete.
当一个Bean被注解为@RedirectScoped时,Bean的状态会超出单个请求。然而,在POST、重定向和Get完成后,该状态会被销毁。用@RedirectScoped划定的Bean在POST、重定向和GET完成后被销毁。
For instance, assume that the bean User has annotation @RedirectScoped:
例如,假设Bean User 有注解@RedirectScoped。
@RedirectScoped
public class User
{
private String id;
private String name;
// getters and setters
}
Next, inject this bean into a controller:
接下来,将这个Bean注入到一个控制器中。
@Controller
@Path("user")
public class UserController {
@Inject
private User user;
@POST
public String post() {
user.setName("John Doe");
return "redirect:/submit";
}
@GET
public String get() {
return "success.jsp";
}
}
Here, the bean User is available for the POST and subsequent redirect and GET. Hence, the success.jsp can access the bean’s name attribute using an EL.
在这里,Bean User可用于POST和随后的重定向和GET。因此,success.jsp可以使用EL访问bean的name属性。
3.5. jakarta.mvc.UriRef
3.5.jakarta.mvc.UriRef
We can use an @UriRef annotation only for a resource method. @UriRef enables us to provide a name to the resource methods. Instead of controller path URIs, we can use these names to call our controllers in the views.
我们只能对一个资源方法使用@UriRef 注解。@UriRef使我们能够为资源方法提供一个名称。我们可以在视图中使用这些名称来调用我们的控制器,而不是控制器路径URI。
Assume that there is a user form with a href:
假设有一个带有href的用户表单。
<a href="/app/user">Clich Here</a>
Clicking Click Here invokes the controller mapped to GET /app/user.
点击Click Here会调用映射到GET /app/user的控制器。
@GET
@UriRef("user-details")
public String getUserDetails(String userId) {
userService.getUserDetails(userId);
}
Here, we named our controller with user-details. Now, we can reference this name in our views instead of a URI:
在这里,我们用user-details命名我们的控制器。现在,我们可以在我们的视图中引用这个名字而不是URI。
<a href="${mvc.uri('user-details')}">Click Here</a>
3.6. jakarta.mvc.security.CsrfProtected
3.6.jakarta.mvc.security.CsrfProtected
This annotation mandates that the CSRF validation is necessary for invoking the resource method. If the CSRF token is invalid, the client receives a ForbiddenException (HTTP 403) exception. Only a resource method can have this annotation.
该注解规定,CSRF验证对于调用资源方法是必要的。如果CSRF令牌无效,客户端会收到一个ForbiddenException (HTTP 403)的异常。只有资源方法可以有这个注解。
Consider a controller:
考虑一个控制器。
@POST
@Path("user")
@CsrfProtected
public String saveUser(User user) {
service.saveUser(user);
}
Given that the controller has a @CsrfProtected annotation, the request reaches the controller only if it contains a valid CSRF token.
鉴于控制器有一个@CsrfProtected注解,请求只有在包含有效的CSRF标记时才能到达控制器。
4. Building an MVC Application
4.构建一个MVC应用程序
Next, let’s build a web application with a REST API and a controller. Finally, let’s deploy our web application in the latest version of Eclipse Glassfish.
接下来,让我们建立一个带有REST API和控制器的Web应用程序。最后,让我们在最新版本的Eclipse Glassfish中部署我们的Web应用程序。
4.1. Generate Project
4.1.生成项目
Firstly, let’s use the Maven archetype:generate to generate the Jakarta MVC 2.0 project:
首先,让我们使用Maven的archetype:generate来生成Jakarta MVC 2.0项目。
mvn archetype:generate
-DarchetypeGroupId=org.eclipse.krazo
-DarchetypeArtifactId=krazo-jakartaee9-archetype
-DarchetypeVersion=2.0.0 -DgroupId=com.baeldung
-DartifactId=krazo -DkrazoImpl=jersey
The above archetype generates a maven project with the required artifacts, similar to:
上述原型会生成一个包含所需工件的maven项目,类似于。
Also, the generated pom.xml contains the jakarta.platform, jakarta.mvc and org.eclipse.krazo dependencies:
此外,生成的pom.xml包含jakarta.platform、jakarta.mvc和org.eclipse.krazo依赖项。
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.mvc</groupId>
<artifactId>jakarta.mvc-api</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.krazo</groupId>
<artifactId>krazo-jersey</artifactId>
<version>2.0.0</version>
</dependency>
4.2. Controllers
4.2.控制器
Next, let’s define controllers for displaying a form, saving the user details, and an API for fetching user details. But, first, let’s define our application path:
接下来,让我们定义控制器,用于显示表单,保存用户的详细信息,以及用于获取用户详细信息的API。但是,首先,让我们定义我们的应用程序路径。
@ApplicationPath("/app")
public class UserApplication extends Application {
}
The application path is defined as /app. Next, let’s define our controllers that forwards the user to a user details form:
应用程序路径被定义为/app。接下来,让我们定义我们的控制器,将用户转发到一个用户详情表单。
@Path("users")
public class UserController {
@GET
@Controller
public String showForm() {
return "user.jsp";
}
}
Next, under WEB-INF/views, we can create a view user.jsp, and build and deploy the application:
接下来,在WEB-INF/views下,我们可以创建一个视图user.jsp,并构建和部署应用程序。
mvn clean install glassfish:deploy
This Glassfish Maven plugin builds, deploys, and runs on port 8080. After successful deployment, we can open a browser and hit the URL:
这个Glassfish Maven插件在8080端口进行构建、部署和运行。部署成功后,我们可以打开浏览器,点击URL。
http://localhost:8080/mvc-2.0/app/users:
http://localhost:8080/mvc-2.0/app/users:
Next, let’s define an HTTP POST that handles the form submit action:
接下来,让我们定义一个处理表单提交动作的HTTP POST。
@POST
@Controller
public String saveUser(@Valid @BeanParam User user) {
return "redirect:users/success";
}
Now, when the user clicks on Create button, the controller handles the POST request and redirects the user to a success page:
现在,当用户点击创建按钮时,控制器处理POST请求,并将用户重定向到一个成功页面。
Let’s make use of Jakarta Validations, CDI, and @MvcBinding to provide form validations:
让我们利用Jakarta Validations、CDI和@MvcBinding来提供表单验证。
@Named("user")
public class User implements Serializable {
@MvcBinding
@Null
private String id;
@MvcBinding
@NotNull
@Size(min = 1, message = "Name cannot be blank")
@FormParam("name")
private String name;
// other validations with getters and setters
}
Once we have the form validations, let’s check for binding errors. If there are any binding errors, we must present the validation messages to the user. For this, let’s inject BindingResult to handle the invalid form parameters. Let’s update our saveUser method:
一旦我们有了表单验证,让我们检查一下绑定错误。如果有任何绑定错误,我们必须向用户展示验证信息。为此,让我们注入BindingResult来处理无效的表单参数。让我们更新我们的saveUser方法。
@Inject
private BindingResult bindingResult;
public String saveUser(@Valid @BeanParam User user) {
if (bindingResult.isFailed()) {
models.put("errors", bindingResult.getAllErrors());
return "user.jsp";
}
return "redirect:users/success";
}
With the validations in place, if a user submits the form without the mandatory parameters, we display the validation errors:
随着验证的到位,如果用户在提交表单时没有填写必须的参数,我们会显示验证错误。
Next, let’s protect our POST method from CSRF attacks by using @CsrfProtected. Add @CsrfProtected to the method saveUser:
接下来,让我们通过使用@CsrfProtected来保护我们的POST方法免受CSRF攻击。将@CsrfProtected添加到方法saveUser。
@POST
@Controller
@CsrfProtected
public String saveUser(@Valid @BeanParam User user) {
}
Next, let’s try to click Create button:
接下来,让我们试着点击Create按钮。
When a controller is protected from CSRF attacks, the client should always pass a CSRF token. So, let’s add a hidden field in user.jsp that adds a CSRF token on every request:
当一个控制器受到CSRF攻击的保护时,客户端应该始终传递一个CSRF令牌。因此,让我们在user.jsp中添加一个隐藏字段,在每个请求中添加一个CSRF令牌。
<input type="hidden" name="${mvc.csrf.name}" value="${mvc.csrf.token}"/>
Similarly, let’s develop a REST API now:
同样地,我们现在来开发一个REST API。
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<User> getUsers() {
return users;
}
This HTTP GET API returns a list of users.
这个HTTP GET API返回一个用户列表。
5. Conclusion
5.总结
In this article, we learned about the Jakarta MVC 2.0 and how to develop a web application and a REST API using Eclipse Krazo. We have seen how MVC 2.0 standardizes the way we build MVC-based web applications in Java.
在这篇文章中,我们了解了Jakarta MVC 2.0以及如何使用Eclipse Krazo开发一个Web应用程序和一个REST API。我们已经看到了MVC 2.0是如何将我们在Java中构建基于MVC的Web应用程序的方式标准化的。
As always, the complete source code is available over on GitHub.
一如既往,完整的源代码可在GitHub上获得,。