欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

模仿J2EE的session機制的App后端會話信息管理實例

 更新時間:2017年11月24日 09:02:47   作者:牛孝祖  
下面小編就為大家分享一篇模仿J2EE的session機制的App后端會話信息管理實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

此文章只將思想,不提供具體完整實現(xiàn)(博主太懶,懶得整理),有疑問或想了解的可以私信或評論

背景

在傳統(tǒng)的java web 中小型項目中,一般使用session暫存會話信息,比如登錄者的身份信息等。此機制是借用http的cookie機制實現(xiàn),但是對于app來說每次請求都保存并共享cookie信息比較麻煩,并且傳統(tǒng)的session對集群并不友好,所以一般app后端服務都使用token來區(qū)分用戶登錄信息。

j2ee的session機制大家都很了解,使用非常方便,在傳統(tǒng)java web應用中很好用,但是在互聯(lián)網(wǎng)項目中或用得到集群的一些項目就有些問題,比如序列化問題,同步的延時問題等等,所以我們需要一個使用起來類似session的卻能解決得了集群等問題的一個工具。

方案

我們使用cache機制來解決這個問題,比較流行的redis是個nosql內存數(shù)據(jù)庫,而且?guī)в衏ache的失效機制,很適合做會話數(shù)據(jù)的存儲。而token字符串需要在第一次請求時服務器返回給客戶端,客戶端以后每次請求都使用這個token標識身份。為了對業(yè)務開發(fā)透明,我們把app的請求和響應做的報文封裝,只需要對客戶端的http請求工具類做點手腳,對服務端的mvc框架做點手腳就可以了,客戶端的http工具類修改很簡單,主要是服務端的協(xié)議封裝。

實現(xiàn)思路

一、制定請求響應報文協(xié)議。

二、解析協(xié)議處理token字符串。

三、使用redis存儲管理token以及對應的會話信息。

四、提供保存、獲取會話信息的API。

我們逐步講解下每一步的實現(xiàn)方案。

一、制定請求響應報文協(xié)議。

既然要封裝報文協(xié)議,就需要考慮什么是公共字段,什么是業(yè)務字段,報文的數(shù)據(jù)結構等。

請求的公共字段一般有token、版本、平臺、機型、imei、app來源等,其中token是我們這次的主角。

響應的公共字段一般有token、結果狀態(tài)(success,fail)、結果碼(code)、結果信息等。

報文數(shù)據(jù)結構,我們選用json,原因是json普遍、可視化好、字節(jié)占用低。

請求報文如下,body中存放業(yè)務信息,比如登錄的用戶名和密碼等。

{
  "token": "客戶端token",
  /**客戶端構建版本號*/
  "version": 11,
  /**客戶端平臺類型*/
  "platform": "IOS",
  /**客戶端設備型號*/
  "machineModel": "Iphone 6s",
  "imei": "客戶端串號(手機)",
  /**真正的消息體,應為map*/
  "body": {
    "key1": "value1",
    "key2": {
      "key21": "value21"
    },
    "key3": [
      1,

    ]
  }
}

響應的報文

{
    /**是否成功*/
    "success": false,
    /**每個請求都會返回token,客戶端每次請求都應使用最新的token*/
    "token": "服務器為當前請求選擇的token",
    /**失敗碼*/
    "failCode": 1,
    /**業(yè)務消息或者失敗消息*/
    "msg": "未知原因",
    /**返回的真實業(yè)務數(shù)據(jù),可為任意可序列化的對象*/
    "body": null
  }
}

二、解析協(xié)議處理token字符串。

服務端的mvc框架我們選用的是SpringMVC框架,SpringMVC也比較普遍,不做描述。

暫且不提t(yī)oken的處理,先解決制定報文后怎么做參數(shù)傳遞。

因為請求信息被做了封裝,所以要讓springmvc框架能正確注入我們在Controller需要的參數(shù),就需要對報文做解析和轉換。

要對請求信息做解析,我們需要自定義springmvc的參數(shù)轉換器,通過實現(xiàn)HandlerMethodArgumentResolver接口可以定義一個參數(shù)轉換器

RequestBodyResolver實現(xiàn)resolveArgument方法,對參數(shù)進行注入,以下代碼為示例代碼,切勿拿來直用。

@Override
  public Object resolveArgument(MethodParameter parameter,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
      WebDataBinderFactory binderFactory) throws Exception {
    String requestBodyStr = webRequest.getParameter(requestBodyParamName);//獲取請求報文,可以使用任意方式傳遞報文,只要在這獲取到就可以
    if(StringUtils.isNotBlank(requestBodyStr)){
      String paramName = parameter.getParameterName();//獲取Controller中參數(shù)名
      Class<?> paramClass = parameter.getParameterType();//獲取Controller中參數(shù)類型
      /* 通過json工具類解析報文 */
      JsonNode jsonNode = objectMapper.readTree(requestBodyStr);
      if(paramClass.equals(ServiceRequest.class)){//ServiceRequest為請求報文對應的VO
        ServiceRequest serviceRequest = objectMapper.readValue(jsonNode.traverse(),ServiceRequest.class);
        return serviceRequest;//返回這個object就是注入到參數(shù)中了,一定要對應類型,否則異常不容易捕獲
      }
      if(jsonNode!=null){//從報文中查找Controller中需要的參數(shù)
        JsonNode paramJsonNode = jsonNode.findValue(paramName);
        if(paramJsonNode!=null){
          return objectMapper.readValue(paramJsonNode.traverse(), paramClass);
        }
        
      }
    }
    return null;
  }

將自己定義的參數(shù)轉換器配置到SrpingMVC的配置文件中<mvc:argument-resolvers>

<mvc:argument-resolvers>
  <!-- 統(tǒng)一的請求信息處理,從ServiceRequest中取數(shù)據(jù) -->
     <bean id="requestBodyResolver" class="com.niuxz.resolver.RequestBodyResolver">
       <property name="objectMapper"><bean class="com.shoujinwang.utils.json.ObjectMapper"></bean></property>
       <!-- 配置請求中ServiceRequest對應的字段名,默認為requestBody -->
       <property name="requestBodyParamName"><value>requestBody</value></property>
     </bean>
</mvc:argument-resolvers>

這樣就可以使報文中的參數(shù)能被springmvc正確識別了。

接下來我們要對token做處理了,我們需要添加一個SrpingMVC攔截器將每次請求都攔截下來,這屬于常用功能,不做細節(jié)描述

Matcher m1 =Pattern.compile("\"token\":\"(.*?)\"").matcher(requestBodyStr);
  
if(m1.find()){
  token = m1.group(1);
}
tokenMapPool.verifyToken(token);//對token做公共處理,驗證

這樣就簡單的獲取到了token了,可以做公共處理了。

三、使用redis存儲管理token以及對應的會話信息。

其實就是寫一個redis的操作工具類,因為使用了spring作為項目主框架,而且我們用到redis的功能并不多,所以直接使用spring提供的CacheManager功能

配置org.springframework.data.redis.cache.RedisCacheManager

<!-- 緩存管理器 全局變量等可以用它存取-->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
  <constructor-arg>
    <ref bean="redisTemplate"/>
  </constructor-arg>
  <property name="usePrefix" value="true" />
  <property name="cachePrefix">
    <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
      <constructor-arg name="delimiter" value=":@WebServiceInterface"/>
    </bean>
  </property>
  <property name="expires"><!-- 緩存有效期 -->
    <map>
      <entry>
        <key><value>tokenPoolCache</value></key><!-- tokenPool緩存名 -->
        <value>2592000</value><!-- 有效時間 -->
      </entry>
    </map>
  </property>
</bean>

四、提供保存、獲取會話信息的API。

通過以上前戲我們已經(jīng)把token處理的差不多了,接下來我們要實現(xiàn)token管理工作了

我們需要讓業(yè)務開發(fā)方便的保存獲取會話信息,還要使token是透明的。

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;

/**
 * 
 * 類      名:  TokenMapPoolBean
 * 描      述:  token以及相關信息調用處理類
 * 修 改 記 錄:  
 * @version  V1.0
 * @date  2016年4月22日
 * @author  NiuXZ
 *
 */
public class TokenMapPoolBean {
  
  
  private static final Log log = LogFactory.getLog(TokenMapPoolBean.class);
  
  /** 當前請求對應的token*/
  private ThreadLocal<String> currentToken;
  
  private CacheManager cacheManager;
  
  private String cacheName;
  
  private TokenGenerator tokenGenerator;
  
  public TokenMapPoolBean(CacheManager cacheManager, String cacheName, TokenGenerator tokenGenerator) {
    this.cacheManager = cacheManager;
    this.cacheName = cacheName;
    this.tokenGenerator = tokenGenerator;
    currentToken = new ThreadLocal<String>();
  }
  
  /**
   * 如果token合法就返回token,不合法就創(chuàng)建一個新的token并返回,
   * 將token放入ThreadLocal中 并初始化一個tokenMap
   * @param token
   * @return token
   */
  public String verifyToken(String token) {
    //    log.info("校驗Token:\""+token+"\"");
    String verifyedToken = null;
    if (tokenGenerator.checkTokenFormat(token)) {
      //      log.info("校驗Token成功:\""+token+"\"");
      verifyedToken = token;
    }
    else {
      verifyedToken = newToken();
    }
    currentToken.set(verifyedToken);
    Cache cache = cacheManager.getCache(cacheName);
    if (cache == null) {
      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);
    }
    ValueWrapper value = cache.get(verifyedToken);
    //token對應的值為空,就創(chuàng)建一個新的tokenMap放入緩存中
    if (value == null || value.get() == null) {
      verifyedToken = newToken();
      currentToken.set(verifyedToken);
      Map<String, Object> tokenMap = new HashMap<String, Object>();
      cache.put(verifyedToken, tokenMap);
    }
    return verifyedToken;
  }
  
  /**
   * 生成新的token
   * @return token
   */
  private String newToken() {
    Cache cache = cacheManager.getCache(cacheName);
    if (cache == null) {
      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);
    }
    String newToken = null;
    int count = 0;
    do {
      count++;
      newToken = tokenGenerator.generatorToken();
    }
    while (cache.get(newToken) != null);
    //    log.info("創(chuàng)建Token成功:\""+newToken+"\" 嘗試生成:"+count+"次");
    return newToken;
  }
  
  /**
   * 獲取當前請求的tokenMap中對應key的對象
   * @param key
   * @return 當前請求的tokenMap中對應key的屬性,模擬session
   */
  public Object getAttribute(String key) {
    Cache cache = cacheManager.getCache(cacheName);
    if (cache == null) {
      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);
    }
    ValueWrapper tokenMapWrapper = cache.get(currentToken.get());
    Map<String, Object> tokenMap = null;
    if (tokenMapWrapper != null) {
      tokenMap = (Map<String, Object>) tokenMapWrapper.get();
    }
    if (tokenMap == null) {
      verifyToken(currentToken.get());
      tokenMapWrapper = cache.get(currentToken.get());
      tokenMap = (Map<String, Object>) tokenMapWrapper.get();
    }
    return tokenMap.get(key);
  }
  
  /**
   * 設置到當前請求的tokenMap中,模擬session<br>
   * TODO:此種方式設置attribute有問題:<br>
   * 1、可能在同一token并發(fā)的情況下執(zhí)行cache.put(currentToken.get(),tokenMap);時,<br>
   *   tokenMap可能不是最新,會導致丟失數(shù)據(jù)。<br>
   * 2、每次都put整個tokenMap,數(shù)據(jù)量太大,需要優(yōu)化<br>
   * @param key value
   */
  public void setAttribute(String key, Object value) {
    Cache cache = cacheManager.getCache(cacheName);
    if (cache == null) {
      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);
    }
    ValueWrapper tokenMapWrapper = cache.get(currentToken.get());
    Map<String, Object> tokenMap = null;
    if (tokenMapWrapper != null) {
      tokenMap = (Map<String, Object>) tokenMapWrapper.get();
    }
    if (tokenMap == null) {
      verifyToken(currentToken.get());
      tokenMapWrapper = cache.get(currentToken.get());
      tokenMap = (Map<String, Object>) tokenMapWrapper.get();
    }
    log.info("TokenMap.put(key=" + key + ",value=" + value + ")");
    tokenMap.put(key, value);
    cache.put(currentToken.get(), tokenMap);
  }
  
  /** 
   * 獲取當前線程綁定的用戶token
   * @return token
   */
  public String getToken() {
    if (currentToken.get() == null) {
      //初始化一次token
      verifyToken(null);
    }
    return currentToken.get();
  }
  
  /**
   * 刪除token以及tokenMap
   * @param token
   */
  public void removeTokenMap(String token) {
    if (token == null) {
      return;
    }
    Cache cache = cacheManager.getCache(cacheName);
    if (cache == null) {
      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);
    }
    log.info("刪除Token:token=" + token);
    cache.evict(token);
  }
  
  public CacheManager getCacheManager() {
    return cacheManager;
  }
  
  public void setCacheManager(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
  }
  
  public String getCacheName() {
    return cacheName;
  }
  
  public void setCacheName(String cacheName) {
    this.cacheName = cacheName;
  }
  
  public TokenGenerator getTokenGenerator() {
    return tokenGenerator;
  }
  
  public void setTokenGenerator(TokenGenerator tokenGenerator) {
    this.tokenGenerator = tokenGenerator;
  }
  
  public void clear() {
    currentToken.remove();
  }
  
}

這里用到了ThreadLocal變量是因為servlet容器一個請求對應一個線程,在一個請求的生命周期內都是處于同一個線程中,而同時又有多個線程共享token管理器,所以需要這個線程本地變量來保存token字符串。

注意事項:

1、verifyToken方法的調用,一定要在每次請求最開始調用。并且在請求結束后調用clear做清除,以免下次有未知異常導致verifyToken未被執(zhí)行,卻在返回時從ThreadLocal里取出token返回。(這個bug困擾我好幾天,公司n個開發(fā)檢查代碼也沒找到,最后我經(jīng)過測試發(fā)現(xiàn)是在發(fā)生404的時候沒有進入攔截器,所以就沒有調用verifyToken方法,導致返回的異常信息中的token為上一次請求的token,導致詭異的串號問題。嗯,記我一大鍋)。

2、客戶端一定要在封裝http工具的時候把每次token保存下來,并用于下一次請求。公司ios開發(fā)請的外包,但是外包沒按要求做,在未登錄時,不保存token,每次傳遞的都是null,導致每次請求都會創(chuàng)建一個token,服務器創(chuàng)建了大量的無用token。

使用

使用方式也很簡單,以下是封裝的登錄管理器,可以參考一下token管理器對于登陸管理器的應用

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;

import com.niuxz.base.Constants;

/**
 * 
 * 類      名:  LoginManager
 * 描      述:  登錄管理器
 * 修 改 記 錄:  
 * @version  V1.0
 * @date  2016年7月19日
 * @author  NiuXZ
 *
 */
public class LoginManager {
  
  
  private static final Log log = LogFactory.getLog(LoginManager.class);
  
  private CacheManager cacheManager;
  
  private String cacheName;
  
  private TokenMapPoolBean tokenMapPool;
  
  public LoginManager(CacheManager cacheManager, String cacheName, TokenMapPoolBean tokenMapPool) {
    this.cacheManager = cacheManager;
    this.cacheName = cacheName;
    this.tokenMapPool = tokenMapPool;
  }
  public void login(String userId) {
    log.info("用戶登錄:userId=" + userId);
    Cache cache = cacheManager.getCache(cacheName);
    ValueWrapper valueWrapper = cache.get(userId);
    String token = (String) (valueWrapper == null ? null : valueWrapper.get());
    tokenMapPool.removeTokenMap(token);//退出之前登錄記錄
    tokenMapPool.setAttribute(Constants.LOGGED_USER_ID, userId);
    cache.put(userId, tokenMapPool.getToken());
  }
  
  public void logoutCurrent(String phoneTel) {
    String curUserId = getCurrentUserId();
    log.info("用戶退出:userId=" + curUserId);
    tokenMapPool.removeTokenMap(tokenMapPool.getToken());//退出登錄
    if (curUserId != null) {
      Cache cache = cacheManager.getCache(cacheName);
      cache.evict(curUserId);
      cache.evict(phoneTel);
    }
  }
  
  /**
   * 獲取當前用戶的id
   * @return
   */
  public String getCurrentUserId() {
    return (String) tokenMapPool.getAttribute(Constants.LOGGED_USER_ID);
  }
  
  public CacheManager getCacheManager() {
    return cacheManager;
  }
  
  public String getCacheName() {
    return cacheName;
  }
  
  public TokenMapPoolBean getTokenMapPool() {
    return tokenMapPool;
  }
  
  public void setCacheManager(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
  }
  
  public void setCacheName(String cacheName) {
    this.cacheName = cacheName;
  }
  
  public void setTokenMapPool(TokenMapPoolBean tokenMapPool) {
    this.tokenMapPool = tokenMapPool;
  }
  
}

下面是一段常見的發(fā)送短信驗證碼接口,有的應用也是用session存儲驗證碼,我不建議用這種方式,存session弊端相當大。大家看看就好,不是我寫的

public void sendValiCodeByPhoneNum(String phoneNum, String hintMsg, String logSuffix) {
    validatePhoneTimeSpace();
    // 獲取6位隨機數(shù)
    String code = CodeUtil.getValidateCode();
    log.info(code + "------->" + phoneNum);
    // 調用短信驗證碼下發(fā)接口
    RetStatus retStatus = msgSendUtils.sendSms(code + hintMsg, phoneNum);
    if (!retStatus.getIsOk()) {
      log.info(retStatus.toString());
      throw new ThrowsToDataException(ServiceResponseCode.FAIL_INVALID_PARAMS, "手機驗證碼獲取失敗,請稍后再試");
    }
    // 重置session
    tokenMapPool.setAttribute(Constants.VALIDATE_PHONE, phoneNum);
    tokenMapPool.setAttribute(Constants.VALIDATE_PHONE_CODE, code.toString());
    tokenMapPool.setAttribute(Constants.SEND_CODE_WRONGNU, 0);
    tokenMapPool.setAttribute(Constants.SEND_CODE_TIME, new Date().getTime());
    log.info(logSuffix + phoneNum + "短信驗證碼:" + code);
  }

處理響應

有的同學會問了 那么響應的報文封裝呢?

@RequestMapping("record")
@ResponseBody
public ServiceResponse record(String message){
  String userId = loginManager.getCurrentUserId(); 
  messageBoardService.recordMessage(userId, message);
  return ServiceResponseBuilder.buildSuccess(null);
}

其中ServiceResponse是封裝的響應報文VO,我們直接使用springmvc的@ResponseBody注解就好了。關鍵在于這個builder。

import org.apache.commons.lang3.StringUtils;

import com.niuxz.base.pojo.ServiceResponse;
import com.niuxz.utils.spring.SpringContextUtil;
import com.niuxz.web.server.token.TokenMapPoolBean;

/**
 * 
 * 類 名: ServiceResponseBuilder
 * 
 * @version V1.0
 * @date 2016年4月25日
 * @author NiuXZ
 *
 */
public class ServiceResponseBuilder {

  /**
   * 構建一個成功的響應信息
   * 
   * @param body
   * @return 一個操作成功的 ServiceResponse
   */
  public static ServiceResponse buildSuccess(Object body) {
    return new ServiceResponse(
        ((TokenMapPoolBean) SpringContextUtil.getBean("tokenMapPool"))
            .getToken(),
        "操作成功", body);
  }

  /**
   * 構建一個成功的響應信息
   * 
   * @param body
   * @return 一個操作成功的 ServiceResponse
   */
  public static ServiceResponse buildSuccess(String token, Object body) {
    return new ServiceResponse(token, "操作成功", body);
  }

  /**
   * 構建一個失敗的響應信息
   * 
   * @param failCode
   *      msg
   * @return 一個操作失敗的 ServiceResponse
   */
  public static ServiceResponse buildFail(int failCode, String msg) {
    return buildFail(failCode, msg, null);
  }

  /**
   * 構建一個失敗的響應信息
   * 
   * @param failCode
   *      msg body
   * @return 一個操作失敗的 ServiceResponse
   */
  public static ServiceResponse buildFail(int failCode, String msg,
      Object body) {
    return new ServiceResponse(
        ((TokenMapPoolBean) SpringContextUtil.getBean("tokenMapPool"))
            .getToken(),
        failCode, StringUtils.isNotBlank(msg) ? msg : "操作失敗", body);
  }
}

由于使用的是靜態(tài)工具類的形式,不能通過spring注入tokenMapPool(token管理器)對象,則通過spring提供的api獲取。然后構建響應信息的時候直接調用tokenMapPool的getToken()方法,此方法會返回當前線程綁定的token字符串。再次強調在請求結束后一定要手動調用clear(我通過全局攔截器調用)。

以上這篇模仿J2EE的session機制的App后端會話信息管理實例就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • 面試題:用 Java 逆序打印鏈表

    面試題:用 Java 逆序打印鏈表

    這篇文章主要介紹了面試題:用 Java 逆序打印鏈表,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • 理解java中的深復制和淺復制

    理解java中的深復制和淺復制

    這篇文章主要幫助大家理解java中的深復制和淺復制,對java中的深復制和淺復制進行剖析,感興趣的小伙伴們可以參考一下
    2016-02-02
  • Swagger及knife4j的基本使用詳解

    Swagger及knife4j的基本使用詳解

    Swagger是一個規(guī)范和完整的框架,用于生成、描述、調用和可視化RESTful風格的?Web?服務,這篇文章主要介紹了Swagger以及knife4j的基本使用,需要的朋友可以參考下
    2022-08-08
  • 詳解Spring Cloud中Hystrix的請求合并

    詳解Spring Cloud中Hystrix的請求合并

    這篇文章主要介紹了詳解Spring Cloud中Hystrix的請求合并,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • Java函數(shù)式編程(十一):遍歷目錄

    Java函數(shù)式編程(十一):遍歷目錄

    這篇文章主要介紹了Java函數(shù)式編程(十一):遍歷目錄,本文是系列文章的第11篇,其它文章請參閱本文底部的相關文章,需要的朋友可以參考下
    2014-09-09
  • mybatis-plus雪花算法自動生成機器id原理及源碼

    mybatis-plus雪花算法自動生成機器id原理及源碼

    Mybatis-Plus是一個Mybatis的增強工具,它在Mybatis的基礎上做了增強,卻不做改變,Mybatis-Plus是為簡化開發(fā)、提高開發(fā)效率而生,但它也提供了一些很有意思的插件,比如SQL性能監(jiān)控、樂觀鎖、執(zhí)行分析等,下面一起看看mybatis-plus雪花算法自動生成機器id原理解析
    2021-06-06
  • Java多線程之線程安全問題詳解

    Java多線程之線程安全問題詳解

    這篇文章主要為大家詳細介紹了Java多線程之線程安全問題,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • SpringMVC中的DispatcherServlet詳細解析

    SpringMVC中的DispatcherServlet詳細解析

    這篇文章主要介紹了SpringMVC中的DispatcherServlet詳細解析,DispatcherServlet也是一個Servlet,它也能通過Servlet的API來響應請求,從而成為一個前端控制器,Web容器會調用Servlet的doGet()以及doPost()等方法,需要的朋友可以參考下
    2023-12-12
  • SpringBoot整合Kotlin構建Web服務的方法示例

    SpringBoot整合Kotlin構建Web服務的方法示例

    這篇文章主要介紹了SpringBoot整合Kotlin構建Web服務的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-02-02
  • Java畢業(yè)設計實戰(zhàn)之藥店信息管理系統(tǒng)的實現(xiàn)

    Java畢業(yè)設計實戰(zhàn)之藥店信息管理系統(tǒng)的實現(xiàn)

    這是一個使用了java+SSM+JSP+layui+maven+mysql開發(fā)的藥店信息管理系統(tǒng),是一個畢業(yè)設計的實戰(zhàn)練習,具有藥店信息管理該有的所有功能,感興趣的朋友快來看看吧
    2022-01-01

最新評論