1.情景展示
对于习惯于全栈式开发的我们,前端获取后台的数据,jsp可以说是最好的选择。
2.原因分析
但是,由于springboot推崇的是前后端分离,所以,springboot的内置tomcat没有添加对jsp的支持。这样,我们就不得不以json格式作为响应数据,这样,我们在写前端html的时候,就又不得不使用ajax来和后端进行数据交互,对于没有进行前后端分离的开发人员来说,无疑是增加了获取数据的开发时间成本。
所以,springboot整合jsp是十分有必要的,因为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> </dependency> <!--增加对 JSP 文件的支持--> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jsp-api</artifactId> <version>9.0.36</version> </dependency>
对于这两个jar包的引用方式,不同的部署方式,引用方式是不同的。
我们在网上看到的会多一个属性标签
至于为什么加上这个标签,他并没有告诉你。这个原因我们必须不得不弄得明明白白,否则,你在引入之后会引发各种意想不到的问题。
<scope></scope>标签,用来声明jar包的作用范围,maven的生命周期包含编译、测试、打包这三个阶段,最终,在项目部署前,我们需要对项目进行打包,maven在执行打包命令时,会先对项目进行编译和测试(其中,测试阶段可以通过命令跳过),至于为什么要讲这个,先挖个坑。
provided,应用到maven的生命周期里就是:编译和测试,换言之就是被它修饰的依赖,只有在编译和测试阶段,有效。更准确地来说,用来欺骗maven用的,因为如果编译和测试阶段没有通过的话,maven是不会执行打包命令的。这样做,虽然通过了编译和测试阶段,在最终打包时,该jar包是不会被打包到项目当中的(这一点,我已经印证过了,自己可以试一下,也可以看文末推荐的具体过程)(PS:在本示例中,没有引入jsp项目相关jar包并不会造成maven打包失败)。
那么,回到上面的问题:为什么要加上这个标签?因为他不想让对应的jar包添加到项目当中。
现在,我们来理一理思路:
第一阶段:使用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有jsp所需的jar包,这样一来,当我们把项目部署到外置tomcat上时,项目引入的jsp jar包和tomcat的jsp jar包就会冲突。
可以总结为:
使用Apache官方tomcat启动项目,就不需要引入这两个jar包,引入反而是画蛇添足。
这样一来:如果引入jar包不设置作用范围,使用内置tomcat启动项目可以正常访问到jsp页面,但是,部署到外置tomcat上时,就会引发jsp jar包冲突;
如果设置作用范围,内置tomcat启动项目无法访问到jsp页面,而,部署到外置tomcat,我们则可以正常访问项目。
这就是顾此失彼的原因所在,绝大部分人也都是卡在了这里!!!
对此,我一共总结了3种解决方案,供大家选择:
方案一:使用外置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形式,因为,maven在执行打包命令时,jsp是不会被打包到jar包当中的。所以,如果确定部署项目的时候以jar的形式运行的话,则项目就不能使用jsp了。
需要注意的是: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"; } }
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表达式。