Spring Boot 2.1.2整合JSP配置及打包WAR独立运行——IDEA篇
01 前言
有段时间不做项目了,但是由于工作需要,要做一个小项目,就想直接用Spring Boot
+Maven
+JSP
+Spring Data JPA
+Mysql
。之前简单接触过spring Boot,比较方便的一点是能直接打包运行,不需另外配Tomcat,赶紧上车练练手。比较郁闷的是,噼里啪啦敲完V1.0,好了,在打包独立运行的过程中碰到不少坑,查了一通资料,也没真正解决问题。历经几番折腾,总算能用了。在此做一下记录,也给其他碰到类似问题的小伙伴一个参考。
提个醒
此文主要解决以下两个问题(如果没有符合各位实际的问题的话,也没必要继续往后读了):
1、未使用Maven打包前:Spring Boot 整合JSP后,前端访问(通过Controller访问JSP)时变成下载文件。
2、使用Maven打包后:打包jar或war后独立运行,前端访问不到JSP页面(但是Controller返回其他数据正常,JSP就是不行);或者是访问到了JSP页面,但是JSP引用的静态资源无法访问。
02 正文
1、准备
环境:
JDK 1.8+IDEA+MySQL 5.5+Maven 3.5 + Spring Boot 2.1.2
*连接池使用 alibaba 的druid
具体的搭建过程这里就不想细说了,使用向导一步步操作下来即可。主要在解决上文提到的两个问题。
但是已经截了图,还是把图贴上来示意一下。
新建Project完成后的结构:
大体完成V1.0时(此时还未做JSP相关配置,只写了bookinfo对应的entity、dao、service,在application.properties
中配置了数据源相关、JPA相关、日志相关、json相关的项,同时写了一个简单的controller),Project的结构是这样的:
此时controller是这样的:
此时,直接启动工具栏的Run BookmisApplication
,前端访问http://127.0.0.1:8080/getDate
,毫无疑问是可以的:
但是要访问http://127.0.0.1:8080/getbook/b0001
却还不行:
提示:entity、dao、service的各层需加上对应的注解,同时主类
BookmisApplication
上需加@AutoConfigurationPackage
来自动扫描配置
下面说一下配置JSP
(1)配置pom.xml
- 设置项目打包为war:
<packaging>war</packaging>
- 添加tomcat支持和JSTL标签库:
<!-- tomcat支持 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- jstl标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
(2)添加Web目录:在src/main
下建立webapp/WEB-INF/pages
目录结构,webapp
与java
目录同一级。如果此时想在pages
下新建一个jsp文件,右键,发现没有jsp的选项,这是因为此目录还未关联Web,如图:
(3)关联Web目录:
①设置:
②设置Facets
,添加web,关联到前面创建的webapp
目录:
其中,将web.xml
放到webapp/WEB-INF
下:
同时,将根目录映射为webapp
:
此时,如果下方提示未建立对应的artifact
,则单击创建:
③设置Artifacts
,默认即可:
完成以上三步后,应用并确定。
此时观察项目结构略有变化:
同时,在pages
目录右键,可以创建JSP文件:
(4)配置application.properties
- 设置JSP视图前、后缀:
spring.mvc.view.prefix=/WEB-INF/pages/
spring.mvc.view.suffix=.jsp
以上,已经可以进行jsp访问了,但是启动方式要注意:
不能直接在右上角的工具栏上的Run BookmisApplication
,而是要打开Maven Projects
窗口,找到spring-boot:run
,右键,运行,,如图:
若直接使用工具栏的运行,如果无法访问JSP,后台可能会这么输出:
经调试(断点处参考文尾附表, PathResourceResolver.java类中151行)发现,通过工具类直接启动的这种方式,虽然在application.properties中配置了jsp前后缀,但是在处理时只会查找默认的5个位置(classpath:/META-INF/resources/,classpath:/resources/,classpath:static/,classpath:/public/,classpath:/),并不会去查找
WEB-INF
。这个地方表示很疑惑,知道的大神可以交流下。可能是通过这种方式运行,不会将webapp
目录下的东西拷贝到运行时tomcat目录下?或者哪里设置不对。
下面试试效果:
- 已经在
pages
目录下创建两个jsp(由index.jsp
跳转到book.jsp
):index.jsp
和book.jsp
- controller也修改了一下:
那么,现在的项目结构是这样的:
在Maven Projects
窗口启动spring-boot:run
,前端访问http://127.0.0.1:8080
,完全OK:
数据库的表:
2、针对问题1
参考上文的JSP配置步骤操作下来,一般此问题不会再有。如有,请检查配置:添加依赖;打war
包;添加web
模块并关联到webapp
目录;配jsp视图前后缀;使用Maven Projects
窗口的命令spring-boot:run
启动项目。
3、针对问题2
其实这个主要是注意目录对应关系。
(1)配置application.properties
#静态资源默认读取路径(默认有这4个位置,可以根据需要自己添加)
#spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
#静态资源访问路径规则
spring.mvc.static-path-pattern=/static/**
(2)举例
①现在把jquery.min.js
放到resources/static
下,然后在index.jsp中调用(注意js和jsp的相对位置):
此处jsp中引用位置写成
/static/jquery.min.js
也是可以的
②在Maven Projects
窗口使用Maven打包(打war包,前文已经提到过,jar包万万不行的):
③打包完成:
④单独运行(war包也可以直接通过java -jar
运行:
⑤前端访问http://127.0.0.1:8080
,顺利运行:
跳转也没问题:
如果没理解这波操作也不要紧,先照步骤跑起来,然后用解压工具打开生成的war文件,看看里面的目录结构,相信很快就明白过来了(从后往前推)~
war如图:
上面只是一种实现,而实际开发中,这样把静态资源放在resources/static
目录下,而在另一边目录的JSP中引用,Ctrl
+鼠标
放上去,IDE不能自动跳转,很不方便。
所以也可以这样:在WEB-INF/
下创建目录static
,与pages
同一级,将静态资源放在此处,然后在application.properties
中将spring.resources.static-locations
配置成这样:
spring.resources.static-locations=/WEB-INF/static/,classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
如图:
总之一句话:要么把静态资源放在默认的4个路径下,要么将自定义路径加入到application.properties
的spring.resources.static-locations
中。
03 后记
有时间还是要多看看官网的文档。配置文档参考
项目已经打包上传到GitHub(数据库文件也在里面)。 【点我传送】
附件1:application.properties
server.port=8080
#配置数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/bookmis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=
#配置druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.filters=stat
spring.datasource.druid.max-active=20
spring.datasource.druid.initial-size=1
spring.datasource.druid.max-wait=60000
spring.datasource.druid.min-idle=1
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.validation-query=select 'x'
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-open-prepared-statements=20
#静态资源默认读取路径(默认有4个)
spring.resources.static-locations=/WEB-INF/static/,classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
#mvc
spring.mvc.view.prefix=/WEB-INF/pages/
spring.mvc.view.suffix=.jsp
#JPA
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.open-in-view=true
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#json
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#关闭thymeleaf模板
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=false
#日志
logging.path=./logs/
#热部署
#spring.devtools.restart.additional-paths=src/main/webapp/WEB-INF/pages
附件2 :pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>bookmis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bookmis</name>
<description>图书管理系统</description>
<packaging>war</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!--<scope>test</scope>-->
</dependency>
<!-- tomcat支持 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- jstl标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
最后,作为一名帅气又new比的coder,怎能不会充分利用调试功能。所以附上个人的断点处作为参考,顺着这个思路捋一捋应该问题不大(自行酌用)。ε=(´ο`*)))唉,此番折腾下来,很spring boot呢。
如表:
序号 | 类 | 行 | 代码 | 备注 |
---|---|---|---|---|
1 | DispatcherServlet.java | 1044 | applyDefaultViewName(processedRequest, mv); | |
2 | DispatcherServlet.java | 1116 | render(mv, request, response); | |
3 | DispatcherServlet.java | 1370 | view.render(mv.getModelInternal(), request, response); | |
4 | RequestContext.java | 214 | WebApplicationContext wac = (WebApplicationContext) request.getAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE); | |
5 | DispatcherServlet.java | 1347 | view = resolveViewName(viewName, mv.getModelInternal(), locale, request); | |
6 | DispatcherServlet.java | 1055 | processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); | |
7 | DispatcherServlet.java | 1411 | View view = viewResolver.resolveViewName(viewName, locale); | |
8 | InternalResourceView.java | 147 | String dispatcherPath = prepareForRendering(request, response); | |
9 | InternalResourceView.java | 170 | rd.forward(request, response); | |
10 | ApplicationDispatcher.class | 130 | this.processRequest(request, response, state); | |
11 | ApplicationDispatcher.class | 76 | this.doForward(request, response); | |
12 | ApplicationDispatcher.class | 91 | ApplicationDispatcher.State state = new ApplicationDispatcher.State(request, response, false); | |
13 | DispatcherServlet.java | 1038 | mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); | |
14 | ApplicationDispatcher.class | 178 | this.invoke(state.outerRequest, response, state); | |
15 | ApplicationDispatcher.class | 347 | ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, this.wrapper, servlet); | |
16 | FrameworkServlet.java | 897 | processRequest(request, response); | |
17 | DispatcherServlet.java | 942 | doDispatch(request, response); | |
18 | ResourceHttpRequestHandler.java | 515 | path = processPath(path); | |
19 | ResourceHttpRequestHandler.java | 451 | Resource resource = getResource(request); | |
20 | ResourceHttpRequestHandler.java | 526 | Resource resource = this.resolverChain.resolveResource(request, path, getLocations()); | |
21 | PathResourceResolver.java | 151 | for (Resource location : locations) { | |
22 | PathResourceResolver.java | 154 | Resource resource = getResource(pathToUse, location); |
新手上路,欢迎交流~
Spring Boot 车队,我来了~