SpringBoot整合JSP一站式解决方案

1.情景展示

  对于习惯于全栈式开发的我们,前端获取后台的数据,jsp可以说是最好的选择。

2.原因分析  

  但是,由于springboot推崇的是前后端分离,不推荐使用jsp开发,所以,springboot的内置tomcat没有添加对jsp的支持。

这样,当我们在写html的时候,必须使用ajax或form表单来获取后端返回的数据(再也不能像之前那样,后台根据请求决定要转发的页面时,可以携带数据;虽然使用html也可以转发数据,但是,html是无法拿不到转发的数据的),这对于没有进行前后端分离的开发人员来说,无疑是增加了获取后台响应数据的开发时间成本。

  所以,springboot整合jsp是十分有必要的,因为jsp获取数据相当简单(EL表达式、c标签库、写java代码),也不用我们配置视图解析器,tomcat会自动帮我们解析jsp(前后端已经分离的,就没有必要整合jsp啦,只需关注后台crud开发就可以了),下面讲讲如何整合jsp以及期间我所遇到的问题及解决办法。

前后端分离的本质是:将页面完全交由前端来管理,后端释放对于跳转(转发)页面的控制权(不再根据请求来决定要响应什么样的页面,只需专注根据请求返回数据即可);

这种方式有好有坏,对于项目而言,如果配有前端开发人员,建议使用前后端分离,各司其职,节约开发时间;如果没有前端开发的话,不要搞前后端分离,反正最后前端的活儿还是你来干,用jsp多省心。

3.解决方案

  以maven项目为例,最重要的一点就是:添加jsp依赖。

  第一步:添加对jsp的依赖

  需要引入两个jar包:(不着急复制,这不一定符合你的要求)

<!-- 使用jsp引擎,springboot内置tomcat没有此依赖 -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>9.0.36</version>
    <!--只有编译和测试阶段生效-->
    <scope>provided</scope>
</dependency>
<!--增加对 JSP 文件的支持-->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jsp-api</artifactId>
    <version>9.0.36</version>
    <!--只有编译和测试阶段生效-->
    <scope>provided</scope>
</dependency>

  对于这两个jar包定义的scope,是可变更的,按实际需求更改。

  为什么加上这个标签?这个原因我们必须不得不弄得明明白白。

  <scope></scope>标签,用来声明jar包的作用范围,maven的生命周期包含编译、测试、打包这三个阶段,最终,在项目部署前,我们需要对项目进行打包,maven在执行打包命令时,会先对项目进行编译和测试(其中,测试阶段可以通过命令跳过),至于为什么要讲这个,先挖个坑。

  provided,应用到maven的生命周期里就是:编译和测试,换言之就是被它修饰的依赖,只有在编译和测试阶段,有效。更准确地来说,用来欺骗maven用的,因为如果编译和测试阶段没有通过的话,maven是不会执行打包命令的。

这样做,虽然通过了编译和测试阶段,在最终打包时,需要分两种情况:

情形一:打war包;

该jar包是不会被打包到项目的lib目录下,而是被放在了lib-provided当中。

情形二:打jar包。

打jar包的时候,maven照样会将provided修饰的jar包打进来。

另外,打jar包的时候,maven会自动将webapp目录排除在外(webapp下所有的文件,都不会被纳入jar包当中)。

  在idea当中,程序入口xxApplication.java启动项目,本质就是:使用springboot内置的tomcat启动的;

  而springboot内置tomcat不支持jsp;

 

  spring-boot-starter-web包含spring-boot-starter-tomcat

  访问一个jsp页面的请求

  chrome浏览器下,会自动下载该请求的响应内容,并不会将其以html的形式展示到浏览器上(jsp会被tomcat解析成java格式的servlet) 

  所以说:

  使用springboot内置tomcat启动项目,要想使jsp生效,必须引入jsp依赖;

  另外,外置tomcat是支持jsp的:

  换句话说,就是:Apache官方的tomcat的lib目录下有jsp所需的jar包;

这样一来,当我们把项目部署到外置tomcat上时,项目引入的jsp jar包和tomcat的jsp jar包就会冲突。

  使用Apache官方tomcat启动项目,就不需要引入这两个jar包,引入反而是画蛇添足。

  这样一来:如果引入jar包不设置作用范围,使用内置tomcat启动项目可以正常访问到jsp页面,但是,部署到外置tomcat上时,可能会引发jsp jar包冲突;

所以,我们最好设置以上两个jar包的作用范围。

设置好作用范围后,在idea当中,通过内置tomcat启动项目是可以,正常访问到jsp页面的;

如果无法访问到jsp页面,请按照下面两个步骤进行检查:

一般情况下,Include dependencies with "Provided" scope,默认是选中状态的。

意思是:在启动项目的时候,依赖这些jar包;

这样一来,即使jar包被provided限制,也不影响我们在idea当中正常使用。

但是,如果即使设置了也不生效的话,事实上,你也不可能不会生效。


下面是我之前总结的3种解决方案,在idea当中没有用的必要,这部分别看了。

  方案一:使用外置tomcat启动项目。

  这一点,很好理解,因为,在没有springboot前,我们都是将项目添加到tomcat中,然后启动;

  所以,我们只需要将springboot的内置tomcat踢掉即可,不用添加对jsp的依赖。

  使用这种方式进行开发,需要我们将springboot项目当成普通的web项目,在idea里为该项目添加tomcat,部署,启动。

  方案二:注释。

  在开发环境下,使用springboot启动项目,引入jsp依赖

  在生产环境下(也就是部署时), 删除这两个jar包的引用或者声明为编译和测试阶段有效。

  同时,还需要剔除内置tomcat的依赖。

  方案三:开发环境和生产环境分离。

  通过maven的profile来实现

<profiles>
    <!--开发环境-->
    <profile>
        <id>dev</id>
        <!-- 是否激活本环境 -->
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
        <properties>
            <!--打包方式-->
            <project.packaging>jar</project.packaging>
            <!--打包时,需要进行测试-->
            <skipTests>false</skipTests>
        </properties>
        <dependencies>
            <!--jsp不能够在jar中使用,只能够在War中使用
            所以,如果确定部署项目的时候以jar的形式运行的话,则项目就不能使用jsp了,
            因为,maven在执行打包命令时,jsp是不会被打包到jar包当中的-->
            <!-- 使用jsp引擎,springboot内置tomcat没有此依赖 -->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
                <version>9.0.36</version>
            </dependency>
            <!--增加对 JSP 文件的支持-->
            <dependency>
                <groupId>org.apache.tomcat</groupId>
                <artifactId>tomcat-jsp-api</artifactId>
                <version>9.0.36</version>
            </dependency>
            <!-- 添加jstl标签库依赖模块 -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
        </dependencies>
    </profile>
    <!--生产环境-->
    <profile>
        <id>prod</id>
        <!-- 默认激活本环境 -->
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <!--如果不指定成war,默认打的将是jar包-->
            <project.packaging>war</project.packaging>
            <!--打包时,跳过测试阶段(因为测试阶段会去连接数据库,正式数据库本地无法访问,会导致打包失败)-->
            <skipTests>true</skipTests>
        </properties>
        <!--项目中,编译和测试阶段用到的jar包,但tomcat中存在这些jar包,此时,在部署到tomcat中时,我们就需要把它们踢掉-->
        <dependencies>
            <!--内置tomcat(剔除该jar包)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <!--只有编译和测试阶段生效-->
                <scope>provided</scope>
            </dependency>
            <!-- servlet依赖(只在开发时使用,因为部署到tomcat上时,tomcat有对应的jar包) -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <scope>provided</scope>
            </dependency>
            <!-- 添加jstl标签库依赖模块 -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </profile>
</profiles>  

  profiles标签需要放在dependencies标签上面 

  开发环境,勾选dev;生产环境,勾选prod,然后再导包,clean,package。 

  我使用的是方案三。


  另外,要想使用jsp,就必须将项目打包成war形式;

如果确定部署项目的时候以jar的形式运行的话,则项目就不能使用jsp了。可以在resources/template目录下使用html。(原因见文末那篇部署成功却无法访问的文章)

  需要注意的是:tomcat的jsp模板引擎与高版本oracle不兼容(解决方案仔细看)

<!-- oracle -->
<!--高版本的oracle jar包与jsp引擎 jar包冲突,启动会报错:找不到
ORACLE相关jar包:oraclepki.jar、osdt_core.jar、osdt_cert.jar、
ORAI18N相关jar包:orai18n-mapping.jar、orai18n-utility.jar、orai18n-collation.jar、orai18n-translation.jar、orai18n-net.jar、orai18n-servlet.jar、orai18n-lcsd.jar、orai18n-tools.jar、gdk_custom.jar
tomcat-embed-jasper相关jar包:xercesImpl.jar、xml-apis.jar、serializer.jar
等之类的jar包,但并不影响项目的正常运行-->
<dependency>
    <groupId>com.oracle.ojdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>19.3.0.0</version>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.oracle.ojdbc</groupId>
    <artifactId>orai18n</artifactId>
    <version>19.3.0.0</version>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<!--解决方案有三种:
1.不用理会:虽然启动报错,但并不影响项目的正常运行
2.切换成低版本的ORACLE驱动:比如OJDBC6
3.去掉对jsp模板引擎的引用:tomcat-embed-jasper,但是这样,项目将无法使用jsp文件
PS:由于生产环境下,如果项目最终是要部署到tomcat上时,我们需要去除对于jsp模板引擎jar包的引用,部署到tomcat上,以上的jar包缺失的报错信息并不存在,
所以,最佳的方案是第一种-->
<!--<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>11.2.0.4</version>
</dependency>-->
<!-- json -->

  出现了类似xalan相关jar包找不到的错误,也不用管。

  到这里,本文的重点也就讲完啦,基本上使用jsp就没有问题啦。下面进行controller与jsp进行交互讲解。

  第二步:配置视图转发jsp所在路径

  这一步可有可无,看个人喜好。

  修改配置文件application.properties或者application.yml

####spring配置####
spring:
 ###控制器
  mvc:
    ##视图
    view:
      #响应路径前缀
      prefix: /jsp/
      #响应路径后缀
      suffix: .jsp

  根据你的jsp实际所在位置设置路径(如果jsp放置在webapp目录下,则访问路径不用添加该目录) 

  第三步:创建JSP控制器    

  根据自己的实际需要创建controller的目录即可,这里仅供参考 

  配置欢迎页和404页面

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * 项目欢迎页和404页配置
 * @author: Marydon
 * @date: 2020年07月10日 0010 12:03
 */
@Controller
public class JspController {
    // 引用日志
    private static Logger logger = LoggerFactory.getLogger(JspController.class);

    /**
     * 首页
     * @date: 2020年07月10日 0010 17:13
     * @param: 
     * @return: java.lang.String
     */
    // 这3个请求都会进入这个方法(这两种注解方式都可以)
    // @RequestMapping(value = {"/","/index","/index.do"}, method = RequestMethod.GET)
    @GetMapping({"/","/index","/index.do"})
    public String index() {
        // 跳转到欢迎页
        return "index";
    }

    /**
     * 不存在的请求,跳转到404页面
     * @description: ErrorConfig已经拦截了404请求,然后映射到这个请求上
     * @date: 2020年07月10日 0010 17:10
     * @param: 
     * @return: java.lang.String
     */
    @GetMapping("/404.do")
    public String notFound() {
        // 跳转到404页
        return "404";
    }

} 
  请求转发至jsp页面并携带响应数据的三种方式
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 请求转发至页面(携带响应数据)的三种方式
 * 其中,第一种和第三种本质上一致
 * @author: Marydon
 * @date: 2020年07月10日 0010 12:03
 */
// 请求前缀
@RequestMapping("/demo")
// 返回页面需要用这个注解
@Controller
public class demoDataController {
    // 引用日志
    private static Logger logger = LoggerFactory.getLogger(demoDataController.class);

    /*
     * 方式一:跳转页面并响应Map数据
     * @date: 2020年07月10日 0010 18:16
     * @param: map
     * @return: java.lang.String
     */
    @RequestMapping("/getMap.do")
    public String responseMap(Map<String, Object> map){
        map.put("time", new Date());
        map.put("message", "Map取值");
        //return 的是文件的路径+名字
        // /jsp/demo/map.jsp
        return "demo/map";
    }

    /**
     * 方式二:返回ModelAndView
     * @date: 2020年07月10日 0010 18:23
     * @param: 
     * @return: org.springframework.web.servlet.ModelAndView
     */
    @RequestMapping("/getModelAndView.do")
    public ModelAndView responseModelAndView(){
        // 页面位置 /jsp/demo/modelAndView.jsp
        ModelAndView mav = new ModelAndView("demo/modelAndView");
        mav.addObject("time", new Date());
        mav.addObject("message", "ModelAndView取值");
        return mav;
    }

    /*
     * 方法三:直接使用Model封装内容,并返回页面字符串
     * @date: 2020年07月10日 0010 18:38
     * @param: model
     * @return: java.lang.String
     */
    @RequestMapping("/getModel.do")
    public String responseModel(Model model){
        // 页面位置 /jsp/demo/model.jsp
        model.addAttribute("time", new Date());
        model.addAttribute("message", "model");
        return "demo/model";
    }

    /*
     * 返回json数据
     * @decription:springmvc返回Json的两种方式
     *  方式一:类添加注解@Controller,方法上添加@ResponseBody
     *  方式二:类上只需添加@RestController,方法上不用添加@
     *  使用这种方式,对应的类就只能返回json数据,而不能转发至页面上了(前后端分离一般采用的就是这种方式)
     * @date: 2020年07月17日 0017 17:14
     * @param: 
     * @return: java.util.Map<java.lang.String,java.lang.Object>
     */
    @ResponseBody
    @RequestMapping("/getJson.do")
    public Map<String, Object> responseJSON(){
        Map<String, Object> map = new HashMap<>();
        map.put("data","Json数据");
        return map;
    }
}

  介绍已经够详细的了,看注释就能懂。

  第四步:配置响应页面并取值

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>数据交互一</title>
</head>

<body>
<h1>${message}:${time}</h1>
</body>
</html>

  map.jsp、model.jsp、modelAndView.jsp这三个页面的取值方式用的都是EL表达式。

4.关于整合jsp的延伸

2022年2月24日19:06:52

通常情况下,使用jsp进行前端功能开发,我们会使用c标签库,所以,需要添加jstl依赖。

查看代码
<!-- 添加jstl标签库依赖模块 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    <!--只有编译和测试阶段生效-->
    <scope>provided</scope>
</dependency>
<!-- 添加jstl标签库依赖 -->
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
    <!--只有编译和测试阶段生效,会被打包到lib-provided目录下-->
    <scope>provided</scope>
</dependency>

由第一步我们知道:在idea当中,即使jar包被设置成了只在编译和测试阶段有效,idea也可以随心所欲地使用这些jar包。

基于idea的这个功能,我们不放对jar包做进一步优化:

查看代码
<!--2022年2月24日18:31:22-->
<!--springboot内置tomcat-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <!--只有编译和测试阶段生效,会被打包到lib-provided目录下-->
    <scope>provided</scope>
</dependency>
<!-- 使用jsp引擎,springboot内置tomcat没有此依赖 -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>9.0.36</version>
    <!--只有编译和测试阶段生效,会被打包到lib-provided目录下-->
    <!--在idea当中,即使被设置成provided,springboot在运行的时候,也会引用该jar包-->
    <!--(只在开发时使用,因为部署到tomcat上时,tomcat的lib目录下有对应的jar包)-->
    <scope>provided</scope>
</dependency>
<!--增加对 JSP 文件的支持-->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jsp-api</artifactId>
    <version>9.0.36</version>
    <!--只有编译和测试阶段生效,会被打包到lib-provided目录下-->
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <!--只有编译和测试阶段生效,会被打包到lib-provided目录下-->
    <scope>provided</scope>
</dependency>

因为上面提到的idea的功能,所以,我们完全可以将外置tomcat不需要的jar包全部用provided进行限定;

这样一来,也不用区分是开发环境还是生产环境了。

另,关于jstl的两个jar包的引用方式即可以使用被provided限定,也可以不限定。

查看代码
<!-- 添加jstl标签库依赖模块 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    <!--外置tomcat的lib目录下没有此jar包,如果有,可以将其从打包后的war包当中删掉-->
    <!--<scope>provided</scope>-->
</dependency>
<!-- 添加jstl标签库依赖 -->
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
    <!--外置tomcat的lib目录下没有此jar包,如果有,可以将其从打包后的war包当中删掉-->
    <!--<scope>provided</scope>-->
</dependency>

如果限定的话,当tomcat的lib目录下没有这两个jar包时,我们可以将其从lib-provided目录中抽出来,放到tomcat的lib目录下或自身的lib下。 

如果不限定的话,它俩将会打包到自身项目的lib目录下,当tomcat的lib下有这两个jar,且冲突的时候,我们可以将tomcat或者自身下的jar包进行移除。

2022年3月23日15:48:15

5.关于maven<scope></scope>的说明

compile:默认值,表示当前依赖包,要参与当前项目的编译,后续测试,运行时,打包;

provided:代表在编译和测试的时候用,运行,打包的时候不会打包进去;

test:表示当前依赖包只参与测试时的工作:比如Junit;

runtime:表示当前依赖包只参与运行周期,其他跳过了;

system:从参与度和provided一致,不过被依赖项不会从maven远程仓库下载,而是从本地的系统拿(需要通过systemPath属性来定义路径)。

写在最后

  哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!

 相关推荐:

posted @ 2020-08-24 11:02  Marydon  阅读(3575)  评论(0编辑  收藏  举报