注意: 为了方便大家参考最终的代码,特此创建CSDN技术博客专用的Git仓库,
https://github.com/redbeyond/CSDNTecBlogRepo.git
,需要的朋友可以利用一下。
在之前的文章中我们尝试了利用JSF作为Spring MVC的视图,参见尝试利用JSF作为Spring MVC的视图(探索篇之一)之表格的排序与选择,为了简化配置,更加快速的构建工程,将更多的时间留给JSF的探索,这里引入Spring Boot。闲话少说,行动起来!
因为是探索,所以不用Spring Boot的向导创建工程,还是从Maven的基本工程开始手动创建(参考了通过向导创建的结果),这样会加深理解。首先让我们创建一个Maven简单工程。
填写父工程信息如下,当然也可以在后面修改pom.xml
。
- Group Id:
org.springframework.boot
- Artifact Id:
spring-boot-starter-parent
- Version:
2.1.1.RELEASE
注意: 这些信息都可以在https://mvnrepository.com查到。
点击Finish
就得到了一个最初的工程,pom.xml
如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
<groupId>org.study.jsf.basic</groupId>
<artifactId>SpringBootJSFBasic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</project>
然后我们要添加一些参数设置和依赖,这里简单说明一下:
- 首先是
properties
的设置,查看了默认的设置如下(整理结果):
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
<resource.delimiter>@</resource.delimiter>
</properties>
可以看出,我们需要关注的就是JDK版本
还有字符集
。之前建立的工程的字符集
是utf-8
的,所以这里加上JDK版本
的显式指定就够了。
<properties>
<java.version>1.8</java.version>
</properties>
- 接着是依赖的添加,直接参照下面的依赖内容:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-api</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>6.2</version>
</dependency>
</dependencies>
接下来是是把工程转换成Web工程,支持war
发布,这样就可以部署到Tomcat
里面。同以前一样,在pom.xml
里面加入:
<packaging>war</packaging>
然后刷新工程,Alt + F5
启动对话框,点击OK
。
不出意外,工程应该变成下面的样子:
把工程添加到Tomcat
中去。
至此准备工作完毕,开始添加代码,我们要做的是像之前一样显示学生列表和选择效果。
- 添加配置文件
application.yml
,位置是在src/main/resources/config
,内容如下:
server:
port: 9000
spring:
mvc:
view:
prefix: /views/
suffix: .xhtml
profiles:
active:
- dev
- 添加代码文件
Application.java
,注意这里继承了SpringBootServletInitializer
,配置会在Tomcat
中生效。
package org.study.jsf.basic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
- 添加代码文件
JSFServletContextInitializer.java
,作为开发环境相关的配置。
package org.study.jsf.basic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
@Profile("dev")
public class JSFServletContextInitializer implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("javax.faces.PROJECT_STAGE", "Development");
}
}
- 添加代码文件
Student.java
。
package org.study.jsf.basic.repository.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private long id;
private String name;
private int age;
private String description;
}
- 添加代码文件
StudentService.java
。
package org.study.jsf.basic.service;
import java.util.List;
import org.study.jsf.basic.repository.data.Student;
public interface StudentService {
Student findStudentById(long id);
List<Student> findAllStudents();
}
- 添加代码文件
StudentServiceImpl.java
。
package org.study.jsf.basic.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.study.jsf.basic.repository.data.Student;
import org.study.jsf.basic.service.StudentService;
@Service
public class StudentServiceImpl implements StudentService {
private Map<Long, Student> mapStudent;
public StudentServiceImpl() {
this.initForTest();
}
@Override
public Student findStudentById(long id) {
return this.mapStudent.get(id);
}
@Override
public List<Student> findAllStudents() {
return new ArrayList<Student>(this.mapStudent.values());
}
private void initForTest() {
this.mapStudent = new HashMap<Long, Student>();
this.mapStudent.put(1L, new Student(1, "学生一", 25, "擅长前端开发"));
this.mapStudent.put(2L, new Student(2, "学生二", 26, "擅长后端开发"));
this.mapStudent.put(3L, new Student(3, "学生三", 24, "擅长算法设计"));
}
}
- 添加代码文件
StudentController .java
。
package org.study.jsf.basic.mvc.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.study.jsf.basic.mvc.view.StudentView;
import org.study.jsf.basic.repository.data.Student;
import org.study.jsf.basic.service.StudentService;
@Controller
@RequestMapping("/student")
@SessionAttributes("studentView")
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping(value = "/info", method = RequestMethod.GET)
public String showInfo(Model model) {
StudentView studentView = new StudentView();
List<Student> students = this.studentService.findAllStudents();
studentView.setStudents(students);
model.addAttribute("studentView", studentView);
return "showInfo";
}
}
- 添加代码文件
StudentView .java
。
package org.study.jsf.basic.mvc.view;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import org.primefaces.event.SelectEvent;
import org.primefaces.event.UnselectEvent;
import org.study.jsf.basic.repository.data.Student;
import lombok.Data;
@Data
public class StudentView {
private Student selectedStudent;
private List<Student> students;
// 对应的是数据行选择事件
public void onRowSelect(SelectEvent event) {
FacesMessage msg = new FacesMessage("Student Selected", ((Student) event.getObject()).getName());
FacesContext.getCurrentInstance().addMessage(null, msg);
}
// 对应的是数据行选择取消事件
public void onRowUnselect(UnselectEvent event) {
FacesMessage msg = new FacesMessage("Student Unselected", ((Student) event.getObject()).getName());
FacesContext.getCurrentInstance().addMessage(null, msg);
}
}
- 添加代码文件
showInfo.xhtml
,位置是webapp/views
。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<h:form id="form">
<p:growl id="msgs" showDetail="true" />
<p:dataTable value="#{studentView.students}" var="student"
selectionMode="single" rowKey="#{student.id}">
<f:facet name="header">全体学生</f:facet>
<p:ajax event="rowSelect" listener="#{studentView.onRowSelect}"
update=":form:msgs" />
<p:ajax event="rowUnselect" listener="#{studentView.onRowUnselect}"
update=":form:msgs" />
<p:column headerText="姓名" sortBy="#{student.name}">
<h:outputText value="#{student.name}" />
</p:column>
<p:column headerText="年龄" sortBy="#{student.age}">
<h:outputText value="#{student.age}" />
</p:column>
<p:column headerText="特长">
<h:outputText value="#{student.description}" />
</p:column>
</p:dataTable>
</h:form>
</h:body>
</html>
现在可以启动Tomcat
,访问http://http://127.0.0.1:8080/SpringBootJSFBasic/student/info
。
实际上效果和之前的文章没有什么不同,只是利用了Spring Boot简化配置,美中不足的是以Spring Boot应用的方式启动是没有效果的,还是缺少一些和Spring Boot进行整合的相关设置。大家可以先关注一下JoinFaces这个项目,访问http://joinfaces.org/
进一步了解一下。这里先分享一下我的研究结果,感觉经验值又增长了,有问题还是要分析日志并且阅读源代码。
其实根本问题在于对Spring Boot的自动配置和FacesServlet
的初始化了解不深,所以恶补了一下相关知识,参考了JoinFaces项目的实现,大致明白了一些原理。闲话少说,先上代码:
- 首先添加一个自定义的初始化用的类
JsfServletRegistrationBean
,可以看到,涉及到了ServletContainerInitializer
、WebServerFactoryCustomizer
、ConfigurableServletWebServerFactory
,有兴趣可以进一步深入了解一下。
package org.study.jsf.basic;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@RequiredArgsConstructor
@Getter
@Setter
public class JsfServletRegistrationBean<T extends ServletContainerInitializer>
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private final Class<T> servletContainerInitializerClass;
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
Set<Class<?>> clazz = new HashSet<Class<?>>();
ServletContextInitializer servletContextInitializer = new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
T servletContextInitializer = BeanUtils.instantiateClass(getServletContainerInitializerClass());
servletContextInitializer.onStartup(clazz, servletContext);
}
};
factory.addInitializers(servletContextInitializer);
}
}
- 然后需要对
Application.java
的代码进行如下修改,注意添加的新内容。主要做了两件事:
- 实例化
MyFacesContainerInitializer
,这是JSF容器
。 - 添加一个
Listener
,具体是org.apache.myfaces.webapp.StartupServletContextListener
。
package org.study.jsf.basic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.myfaces.ee.MyFacesContainerInitializer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
@Bean
public JsfServletRegistrationBean<MyFacesContainerInitializer> jsfServletRegistrationBean() {
return new JsfServletRegistrationBean<MyFacesContainerInitializer>(MyFacesContainerInitializer.class);
}
@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> myFacesStartupServletContextListener() {
ServletContextInitializer servletContextInitializer = new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener("org.apache.myfaces.webapp.StartupServletContextListener");
}
};
return new WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>() {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.addInitializers(servletContextInitializer);
}
};
}
}
经过上面的加工,我们就可以愉快地利用Spring Boot体验JSF的绚丽页面效果了。
另外,上面的代码原本是利用了
lambda
表达式的写法,我改写成了内部类,下面放上原始代码片段,供参考。可以看到,代码变得更加精简了。
JsfServletRegistrationBean.java
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
Set<Class<?>> clazz = new HashSet<Class<?>>();
factory.addInitializers(servletContext -> {
T servletContextInitializer = BeanUtils.instantiateClass(getServletContainerInitializerClass());
servletContextInitializer.onStartup(clazz, servletContext);
});
}
Application.java
@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> myFacesStartupServletContextListener() {
return factory -> factory.addInitializers(servletContext -> servletContext
.addListener("org.apache.myfaces.webapp.StartupServletContextListener"));
}
好了,本次的探索就到这里了,收获还是不少的,而且通过这次探索发现——要学习内容还有很多,很多……今后会不断加强探索,不断增长自己的经验值。