开发过程中使用的操作系统是OS X,关于软件安装的问题请大家移步高效的Mac环境设置。
本文是我对自己学习过程的一个回顾,应该还有不少问题待改进,例如目录的设置、编码习惯和配置文件的处理等,请大家多多指正。
文中用到的开发工具列举如下:
第一步新建工程,选择Maven工程,如图1所示。注意,“create from archetype”是一些Maven的工程模板,在这里我们为了学习要从头开始自己配置。
点击next,出现设置工程坐标的页面,如图2所示。GroupId 是公司组织的标号;ArtifactId是项目名称;综合来看,在src/main/java目录下会新建对应的包结构:GroupId.ArtifactId。
点击next,出现设置工程名字的界面,如图3所示。这里跟之前的ArtifactId一样就可以,设置完后选择finish完成工程构建。我们这个示例项目采用的是单模块项目,我猜这里是设置模块相关的吧,还需继续学习。
我们不需要从头开始写.gitignore文件,已经有人为我们准备好了模板文件,只需要在模板文件的基础上稍作修改即可。
首先要给IDEA安装.gitignore插件,然后在工程名字上右击建立.gitignore文件,通过插件可以根据项目的内容选择需要忽略的文件或者文件夹。在这里我选择了三个类型:Java、JetBrains、OSX三个模板的组合文件。因为Maven编译生成的目录名为target,我又增加了target文件夹的忽略,如图4所示。
通过SourceTree,创建本地仓库,将目标路径设置为usersDemo工程的根目录,如图5所示。仓库初始化完成后,我们的项目就在Git管理之下了,可以开始下一步了。
首先在pom.xml文件中修改配置,通过properties标签统一管理依赖库的版本,方便后续更新;通过dependencies标签管理所有的库依赖,本次增加的配置代码如下所示:
<properties>
<spring.version>4.1.7.RELEASE</spring.version>
</properties>
<dependencies>
<!-- spring support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- jsp support -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope> <!-- 仅仅在编译时使用 -->
</dependency>
</dependencies>
接下来为项目增加Spring MVC框架支持,也就是每个web项目都应该有的web文件夹等等。具体操作如图6和图7所示。其中Spring MVC框架的库已经不用下载,使用我们之前在pom中下载好的库即可。
选择相应的Web框架和库文件
添加完成后,要对项目的目录结构做一些调整:将web文件夹移动到src/main/目录下,并重命名为webapp,调整后的目录结构如图8所示。
web.xml的作用是配置DispatcherServlet,在SpringMVC项目中DispatcherServlet作为前端控制器。服务器给用户的接口名并不是真正的servlet类的名字,只是一个逻辑名称,由DispatcherServlet完成这个逻辑名称到真正的servlet类的映射过程。
在web.xml的代码中,org.springframework.web.servlet.DispatcherServlet的实例名称为usersDemo,这个servlet-name 非常重要,默认情况下,DispatcherServlet在加载时会从一个机遇这个servlet名字的XML文件中加载Spring应用上下文,在这里,因为servlet-name是usersDemo,所以DispatcherServlet将会从usersDemo-servlet.xml文件中加载应用上下文。现在项目的目录结构图如图9所示。
通过servlet-mapping标签指定由usersDemo这个DispatcherServlet实例处理哪些映射,在这里我们设置为“/”,即声明该DispatcherServlet实例会吹所有的请求,包括静态资源的请求。
最后,web.xml的代码列举如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>usersDemo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/usersDemo-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>usersDemo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
DispatcherServlet需要咨询一个或者多个处理器映射器来决定要将请求发送给哪个控制器,我们这里常用的处理器映射器是DefaultAnnotationHandlerMapping:即将请求映射给使用@RequestMapping注解的控制器和控制器方法。
最新的Spring的发展趋势是依靠注解来减少XML配置,因此我们在usersDemo.xml中添加下面一行配置,就可以得到Spring MVC提供的注解驱动测试
<mvc:annotation-driven/>
我们将会给控制器类添加@Controller来表明这是一个控制器类,这个类是@Component的子类,也就是说可以通过”context:component-scan标签”来查找控制器类并将其自动注册为Bean。需要再usersDemo.xml中添加下面一行配置:
<context:component-scan base-package="com.alibaba.yunos.usersDemo.controller"/>
经过了上一步的铺垫,控制器的代码比较简单。@Controller注解告诉Spring这是一个控制器类,要将它注册为Bean;@RequestMapping注解告诉Spring将”/showUsers“接口,并且HTTP方法是GET的请求由showUser方法处理。
在showUser方法中我们使用servlet直接打印HTTP响应内容,很熟悉的hello系列。
UsersController的代码列举如下:
package com.alibaba.yunos.usersDemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** * Created by duqi on 15/8/19. */
@Controller
public class UsersController {
@RequestMapping(value = "/showUser",method = RequestMethod.GET)
public void showUser(HttpServletResponse response) throws IOException {
response.getWriter().print("<h1>Hello SpringMVC</h1>");
response.flushBuffer();
}
}
<packaging>war</packaging>
<build>
<finalName>usersDemo</finalName>
</build>
使用SourceTree对刚才修改和增加的代码进行提交,如图15所示,对于commit message要尽量简洁。
Velocity的存在是为了辅助前后端分离:后端接口开发人员可以专心于提供数据、前端人员可以使用占位符(模板文件)暂时代替数据。渲染:将占位符替换为真正的变量值,并生成最终的网页页面。
首先在pom.xml中编辑,下载Velocity的支持库,包括三个支持:Velocity、Velocity-tool、spring-context-support。
添加的依赖代码如下:
<!-- velocity support-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>${velocity-tools.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
在usersDemo-servlet.xml文件中配置Velocity视图解析器。配置代码如下:
<!--规定模板文件的类型和位置-->
<bean id="velocityConfigurer" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/templates/" />
<property name="velocityProperties">
<props>
<prop key="input.encoding">utf-8</prop>
<prop key="output.encoding">utf-8</prop>
</props>
</property>
</bean>
<!--配置附加工具,以及将后缀为vm的文件交给下面的Resolver处理-->
<bean id="velocityViewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="suffix" value=".vm" />
<property name="contentType" value="text/html;charset=utf-8" />
</bean>
控制器的作用是根据请求调用BLL层提供的Service实例,当服务接口返回处理结果后,由控制器将模型对象和逻辑视图名称返回。在这里还不涉及模型数据,因此只关注逻辑视图,解析器根据这个逻辑视图名称,再加上在usersDemo-servlet.xml文件中定义的视图解析器设置,找到对应的模板文件进行渲染。
控制器的代码如下:
package com.alibaba.yunos.usersDemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/** * Created by duqi on 15/8/19. */
@Controller
public class UsersController {
@RequestMapping(value = "/showUser",method = RequestMethod.GET)
public String showUser() {
//1.调用BLL层的服务接口
//2.设置模型数据
//3.返回逻辑视图名称
return "showUser";
}
}
现在的模板文件非常简单,就是一句话:”hello velocity!!“,现在项目目录结构和模板文件如图16所示(注意路径与usersDemo-servlet.xml中配置的对应关系)。
启动tomat服务器,运行结果如图17所示。
通过SourceTree提交commit。
Mybatis 的着力点,则在于POJO 与SQL之间的映射关系。然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。 相对Hibernate“O/R”而言,Mybatis是一种“Sql Mapping”的ORM实现。
在pom.xml文件中增加相应的支持库,包括mybatis、mybatis-spring、commons-dbcp2、mysql-connector-java等库。其中commons-dbcp2是用作管理数据库连接池。
增加的配置代码如下:
<!-- mybatis support -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
</dependencies>
数据源的配置在applicationContext.xml中完成,具体的配置代码如下:
<!-- 数据库配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="databasename"/>
<property name="password" value="yourpassword"/>
</bean>
在数据库中,id字段我们设置为自动增加。User.java的代码如下
package com.alibaba.yunos.usersDemo.model;
/** * Created by duqi on 15/8/19. */
public class User {
private String NAME;
private String age;
public String getNAME() {
return NAME;
}
public void setNAME(String NAME) {
this.NAME = NAME;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
要和 Spring 一起使用 MyBatis,你需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。在 MyBatis-Spring 中,SqlSessionFactoryBean 是用于创建 SqlSessionFactory 的。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
首先新建一个接口UserMapper,完成请求方法(getUser)到SQL语句的映射,代码下所示:
package com.alibaba.yunos.usersDemo.mapper;
import com.alibaba.yunos.usersDemo.model.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/** * Created by duqi on 15/8/19. */
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId")String userId);
}
接着在applicationContext.xml文件中增加配置,将UserMapper接口加入到Spring容器中,配置代码如下所示:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" >
<property name="mapperInterface" value="com.alibaba.yunos.usersDemo.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
至此,DAO和数据库层就已经配置好了。
首先增加UserService接口,代码如下:
package com.alibaba.yunos.usersDemo.service;
import com.alibaba.yunos.usersDemo.model.User;
/** * Created by duqi on 15/8/19. */
public interface UserService {
User getUser(String userId);
}
然后增加UserServiceImpl实现,代码如下:
package com.alibaba.yunos.usersDemo.service.impl;
import com.alibaba.yunos.usersDemo.mapper.UserMapper;
import com.alibaba.yunos.usersDemo.model.User;
import com.alibaba.yunos.usersDemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
/** * Created by duqi on 15/8/19. */
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public User getUser(String userId) {
return this.userMapper.getUser(userId);
}
}
在applicationContext.xml中配置UserServieImpl的实例Bean,由于已经在代码中使用@Autiwired注解,因此不需要在配置文件中显式得规定属性以及提供setter函数。配置代码如下:
<!-- Service层的设置 -->
<bean id="userService" class="com.alibaba.yunos.usersDemo.service.impl.UserServiceImpl"/>
控制器的逻辑依旧十分简单,就是三个步骤:
修改后的控制器代码如下:
package com.alibaba.yunos.usersDemo.controller;
import com.alibaba.yunos.usersDemo.model.User;
import com.alibaba.yunos.usersDemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/** * Created by duqi on 15/8/19. */
@Controller
public class UsersController {
@Autowired
private UserService userService;
@RequestMapping(value = "/showUser",method = RequestMethod.GET)
public String showUser(@RequestParam("id") String id, ModelMap modelMap) {
//1.调用BLL层的服务接口
User user = userService.getUser(id);
//2.设置模型数据
modelMap.put("user",user);
//3.返回逻辑视图名称
return "showUser";
}
}
在模板中使用数据就像使用真正的java对象的数据一样,我们修改后的模板代码如下:
#if(${user})
${user.NAME}
#else
您查找的用户不存在!
#end
该URL对应的结果如图20所示。
至此,一个Spring+Mybatis+Velocity框架构成的简陋的Demo就完成一个查询功能了,通过SourceTree记录里程碑。
我们在这一步还做了一个调整,将applicationContext.xml调整到src/main/resources文件夹下。对此我的想法是将应用程序配置文件放在resources目录,至于是不是合理,还请各位看官讨论。
如上所示,一个接口从前端后数据库已经打通了,但是,每次都要等前端页面写好了才能开始测试?这样效率太低了,可不可以将前后端的工作分开,让后端人员能够专注于提供接口,并可以及时测试?可以,单元测试。
由于控制器层是非常薄的一层,负责将传入的URL请求传到BLL层对应的Service实例进行处理。我们可以假定控制器层的代码不需要测试,那么只要Service层保证自己的接口正确就ok。Java中最流行的单元测试框架是Junit,这里探讨如何在Junit的TestCase中自动注入Service实例。
首先在pom.xml中添加测试库支持,配置代码如下:
<!-- test support -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
第二,在src/test/java下新建包,与src/main下保持一致,在这里我要测试的类是UserServiceImpl,因此新建com.alibaba.yunos.usersDemo.service。
新建测试类UserServiceImplTest,该类的代码如下:
package com.alibaba.yunos.usersDemo.service;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.alibaba.yunos.usersDemo.model.User;
/** * Created by duqi on 15/8/19. */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:/applicationContext.xml")
public class UserServiceImplTest {
@Autowired
private UserService userService;
@Test
public void getUserTest(){
User user = userService.getUser("1");
Assert.assertNotNull(user);
}
}
@RunWith(SpringJUnit4ClassRunner.class)是为Spring 3接入Junit 4框架,从Spring 3开始提供;
@ContextConfiguration(“classpath*:/applicationContext.xml”)是加载该类中使用的Bean所在的配置文件
根据上文第一到第六步,我们接着给这个Demo增加新的接口:addUser、allUsers和deleteUser。
具体的步骤如下:
在UserMapper中增加接口getAllUsers,代码如下:
@Select("SELECT * FROM users") List<User> getAllUsers();
在UserService中增加新的接口getAllUsers,代码如下:
List<User> getAllUsers();
在UserServiceImpl中实现该接口,代码如下:
public List<User> getAllUsers() {
return this.userMapper.getAllUsers();
}
在UserController控制器中增加allUsers接口,代码如下:
@RequestMapping(value ="/allUsers", method = RequestMethod.GET)
public String allUsers(ModelMap modelMap){
List<User> users = userService.getAllUsers();
modelMap.put("users",users);
return "allUsers";
}
在/WEB-INF/templates下增加allUsers.vm文件,内容为:
#if(${users})
#foreach(${user} in ${users})
${user.NAME}<br>
#end
#else
目前没有数据!
#end
重新启动Web服务器,输入URL:http://localhost:8080/usersDemo/allUsers
结果如图21所示:
@Insert("INSERT into users(NAME,age) values(#{userName},#{userAge})")
void addUser(@Param("userName")String userName, @Param("userAge")String userAge);
void addUser(User user);
public void addUser(User user) {
this.userMapper.addUser(user.getNAME(),user.getAge());
}
@RequestMapping(value = "/addUser", method = RequestMethod.GET) public String addUser(@RequestParam("name")String name, @RequestParam("age")String age,ModelMap modelMap){
User user = new User();
user.setNAME(name);
user.setAge(age);
userService.addUser(user);
return "redirect:/allUsers";
}
启动Web服务器运行,访问URL:http://localhost:8080/usersDemo/addUser?name=小红&age=15
发现有乱码如图22所示:
乱码错误是WEB开发中经常遇到的问题,我的经验是在每个数据传输的节点上都要保持一致,在这里我们用UTF-8。看一下数据从前端页面输入到存到后台数据库的流程可以看到,有几个关键点:页面字符、页面到Controller、DAO层到数据库;最终现在的问题我发现是在页面向Controller转换的时候没有强制处理,可能有问题。因此我在Contoller里的addUser方法一开始加了一行代码System.out.println(name);
,再次运行发现终端输出乱码,因此确定错误位置。
解决错误的方法是:在web.xml里增加过滤器,即当页面向Controller映射之前要先经过该字符过滤器处理,过滤器设置的代码如下:
<!-- 设置字符过滤器,用于处理URL到Controller的字符设置 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
再次运行Web服务器测试,访问URL:localhost:8080/usersDemo/addUser?name=哈哈&age=18
发现运行结果如图23所示:
要通过查询参数给定一个id,然后BLL层根据给定的id删除指定用户,这里没有考虑到数据库出错的处理方式。
@Delete("DELETE FROM users WHERE id = #{userId}")
void deleteUser(@Param("userId")String userId);
void deleteUser(String userId);
public void deleteUser(String userId){
this.userMapper.deleteUser(userId);
}
@RequestMapping(value = "/deleteUser",method = RequestMethod.GET)
public String deleteUser(@RequestParam("id")String id, ModelMap modelMap){
userService.deleteUser(id);
return "redirect:/allUsers";
}
为了便于验证,将用户的id也取出来,需要做下面两处修改
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#if(${users})
#foreach(${user} in ${users})
${user.id},${user.NAME}<br>
#end
#else
目前没有数据!
#end
代码修改完成,启动Web服务器,依次访下列的URL:
http://localhost:8080/usersDemo/deleteUser?id=8
http://localhost:8080/usersDemo/deleteUser?id=9
http://localhost:8080/usersDemo/deleteUser?id=100
然后再访问:http://localhost:8080/usersDemo/allUsers
结果截图如图24所示:
这里有一个疑问,删除id为100的时候,数据库中明显没有这个数据,但是后台也没报出异常,原因还有待我继续学习,有知道的朋友请留言给我,非常感谢。
写这篇文字的最初目的是帮助自己回顾一遍前几天学习的东西,如果能碰巧帮助后来的同学就更好了。排版还有点乱,代码还很简陋,希望各位朋友指点一二。
————————