Live-Server-5-SpringBoot中一个请求的流程

Live项目:
1. Idea IDE搭建SpringBoot
2. Mybatis-generator逆向生成Pojo、Mapper接口和XML等
3. WebMvcConfigurerAdapter资源拦截
4. Shiro的简单使用

在上述文章中,已经完成了Springboot环境的搭建、数据库的配置、Pojo、MyBatis映射文件、Dao层、MVC的配置、资源的过滤与拦截、用户的认证与权限等。Spring一般搭配SpringMvc,也就是MVC模式一起使用,那么在MVC中,我们已经完成了model的编写,接下来要写Controller层、Service层如何响应请求、如何请求数据。

Spring注解

在SpringBoot中,有如下常用的注解:

  1. @Controller:用于定义控制器类,在Spring羡慕中由控制器负责将用户发来的URL请求转发到对应的服务接口(Service层)
  2. @RequestMapping:提供路由信息,复杂URL到Controller中具体函数的映射。通常包含请求的相对地址、GET\Post等请求方式
  3. @ResponseBody:该注解表示该方法返回的结果直接写入HTTP Response Body中,,如果使用该注解,返回值就是Json数据,不使用该注解函数的返回值就解析为跳转路径。通常的用法就是通过Map<String,Object>来设置Json数据的键值对。
  4. @RestController:用户表明控制层组件,是@ResponseBody和@Controller的集合。该注解定义的类中所有的类方法返回值类型都是Json数据。
  5. @GetMapping@PostMapping:是一个组合注解,是@RequestMapping(method = RequestMethod.GET\POST)的缩写
  6. @SpringBootApplication:让SpringBoot自动给程序进行配置的注解。
  7. @Configuration:相当于传统的Xml配置文件,只不过该注解作用与类中,通过Java代码来设置配置。
  8. @Import:用来导入其他配置类
  9. @ImportResource:用来加载xml配置文件
  10. @Autowired:自动导入依赖的bean,自动注入,直接使用配置好的bean,常常用于对类成员变量、方法以及构造方法进行标注,完成自动装配工作。
  11. @Service:用于修饰Service层的组件

Spring层的概念

也许,看到这里,你会有些奇怪,什么是Dao层,什么是Service层,Controller层又是什么呢?为什么要用Controller来处理响应呢?@Controller注解与Controller又有什么关系呢?这里就要提到SpringMVC层的概念:

  1. Controller层:负责具体业务模块流程的控制,调用Service层的接口来控制业务流程,并实现url映射 。在Spring中,Controller层通过@Controller、@RestController来标注
  2. Service层:建立在Dao层之上,Controller之下,负责业务模块的逻辑设计,先设计接口,再完成其实现类。Service层通过@Service来标注
  3. Dao层:负责数据的持久化,与数据库打交道的都封装在其中,Dao的数据源在Spring配置文件中进行配置。主要还是实现数据库CRUD方法并支持Service的拓展。Dao层类通过@Dao来标注。
  4. Pojo:数据实体类
    SpringMVC层的概念.png

层的引用、请求的流程

MyBatis-generator工具已经帮我们实现了Dao层的大部分工作,但是我们还需要在Spring配置文件中配置数据源。

Dao层概览如下:

首先是Dao层的映射接口:

Dao-1.png
这里以UserMapper映射为例,如无特殊的需求(将两个实体的数据合并为一个传递给Service),一般而言,Dao层的映射文件都不需要进行修改。接口名字也清晰明了,基本上都是通过主键或者Example来对数据进行条件的筛选,然后筛选后的数据。 这里特别强调 updateByExample、updateByExampleSelective这两个接口:

  • updateByExample:在更新时,需要将实体对象中所有的属性都提供,不提供的属性将会赋值为NULL
  • updateByExampleSelective:这个接口就明智多了,只对提供的属性进行修改,其他熟悉不变。
public interface UserMapper {
    int countByExample(UserExample example);  
    int deleteByExample(UserExample example);
    int deleteByPrimaryKey(Integer id);
    int insert(User record);
    int insertSelective(User record);
    List<User> selectByExample(UserExample example);
    User selectByPrimaryKey(Integer id);
    int updateByExampleSelective(@Param("record") User record, @Param("example") UserExample example);
    int updateByExample(@Param("record") User record, @Param("example") UserExample example);
    int updateByPrimaryKeySelective(User record);
    int updateByPrimaryKey(User record);
}
复制代码

Dao层具体的映射文件如下:

Dao-2.png

<?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="com.ljh.dao.UserMapper" >
  <resultMap id="BaseResultMap" type="com.ljh.po.User" >
    <id column="id" property="id" jdbcType="INTEGER" />
    <result column="name" property="name" jdbcType="VARCHAR" />
    <result column="account" property="account" jdbcType="VARCHAR" />
    <result column="password" property="password" jdbcType="VARCHAR" />
    <result column="role_id" property="roleId" jdbcType="INTEGER" />
  </resultMap>
  <sql id="Example_Where_Clause" >
    <where >
      <foreach collection="oredCriteria" item="criteria" separator="or" >
        <if test="criteria.valid" >
          <trim prefix="(" suffix=")" prefixOverrides="and" >
            <foreach collection="criteria.criteria" item="criterion" >
              <choose >
                <when test="criterion.noValue" >
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue" >
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue" >
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue" >
                  and ${criterion.condition}
                  <foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Update_By_Example_Where_Clause" >
    <where >
      <foreach collection="example.oredCriteria" item="criteria" separator="or" >
        <if test="criteria.valid" >
          <trim prefix="(" suffix=")" prefixOverrides="and" >
            <foreach collection="criteria.criteria" item="criterion" >
              <choose >
                <when test="criterion.noValue" >
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue" >
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue" >
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue" >
                  and ${criterion.condition}
                  <foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Base_Column_List" >
    id, name, account, password, role_id
  </sql>
  <select id="selectByExample" resultMap="BaseResultMap" parameterType="com.ljh.po.UserExample" >
    select
    <if test="distinct" >
      distinct
    </if>
    <include refid="Base_Column_List" />
    from user
    <if test="_parameter != null" >
      <include refid="Example_Where_Clause" />
    </if>
    <if test="orderByClause != null" >
      order by ${orderByClause}
    </if>
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
    delete from user
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="com.ljh.po.User" >
    insert into user (id, name, account, 
      password, role_id)
    values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{account,jdbcType=VARCHAR}, 
      #{password,jdbcType=VARCHAR}, #{roleId,jdbcType=INTEGER})
  </insert>
  <update id="updateByExample" parameterType="map" >
    update user
    set id = #{record.id,jdbcType=INTEGER},
      name = #{record.name,jdbcType=VARCHAR},
      account = #{record.account,jdbcType=VARCHAR},
      password = #{record.password,jdbcType=VARCHAR},
      role_id = #{record.roleId,jdbcType=INTEGER}
    <if test="_parameter != null" >
      <include refid="Update_By_Example_Where_Clause" />
    </if>
  </update>
</mapper>
复制代码

代码有所删减,在该映射文件中首先定义了xml文件的类型,随后定义了User这个类及类属性在数据库中的定义,再定义example,最后就是各个接口文件对于数据库的具体实现。

上述的类、映射文件都是通过数据库逆向生成的,那么如果我需要在SpringBoot中查询某一数据,我该到哪里去查呢?这时候就要定义数据源。在项目的application.properties文件中添加如下配置:

#数据源配置,默认使用tomcate-jdbc连接池
spring.datasource.url=jdbc:mysql://数据库地址:3306/数据库名字?useUnicode=true&characterEncoding=UTF8
spring.datasource.username=用户名
spring.datasource.password=用户密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
复制代码

Service层

既然已经打通了Dao层与数据库之间的障碍,那么我们来看一下Service层是如何实现他的价值。我觉得写代码时,只要敢想,就没有做不到的事情,一个接口可以完成多种应用场景。灵活,就是我对Service接口的评价。

public interface UserService {
  ...
    /**
     * 注册成功则无异常抛出
     * 用户名/手机被使用,则抛出异常
     * @param user
     * @throws ServiceException
     */
    void register(User user) throws ServiceException;

    /**
     * 查找相似的user
     * @param likeUser
     * @return
     */
    List<User> getUsers(User likeUser);
}
复制代码

就这么一个通过传入的user对象来获取类似User的接口,实现方式和应用的场景却是变化万千。 先来看看简单的方式:

@Service
public class UserServiceImpl implements UserService {
    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserMapper userMapper;  //Dao层接口

    @Override
    public List<User> getUsers(User likeUser) {
        //构造example
        UserExample example = new UserExample();
        UserExample.Criteria criteria = example.createCriteria();
        if (null != likeUser) {
            if (null != likeUser.getId()) {
                criteria.andIdEqualTo(likeUser.getId());
            }
            if (null != likeUser.getName()) {
                criteria.andNameLike("%" + likeUser.getName() + "%");
            }
            if (null != likeUser.getAccount()) {
                criteria.andNameLike("%" + likeUser.getAccount() + "%");
            }
        }
        return userMapper.selectByExample(example);
    }
}
复制代码

在上面的这个实现方式中,通过对传入的user对象属性进行判断,如果存在,则将该条件添加到example中,随后通过Dao层获取到类似的User数据。这个接口可以用于模糊搜索用户、查看某用户信息、如果用户的属性再复杂一些,就可以实现用户排序等功能。

Controller层

先来看下Controller层处理Url请求的方式

  1. 通过@RequestMapping(path = {"xxx"}, method = {RequestMethod.GET/POST}) 注解,标注目前方法对应的url相对请求是xxx,并可以定义为GET、POST、DELETE等方式。
  2. 处理方法参数: ①@PathVariable: 获取路径参数,即url/{id}的形式 ②@RequestParam:获取查询参数,即url?name=的形式 ③@RequestBody:如果传入的是一个Body参数(通常是Json数据),那么就需要该注解对该对象进行标注,然后会对该对象进行解析与赋值。 ④Form表单的请求:如果方法参数没有标注别名,那么form表单中的名字就必须与方法参数的名字相同,否则获取不到对应的数值。

看看代码实例:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(path = {"register"}, method = {RequestMethod.POST})
    @ResponseBody
    public Map<String, Object> register(String account, String password, String name, String code) {
        Map<String, Object> map = new HashMap<>();

        //判断传入的数据是否为空
       ...
        //判断传入的数据格式是否正确
       ...
        //会话状态的验证
        Session session = SecurityUtils.getSubject().getSession();
       
        try {
            User user = new User();
            user.setAccount(account);
            if (name == null) {
                user.setName(user.getAccount());
            }
            user.setName(name);
            user.setPassword(password);
            user.setRoleId(2);//设置用户角色
            //调用UserService对用户进行注册操作
            userService.register(user);
            map.put("msg", "注册成功");
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }
}
复制代码

上述的方法是一个Post的form表单请求,看起来参数很多,有点复杂,那么直接传入一个对象要怎么处理呢?

    @RequestMapping(path = {"register"}, method = {RequestMethod.POST})
    @ResponseBody
    public Map<String, Object> register(@RequestBody User user) {
        Map<String, Object> map = new HashMap<>();

        //判断传入的数据是否为空
       ...
        //判断传入的数据格式是否正确
       ...
        //会话状态的验证
        ...
    }
复制代码

使用注解@ResponseBody返回Json格式的数据,如果不使用该注解,则跳转到对应的页面。

整个请求的时序图如下:

时序图.png

猜你喜欢

转载自juejin.im/post/5d5d3ae26fb9a06b2c329432