文章目录
- 一.`fruit`
- 1.`controller`
- 1.1`update()`
- 1.2`edit()`:发报机-->`edit()`-->`edit.html`-->`update()`-->主界面
- 1.3`delete()`
- 1.4`index()`
- 1.5`add()`:发报机-->`add.html`-->`add()`-->主界面
- 2.`dao`:`DAO(DataAccessobjects)`层
- 2.1`FruitDAO`
- 2.2`impl.FruitDAOImpl`
- 2.2.1继承:`BaseDAO`类 和 `FruitDAO`接口
- 2.2.2实现:重写实现`FruitDAO`中的方法
- 3.`pojo`
- 3.1`Fruit`基本类,对应一张表
- 4.`service`业务层
- 4.1`FruitService`抽象类
- 4.2`FruitServiceImpl`实现类
- 二.`myssm`
- 1.`basedao`数据库交互包
- 1.1`BaseDAO`数据库通用语句执行
- 1.2`ConnUtil`数据库连接工具类
- 1.3`DAOException`异常类型
- 2.`filters`过滤器的实现类包
- 2.1`CharacterEncodingFilter`设置编码
- 2.2`OpenSessionInViewFilter`事务管理(执行提交与回滚)
- 3.`ioc(Inversion of Control)`控制反转or依赖注入`(DI)`
- 3.1`BeanFactory`抽象类
- 3.2`ClassPathXmlApplicationContext`实现类
- 3.2.1初始化:即构造器
- 3.2.2调用`getBean`
- 4.`listeners`监听器
- 5.`myspringmvc`
- 5.1`DispatcherServlet`发报机,决定调用哪个组件
- 5.1.1注解`@WebServlet("*.do")`用于响应浏览器的`"*.do"`请求
- 5.1.2继承:`ViewBaseServlet`
- 5.1.3初始化方法:获取`beanMap`容器
- 5.1.4`service`方法
- 5.2`DispatcherServletException`异常类型
- 5.3`ViewBaseServlet`模板:用于设置渲染
- 5.3.1继承:`HttpServlet`
- 5.3.1设置web.xml配置文件:前缀和后缀
- 5.3.3重写`init()`方法,初始化模板引擎
- 5.3.4处理模板`processTemplate()`
- 6.`trans`
- 6.1事务管理类`TransactionManage`
- 7.`util`工具包
- 7.1`StringUtil`判断字符串是否为空
- 三.WEB类
- 1.页面`index.html`
- 2.页面`eidt.html`
- 3.页面`add.html`
- 4.`web.xml`文件
一.fruit
1.controller
调用fruitService
中的基本业务,实现封装的业务功能
1.1update()
private String update(Integer fid, String fname, Integer price, Integer fcount, String remark){
//更新数据库
fruitService.updateFruit(new Fruit(fid,fname,price,fcount,remark));
//渲染页面,执行跳转
return "redirect:fruit.do";
}
1.2edit():发报机–>edit()–>edit.html–>update()–>主界面
private String edit(Integer fid, HttpServletRequest req){
if(fid != null){
Fruit fruit = fruitService.getFruitByFid(fid);
req.setAttribute("fruit", fruit);
return "edit";
}
return "error";
}
1.3delete()
private String delete(Integer fid){
if(fid != null){
//删除数据库记录
fruitService.deleteFruit(fid);
//客户端重定向语句
return "redirect:fruit.do";
}
return "error";
}
1.4index()
private String index(String oper, String keyword,Integer pageNo, HttpServletRequest req){
//获取会话
HttpSession session = req.getSession();
if(pageNo == null){
pageNo = 1;
}
//判断是否为查询操作
if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){//搜索操作
//获取关键词
if(StringUtil.isEmpty(keyword)){
keyword = "";
}
//将关键词保存到session
session.setAttribute("keyword",keyword);
}else{//显示全部列表
//获取session关键词
Object keyObj = session.getAttribute("keyword");
if(keyObj == null){
keyword = "";
}else{
keyword = (String)keyObj;
}
//设置关键词
session.setAttribute("keyword", keyword);
}
//保存当前页码到session作用域
session.setAttribute("pageNo",pageNo);
keyword = (String)session.getAttribute("keyword");
//保存总页码到session作用域
int pageCount = fruitService.getPageCount(keyword);
session.setAttribute("pageCount",pageCount);
//从数据库读取fruitList
List<Fruit> fruitList = fruitService.getFruitList(keyword, pageNo);
//保存到session作用域
session.setAttribute("fruitList", fruitList);
//此处的视图名称为 index
//thymleaf会将 逻辑视图名称 对应到 物理视图名称 上去
//逻辑视图名称:index
//物理视图名称:view-prefix + 逻辑视图名称 + view-suffix
//super.processTemplate("index", request, response);
return "index";
}
1.5add():发报机–>add.html–>add()–>主界面
private String add(String fname, Integer price, Integer fcount, String remark){
//更新数据库
fruitService.addFruit(new Fruit(0,fname,price,fcount,remark));
//返回客户端重定向语句
return "redirect:fruit.do";
}
2.dao:DAO(DataAccessobjects)层
2.1FruitDAO
需要实现与数据库交互的基本功能:增删改查(总列表,单条记录,总数目)
public interface FruitDAO {
//获取指定页码的库存列表信息,每页显示5条
List<Fruit> getFruitList(String keyword, Integer pageNo);
//根据水果id获取库存信息
Fruit getFruitByFid(Integer fid);
//修改制定的库存记录
Boolean updateFruit(Fruit fruit);
//根据fid删除库存记录
Boolean delFruitById(Integer fid);
//添加新库存
Boolean addFruit(Fruit fruit);
//查询总记录条数的方法
int getFruitCount(String keyword);
}
2.2impl.FruitDAOImpl
2.2.1继承:BaseDAO类 和 FruitDAO接口
2.2.2实现:重写实现FruitDAO中的方法
3.pojo
3.1Fruit基本类,对应一张表
- 1.类的属性与表的列分别对应。
- 2.类的属性体现类之间的关系。
4.service业务层
4.1FruitService抽象类
public interface FruitService {
//获取指定页面的库存信息
List<Fruit> getFruitList(String keyword, Integer pageNo);
//添加库存记录
void addFruit(Fruit fruit);
//根据id查看库存信息
Fruit getFruitByFid(Integer fid);
//删除指定库存信息
void deleteFruit(Integer fid);
//获取总页数
Integer getPageCount(String keyword);
//修改特定库存记录
void updateFruit(Fruit fruit);
}
4.2FruitServiceImpl实现类
调用fruitDAO
中的方法实现相应的功能
二.myssm
1.basedao数据库交互包
1.1BaseDAO数据库通用语句执行
构造器:
public BaseDAO() {
//getClass() 获取Class对象,当前我们执行的是new FruitDAOImpl() , 创建的是FruitDAOImpl的实例
//那么子类构造方法内部首先会调用父类(BaseDAO)的无参构造方法
//因此此处的getClass()会被执行,但是getClass获取的是FruitDAOImpl的Class
//所以getGenericSuperclass()获取到的是BaseDAO的Class
Type genericType = getClass().getGenericSuperclass();
//ParameterizedType 参数化类型
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
//获取到的<T>中的T的真实的类型
Type actualType = actualTypeArguments[0];
try {
entityClass = Class.forName(actualType.getTypeName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new DAOException("BaseDAO 构造方法出错了,可能的原因是没有指定<>中的类型");
}
}
- 获取连接:
getConn()
调用ConnUtil
类中的获取连接方法,保证只有一个连接(为了方便事务管理中的回滚) - ~~关闭资源:
close()
~~取消该方法,方便事务管理 - 给预处理命令设置参数:
setParams(PreparedStatement psmt , Object... params)
- 执行更新数据库
executeUpdate(String sql , Object... params)
- 执行查询,返回一条记录:
load(String sql , Object... params)
//通过rs可以获取结果集的元数据
//元数据:描述结果集数据的数据 , 简单讲,就是这个结果集有哪些列,什么类型等等
ResultSetMetaData rsmd = rs.getMetaData();
//获取结果集的列数
int columnCount = rsmd.getColumnCount();
if(rs.next()){
for(int i = 0 ; i<columnCount;i++){
String columnName = rsmd.getColumnName(i+1); //fid fname price
Object columnValue = rs.getObject(i+1); //33 苹果 5
setValue(entity,columnName,columnValue);
}
}
- 执行查询,返回多条记录:
executeQuery(String sql , Object... params)
- 执行复杂查询,统计统计结果:
Object[] executeComplexQuery(String sql , Object... params)
- 通过反射技术给property属性赋值:
setValue(Object obj, String property, Object propertyValue)
1.2ConnUtil数据库连接工具类
- 创建连接:
createConnection)()
- 获取连接:
getConnection()
从本地线程获取连接.若连接不存在,则创建连接并保存到本地线程. - 断开连接:
closeConn()
从本地线程获取连接,若连接存在,则关闭连接.
1.3DAOException异常类型
定义一个DAOException异常
2.filters过滤器的实现类包
包括初始化、过滤、销毁三个方法。
需要使用注解或者配置文件设置过滤对象:
//1.注解:
@WebFilter("*.do")
<!--2.xml文件-->
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.clucky.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.1CharacterEncodingFilter设置编码
- 注解:拦截
*.do
结尾的请求
@WebFilter(urlPatterns = {“*.do”}, initParams = {@WebInitParam(name = “encoding”, value = “utf-8”)})
2. 初始化: 读取注解或者配置文件中的初始化参数设置编码*(可能是过滤器的初始化)*
```java
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String encodingStr = filterConfig.getInitParameter("encoding");
if(StringUtil.isNotEmpty(encodingStr)){
encoding = encodingStr;
}
}
- 过滤前后的语句: 设置编码
((HttpServletRequest)servletRequest).setCharacterEncoding(encoding);
2.2OpenSessionInViewFilter事务管理(执行提交与回滚)
- 开启事务–>执行service–>执行完成提交事务
但是若事务执行异常–>回滚
try{
TransactionManager.beginTrans();
System.out.println("开启事务...");
filterChain.doFilter(servletRequest, servletResponse);
TransactionManager.commit();
System.out.println("提交事务...");
} catch (Exception e) {
e.printStackTrace();
try {
TransactionManager.rollback();
System.out.println("回滚事务...");
} catch (Exception e1) {
e1.printStackTrace();
System.out.println("回滚异常");
}
}
- 需要
TransactionManage
的工具类进行管理事务的开启、提交、和回滚。
3.ioc(Inversion of Control)控制反转or依赖注入(DI)
3.1BeanFactory抽象类
3.2ClassPathXmlApplicationContext实现类
3.2.1初始化:即构造器
- 利用
applicationContext.fxml
文件建立类与类之间的关系
<?xml version="1.0" encoding="utf-8"?>
<beans>
<!-- 建立fruit和对应类的映关系,方便初始化的时候调用 -->
<bean id="fruitDAO" class="com.atlff.fruit.dao.impl.FruitDAOImpl"></bean>
<bean id="fruitService" class="com.atlff.fruit.service.impl.FruitServiceImpl">
<!-- 表示属性:name是属性名,ref表示引用其他bean的id -->
<property name="fruitDAO" ref="fruitDAO"></property>
</bean>
<bean id="fruit" class="com.atlff.fruit.controllers.FruitController">
<property name="fruitService" ref="fruitService"></property>
</bean>
</beans>
- 获取配置文件的输入流,建立创建
Document
对象
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);
//1.创建DocumentBuilderFactory对象
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2.创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//3.创建Document对象
Document document = documentBuilder.parse(inputStream);
- 获取
Bean
结点,创建相应的对象,放置到beanMap容器中
//4.获取所有bean结点
NodeList beanNodeList = document.getDocumentElement().getElementsByTagName("bean");
for (int i = 0; i < beanNodeList.getLength(); i++) {
Node beanNode = beanNodeList.item(i);
if (beanNode.getNodeType() == Node.ELEMENT_NODE) {
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");//获取id属性
String className = beanElement.getAttribute("class");//获取class属性(返回字符串)
//利用反射得到bean对象
Class<?> beanClass = Class.forName(className);
Object beanObj = beanClass.newInstance();
//将bean对象保存到Map容器
beanMap.put(beanId, beanObj);
}
}
- 构建
Bean
结点之间的依赖关系
//5.组装bean之间的依赖关系
for (int i = 0; i < beanNodeList.getLength(); i++) {//遍历beanNodeList链表
Node beanNode = beanNodeList.item(i);
if (beanNode.getNodeType() == Node.ELEMENT_NODE) {
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");
NodeList beanChildNodeList = beanElement.getChildNodes();//获取bean元素的子结点
for(int j = 0; j < beanChildNodeList.getLength(); j++){
Node beanChildNode = beanChildNodeList.item(j);
if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
Element propertyElement = (Element)beanChildNode;
String propertyName = propertyElement.getAttribute("name");
String propertyRef = propertyElement.getAttribute("ref");
//1)找到propertyRef对应的对象
Object refObj = beanMap.get(propertyRef);
//2)将refObj设置到当前bean对应的实例的property属性上去
Object beanObj = beanMap.get(beanId);
Class<?> beanClazz = beanObj.getClass();
Field propertyField = beanClazz.getDeclaredField(propertyName);//设置类的属性
propertyField.setAccessible(true);
propertyField.set(beanObj,refObj);
}
}
}
}
3.2.2调用getBean
@Override
public Object getBean(String id) {
return beanMap.get(id);
}
4.listeners监听器
当对象发生时自动执行,类似于触发器。
功能:创建IOC
容器并容器保存到session
作用域,方便之后调用
注解:@WebListener
接口:ServletContextListener
方法实现:
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//1.获取ServletContext对象
ServletContext application = servletContextEvent.getServletContext();
//2.获取上下文的初始化参数
String path = application.getInitParameter("contextConfigLocation");
//3.创建IOC容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext(path);
//4.将IOC容器保存到application作用域
application.setAttribute("beanFactory", beanFactory);
}
5.myspringmvc
5.1DispatcherServlet发报机,决定调用哪个组件
5.1.1注解@WebServlet("*.do")用于响应浏览器的"*.do"请求
5.1.2继承:ViewBaseServlet
5.1.3初始化方法:获取beanMap容器
public void init() throws ServletException {
super.init();
//优化:可以在ContextListener中初始化给参数赋值
Object beanFactoryObj = getServletContext().getAttribute("beanFactory");
if(beanFactoryObj != null){
beanFactory = (BeanFactory)beanFactoryObj;
}else{
throw new RuntimeException("IOC容器获取失败!");
}
System.out.println("初始化完成!");
}
5.1.4service方法
- 获取servlet的名字,决定调用哪个组件:
fruit.do --> fruit
//获取servlet的名字
String servletPath = req.getServletPath();
int lastDot = servletPath.lastIndexOf(".do");
servletPath = servletPath.substring(1, lastDot);
//得到servlet名字对应的控制器类
Object contollerBeanObj = beanFactory.getBean(servletPath);
- 获取控制器中对应的方法:即操作“业务”
String operate = req.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index";
}
- 利用反射调用
contollerBeanObj
中的operate
方法,并获取参数值,并处理视图
Method[] declaredMethods = contollerBeanObj.getClass().getDeclaredMethods();
for(Method method:declaredMethods){
if(operate.equals(method.getName())){
//1.统一获取请求参数
//1.1获取当前方法的参数名称,返回参数数组
Parameter[] parameters = method.getParameters();
Object[] parameterValues = new Object[parameters.length];//保存参数的值
for(int i = 0; i < parameters.length; i++){
Parameter parameter = parameters[i];
String parameterName = parameter.getName();
if("req".equals(parameterName)){
parameterValues[i] = req;
}else if("resp".equals(parameterName)){
parameterValues[i] = resp;
}else if("session".equals(parameterName)){
parameterValues[i] = req.getSession();
}else{
//从请求中获取参数值
String parameterValue = req.getParameter(parameterName);
String typeName = parameter.getType().getName();
Object parameterObj = parameterValue;
//设置参数值的类型
if(parameterObj != null && "java.lang.Integer".equals(typeName)){
parameterObj = Integer.parseInt(parameterValue);
}
parameterValues[i] = parameterObj;
}
}
//2,controller组件中的方法调用
method.setAccessible(true);
Object returnObj = (String)method.invoke(contollerBeanObj, parameterValues);
//3,视图处理
String methodReturnStr = (String) returnObj;
if(methodReturnStr.startsWith("redirect:")){ //比如:redirect:fruit.do
String redirectStr = methodReturnStr.substring("redirect:".length());
resp.sendRedirect(redirectStr);
}else{//比如:edit index
super.processTemplate(methodReturnStr,req,resp);
}
}
}
5.2DispatcherServletException异常类型
抛出发报机异常
5.3ViewBaseServlet模板:用于设置渲染
5.3.1继承:HttpServlet
5.3.1设置web.xml配置文件:前缀和后缀
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 在上下文参数中配置视图前缀和视图后缀 -->
<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>applicationContext</param-value>
</context-param>
</web-app>
5.3.3重写init()方法,初始化模板引擎
@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");//获取xml文件中对应的字符串
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
5.3.4处理模板processTemplate()
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
6.trans
6.1事务管理类TransactionManage
- 通过
ConnUtil
中的获取的Connection
进行设置 - 分别设置开启事务、提交事务、回滚事务
//开启事务
public static void beginTrans() throws SQLException {
ConnUtil.getConnection().setAutoCommit(false);
}
//提交事务
public static void commit() throws SQLException {
Connection conn = ConnUtil.getConnection();
conn.commit();
ConnUtil.closeConn();
}
//回滚事务
public static void rollback() throws SQLException {
Connection conn = ConnUtil.getConnection();
conn.rollback();
ConnUtil.closeConn();
}
7.util工具包
7.1StringUtil判断字符串是否为空
三.WEB类
1.页面index.html
- 渲染页面
<!-- 若列表为空,显示空页面 -->
<tr th:if="${#lists.isEmpty(session.fruitList)}">
<td colspan="4">对不起!库存为空</td>
</tr>
<!-- 若列表有数据,遍历数据显示 -->
<tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit : ${session.fruitList}">
<td><a th:text="${fruit.fname}" th:href="@{/fruit.do(fid=${fruit.fid},operate='edit')}">苹果</a></td>
<td th:text="${fruit.price}">5</td>
<td th:text="${fruit.fcount}">20</td>
<!-- <td ><img src="imgs/del.jpg" class="delImg" th:οnclick="'delFruit(' + ${fruit.fid} + ')'"/></td> 拼接字符串方法-->
<td ><img src="imgs/del.jpg" class="delImg" th:onclick="|delFruit(${fruit.fid})|"/></td>
</tr>
- 查询表单
<form th:action="@{fruit.do}" method="post" style="float:left">
<input type="hidden" name="oper" value="search"/>
请输入查询关键字:<input type="text" name="keyword" th:value="${session.keyword}"/>
<input type="submit" value="查询" class="btn">
</form>
- 编辑表单
<td><a th:text="${fruit.fname}" th:href="@{/fruit.do(fid=${fruit.fid},operate='edit')}">苹果</a></td>
- 添加表单: 跳转到
add.html
页面
<a th:href="@{/add.html}" style="margin-bottomt: 4px; float:right">添加新库存记录</a>
- 页面跳转
<input type="button" value="首 页" class="btn" th:onclick="|page(1)|" th:disabled="${session.pageNo == 1}"/>
<input type="button" value="上一页" class="btn" th:onclick="|page(${session.pageNo - 1})|" th:disabled="${session.pageNo == 1}"/>
<input type="button" value="下一页" class="btn" th:onclick="|page(${session.pageNo + 1})|" th:disabled="${session.pageNo == session.pageCount}"/>
<input type="button" value="尾 页" class="btn" th:onclick="|page(${session.pageCount})|" th:disabled="${session.pageNo == session.pageCount}"/>
2.页面eidt.html
- 渲染表单
- 提交表单
<form th:action="@{/fruit.do}" method="post" th:object="${fruit}">
<input type="hidden" name="operate" value="update">
<table id="tbl_fruit">
<!--隐藏域,不显示但发送表单-->
<input type="hidden" name="fid" th:value="*{fid}">
<tr>
<th class="w20">名称:</th>
<td><input type="text" name="fname" th:value="*{fname}"></td>
</tr>
<tr>
<th class="w20">单价:</th>
<td><input type="text" name="price" th:value="*{price}"></td>
</tr>
<tr>
<th class="w20">库存:</th>
<td><input type="text" name="fcount" th:value="*{fcount}"></td>
</tr>
<tr>
<th class="w20">备注:</th>
<td><input type="text" name="remark" th:value="*{remark}"></td>
</tr>
<tr>
<th class="w20" colspan="2"><input type="submit" value="修改"></th>
</tr>
</table>
</form>
3.页面add.html
- 提交表单
<form action="fruit.do" method="post">
<input type="hidden" name="operate" value="add">
<table id="tbl_fruit">
<tr>
<th class="w20">名称:</th>
<td><input type="text" name="fname"></td>
</tr>
<tr>
<th class="w20">单价:</th>
<td><input type="text" name="price"></td>
</tr>
<tr>
<th class="w20">库存:</th>
<td><input type="text" name="fcount"></td>
</tr>
<tr>
<th class="w20">备注:</th>
<td><input type="text" name="remark"></td>
</tr>
<tr>
<th class="w20" colspan="2"><input type="submit" value="添加"></th>
</tr>
</table>
</form>
4.web.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 在上下文参数中配置视图前缀和视图后缀 -->
<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>applicationContext</param-value>
</context-param>
</web-app>
## 4.`web.xml`文件
```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 在上下文参数中配置视图前缀和视图后缀 -->
<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>applicationContext</param-value>
</context-param>
</web-app>