在STEP3.2 服务器对于HTTP请求的处理流程中,我们认识了一个后端服务应该有的项目结构(Controller,Service,Dao),以及基本调用流程(Controller→Service→Dao);
在STEP4.1 第一个SpringBoot项目中,我们认识了yml全局配置文件,学会了如何写一个服务(/hello),以及如何启动、访问我们的服务;
在STEP4.2 认识pom.xml中,我们认识了SpringBoot依赖Maven来帮助我们的项目进行管理。
在STEP4.3 会话中,我们了解了会话的基本概念,并做了Cookie和HttpSession的简单实践。
到这里,我们应该已经能够自行搭建一些我们想要的服务了。我们来做一个稍微综合一点的实践,来实现一个简易登录模块。也就是我们在4.1节中提到的需求。
我们先来回顾一下老板的三个需求:
- 用户可以通过账号密码登录
- 已经登录的用户可以查看全用户名列表(没登录就不能看)
- 用户可以注销
注:
- 由于我们目前已学的内容中不涉及数据库的连接,我们会手写dao层的方法来模拟数据访问。
- 考虑到Controller层依赖Service层,Service层依赖Dao层。为了方便叙述,我们从Dao开始自底向上编码,但实际开发过程中势必会在三层间有多次辗转(比如写Controller的时候突然发现自己又需要一个新服务,所以又切换回去给service添加一个方法)。
接下来,既然需求定档了,就让我们开始动代码吧!
一、新建项目并分层
controller,service,dao大家都已经很熟悉了。这里的entity文件夹我们一般会称之为“实体层”,但它严格来说不算一个层次,因为它并不在我们的http请求处理流程(控制-服务-数据访问)里面。entity包里一般会存POJO(一般java对象)的类,比如User用户类、Student学生类等等,一般会与数据库的表一一对应。
二、构建用户实体
我们先在entity里写一个User类,来表达我们的用户信息:
User类中有以下字段:
我们在类中添加一个无参构造器、一个全参构造器、以及所有的getter/setter方法。
public class User {
private Integer id;
private String username;
private String password;
public User() {
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
三、手写dao层接口方法,模拟数据
我们根据一开始提出的几个需求来设计一下dao层的接口:
import java.util.List;
public interface UserDao {
//获取全用户列表
List<User> getUserList();
//获取全用户名列表
List<String> getUsernameList();
//根据用户名搜索用户
User getUserByUsername(String username);
//根据用户名密码搜索用户
User getUserByUsernameAndPassword(String username,String password);
}
为什么要用接口而不是直接写类?如果你对这个问题感到好奇,请参考这篇文章:
为什么dao层和service层要用接口?blog.csdn.net然后我们对dao层接口进行一个实现:(无需在意实现细节,注意返回内容即可。在之后引入ORM框架mybatis后,这里都应该用SQL语言来进行查询,而不是用代码模拟)
import java.util.ArrayList;
import java.util.List;
public class UserDaoImpl implements UserDao {
@Override
public List<User> getUserList() {
//模拟从数据库取出两条数据,分别是id=1和id=2的用户,并返回
User user1 = new User(1,"Jack","qwe");
User user2 = new User(2,"Mary","asd");
List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
return userList;
}
@Override
public List<String> getUsernameList() {
List<User> userList = getUserList();
List<String> usernameList = new ArrayList<>();
for(User user:userList){
usernameList.add(user.getUsername());
}
return usernameList;
}
@Override
public User getUserByUsername(String username) {
List<User> userList = getUserList();
for(User user:userList){
if((user.getUsername()+"").equals(username)){
return user;
}
}
//如果找不到该用户就返回null
return null;
}
@Override
public User getUserByUsernameAndPassword(String username, String password) {
User user = getUserByUsername(username);
if((user.getPassword()+"").equals(password))return user;
return null;
}
}
我们使用这段代码模拟了从数据库取数据的操作。
相当于假定了数据库有这样两个用户:
四、构建服务层
还是先构建接口
import java.util.List;
public interface UserService {
//根据用户名密码获取用户
User getUser(String username, String password);
//获取用户名列表
List<String> getUsernames();
}
再实现服务
import java.util.ArrayList;
import java.util.List;
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDao();
@Override
public User getUser(String username,String password) {
User user = userDao.getUserByUsernameAndPassword(username,password);
if(user == null)throw new RuntimeException();
return user;
}
@Override
public List<String> getUsernames() {
return new ArrayList<>(userDao.getUsernameList());
}
}
注:这里有一个throw new RuntimeException,含义是“如果用户登录时用户名找不到或密码错误,就向控制层抛出一个运行时异常”。这个异常类理应由开发者自行设计,不要用java自带的RuntimeException,而是自行设计一个RuntimeException的子类,来捎带一些必要的信息(如错误信息文本,异常代码等)。此处为了方便,不作子类设计。
五、设计接口,开发控制层
先写一个登录控制器
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import undestiny.logindemo.service.UserService;
import javax.servlet.http.HttpSession;
@RestController
public class LoginController {
private UserService userService = new UserService();
@GetMapping("/login/{username}/{password}")
public String login(@PathVariable(value = "username",required = true)String username,
@PathVariable(value = "password",required = true)String password,
HttpSession session){
//参数校验
if(username.length() < 2 || username.length() > 20
|| password.length() < 2 || password.length() > 20){
return "Login failed";
}
//请求转发,会话管理
try{
session.setAttribute("user",userService.getUser(username,password));
}catch (RuntimeException e){
return "Login failed";
}
return "Login successfully";
}
@GetMapping("/logout")
public String logout(HttpSession session){
//注销session(在服务器里删除该session)
session.invalidate();
return "Logout successfully";
}
}
再写一个用户控制器(提供查看用户名列表的方法)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import undestiny.logindemo.service.UserService;
import javax.servlet.http.HttpSession;
import java.util.List;
@RestController
public class UserController {
private UserService userService = new UserService();
@GetMapping("/getUsernames")
public List<String> getUsernames(HttpSession session){
//检查是否登录(session是否存在)
if(session.getAttribute("user") != null) {
return userService.getUsernames();
}
return null;
}
}
六、启动服务
在我们的启动类xxxApplication.java上右键运行,打开我们的浏览器进行测试:
首先输入http://localhost:8080/login/Jack/qwe回车
然后我们输入http://localhost:8080/getUsernames试一下:
由于此时是登录状态,所以可以获取到用户名列表。
我们尝试注销:输入http://localhost:8080/logout回车
然后再尝试获取用户名列表:
由于没有登录,就获取不到用户名列表了。
最后附上我们login-demo的项目源码:
undestiny/JavaWeb后端学习小组gitee.com不过细心的读者可能会发现,源码里的和本篇博客中的内容有细微的出入。
这是因为源码中已经根据Spring IOC的特性做了简单优化。请移步下一篇:
孑辞:STEP4.5 初识SpringIOCzhuanlan.zhihu.com