1、本篇博客的背景和目的
目前我本人正在学习SpringFramework的知识,这也是这个专栏的主题。我前面的几篇博文中,简单的认识了一下SpringFramework,记录了SpringFramework的环境搭建,记录了SpringIOC如何加载配置文件,记录了SpringIOC三种Bean对象的实例化方法。还记录了SpringIOC手动装配(注入)的方法之一:set方法注入。还有另外三个不太常用的SpringIOC手动注入的方法。其实目前大部分都使用的是自动注入的方法,手动注入使用的不多。就算是使用手动注入的方法,使用的主流也是:set方法注入。自动注入的方法主要是使用两个注解,@Autowired注解或者是@Resource注解。上面两篇博客我都记录到了。上一篇博客我记录了一下如何使用SpringIOC扫描器。本篇博客我记录使用Spring简单模拟一下用户登录的过程,并没有使用数据库和持久层MyBatis框架。
2、我的上一篇博文
3、分析一下用户登录的过程
简单的来说,用户登录的过程有以下几步:
1、我们输入参数,也可以说是接收我们的参数,即用户名和密码
2、判断参数,也就是进行参数的非空校验或者还可以检查是否符合我们的正则表达式要求。这一步是可以在前端直接使用JS完成的。
3、查询数据库中是否有这个用户名?
4、如果有这个用户名,要判断密码是否正确。
5、如果密码正确,那么登录成功。
多说一下:以上是简单的登录步骤而已,其实我们平常的登录步骤是比这个更加严谨的,比如需要填写验证码,数据库中存储的密码常见简单一点的是经过MD5加密后存储的。这类功能也是有框架的,比如Shiro框架,SpringSecurity框架等。
4、代码的分层次思想
我们在平常的编码中,一般都使用的是MVC的设计模式,后端代码会被分为controller包(层),service包(层),dao包(层)。这样首先是为了规范,都是这样编码的。其次,会降低我们代码的耦合度(项目的高内聚,低耦合一直是我们的追求之一)。关于controller层,service层,dao层之间的相互调用,我在我的 实心期间学习springcloud 专栏中有比较详细的记录,不明白的读者可以前往阅读。
controller层(控制层,接收请求,响应结果)功能如下:
1、接收参数或者说是数据,(用户名和密码)
2、调用service层的方法,得到登录结果。按照面向对象编程的万物皆对象的思想,登录结果也会是一个对象。我这里称这个登录结果类名是ResultInfo。
3、响应结果,也就是响应结果。
service层(业务逻辑层次:判断数据,业务逻辑数据的处理)功能如下:
其实吧,当一些业务逻辑比较简单的时候,就直接将一些业务逻辑的处理代码放在了controller层的方法里面。
1、拿到参数,判断参数是否为空或者是否符合我们正则表达式的要求。如果为空或者不符合我们正则表达式的要求,那么就需要组装结果对象ResultInfo对象然后返回结果对象。
2、如果不为空并且符合正则表达式,那么就要调用dao层次的数据查询方法。
3、判断dao层返回的结果,是否存在这个用户
4、如果存在这个用户,那么根据用户名判断密码是否正确,然后返回登录成功的结果。当然啦,如果密码不正确,返回的也是密码错误的结果。
dao层(数据库访问层,也就是进行数据库的增删改查操作)功能如下:
1、查询数据库然后返回数据
5、我的代码文件结构
首先看一下我的代码的结构,这里我截取一张图:
6、我的POM文件
下面是我POM文件的代码:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dcy</groupId>
<artifactId>spring04</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring04</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<!-- 这个是Spring框架的核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
我创建项目的时候使用了一个模板,因此里面有很多的插件,其实没有必要。读者可以创建一个空的Maven项目就可以。我的POM文件里面主要的就是两个依赖,分别是Spring的核心依赖和跟测试有关的junit依赖。如果不使用单元测试文件夹的话,只需要Spring的核心依赖就可以。我使用的JDK版本是JDK11,建议使用11及以下的。
7、结果集类ResultInfo代码
无论登录的成功或者是失败,都应该给用户一个返回结果,按照面向对象的编程思想,这个返回结果也是一个对象。因此我们创建一个结果类,代码如下:
package com.dcy1.model;
/*
是我们的返回封装对象, 用来存储状态以及提示信息
这里多说一下:这个类是不能交给SpringIOC管理的,因为每次返回的对象都可能不一样
*/
public class ResultInfo {
private Integer code; //作为状态码 200=成功,500=失败
private String msg; //提示信息 失败的具体原因
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "ResultInfo{" +
"code=" + code +
", msg='" + msg + '\'' +
'}';
}
}
这个类只有两个字段,分别是code存储状态码,类型是整型(这里使用包装类),msg存储成功或者失败的原因,类型是字符串String类型(引用类型)。对这两个字段使用IDEA自动生成get、set方法。然后我们重写了toString()方法,也就是当我们直接打印这个类的某一个对象的时候,会调用toString()方法,而不是打印对象的地址。注意我代码中的注释:ResultInfo类不能交给SpringIOC管理,因为每一次返回的信息都是不同的。我们依然可以将这个类放在扫描的范围内,只不过不在类上面加@component注解就行。
多说一下:我上面一段记录中,红色部分都是真正开发项目中的规范和注意点,不熟悉的读者可以刻意的记一下。
8、用户类UserInfo代码
我们假定每一个用户对象只有三个字段,分别是用户编号userId(唯一标识一个用户,今后在数据库中作为主键)、用户名userName、密码userPwd。代码如下所示:
package com.dcy1.po;
public class UserInfo {
private Integer userId;
private String userName;
private String userPwd;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
}
当然,get和set方法是必须的,使用IDEA自动生成了。这里我没有重写toString()方法。我们不需要打印一个用户的信息。由于每个用户的信息都不一样,因此用户类UserInfo也不能交给SpringIOC管理。
9、Dao层(最接近数据库的一层)UserInfoDao类代码
按照逻辑,Dao层是和数据库直接交互的一层。如果我们使用最原始根本的JDBC连接数据库的方法的话,加载驱动,创建连接等语句都是应该在这一层的。目前我先不使用数据库,因此我们这里的代码里面假装是从数据库中查询出来的数据。代码如下:
package com.dcy1.dao;
import com.dcy1.po.UserInfo;
import org.springframework.stereotype.Repository;
@Repository
public class UserInfoDao {
//假设这是我们数据库中的用户名和密码
private final String USER_NMAE="admin";
private final String USER_PWD="123456";
public UserInfo queryUserByName(String userName){
UserInfo userInfo=null;
//下面我们先判断有没有这个用户名
if(USER_NMAE.equals(userName)){//说明数据库中有这个用户
userInfo=new UserInfo();
userInfo.setUserId(1);
userInfo.setUserName(userName);
userInfo.setUserPwd(USER_PWD);
}
return userInfo;
}
}
首先这个类是要交给SpringIOC管理的,因此在类的上面使用了@Repository注解。两个私有的final类型的变量,是常量,属于对象的,每次创建对象的时候被初始化,被初始化以后就不可更改。不可变的被final修饰的变量名都使用大写字母和下划线,这是规范!!。我们假定USER_NAME就是我们从数据库中查询出来的正确用户名,USER_PWD就是我们从数据库中查询出来的对应密码。
下面是一个queryUserByName方法,参数是一个字符串。这个方法的逻辑如下:接收传过来的用户名参数,根据这个用户名参数,查询数据库中是否有这个用户名,如果有,那么就根据这个用户名查询出密码,然后将配套的用户名,密码,唯一标识ID封装成一个UserInfo用户对象返回。如果数据库中没有这个用户名,那么返回的就是一个空对象。
10、Service层(业务逻辑处理)UserInfoService类代码
按照逻辑,这一层是进行业务逻辑处理的,包括判断用户输入的信息是否为空,用户输入的用户名或者是密码是否正确,每一种情况都封装返回对应的一个结果集ResultInfo对象。代码如下:
package com.dcy1.service;
import com.dcy1.dao.UserInfoDao;
import com.dcy1.model.ResultInfo;
import com.dcy1.po.UserInfo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserInfoService {
@Resource
private UserInfoDao userInfoDao;
public ResultInfo userLogin(String uname,String upwd){
ResultInfo resultInfo=new ResultInfo();
//首先我们进行参数判断,判断参数是否为空
if(isEmpty(uname)||isEmpty(upwd)){
resultInfo.setCode(500);
resultInfo.setMsg("用户名或者密码不能为空");
return resultInfo;
}
//如果前面两个都不是空,那么就会走到下面
UserInfo userInfo = userInfoDao.queryUserByName(uname);
//下面检查数据库中有没有这个用户名
if(userInfo==null){
resultInfo.setCode(500);
resultInfo.setMsg("用户名不存在");
return resultInfo;
}
//下面我们要判断密码是否正确
if(!upwd.equals(userInfo.getUserPwd())){
//如果密码不正确
resultInfo.setCode(500);
resultInfo.setMsg("用户密码不正确");
return resultInfo;
}
//走到这一步的话,就表明成功了
resultInfo.setCode(200);
resultInfo.setMsg("登录成功");
return resultInfo;
}
//这个方法判断字符串是否为空, 为空的话就返回true,否则返回false
public Boolean isEmpty(String str){
if (str==null||"".equals(str.trim())){
return true;
}
return false;
}
}
首先这个类需要交给SpringIOC管理,所以在类上面加上了@Service注解(使用这个注解是因为这个类属于service层)。然后使用@Resource注解将Dao层的类实例化注入进来(就相当于new了),因为我们要使用Dao层中查询数据库的方法。最下面是一个判断用户输入的用户名或密码是否为空的方法isEmpty,如果为空那么返回true,不为空返回false。在isEmpty方法中,我们要注意if后面的条件,使用了null和空字符串""两个比较对象。str.trim()是调用字符串的trim()方法,用来去除字符串两端的空格和制表符。
主要的方法就是userLogin()方法,返回的是一个结果集对象,也就是返回登录结果信息对象。这个方法的参数是用户名uname和密码upwd。1、首先new出来一个结果集对象resultInfo。先判断用户名或者是密码是否为空(当然调用的就是isEmpty()方法),如果为空直接就可以设置结果集对象信息并返回了。设置状态码code为500,设置提示信息msg为 用户名或密码不能为空。2、如果用户名和密码都不是空,那么就调用注入进来的Dao层的查询方法,把uname作为实参,得到返回结果UserInfo的一个对象。如果这 个UserInfo对象为null,按照Dao层查询用户的方法的逻辑,说明用户名不存在,这个时候就可以组装结果集对象,设置状态码为500,设置提示信息为 用户名不存在。3、如果返回的结果UserInfo不是null,就说明数据库中有这个用户名,那么就要比对返回结果中的密码和用户输入的密码upwd是否一样?如果不一样,那就可以组装结果集对象,设置状态码为500,设置提示信息为 用户密码不正确。4、如果返回结果中的密码和用户输入的密码upwd一样,那么同样要封装返回结果集对象,设置状态码为200,设置提示信息为 登录成功。
11、controller层(接收请求,响应结果)UserInfoController类
代码如下所示:
package com.dcy1.controller;
import com.dcy1.model.ResultInfo;
import com.dcy1.service.UserInfoService;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class UserInfoController {
@Resource
private UserInfoService userInfoService;
public ResultInfo userLogin(String uname,String upwd){
ResultInfo resultInfo=userInfoService.userLogin(uname,upwd);
return resultInfo;
}
}
首先是在类名UserInfoController上面使用了@Controller注解,表明这个类处于扫描器的扫描范围的前提下,将这个类交给SpringIOC管理。然后使用@Resource注解将service层的类自动注入进来(这是需要在XML配置文件中开启自动化配置作为前提的)。下面是userLogin()方法,参数是用户名和密码,返回的结果是一个结果集对象。在方法里面,我们直接调用注入的service层属性的方法,得到结果集对象并返回即可。
12、XML配置文件
XML配置文件的代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启自动化装备(注入),也就是注解注入 @Autowired或者@Resource-->
<!-- 好处就是我们不需要在手动写 property标签,不需要使用set方法或者是构造器方法
去手动写类里面的参数关系
-->
<context:annotation-config/>
<!-- 开启SpringIOC的自动扫描,设置扫描包的范围 一般都是根目录范围下的所有
这个时候我们就可以使用 @Controller,@Service注解了
此时还没有在Main方法的类上面使用 @SpringBootApplication注解
-->
<context:component-scan base-package="com.dcy1"></context:component-scan>
</beans>
XML配置文件中主要只有两行,分别是开启了自动化装配和配置了扫描器的扫描范围。开启自动化装配使得我们可以使用@Resource注解,不需要写property标签;配置扫描器的扫描范围使得我们可以使用@Component,@Controller,@Service,@Repository注解,不需要写bean标签。
13、在Main方法中简单进行测试
package com.dcy1;
import com.dcy1.controller.UserInfoController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Starter {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring03.xml");
UserInfoController userInfoController = (UserInfoController) applicationContext.getBean("userInfoController");
System.out.println(userInfoController.userLogin("","123456"));
}
}
我们首先加载了配置文件spring03.xml。然后通过getBean()方法得到一个UserInfoController对象(注意一点:使用扫描器的时候,默认某个类对应的bean标签的id值是类名的首字母小写后的字符串。这个是我们在getBean()方法里面需要使用的)。
下面使用了一个打印语句,打印的是controller层对象方法的返回值,返回值是一个结果集对象。我们之所以能够这样使用是因为我们在结果集类中重写了toString()方法,保证打印出来的不是对象的地址。
运行结果如下所示: