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,如果我有沒講明白的地方,就去那里看吧。