0x0 前言
標(biāo)題里的這些都是什么我就不細(xì)說(shuō)了骄蝇,點(diǎn)開(kāi)這篇博客的你至少應(yīng)該已經(jīng)知道了一些。最近工作需要,想學(xué)點(diǎn)Spring的東西可缚,看了一些網(wǎng)上關(guān)于Spring入門的博客,感覺(jué)這些博客面向初學(xué)者的話還是有不少?zèng)]有講清楚的地方斋枢,比如至關(guān)重要的配置文件的在工程中的路徑帘靡,還有某些配置項(xiàng)在xml文件中的所處的節(jié)點(diǎn)。也許大牛們覺(jué)得這些都太基礎(chǔ)了沒(méi)必要全寫(xiě)出來(lái)占著篇幅瓤帚,要留給展示操作過(guò)程的ide界面截圖描姚。這是很多代碼玩家的另外一個(gè)問(wèn)題,過(guò)于依賴ide功能戈次,而不去嘗試脫離ide完成同樣功能的方式轩勘,所以為了展示一個(gè)操作,不得不放上ide的截圖(如果截圖暴露了作者還用的是xp這種古董操作系統(tǒng)或者vc6.0這種古董ide怯邪,那簡(jiǎn)直要low穿地殼了)绊寻。所以本教程中————雖然內(nèi)容是入門級(jí)的————我采用了一些我認(rèn)為逼格比較高的方式,首先盡可能的描述清楚每一行代碼所處的文件的在工程中的路徑悬秉,和代碼在文件中的位置澄步,當(dāng)前限于篇幅,我也不可能精確到每個(gè)細(xì)節(jié)和泌,所以我給出了完整工程托管在github上的路徑村缸。另外,盡可能脫離ide武氓,構(gòu)建和部署均采用命令行和修改配置文件的方式梯皿。
數(shù)據(jù)庫(kù):MySql 5.7
構(gòu)建工具:apache-maven-3.3.9
容器:apache-tomcat-9.0.0.M4
0x1 還是從最簡(jiǎn)單的hello world開(kāi)始
一個(gè)最精簡(jiǎn)的基于SpringMVC的java web工程需要以下幾個(gè)文件:
首先是導(dǎo)入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
項(xiàng)填寫(xiě)war
才可以最終將工程打包成war包;依賴管理部分县恕,只要導(dǎo)入spring-webmvc
东羹,maven就會(huì)自動(dòng)將其依賴的spring-core、spring-context忠烛、spring-bean等等一并包含進(jìn)來(lái)属提。
然后是作為一個(gè)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>
這里定義了一個(gè)名為SpringMVCStart的servlet,其接收類為org.springframework.web.servlet.DispatcherServlet
况木,容器啟動(dòng)時(shí)加載垒拢,然后在servlet-mapping
中將這個(gè)servlet映射給url/
,即所有路徑下的請(qǐng)求均由org.springframework.web.servlet.DispatcherServlet
這個(gè)類進(jìn)行分發(fā)火惊。于是在這里就引入了spring的webmvc框架。
接下來(lái)我們就需要spring的webmvc框架的配置文件奔垦。
由于我們的servlet名字叫'SpringMVCStart'屹耐,在默認(rèn)情況下,springmvc框架會(huì)加載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/>
意味著開(kāi)啟spring mvc相關(guān)的注解寿弱,<context:component-scan>...</context:component-scan>
的意思是,掃描org.home.knightingal.controller
路徑下所有帶有org.springframework.stereotype.Controller
注解的類按灶,將其作為controller症革。
那么接下來(lái)就是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框架可以掃描到這個(gè)類鸯旁,將其作為一個(gè)controller噪矛,接下來(lái)兩個(gè)@RequestMapping
注解將index
方法綁定給url路徑/helloworld/index
。@ResponseBody
表示將返回值直接作為響應(yīng)的消息體铺罢,否則框架會(huì)將方法返回的字符串值理解為響應(yīng)頁(yè)面的名字艇挨。新版的springmvc框架對(duì)于將方法的返回值直接作為響應(yīng)體的場(chǎng)景有了更簡(jiǎn)便的注解類型,這里還是沿用老辦法韭赘。
到目前為止一個(gè)基于springmvc的java web項(xiàng)目就搭建完成了缩滨,雖然我們只有mvc中的c部分。
我假定你已經(jīng)配置好了jdk和maven的環(huán)境變量泉瞻,接下來(lái)我們?cè)赾md或者shell中輸入命令
mvn package
maven就會(huì)執(zhí)行打包程序脉漏,它會(huì)首先去maven中央倉(cāng)庫(kù)下載必要的jar包和插件,首次運(yùn)行這部分過(guò)程可能會(huì)持續(xù)比較長(zhǎng)的時(shí)間袖牙,取決于你和maven中央倉(cāng)庫(kù)的連接網(wǎng)速鸠删,必要時(shí)請(qǐng)自行科學(xué)上網(wǎng)。當(dāng)jar包和插件都下載完畢贼陶,它會(huì)執(zhí)行javac去編譯java源碼刃泡,如果有單元測(cè)試的話此編譯完成后它還會(huì)執(zhí)行單元測(cè)試,最后它會(huì)將編譯后的class文件碉怔,以及java web需要的各類配置文件一并打成war包烘贴,放入target目錄下。
如果一切正常撮胧,那么此時(shí)你應(yīng)該可以看到
[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輸出的完成記錄稍微過(guò)目一下芹啥,了解一下maven構(gòu)建和打包一個(gè)java工程的打包過(guò)程锻离。
下面是部署這個(gè)web工程,這里我就不說(shuō)怎么用eclipse全家桶自動(dòng)部署了墓怀,單單說(shuō)一下手動(dòng)部署汽纠。
打開(kāi)tomcat路徑下的/conf/server.xml文件,找到<Host>
節(jié)點(diǎn)傀履,在這個(gè)節(jié)點(diǎn)下添加下面的內(nèi)容
<Context path="/SpringMVCStart" docBase="D:\SpringMVCStart\target\SpringMVCStart-1.0-SNAPSHOT" reloadable="true" >
</Context>
把docBase
屬性的值替換成你自己的版本虱朵。
改好了以后啟動(dòng)tomcat,待啟動(dòng)成功之后,假設(shè)你沒(méi)有改tomcat的監(jiān)聽(tīng)端口碴犬,那么在瀏覽器地址欄輸入http://localhost:8080/SpringMVCStart/helloworld/index絮宁,應(yīng)該就可以看到"hello world"字樣了。
0x2 數(shù)據(jù)庫(kù)支持
在./pom.xml里面添加mysql-connector和jdbc相關(guān)依賴
<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連接參數(shù)
<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"/> <!-- 修改為你自己的數(shù)據(jù)庫(kù)地址-->
<property name="username" value="Knightingal"/> <!-- 修改為你自己的用戶名 -->
<property name="password" value="123456"/> <!-- 修改為你自己的密碼 -->
</bean>
這里配置了一個(gè)org.apache.commons.dbcp.BasicDataSource
類型的實(shí)例服协,以及該實(shí)例中driverClassName
,url
,username
,password
成員變量的值绍昂。后續(xù)配置中可使用dataSource
這個(gè)id引用該實(shí)例。
以及配置jdbcTemplate偿荷。
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
稍后在Controller中便可通過(guò)@Autowired
注解將org.springframework.jdbc.core.JdbcTemplate
類型的實(shí)例反向注入到名為jdbcTemplate
的成員變量上窘游,并且該實(shí)例中的dataSource
成員變量已經(jīng)注入了之前配置的id為dataSource
的org.apache.commons.dbcp.BasicDataSource
類型實(shí)例。
至此數(shù)據(jù)源的配置就完成了遭顶。
0x3 在Controller中使用JdbcTemplate進(jìn)行數(shù)據(jù)庫(kù)操作
現(xiàn)在我們配置一個(gè)新的Controller進(jìn)行數(shù)據(jù)庫(kù)操作张峰。在此之前我們先了解一下需要查詢的數(shù)據(jù)庫(kù)的表結(jié)構(gòu)及數(shù)據(jù)。
這里我們使用MySql預(yù)裝的Demo庫(kù)World
棒旗,其中有city
,country
,countrylanguage
三張表喘批,從名字就能猜出里面的數(shù)據(jù)是什么。本教程中只需要其中的city
就可以了铣揉。也許你的MySql里面沒(méi)有預(yù)裝這個(gè)Demo庫(kù)饶深,沒(méi)關(guān)系,我把city
的表結(jié)構(gòu)和一部分?jǐn)?shù)據(jù)導(dǎo)出來(lái)了放到這里了https://github.com/knightingal/SpringMVCStart/blob/master/dbsetup/city.sql
簡(jiǎn)單的說(shuō)逛拱,city
表的表結(jié)構(gòu)如下:
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 |
根據(jù)這表結(jié)構(gòu)敌厘,我們可以建立一個(gè)名為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...
}
接下來(lái)新建一個(gè)Controller,接受一個(gè)請(qǐng)求來(lái)查詢這張表朽合,將查詢結(jié)果放入City
的列表作為查詢請(qǐng)求的響應(yīng)俱两。
./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;
}
}
重新構(gòu)建之后在瀏覽器中輸入http://localhost:8080/SpringMVCStart/city/simpleQueryCities,返回了500錯(cuò)誤曹步。但是在Tomcat的運(yùn)行窗口中我們看到打印出了查詢到的城市名字宪彩,說(shuō)明查詢是成功的。
那我們回過(guò)頭看看500錯(cuò)誤的描述讲婚,可以看到這樣的信息:
No converter found for return value of type: class java.util.ArrayList
問(wèn)題出在simpleQueryCities
的返回值尿孔,這個(gè)方法返回的是一個(gè)ArrayList
類型的對(duì)象,框架無(wú)法對(duì)這個(gè)對(duì)象進(jìn)行轉(zhuǎn)換筹麸,成為具備良好閱讀性的響應(yīng)體活合。
這時(shí)我們需要引入jackson
作為converter。在./pom.xml里面添加jackson
的依賴
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.3</version>
</dependency>
SpringMVC框架會(huì)自動(dòng)識(shí)別出jackson
并作為其converter物赶,不需要做另行配置白指。
重新打包部署后,再次刷新http://localhost:8080/SpringMVCStart/city/simpleQueryCities頁(yè)面块差,就可以看到處理成json格式的城市信息了侵续。
0x4 引入Mybatis進(jìn)行數(shù)據(jù)庫(kù)操作
在./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>
第一個(gè)是mybatis的主包倔丈,另一個(gè)mybatis-spring可以將mybatis的代碼無(wú)縫整合到spring中憨闰。
接下來(lái)配置一個(gè)簡(jiǎn)單的dao接口状蜗。由于只是個(gè)簡(jiǎn)單的演示項(xiàng)目,所以不打算添加常見(jiàn)的service層了鹉动,controller直接調(diào)用dao層轧坎。
./src/main/java/org/home/knightingal/dao/CityDao.java
public interface CityDao {
List<City> queryCities();
}
在CityController
中加入對(duì)queryCities
的調(diào)用
./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();
}
}
有了接口,總得有實(shí)現(xiàn)吧泽示,還有那個(gè)cityDao
總不能就這樣放著等著運(yùn)行的時(shí)候給你來(lái)個(gè)空指針異常吧缸血。下面是通過(guò)mybatis做的CityDao
的實(shí)現(xiàn)
./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
的實(shí)際父類型是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
注解的作用赤赊,這個(gè)bean會(huì)自動(dòng)注入給變量CityDao cityDao
闯狱。
而org.mybatis.spring.mapper.MapperScannerConfigurer
這個(gè)bean配置則指定了mybatis去那個(gè)路徑下搜索dao接口關(guān)聯(lián)的sql配置文件。
重新編譯運(yùn)行后輸入地址http://localhost:8080/SpringMVCStart/city/queryCities即可看到運(yùn)行結(jié)果抛计。
0x5 加入jsp頁(yè)面
目前為止mvc架構(gòu)中我們已經(jīng)有了controller和model部分哄孤,對(duì)于很多純后臺(tái)系統(tǒng)而言已經(jīng)夠了,并不是每個(gè)系統(tǒng)都一定要有一個(gè)前臺(tái)展示的view吹截。但是呢瘦陈,既然說(shuō)到這了,再把view的部分加上也不是什么難事波俄。
首先是引入jsp依賴的jstl庫(kù)
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
jsp的配置晨逝,就不啰嗦是在哪個(gè)文件里面配的了。
<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
的請(qǐng)求中返回jsp頁(yè)面queryCities
弟断,注意咏花,去掉了@ResponseBody
注解,否則一會(huì)兒你在頁(yè)面上只能看到"queryCities"幾個(gè)字阀趴,是不是頓時(shí)覺(jué)得自己萌萌噠昏翰?
@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()
接口,讓它可以接受一個(gè)City
類型的入?yún)⒘跫保瑤氩樵儣l件棚菊,
public interface CityDao {
List<City> queryCities(City param);
}
這么做的目的是驗(yàn)證mybatis動(dòng)態(tài)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>
最后為了完成查詢結(jié)果的展示叔汁,現(xiàn)學(xué)現(xiàn)賣的寫(xiě)一個(gè)queryCities.jsp
頁(yè)面统求,根據(jù)之前的配置检碗,放在/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>
關(guān)于這個(gè)jsp頁(yè)面我就不做講解了码邻,我所有的前端水平只夠?qū)懗鲞@些來(lái)折剃,一個(gè)樣式看起來(lái)很乏味的表格,顯示了一些從數(shù)據(jù)庫(kù)里查出來(lái)的數(shù)據(jù)像屋。
0x6 結(jié)語(yǔ)
到目前為止怕犁,這個(gè)簡(jiǎn)單的spring+spring-mvc+mybatis工程的搭建演示過(guò)程的講解就算完成了。整個(gè)工程開(kāi)發(fā)從無(wú)到有的過(guò)程都在我之前給出的github倉(cāng)庫(kù)里己莺,每一次提交我都打了tag奏甫,如果我有沒(méi)講明白的地方,就去那里看吧凌受。