整合SSH框架之基础搭建

SpringBoot把各种框架的整合用约定俗成的模板进行了默认配置,简化了各种框架的整合过程。在方便开发的同时也屏蔽了一些基础细节,在微服务大行其道的今天,Spring Boot 和 Spring Cloud 这些框架在开发时无疑是目前的首选,但是却不利于学习各个框架的基础,对于后端的初学者来说应当回过头来把每个框架的基础配置和使用都系统的学习一遍。强迫症的我决定要重新开始用传统的Spring+SpringMVC+Hibernate进行整合,这些框架都尽量采用目前最新的版本,后续对其他框架的整合配置以及实现进行迭代学习。

基本概念

在Java后端开发中,使用的大多数框架都采用SSH、或者SSM,即使采用Spring Boot这样的框架也不例外,因为它本身也属于Spring家族,方便了各个框架的集成,开箱即用。

Spring

Spring是一个Java开发框架,用来简化对象依赖以及生命周期管理。Spring的核心是IOC(控制反转)和AOP(面向切面编程),IOC容器可以创建并管理对象的生命周期,控制反转就是将对象的创建以及生命周期交给Spring 的IOC容器来控制,IOC从另外一个角度来看也叫做依赖注入,利用这一特性可以降低类之间的耦合度,并且通过Spring提供的AOP,可以很方便的定义自己的规则,将切面应用于某个方法上进行方法增强,比如日志输出,事物控制等。
Spring的配置可以采用Java类的方式,但是为了降低与程序代码的依赖,一般使用xml外部配置文件来配置Spring。

Spring MVC

Spring MVC 是用于web端的Servlet开发框架,用来简化web端编程。在Spring MVC出现之前,Java的web端编程一般采用服务器容器提供的原生API,比如Tomcat等主流服务器都支持的Servlet规范所提供的各种API接口,开发者通过HttpServletRequestHttpServletResponse接口提供的方法来接收处理用户的请求与响应请求。当一个项目很庞大且复杂的时候,一般会对Servlet提供的这些接口进行统一封装,比如请求方式的定义,请求体参数的获取,请求响应对象数据等。Spring MVC屏蔽与业务无关的底层代码,帮助开发者做了Servlet的封装,并且依赖Spring的强大特性,开发者只需要将请求配置给Spring MVC提供的DispatcherServlet,就可以在自己的业务逻辑中使用Spring 和SpringMVC带来的强大功能

Hibernate

Hibernate是一个ORM框架,用来简化数据库操作。在Hibernate出现之前,Java操作数据库一般采用JDBC提供的API直接去操作,需要自己编写大量的SQL,容易一不小心就直接拼接上一个非法的参数变量导致SQL注入,而且每次将结果重新赋值给Java对象显得非常繁琐,没有通用性。所以在项目中一般会对JDBC进行再次封装,采用Java反射等技术实现了一个ORM的功能,这种功能和目前主流的Hibernate与Mybatis类似,都对JDBC的数据库操作进行了封装。但是Hibernate更为强大。
众所周知,关系型数据库虽然将现实事物进行了抽象,但是关系型数据库的传统操作还是使用SQL,SQL本身是面向过程的,人类去编写很复杂的一串SQL时如果没有足够的经验很容易会写出BUG,比如多表关联查询时可能要写很长的SQL,并且整个业务系统可能有很多地方有类似的查询,如果全部用SQL编写可能很耗时,所以出现了关系实体映射的ORM框架。Hibernate对SQL进行了抽象,开发人员直接操作Java对象就可以操作数据库,最终的SQL由Hibernate翻译生成,对象操作更不容易出错且更加直观。除此之外Hibernate还做了很多封装,比如数据库不同级别的缓存策略,事物策略等,框架本身是为了简化开发成本。

创建项目

开发工具和框架版本

  • 操作系统: MacOS Mojave 10.14
  • JDK版本: 1.8.0_192
  • IntelliJ IDEA版本: Ultimate 2018.2.5
  • Maven版本: 3.3.9
  • Spring 5.1.1.RELEASE
  • Spring MVC 5.1.1.RELEASE
  • Hibernate 5.3.7.Final

    无论采用IDEA还是Eclipse,在使用Maven构建项目时,勾选Create from archetype,选择从模板创建并找到 org.apache.maven.archetypes:maven-archetype-webapp这个模板底下的maven-archetype-webapp:RELEASE,用来快速创建一个Maven构建的Java Web工程。
    有关Spring的相关文档可查阅官网
    https://docs.spring.io/spring/docs/5.1.1.RELEASE/spring-framework-reference/index.html

引入依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!-- 添加 Spring MVC 依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.1.RELEASE</version>
</dependency>

<!-- 添加 Spring ORM 依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.1.RELEASE</version>
</dependency>

<!-- 添加 Hibernate 依赖 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-agroal</artifactId>
<version>5.3.7.Final</version>
<type>pom</type>
</dependency>

<!-- 添加 C3P0 数据库连接池依赖 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>

<!--添加 MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>

<!-- 添加 jackson-core 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.7</version>
</dependency>

<!-- 添加 jackson-databind 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>

配置web.xml

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<web-app>
<display-name>SSH 框架整合</display-name>

<!-- 配置Spring的监听器,Spring 容器启动时默认会加载 WEB-INF 目录下的 applicationContext.xml 文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!--当 WEB-INF 目录下的 applicationContext.xml 文件不存在时,一定要指定自定义名称的配置文件-->
<!-- 定义自定义名称的 Spring 上下文配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/app-context.xml</param-value>
</context-param>

<!--配置 Spring MVC 的 servlet 拦截器-->
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置初始化时要加载的自定义配置文件,如果不配置则默认为 WEB-INF 目录下的 app-servlet.xml 文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/app-webmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<!-- 配置servlet映射所匹配的url -->
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

配置 Spring

WEB-INF/app-context.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<description>Spring 配置</description>

<!-- 加载外部配置文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<!-- 如果有多个配置文件可以用多个value标签进行配置 -->
<value>classpath:db.properties</value>
</list>
</property>
</bean>

<!-- 配置数据源 dataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 配置连接池基础信息:连接驱动、连接地址、用户名、密码 -->
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>

<!-- 初始化连接池连接数大小,默认值为3 -->
<property name="initialPoolSize" value="${initialPoolSize}"/>

<!-- 最大空闲时间,为0则永不丢弃,默认值为0 -->
<property name="maxIdleTime" value="${maxIdleTime}"/>

<!-- 连接池的最大连接数,默认最大值为15 -->
<property name="maxPoolSize" value="${maxPoolSize}"/>

<!-- 连接池中保持的最小连接数,默认值为3 -->
<property name="minPoolSize" value="${minPoolSize}"/>

<!-- 更多配置以及默认值可查看c3p0依赖库中的 com.mchange.v2.c3p0.impl.C3P0Defaults类 -->
</bean>

<!-- 配置会话工厂 SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置 Hibernate属性-->
<property name="hibernateProperties">
<props>
<!-- hibernate 方言 -->
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
</props>
</property>
<!-- 配置要扫描的包路径 -->
<property name="packagesToScan" value="com.lanshiqin.*.entity"/>
</bean>

<!-- 配置事物管理 transactionManager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<!-- 配置 SessionFactory -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 配置事务,使事务注解生效 默认属性值 transaction-manager="transactionManager",因为上面配置了预定的默认名称,故此处可省略 -->
<tx:annotation-driven/>

</beans>

配置 Spring MVC

WEB-INF/app-webmvc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<description>Spring MVC 配置</description>


<!-- 配置扫描注解的目录 -->
<context:component-scan base-package="com.lanshiqin"/>


<!-- 配置内部资源分配器,为模型视图名称添加前置路径和后缀名称 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/templates/"/>
<property name="suffix" value=".jsp"/>
</bean>

<!-- 配置默认的servlet,处理程序将所有的请求转发到默认处理器,处理静态资源 -->
<mvc:default-servlet-handler/>

<!-- 配置消息视图转换 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>

</beans>

配置Hibernate

在Spring的配置文件app-context.xml中其实已经配置了Hibernate的一些实例属性,应用程序的连接池以及Hibernate的SessionFactory和事务管理等,这里是将xml中的配置提取到外部文件,方便后期对这些配置参数的修改和调整。
resources/db.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 数据库配置
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssh
jdbc.user=xxxx
jdbc.password=xxxxxx

# 初始化连接池连接数大小
initialPoolSize=3

# 最大空闲时间,超值丢弃连接
maxIdleTime=60

# 连接池的最大连接数
maxPoolSize=50

# 连接池中保持的最小连接数
minPoolSize=3

# hibernate 方言
hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

hibernate.show_sql=true

hibernate.format_sql=true

hibernate.hbm2ddl.auto=update

常见问题

pom.xml 中的依赖

在pom.xml中有关Spring的依赖中,为何只引入了Spring MVC 和 Spring ORM 的依赖?

因为 Spring MVC 依赖于 Spring Core、Spring Bean、Spring Context 等其他基础模块,所以引入一个 Spring MVC 依赖,maven会将其关联的依赖一起引入。

由上图可知,Spring模块关系顶层分为两大类,一个是用于web开发的Spring MVC,另一类与数据相关的,底层的大部分基础模块都是共同需要的,开发者可以根据自己需要引入相关的模块。

为何要单独引入fasterxml的jackson的core和databading依赖,不添加会有什么问题?

因为后端接口开发目前基本都有需要输出Json格式的数据场景,所以需要在Spring中配置消息视图转换器,Spring默认没有Json序列化的实现,所以如果不添加json的相关依赖库,将无法处理json转换相关的功能,比如在使用 @ResponseBody注解时不会被解析,如果配置了视图解析器为json,那么会因为找不到json依赖库在容器启动时抛出异常。

web.xml 的配置

listener标签中配置的Spring的ContextLoaderListener有什么作用,为什么要配置这个参数?

1
2
3
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

ContextLoaderListener是Spring的上下文加载监听器,开发者通过该监听器可以实现一些自定义功能。
另外很重要的一点是该配置会使Spring 容器启动时默认加载 WEB-INF 目录下的 applicationContext.xml 文件,如果不想按照约定创建这个文件,则必须要自己指定配置文件。
有关如何指定配置文件,请继续往下看。

为何要配置contextConfigLocation这个参数

1
2
3
4
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/app-context.xml</param-value>
</context-param>

因为在listener标签中配置了ContextLoaderListener,所以Spring 容器启动时默认会加载 WEB-INF 目录下的 applicationContext.xml文件, 由于 WEB-INF 目录下 没有 applicationContext.xml 这个配置文件,所以容器启动时会抛出IO异常。java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/applicationContext.xml],要避免这个问题,需要在 WEB-INF 目录下建立 applicationContext.xml文件。如果开发者不想创建 applicationContext.xml文件,就必须通过contextConfigLocation参数指定自定义配置文件。

为何在<servlet>标签中配置<init-param>初始化参数并且指定contextConfigLocation配置?这个和上面的配置不是重复了吗?

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/app-webmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

因为这个servlet配置的是Spring MVC 的DispatcherServlet,实例创建时默认会加载/WEB-INF/app-servlet.xml,如果开发者在对应目录下创建了该文件,则可以不需要配置这个初始化参数。如果不创建/WEB-INF/app-servlet.xml,则会抛出IO异常java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/app-servlet.xml]。如果不想创建/WEB-INF/app-servlet.xml,就必须在DispatcherServlet所在的servlet标签中配置自定义文件

spring配置文件问题

InternalResourceViewResolver

1
2
3
4
5
<!-- 配置内部资源分配器,为模型视图名称添加前置路径和后缀名称 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/templates/"/>
<property name="suffix" value=".jsp"/>
</bean>

配置内部资源分配器。可以通过属性指定web工程所在的模板视图的路径前缀和模板文件名后缀,即通过Controller重定向到的页面路径为/templates/+ 模板文件名 + .jsp
例如

1
2
3
4
5
6
@RequestMapping("/hello")
public ModelAndView index(ModelAndView modelAndView){
modelAndView.addObject("name","蓝士钦");
modelAndView.setViewName("index");
return modelAndView;
}

该方法通过modelAndView的addObject向页面传递了参数名name和对应的参数值,并且通过setViewName方法指定模板文件名,
所以访问该方法最终会被转发到 /templates/index.jsp 这个页面
在 /templates/ 目录下 创建 index.jsp,可以获取到方法传过来的值
index.jsp

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SSH</title>
</head>
<body>
<h2>Hello ${name}</h2>
</body>
</html>

default-servlet-handler

1
<mvc:default-servlet-handler/>

该配置用来指定DispatcherServlet为默认的servlet,处理程序将所有的请求转发到默认处理器,一定要配置该处理程序,才能使内部资源分配器InternalResourceViewResolver的配置生效。
如果没有这个配置,开发者视图访问控制器所转发的页面时会抛出异常信息

org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET / …
default-servlet-handler 的源码中说明如下:

Configures a handler for serving static resources by forwarding to the Servlet container’s default Servlet.
Use of this handler allows using a “/“ mapping with the DispatcherServlet while still utilizing the Servlet
container to serve static resources.
This handler will forward all requests to the default Servlet. Therefore it is important that it remains last
in the order of all other URL HandlerMappings. That will be the case if you use the “annotation-driven” element
or alternatively if you are setting up your customized HandlerMapping instance be sure to set its “order”
property to a value lower than that of the DefaultServletHttpRequestHandler, which is Integer.MAX_VALUE.

message-converters

我们经常会看到一个Controller中的方法上面标注了一个@ResponseBody注解,特别是经常使用 Spring Boot 的同学会发现加上这个注解之后通过浏览器访问接口会返回Json数据,其实这是 Spring Boot 默认替开发者配置了消息转换器。

@ResponseBody可以输出Json格式的数据,也可以输出XML格式的数据,具体输出什么取决于对消息转换器的配置。
比如下面这段代码

1
2
3
4
5
6
7
8
@GetMapping("info")
@ResponseBody
public User info(){
User user = new User();
user.setUserName("蓝士钦");
user.setPassWord("xxx");
return user;
}

在Spring Boot 中访问该接口会输出对象的json数据

1
2
3
4
{
"userName": "蓝士钦",
"passWord": "123"
}

但是如果是非Spring Boot 工程,比如自己用 Spring MVC 整合的框架中,如果没有对消息转换器进行配置,那么 Spring MVC 默认将不会对@ResponseBody注解进行解析,最终会出现404 - Not Found

所以我们需要配置消息转换器

1
2
3
4
5
6
7
<!-- 配置消息视图转换 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>

由于上面配置了Json信息映射,而Spring本身并没有实现json处理,如果没有添加json相关的依赖库,那么将会在启动过程中
出现org.springframework.web.servlet.FrameworkServlet.initServletBean Context initialization failed,并且输出 RequestMappingHandlerAdapter这个Bean创建失败的信息。

所以一定要记得在pom.xml中添加json处理的依赖库

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 添加 jackson-core 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.7</version>
</dependency>

<!-- 添加 jackson-databind 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>

项目框架将持续集成和更新,GitHub:https://github.com/lanshiqin/ssh-framework

0%