1. 程式人生 > 程式設計 >Live-Server-5-SpringBoot中一個請求的流程

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,role_id)
    values (#{id,jdbcType=INTEGER},#{name,jdbcType=VARCHAR},#{account,#{password,#{roleId,jdbcType=INTEGER})
  </insert>
  <update id="updateByExample" parameterType="map" >
    update user
    set id = #{record.id,name = #{record.name,account = #{record.account,password = #{record.password,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"},Object> register(@RequestBody User user) {
        Map<String,Object> map = new HashMap<>();

        //判斷傳入的資料是否為空
       ...
        //判斷傳入的資料格式是否正確
       ...
        //會話狀態的驗證
        ...
    }
複製程式碼

使用註解@ResponseBody返回Json格式的資料,如果不使用該註解,則跳轉到對應的頁面。

整個請求的時序圖如下:

時序圖.png