0x0 前言
标题里的这些都是什么我就不细说了,点开这篇博客的你至少应该已经知道了一些。最近工作需要,想学点Spring的东西,看了一些网上关于Spring入门的博客,感觉这些博客面向初学者的话还是有不少没有讲清楚的地方,比如至关重要的配置文件的在工程中的路径,还有某些配置项在xml文件中的所处的节点。也许大牛们觉得这些都太基础了没必要全写出来占着篇幅,要留给展示操作过程的ide界面截图。这是很多代码玩家的另外一个问题,过于依赖ide功能,而不去尝试脱离ide完成同样功能的方式,所以为了展示一个操作,不得不放上ide的截图(如果截图暴露了作者还用的是xp这种古董操作系统或者vc6.0这种古董ide,那简直要low穿地壳了)。所以本教程中————虽然内容是入门级的————我采用了一些我认为逼格比较高的方式,首先尽可能的描述清楚每一行代码所处的文件的在工程中的路径,和代码在文件中的位置,当前限于篇幅,我也不可能精确到每个细节,所以我给出了完整工程托管在github上的路径。另外,尽可能脱离ide,构建和部署均采用命令行和修改配置文件的方式。
数据库:MySql 5.7
构建工具:apache-maven-3.3.9
容器:apache-tomcat-9.0.0.M4
0x1 还是从最简单的hello world开始
一个最精简的基于SpringMVC的java web工程需要以下几个文件:
首先是导入SpringMVC依赖jar包的pom.xml文件
./pom.xml
<!-- 省略了头部的名字空间声明 -->
<project
...
>
<modelVersion>4.0.0</modelVersion>
<groupId>SpringMVCStart</groupId>
<artifactId>SpringMVCStart</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
</dependencies>
</project>
packaging
项填写war
才可以最终将工程打包成war包;依赖管理部分,只要导入spring-webmvc
,maven就会自动将其依赖的spring-core、spring-context、spring-bean等等一并包含进来。
然后是作为一个java web工程最重要的web.xml文件
./src/main/webapp/WEB-INF/web.xml
<!-- 省略了头部的名字空间声明 -->
<web-app
...
>
<display-name>SpringMVCStart</display-name>
<servlet>
<servlet-name>SpringMVCStart</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVCStart</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
这里定义了一个名为SpringMVCStart的servlet,其接收类为org.springframework.web.servlet.DispatcherServlet
,容器启动时加载,然后在servlet-mapping
中将这个servlet映射给url/
,即所有路径下的请求均由org.springframework.web.servlet.DispatcherServlet
这个类进行分发。于是在这里就引入了spring的webmvc框架。
接下来我们就需要spring的webmvc框架的配置文件。
由于我们的servlet名字叫'SpringMVCStart',在默认情况下,springmvc框架会加载WEB-INF/SpringMVCStart-servlet.xml文件作为配置文件。
./src/main/webapp/WEB-INF/SpringMVCStart-servlet.xml
<!-- 省略了头部的名字空间声明 -->
<beans
...
>
<mvc:annotation-driven/>
<context:component-scan base-package="org.home.knightingal.controller" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
其中,<mvc:annotation-driven/>
意味着开启spring mvc相关的注解,<context:component-scan>...</context:component-scan>
的意思是,扫描org.home.knightingal.controller
路径下所有带有org.springframework.stereotype.Controller
注解的类,将其作为controller。
那么接下来就是controller类,比如:
./src/main/java/org/home/knightingal/controller/HelloWorldController.java
package org.home.knightingal.controller;
// 省略一堆import
@Controller
@RequestMapping(value="/helloworld")
public class HelloWorldController {
@RequestMapping(value="/index")
@ResponseBody
public String index() {
return "Hello world";
}
}
首先是@Controller
注解使得spring mvc框架可以扫描到这个类,将其作为一个controller,接下来两个@RequestMapping
注解将index
方法绑定给url路径/helloworld/index
。@ResponseBody
表示将返回值直接作为响应的消息体,否则框架会将方法返回的字符串值理解为响应页面的名字。新版的springmvc框架对于将方法的返回值直接作为响应体的场景有了更简便的注解类型,这里还是沿用老办法。
到目前为止一个基于springmvc的java web项目就搭建完成了,虽然我们只有mvc中的c部分。
我假定你已经配置好了jdk和maven的环境变量,接下来我们在cmd或者shell中输入命令
mvn package
maven就会执行打包程序,它会首先去maven中央仓库下载必要的jar包和插件,首次运行这部分过程可能会持续比较长的时间,取决于你和maven中央仓库的连接网速,必要时请自行科学上网。当jar包和插件都下载完毕,它会执行javac去编译java源码,如果有单元测试的话此编译完成后它还会执行单元测试,最后它会将编译后的class文件,以及java web需要的各类配置文件一并打成war包,放入target目录下。
如果一切正常,那么此时你应该可以看到
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.286 s
[INFO] Finished at: 2016-05-12T00:20:08+08:00
[INFO] Final Memory: 12M/147M
[INFO] ------------------------------------------------------------------------
类似字样。尽管不是必须的,但我还是建议你把maven输出的完成记录稍微过目一下,了解一下maven构建和打包一个java工程的打包过程。
下面是部署这个web工程,这里我就不说怎么用eclipse全家桶自动部署了,单单说一下手动部署。
打开tomcat路径下的/conf/server.xml文件,找到<Host>
节点,在这个节点下添加下面的内容
<Context path="/SpringMVCStart" docBase="D:\SpringMVCStart\target\SpringMVCStart-1.0-SNAPSHOT" reloadable="true" >
</Context>
把docBase
属性的值替换成你自己的版本。
改好了以后启动tomcat,待启动成功之后,假设你没有改tomcat的监听端口,那么在浏览器地址栏输入http://localhost:8080/SpringMVCStart/helloworld/index,应该就可以看到"hello world"字样了。
0x2 数据库支持
在./pom.xml里面添加mysql-connector和jdbc相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
在./src/main/webapp/WEB-INF/SpringMVCStart-servlet.xml
中配置datasource连接参数
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/world"/> <!-- 修改为你自己的数据库地址-->
<property name="username" value="Knightingal"/> <!-- 修改为你自己的用户名 -->
<property name="password" value="123456"/> <!-- 修改为你自己的密码 -->
</bean>
这里配置了一个org.apache.commons.dbcp.BasicDataSource
类型的实例,以及该实例中driverClassName
,url
,username
,password
成员变量的值。后续配置中可使用dataSource
这个id引用该实例。
以及配置jdbcTemplate。
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
稍后在Controller中便可通过@Autowired
注解将org.springframework.jdbc.core.JdbcTemplate
类型的实例反向注入到名为jdbcTemplate
的成员变量上,并且该实例中的dataSource
成员变量已经注入了之前配置的id为dataSource
的org.apache.commons.dbcp.BasicDataSource
类型实例。
至此数据源的配置就完成了。
0x3 在Controller中使用JdbcTemplate进行数据库操作
现在我们配置一个新的Controller进行数据库操作。在此之前我们先了解一下需要查询的数据库的表结构及数据。
这里我们使用MySql预装的Demo库World
,其中有city
,country
,countrylanguage
三张表,从名字就能猜出里面的数据是什么。本教程中只需要其中的city
就可以了。也许你的MySql里面没有预装这个Demo库,没关系,我把city
的表结构和一部分数据导出来了放到这里了https://github.com/knightingal/SpringMVCStart/blob/master/dbsetup/city.sql
简单的说,city
表的表结构如下:
Field | Type | Null | Key | Default | Extra | |
---|---|---|---|---|---|---|
ID | int(11) | NO | PRI | auto_increment | ||
Name | char(35) | NO | ||||
CountryCode | char(3) | NO | MUL | |||
District | char(20) | NO | ||||
Population | int(11) | NO | 0 |
根据这表结构,我们可以建立一个名为City
的bean
./src/main/java/org/home/knightingal/bean/City.java
public class City {
private Integer id;
private String name;
private String countryCode;
private String district;
private Integer population;
// getter and setter...
}
接下来新建一个Controller,接受一个请求来查询这张表,将查询结果放入City
的列表作为查询请求的响应。
./src/main/java/org/home/knightingal/controller/CityController.java
//省略package和import
@Controller
@RequestMapping(value="/city")
public class CityController {
@Autowired
JdbcTemplate jdbcTemplate;
@RequestMapping(value="/simpleQueryCities")
@ResponseBody
public List<City> simpleQueryCities() {
final List<City> cities = new ArrayList<City>();
jdbcTemplate.query("select id, name, countryCode, district, population from city limit 0, 10", new RowCallbackHandler() {
public void processRow(ResultSet resultSet) throws SQLException {
System.out.println(resultSet.getString(2));
City city = new City();
city.setId(resultSet.getInt(1));
city.setName(resultSet.getString(2));
city.setCountryCode(resultSet.getString(3));
city.setDistrict(resultSet.getString(4));
city.setPopulation(resultSet.getInt(5));
cities.add(city);
}
});
return cities;
}
}
重新构建之后在浏览器中输入http://localhost:8080/SpringMVCStart/city/simpleQueryCities,返回了500错误。但是在Tomcat的运行窗口中我们看到打印出了查询到的城市名字,说明查询是成功的。
那我们回过头看看500错误的描述,可以看到这样的信息:
No converter found for return value of type: class java.util.ArrayList
问题出在simpleQueryCities
的返回值,这个方法返回的是一个ArrayList
类型的对象,框架无法对这个对象进行转换,成为具备良好阅读性的响应体。
这时我们需要引入jackson
作为converter。在./pom.xml里面添加jackson
的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.3</version>
</dependency>
SpringMVC框架会自动识别出jackson
并作为其converter,不需要做另行配置。
重新打包部署后,再次刷新http://localhost:8080/SpringMVCStart/city/simpleQueryCities页面,就可以看到处理成json格式的城市信息了。
0x4 引入Mybatis进行数据库操作
在./pom.xml中添加如下依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
第一个是mybatis的主包,另一个mybatis-spring可以将mybatis的代码无缝整合到spring中。
接下来配置一个简单的dao接口。由于只是个简单的演示项目,所以不打算添加常见的service层了,controller直接调用dao层。
./src/main/java/org/home/knightingal/dao/CityDao.java
public interface CityDao {
List<City> queryCities();
}
在CityController
中加入对queryCities
的调用
./src/main/java/org/home/knightingal/controller/CityController.java
@Controller
@RequestMapping(value="/city")
public class CityController {
//略...
@Autowired
CityDao cityDao;
@RequestMapping(value="/queryCities")
@ResponseBody
public List<City> queryCities() {
return cityDao.queryCities();
}
}
有了接口,总得有实现吧,还有那个cityDao
总不能就这样放着等着运行的时候给你来个空指针异常吧。下面是通过mybatis做的CityDao
的实现
./src/main/resources/org/home/knightingal/dao/CityDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.home.knightingal.dao.CityDao">
<select id="queryCities" resultType="org.home.knightingal.bean.City">
select id, name, countryCode, district, population from city
limit 0, 10
</select>
</mapper>
以及cityDao
变量的装配。
./src/main/webapp/WEB-INF/SpringMVCStart-servlet.xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="cityDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.home.knightingal.dao.CityDao" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.home.knightingal.dao" />
</bean>
在配置文件中,我们可以看到cityDao
的实际父类型是org.mybatis.spring.mapper.MapperFactoryBean
,<property name="mapperInterface" value="org.home.knightingal.dao.CityDao" />
这行配置了cityDao
的接口。<property name="sqlSessionFactory" ref="sqlSessionFactory" />
这行装配了MapperFactoryBean
中的sqlSessionFactory
成员变量,该变量中装配了dataSource
,即我们之前配置的dataSource。由于@Autowired
注解的作用,这个bean会自动注入给变量CityDao cityDao
。
而org.mybatis.spring.mapper.MapperScannerConfigurer
这个bean配置则指定了mybatis去那个路径下搜索dao接口关联的sql配置文件。
重新编译运行后输入地址http://localhost:8080/SpringMVCStart/city/queryCities即可看到运行结果。
0x5 加入jsp页面
目前为止mvc架构中我们已经有了controller和model部分,对于很多纯后台系统而言已经够了,并不是每个系统都一定要有一个前台展示的view。但是呢,既然说到这了,再把view的部分加上也不是什么难事。
首先是引入jsp依赖的jstl库
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
jsp的配置,就不啰嗦是在哪个文件里面配的了。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="contentType" value="text/html"/>
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
在queryCities
的请求中返回jsp页面queryCities
,注意,去掉了@ResponseBody
注解,否则一会儿你在页面上只能看到"queryCities"几个字,是不是顿时觉得自己萌萌哒?
@RequestMapping(value="/queryCities")
public String queryCities(
@RequestParam(value="countryCode", required=false) String countryCode,
Model model
) {
City param = new City();
param.setCountryCode(countryCode);
List<City> cities = cityDao.queryCities(param);
model.addAttribute("cities", cities);
return "queryCities";
}
这里我还稍微修改了CityDao.queryCities()
接口,让它可以接受一个City
类型的入参,带入查询条件,
public interface CityDao {
List<City> queryCities(City param);
}
这么做的目的是验证mybatis动态sql的特性。
<mapper namespace="org.home.knightingal.dao.CityDao">
<select id="queryCities" resultType="org.home.knightingal.bean.City">
select id, name, countryCode, district, population from city
where
1 = 1
<if test="countryCode != null">
and countryCode = #{countryCode}
</if>
limit 0, 10
</select>
</mapper>
最后为了完成查询结果的展示,现学现卖的写一个queryCities.jsp
页面,根据之前的配置,放在/WEB-INF/views/
目录下。
./src/main/webapp/WEB-INF/views/queryCities.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
import="java.util.List, org.home.knightingal.bean.City"
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>queryCities</title>
</head>
<body>
<table border="1">
<tr>
<th>ID</th>
<th>NAME</th>
<th>COUNTRY CODE</th>
<th>DISTRICT</th>
<th>POPULATION</th>
</tr>
<%
List<City> cities = ((List<City>)request.getAttribute("cities"));
for (int i = 0; i < cities.size(); i++) {
%>
<tr>
<td>
<%= cities.get(i).getId() %>
</td>
<td>
<%= cities.get(i).getName() %>
</td>
<td>
<%= cities.get(i).getCountryCode() %>
</td>
<td>
<%= cities.get(i).getDistrict() %>
</td>
<td>
<%= cities.get(i).getPopulation() %>
</td>
</tr>
<% } %>
</table>
</body>
</html>
关于这个jsp页面我就不做讲解了,我所有的前端水平只够写出这些来,一个样式看起来很乏味的表格,显示了一些从数据库里查出来的数据。
0x6 结语
到目前为止,这个简单的spring+spring-mvc+mybatis工程的搭建演示过程的讲解就算完成了。整个工程开发从无到有的过程都在我之前给出的github仓库里,每一次提交我都打了tag,如果我有没讲明白的地方,就去那里看吧。