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表達式。