尝试利用JSF作为Spring MVC的视图(探索篇之二)之利用Spring Boot简化配置

注意: 为了方便大家参考最终的代码,特此创建CSDN技术博客专用的Git仓库https://github.com/redbeyond/CSDNTecBlogRepo.git,需要的朋友可以利用一下。

  在之前的文章中我们尝试了利用JSF作为Spring MVC的视图,参见尝试利用JSF作为Spring MVC的视图(探索篇之一)之表格的排序与选择,为了简化配置,更加快速的构建工程,将更多的时间留给JSF的探索,这里引入Spring Boot。闲话少说,行动起来!
  因为是探索,所以不用Spring Boot的向导创建工程,还是从Maven的基本工程开始手动创建(参考了通过向导创建的结果),这样会加深理解。首先让我们创建一个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
刷新工程
  不出意外,工程应该变成下面的样子:
Web工程
  把工程添加到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,可以看到,涉及到了ServletContainerInitializerWebServerFactoryCustomizerConfigurableServletWebServerFactory,有兴趣可以进一步深入了解一下。
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的代码进行如下修改,注意添加的新内容。主要做了两件事:
  1. 实例化MyFacesContainerInitializer,这是JSF容器
  2. 添加一个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表达式的写法,我改写成了内部类,下面放上原始代码片段,供参考。可以看到,代码变得更加精简了。

  1. 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);
		});
		
	}
  1. Application.java
	@Bean
	public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> myFacesStartupServletContextListener() {

		return factory -> factory.addInitializers(servletContext -> servletContext
				.addListener("org.apache.myfaces.webapp.StartupServletContextListener"));
		
	}

  好了,本次的探索就到这里了,收获还是不少的,而且通过这次探索发现——要学习内容还有很多,很多……今后会不断加强探索,不断增长自己的经验值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值