写在前面

  招聘信息专题热力图展示系统 项目最开始是使用原生javaWeb+jsp实现的,后来改用SSM框架,最近学习了springboot后将其改为springboot实现。从开发到部署可以说是遇到了无数的问题,特此整理一下。

问题汇总

1. 使用log4j打印日志与springboot自带的日志冲突

springboot项目启动后,控制台会有如下输出:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/E:/develop/maven_repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/develop/maven_repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

mark

  这种情况就是我们在pom.xml中引入的log4j与springboot中使用的logback产生了依赖冲突,当然直接使用springboot自带的日志框架完全可以,不过现在用log4j的人很多,所是流行的日志框架,所以如果需要使用log4j应该怎么办呢?

解决办法:

  只需要在springboot的起步依赖中排除掉日志打印就可以了。即在pom.xml添加如下:

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
    </dependency>

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

  把log4j.properties配置文件放置在resources目录下就可以了,关于配置文件怎么配置,这里不再赘述。启动之后,日志输出如下:

mark

2. springboot无法解析jsp文件

  因为项目使用了jsp,但是项目使用了springboot后,访问index.jsp 无法显示页面,而是将index.jsp当做一个文件下载了。

  一开始怀疑是不能直接把index.jsp放在webapps目录下,然后直接访问,需要使用Controller添加了一个RequestMapping为 / 的控制器进行跳转。把index.jsp移动到WEB-INF/jsp/目录下之后,也添加了控制器,如下

@Controller
public class WebController {

    @RequestMapping("/")
    public String index(){
        return "index";
    }

}

  然后发现启动后,访问仍然无法显示,直接404了。

mark

解决办法:

  springboot其实是不推荐使用jsp的,现在也没有多少程序员使用jsp了,需要给项目添加一个依赖,使之支持jsp解析,所以只需要在pom.xml添加下面一段就可以了。

        <!--用于编译jsp-->
        <!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>

  有了这个依赖,再运行,发现论直接访问index.jsp(放在webapps目录下),还是通过Controller跳转都可以显示jsp页面了,当然了,更推荐使用Controller控制器统一管理项目接口。

3. 项目错误页面处理

  当项目中没有做统一的异常处理时,看到的页面就是这样的…

mark

  这个异常其实已经说明了问题,就是我们没有为项目提供/error 路径的跳转。当然,springboot中完全没有必要自己去定义 /error 路径的跳转,事实上,springboot已经为我们准备好了。

静态异常页面

  自定义的静态异常页面,实际上就是一些显示错误信息的HTML等资源。可以使用http状态码命名,例如404.html、500.html,也可以使用4xx.html这样命名,表示400-499都会显示这个异常页面。默认在classpath:/static/error/目录下定义(也就是项目的resources目录下)。

动态异常页面

  动态异常页面其实与静态类似,可以把页面放在 classpath:/templates/error/目录下,可以使用jsp等显示错误信息,打印异常信息等。

  当静态与动态都定义了的时候,优先使用动态异常页面。完整的查找错误的方法为: 发生了404错误-->查找动态 404.html 页面-->查找静态 404.html --> 查找动态 4xx.html-->查找静态 4xx.html 。

4.Consider defining a bean of type 'cn.xgblack.heatmap.mapper.JobMapper' in your configuration.

  这是对象注入异常,可以理解为spring没有找到这个JobMapper对象。

***************************
APPLICATION FAILED TO START
***************************

Description:

Field jobMapper in cn.xgblack.heatmap.service.impl.JobServiceImpl required a bean of type 'cn.xgblack.heatmap.mapper.JobMapper' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'cn.xgblack.heatmap.mapper.JobMapper' in your configuration.

解决办法

方案一:在 启动类 中添加 @MapperScan("xxx.xxx.xxx.mapper")注解

方案二:将XxxMapper类中的@Repository注解换成@Mapper注解(使用@Mapper不行的话可以试试这两个注解同时使用)

5. Error querying database. Cause: java.lang.IndexOutOfBoundsException: Index: 10, Size: 10

  mybatis查询数组越界异常,Size为多少,Index就为多少。异常如下:

### Error querying database.  Cause: java.lang.IndexOutOfBoundsException: Index: 10, Size: 10
### The error may exist in file [D:\Code\IdeaJava\CareerInfo_HeatMap\target\classes\mapper\JobMapper.xml]
### The error may involve cn.xgblack.heatmap.mapper.JobMapper.findByPage
### The error occurred while handling results
### SQL: SELECT jid,cname,jname,lon,lat,minwage,maxwage,province,highlights,erequir         FROM job                                    LIMIT ?,?
### Cause: java.lang.IndexOutOfBoundsException: Index: 10, Size: 10] with root cause

java.lang.IndexOutOfBoundsException: Index: 10, Size: 10

解决办法

  其实这种情况只需要给项目的查询的 entity 类加上默认的无参构造器就可以了。

  不过一般如果没有自己写带参的构造器的话,无参构造器是默认的,不许有手动添加,在写自定义的构造参数的时候需要把无参构造器也手动添加。那么我是怎么没有无参构造器的呢?

  因为我是用了Lombok框架,在类上加上 @Data注解,会自动为类生成 setter/getter、equals、canEqual、hashCode、toString方法 ,我之前看百度说无参构造器也会生成的,所以我给类只加了@Data和@AllArgsConstructor注解。只需要再加上@NoArgsConstructor这个注解就可以了。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Job {
    /**
     * 主键id
     */
    private Integer jid;
    /**
     * 公司名称
     */
    //...省略
}

6. 注册登录后页面跳转与重定向问题

  由于项目最开始使用原生的JavaWeb开发的,所以,页面转发和重定向都是使用的request和response,换成SSM框架后,没有更改依旧可以用,但是再使用springboot后,竟然跳转不了了…

  其实重定向和转发的方法有很多,我只讲一下我用的方法,因为我是做登录注册验证,根据很多的if判断,进行转发或者重定向,所以有一些方法虽然也可以用,但不是很方便。我使用的是返回String,如下:

/**
     * 注册验证
     * @param request 请求
     * @param session 会话
     * @param verifycode 验证码
     * @param registerUser 注册用户信息
     * @throws ServletException 异常//TODO
     * @throws IOException 异常//TODO
     */
    @RequestMapping("/registerCheck")
    public String register(HttpServletRequest request, HttpSession session , String verifycode , User registerUser) throws ServletException, IOException {

        //获取生成的验证码
        String checkcode_session = (String)session.getAttribute("checkcode_session");

        //去除生成的验证码,以防止验证码被重复使用的bug
        session.removeAttribute("checkcode_session");

        //先判断验证码是否正确
        if (verifycode != null && !"".equals(verifycode) && !verifycode.equalsIgnoreCase(checkcode_session)) {

            //验证码不对,存储错误信息,并跳转
            request.setAttribute("regist_msg","验证码输入错误!");
            return "/user/register";

        }

        // 调用方法查看用户名是否已经被注册
        if (service.usernameIsExist(registerUser.getUsername())) {
            //用户名已存在
            request.setAttribute("regist_msg", "用户名已存在");
            return "/user/register";
        }

        if (service.regist(registerUser)) {
            //注册成功
            request.setAttribute("login_msg", "注册成功,自动登录");
            //注册成功。尝试自动登录
            session.setAttribute("user",registerUser);
            //跳转页面(直接重定向)
            return "redirect:/";
        } else {
            //注册失败
            request.setAttribute("regist_msg", "注册失败,请重试");
            return "/user/register";
        }

    }

  return "redirect:/";重定向到首页,return "/user/register";跳转到WEB-INF/jsp/下的register.jsp文件,即注册页面,转发可以存入一些提示信息。

7. springboot中使用Servlet和Filter

首先要在项目启动类中添加@ServletComponentScan注解,自动扫描。然后再Filter类上添加@WebFilter("/")注解,value填写拦截的路径。

8.springboot 打war包,部署404

  如果把springboot项目也像SSM项目那样直接打包,部署到服务器之后,会直接404,项目无法启动。

解决办法

首先,在pom.xml中, spring-boot-starter-tomcat 依赖下添加上<scope>provided</scope> ,在编译打包的时候去掉springboot的Tomcat,如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

之后,添加一个 ServletInitializer 类,(也可以直接使用启动类)继承自 SpringBootServletInitializer ,并重写其 configure 方法,如下:

public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(CareerInfoHeatMapApplication.class);
    }
}

如果还不可以,可以试试把spring-boot-maven-plugin插件的版本改低一些。

9. springboot 打war包,部署502

502是因为项目出现了异常,每个人可能遇到的都不一样。经过查看日志,虽然没分析出来具体是什么情况,但是可以肯定是mybatis的问题,经过一番排查,发现我自己指定了mybatis-spring-boot-starter这个依赖的版本,且与springboot起步依赖的版本不一样,尝试更改版本号为springboot起步依赖的版本后,果然解决了这个问题。

10. springboot项目打war包,静态资源访问不到,显示404

这个问题是因为使用了Nginx进行反向代理Tomcat8080端口,静态资源算是交给Nginx来管理了。在项目网址上指定8080端口访问,其实是没有问题的。网上说这是项目默认动静分离造成的。

解决办法很简单,只需要把static目录下的所有静态资源放到对应的静态网站该放的位置就好了,即如果在springboot项目中,访问123.png的网址是test.xgblack.cn:8080/img/123.png,只需要把img/这个目录复制到test.xgblack.cn网站根目录就可以了。

11. idea 打包的jar运行报 “XXX中没有主清单属性”

这可能是使用IDEA打包造成的,我之后在cmd中用mvn命令就没有出现这种问题了。也不知道是我没用IDEA打包,还是因为我更改了spring-boot-maven-plugin的版本。

可以参考idea 打包的jar运行报 “XXX中没有主清单属性”

12. springboot项目打jar包,启动后访问404

首先要在pom.xml文件中制定打包方式为jar

之后需要修改 maven打包插件,我用2.x打包出来的jar都不行,网上说用1.4.2 ,版本高了就不兼容 ,我试了一下果然可以。

<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.4.2.RELEASE</version>
                <configuration>
                    <!-- 设置启动类 -->
                <mainClass>cn.xgblack.heatmap.CareerInfoHeatMapApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

我到这一步,打包出来就已经可以用了。如果不可以的话,网上还有下面的一步。参考springboot+jsp打成jar包部署404问题”

<resource>
    <directory>src/main/webapp</directory>
    <!--这里必须是META-INF/resources-->
    <targetPath>META-INF/resources</targetPath>
    <includes>
        <include>**/**</include>
        </includes>
        <filtering>false</filtering>
</resource>

13. 修改Tomcat的server.xml部署Javaweb项目

<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log" suffix=".txt" />
      </Host>
      <Host appBase="/www/wwwroot/study.xgblack.cn" autoDeploy="true" name="study.xgblack.cn" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">
        <Context crossContext="true" docBase="heatmap" path="/heatmap" reloadable="true" />
        <Context crossContext="true" docBase="heatmap_ssm" path="/heatmap_ssm" reloadable="true" />
      </Host>

一个Host表示一个虚拟主机,可以包含一个或多个web应用。

  • appBase : 指定虚拟主机的目录,可以指定绝对目录,也可以指定相对于的相对目录.如果没有此项,默认 为/webapps. 它将匹配请求和自己的Context的路径,并把请求转交给对应的Context来处理。 (这个目录下的war包会被自动解包)
  • autoDeploy : 如果此项设为true,表示Tomcat服务处于运行状态时,能够监测appBase下的文件,如果有新有web应用加入进来,会自运发布这个WEB应用
  • unpackWARs : 如果此项设置为true,表示把WEB应用的WAR文件先展开为开放目录结构后再运行.如果设为false将直接运行为WAR文件

Context元素, 每个web应用有唯一的一个相对应的Context代表web应用自身.servlet容器为第一个web应用创建一个

  • path : 该Context的路径名是"",故该Context是该Host的默认Context
  • docBase : 该Context的根目录
  • reloadable : 如果这个属性设为true, Tomcat服务器在运行状态下会监视在WEB-INF/classes和Web-INF/lib目录CLASS文件的改运.如果监视到有class文件 被更新,服务器自重新加载Web应用

14. 宝塔面板SSL证书错误

这个问题遇到很多次了,应该是使用宝塔面板的bug,虽然不明白原因,但应该可以断定是某一个网站SSL证书导致的。

mark

之前没有截图,图是宝塔的论坛上的。这段错误提示仔细分析一下,不需要看明白,只需要从中找到他报错的那个站点就可以了。然后删除那个站点(注意,只删除站点,不删除数据库,根目录等等),然后照原样新建一个站点就可以了。

小结

  虽然是一个小项目,但是从开发到部署真的是遇到了各种各样的问题。每个问题都是毫无头绪,需要在百度上看好几篇相关的文章才能找到解决的办法,甚至尝试半天也不能解决。不过当最终项目在服务器上正常运行的那一刻,感觉还是很不错的,哈哈哈。

  这篇只是记录一下,防止以后再遇到忘记怎么解决还需要搜半天…


道也者,不可须臾离者也,可离非道也。