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

Spring Boot使用AOP實現(xiàn)REST接口簡易靈活的安全認證的方法

 更新時間:2018年11月06日 14:56:57   作者:JavaIT程序員  
這篇文章主要介紹了Spring Boot使用AOP實現(xiàn)REST接口簡易靈活的安全認證的方法,非常具有實用價值,需要的朋友可以參考下

本文將通過AOP的方式實現(xiàn)一個相對更加簡易靈活的API安全認證服務。

我們先看實現(xiàn),然后介紹和分析AOP基本原理和常用術(shù)語。

一、Authorized實現(xiàn)

1、定義注解

package com.power.demo.common;

import java.lang.annotation.*;

/*
 * 安全認證
 * */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Authorized {

  String value() default "";

}

這個注解看上去什么都沒有,僅僅是一個占位符,用于標志是否需要安全認證。

2、表現(xiàn)層使用注解

@Authorized
  @RequestMapping(value = "/getinfobyid", method = RequestMethod.POST)
  @ApiOperation("根據(jù)商品Id查詢商品信息")
  @ApiImplicitParams({
      @ApiImplicitParam(paramType = "header", name = "authtoken", required = true, value = "authtoken", dataType =
          "String"),
  })
  public GetGoodsByGoodsIdResponse getGoodsByGoodsId(@RequestHeader String authtoken, @RequestBody GetGoodsByGoodsIdRequest request) {

    return _goodsApiService.getGoodsByGoodsId(request);

  }

看上去就是在一個方法上加了Authorized注解,其實它也可以作用于類上,也可以類和方法混合使用。

3、請求認證切面

下面的代碼是實現(xiàn)靈活的安全認證的關(guān)鍵:

package com.power.demo.controller.tool;

import com.power.demo.common.AppConst;
import com.power.demo.common.Authorized;
import com.power.demo.common.BizResult;
import com.power.demo.service.contract.AuthTokenService;
import com.power.demo.util.PowerLogger;
import com.power.demo.util.SerializeUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;

/**
 * 請求認證切面,驗證自定義請求header的authtoken是否合法
 **/
@Aspect
@Component
public class AuthorizedAspect {

  @Autowired
  private AuthTokenService authTokenService;

  @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  public void requestMapping() {
  }

  @Pointcut("execution(* com.power.demo.controller.*Controller.*(..))")
  public void methodPointCut() {
  }

  /**
   * 某個方法執(zhí)行前進行請求合法性認證 注入Authorized注解 (先)
   */
  @Before("requestMapping() && methodPointCut()&&@annotation(authorized)")
  public void doBefore(JoinPoint joinPoint, Authorized authorized) throws Exception {

    PowerLogger.info("方法認證開始...");

    Class type = joinPoint.getSignature().getDeclaringType();

    Annotation[] annotations = type.getAnnotationsByType(Authorized.class);

    if (annotations != null && annotations.length > 0) {
      PowerLogger.info("直接類認證");
      return;
    }

    //獲取當前http請求
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    String token = request.getHeader(AppConst.AUTH_TOKEN);

    BizResult<String> bizResult = authTokenService.powerCheck(token);

    System.out.println(SerializeUtil.Serialize(bizResult));

    if (bizResult.getIsOK() == true) {
      PowerLogger.info("方法認證通過");
    } else {
      throw new Exception(bizResult.getMessage());
    }
  }

  /**
   * 類下面的所有方法執(zhí)行前進行請求合法性認證 (后)
   */
  @Before("requestMapping() && methodPointCut()")
  public void doBefore(JoinPoint joinPoint) throws Exception {

    PowerLogger.info("類認證開始...");

    Annotation[] annotations = joinPoint.getSignature().getDeclaringType().getAnnotationsByType(Authorized.class);

    if (annotations == null || annotations.length == 0) {
      PowerLogger.info("類不需要認證");
      return;
    }

    //獲取當前http請求
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    String token = request.getHeader(AppConst.AUTH_TOKEN);

    BizResult<String> bizResult = authTokenService.powerCheck(token);

    System.out.println(SerializeUtil.Serialize(bizResult));

    if (bizResult.getIsOK() == true) {
      PowerLogger.info("類認證通過");
    } else {
      throw new Exception(bizResult.getMessage());
    }
  }

}

需要注意的是,對類和方法上的Authorized處理,定義了重載的處理方法doBefore。AuthTokenService和上文介紹的處理邏輯一樣,如果安全認證不通過,則拋出異常。

如果我們在類上或者方法上都加了Authorized注解,不會進行重復安全認證,請放心使用。

4、統(tǒng)一異常處理

上文已經(jīng)提到過,對所有發(fā)生異常的API,都返回統(tǒng)一格式的報文至調(diào)用方。主要代碼大致如下:

package com.power.demo.controller.exhandling;

import com.power.demo.common.ErrorInfo;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * 全局統(tǒng)一異常處理增強
 **/
@ControllerAdvice
public class GlobalExceptionHandler {

  /**
   * API統(tǒng)一異常處理
   **/
  @ExceptionHandler(value = Exception.class)
  @ResponseBody
  public ErrorInfo<Exception> jsonApiErrorHandler(HttpServletRequest request, Exception e) {
    ErrorInfo<Exception> errorInfo = new ErrorInfo<>();
    try {
      System.out.println("統(tǒng)一異常處理...");
      e.printStackTrace();

      Throwable innerEx = e.getCause();
      while (innerEx != null) {
        //innerEx.printStackTrace();
        if (innerEx.getCause() == null) {
          break;
        }
        innerEx = innerEx.getCause();
      }

      if (innerEx == null) {
        errorInfo.setMessage(e.getMessage());
        errorInfo.setError(e.toString());
      } else {
        errorInfo.setMessage(innerEx.getMessage());
        errorInfo.setError(innerEx.toString());
      }

      errorInfo.setData(e);
      errorInfo.setTimestamp(new Date());
      errorInfo.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());//500錯誤
      errorInfo.setUrl(request.getRequestURL().toString());
      errorInfo.setPath(request.getServletPath());

    } catch (Exception ex) {
      ex.printStackTrace();

      errorInfo.setMessage(ex.getMessage());
      errorInfo.setError(ex.toString());
    }

    return errorInfo;
  }

}

認證不通過的API調(diào)用結(jié)果如下:


異常的整個堆??梢苑浅7浅7奖愕貛椭覀兣挪榈絾栴}。

我們再結(jié)合上文來看安全認證的時間先后,根據(jù)理論分析和實踐發(fā)現(xiàn),過濾器Filter先于攔截器Interceptor先于自定義Authorized方法認證先于Authorized類認證。

到這里,我們發(fā)現(xiàn)通過AOP框架AspectJ,一個@Aspect注解外加幾個方法幾十行業(yè)務代碼,就可以輕松實現(xiàn)對REST API的攔截處理。

那么為什么會有@Pointcut,既然有@Before,是否有@After?

其實上述簡易安全認證功能實現(xiàn)的過程主要利用了Spring的AOP特性。

下面再簡單介紹下AOP常見概念(主要參考Spring實戰(zhàn)),加深理解。AOP概念較多而且比較乏味,經(jīng)驗豐富的老鳥到此就可以忽略這一段了。

二、AOP

1、概述

AOP(Aspect Oriented Programming),即面向切面編程,可以處理很多事情,常見的功能比如日志記錄,性能統(tǒng)計,安全控制,事務處理,異常處理等。


AOP可以認為是一種更高級的“復用”技術(shù),它是OOP(Object Oriented Programming,面向?qū)ο缶幊蹋┑难a充和完善。AOP的理念,就是將分散在各個業(yè)務邏輯代碼中相同的代碼通過橫向切割的方式抽取到一個獨立的模塊中。將相同邏輯的重復代碼橫向抽取出來,使用動態(tài)代理技術(shù)將這些重復代碼織入到目標對象方法中,實現(xiàn)和原來一樣的功能。這樣一來,我們在寫業(yè)務邏輯時就只關(guān)心業(yè)務代碼。

OOP引入封裝、繼承、多態(tài)等概念來建立一種對象層次結(jié)構(gòu),用于模擬公共行為的一個集合。不過OOP允許開發(fā)者定義縱向的關(guān)系,但并不適合定義橫向的關(guān)系,例如日志功能。日志代碼往往橫向地散布在所有對象層次中,而與它對應的對象的核心功能毫無關(guān)系對于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也都是如此,這種散布在各處的無關(guān)的代碼被稱為橫切(cross cutting),在OOP設(shè)計中,它導致了大量代碼的重復,而不利于各個模塊的重用。

AOP技術(shù)恰恰相反,它利用一種稱為"橫切"的技術(shù),剖解開封裝的對象內(nèi)部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其命名為"Aspect",即切面。

所謂"切面",簡單說就是那些與業(yè)務無關(guān),卻為業(yè)務模塊所共同調(diào)用的邏輯或責任封裝起來,便于減少系統(tǒng)的重復代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護性。

使用"橫切"技術(shù),AOP把軟件系統(tǒng)分為兩個部分:核心關(guān)注點和橫切關(guān)注點。

業(yè)務處理的主要流程是核心關(guān)注點,與之關(guān)系不大的部分是橫切關(guān)注點。橫切關(guān)注點的一個特點是,它們經(jīng)常發(fā)生在核心關(guān)注點的多處,而各處基本相似,比如權(quán)限認證、日志、事務。AOP的作用在于分離系統(tǒng)中的各種關(guān)注點,將核心關(guān)注點和橫切關(guān)注點分離開來。

2、AOP術(shù)語

深刻理解AOP,要掌握的術(shù)語可真不少。


Target:目標類,需要被代理的類,如:UserService

Advice:通知,所要增強或增加的功能,定義了切面的“什么”和“何時”,模式有Before、After、After-returning,、After-throwing和Around

Join Point:連接點,應用執(zhí)行過程中,能夠插入切面的所有“點”(時機)

Pointcut:切點,實際運行中,選擇插入切面的連接點,即定義了哪些點得到了增強。切點定義了切面的“何處”。我們通常使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。

Aspect:切面,把橫切關(guān)注點模塊化為特殊的類,這些類稱為切面,切面是通知和切點的結(jié)合。通知和切點共同定義了切面的全部內(nèi)容:它是什么,在何時和何處完成其功能

Introduction:引入,允許我們向現(xiàn)有的類添加新方法或?qū)傩?/p>

Weaving:織入,把切面應用到目標對象并創(chuàng)建新的代理對象的過程,切面在指定的連接點被織入到目標對象中。在目標對象的生命周期里有多個點可以進行織入:編譯期、類加載期、運行期

下面參考自網(wǎng)上圖片,可以比較直觀地理解上述這幾個AOP術(shù)語和流轉(zhuǎn)過程。


3、AOP實現(xiàn)

(1)動態(tài)代理

使用動態(tài)代理可以為一個或多個接口在運行期動態(tài)生成實現(xiàn)對象,生成的對象中實現(xiàn)接口的方法時可以添加增強代碼,從而實現(xiàn)AOP:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 動態(tài)代理類
 */
public class DynamicProxy implements InvocationHandler {

  /**
   * 需要代理的目標類
   */
  private Object target;

  /**
   * 寫法固定,aop專用:綁定委托對象并返回一個代理類
   *
   * @param target
   * @return
   */
  public Object bind(Object target) {
    this.target = target;
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  }

  /**
   * 調(diào)用 InvocationHandler接口定義方法
   *
   * @param proxy 指被代理的對象。
   * @param method 要調(diào)用的方法
   * @param args  方法調(diào)用時所需要的參數(shù)
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result = null;
    // 切面之前執(zhí)行
    System.out.println("[動態(tài)代理]切面之前執(zhí)行");

    // 執(zhí)行業(yè)務
    result = method.invoke(target, args);

    // 切面之后執(zhí)行
    System.out.println("[動態(tài)代理]切面之后執(zhí)行");

    return result;
  }

}

缺點是只能針對接口進行代理,同時由于動態(tài)代理是通過反射實現(xiàn)的,有時可能要考慮反射調(diào)用的開銷,否則很容易引發(fā)性能問題。

(2)字節(jié)碼生成

動態(tài)字節(jié)碼生成技術(shù)是指在運行時動態(tài)生成指定類的一個子類對象(注意是針對類),并覆蓋其中特定方法,覆蓋方法時可以添加增強代碼,從而實現(xiàn)AOP。

最常用的工具是CGLib:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 使用cglib動態(tài)代理
 * <p>
 * JDK中的動態(tài)代理使用時,必須有業(yè)務接口,而cglib是針對類的
 */
public class CglibProxy implements MethodInterceptor {

  private Object target;

  /**
   * 創(chuàng)建代理對象
   *
   * @param target
   * @return
   */
  public Object getInstance(Object target) {
    this.target = target;
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(this.target.getClass());
    // 回調(diào)方法
    enhancer.setCallback(this);
    // 創(chuàng)建代理對象
    return enhancer.create();
  }

  @Override
  public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object result = null;
    System.out.println("[cglib]切面之前執(zhí)行");

    result = methodProxy.invokeSuper(proxy, args);

    System.out.println("[cglib]切面之后執(zhí)行");

    return result;
  }

}

(3)定制的類加載器

當需要對類的所有對象都添加增強,動態(tài)代理和字節(jié)碼生成本質(zhì)上都需要動態(tài)構(gòu)造代理對象,即最終被增強的對象是由AOP框架生成,不是開發(fā)者new出來的。

解決的辦法就是實現(xiàn)自定義的類加載器,在一個類被加載時對其進行增強。

JBoss就是采用這種方式實現(xiàn)AOP功能。

這種方式目前只是道聽途說,本人沒有在實際項目中實踐過。

(4)代碼生成

利用工具在已有代碼基礎(chǔ)上生成新的代碼,其中可以添加任何橫切代碼來實現(xiàn)AOP。

(5)語言擴展

可以對構(gòu)造方法和屬性的賦值操作進行增強,AspectJ是采用這種方式實現(xiàn)AOP的一個常見的Java語言擴展。

比較:根據(jù)日志,上述流程的執(zhí)行順序依次為:過濾器、攔截器、AOP方法認證、AOP類認證

附:記錄API日志
最后通過記錄API日志,記錄日志時加入API耗時統(tǒng)計(其實我們在開發(fā).NET應用的過程中通過AOP這種記錄日志的方式也已經(jīng)是標配),加深上述AOP的幾個核心概念的理解:

package com.power.demo.controller.tool;

import com.power.demo.apientity.BaseApiRequest;
import com.power.demo.apientity.BaseApiResponse;
import com.power.demo.util.DateTimeUtil;
import com.power.demo.util.SerializeUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

/**
 * 服務日志切面,主要記錄接口日志及耗時
 **/
@Aspect
@Component
public class SvcLogAspect {

  @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  public void requestMapping() {
  }

  @Pointcut("execution(* com.power.demo.controller.*Controller.*(..))")
  public void methodPointCut() {
  }

  @Around("requestMapping() && methodPointCut()")
  public Object around(ProceedingJoinPoint pjd) throws Throwable {

    System.out.println("Spring AOP方式記錄服務日志");

    Object response = null;//定義返回信息

    BaseApiRequest baseApiRequest = null;//請求基類

    int index = 0;

    Signature curSignature = pjd.getSignature();

    String className = curSignature.getClass().getName();//類名

    String methodName = curSignature.getName(); //方法名

    Logger logger = LoggerFactory.getLogger(className);//日志

    StopWatch watch = DateTimeUtil.StartNew();//用于統(tǒng)計調(diào)用耗時

    // 獲取方法參數(shù)
    Object[] reqParamArr = pjd.getArgs();
    StringBuffer sb = new StringBuffer();
    //獲取請求參數(shù)集合并進行遍歷拼接
    for (Object reqParam : reqParamArr) {
      if (reqParam == null) {
        index++;
        continue;
      }
      try {
        sb.append(SerializeUtil.Serialize(reqParam));

        //獲取繼承自BaseApiRequest的請求實體
        if (baseApiRequest == null && reqParam instanceof BaseApiRequest) {
          index++;
          baseApiRequest = (BaseApiRequest) reqParam;
        }

      } catch (Exception e) {
        sb.append(reqParam.toString());
      }
      sb.append(",");
    }

    String strParam = sb.toString();
    if (strParam.length() > 0) {
      strParam = strParam.substring(0, strParam.length() - 1);
    }

    //記錄請求
    logger.info(String.format("【%s】類的【%s】方法,請求參數(shù):%s", className, methodName, strParam));

    response = pjd.proceed(); // 執(zhí)行服務方法

    watch.stop();

    //記錄應答
    logger.info(String.format("【%s】類的【%s】方法,應答參數(shù):%s", className, methodName, SerializeUtil.Serialize(response)));

    // 獲取執(zhí)行完的時間
    logger.info(String.format("接口【%s】總耗時(毫秒):%s", methodName, watch.getTotalTimeMillis()));

    //標準請求-應答模型

    if (baseApiRequest == null) {

      return response;
    }

    if ((response != null && response instanceof BaseApiResponse) == false) {

      return response;
    }

    System.out.println("Spring AOP方式記錄標準請求-應答模型服務日志");

    Object request = reqParamArr[index];

    BaseApiResponse bizResp = (BaseApiResponse) response;
    //記錄日志
    String msg = String.format("請求:%s======應答:%s======總耗時(毫秒):%s", SerializeUtil.Serialize(request),
        SerializeUtil.Serialize(response), watch.getTotalTimeMillis());

    if (bizResp.getIsOK() == true) {
      logger.info(msg);
    } else {
      logger.error(msg);//記錄錯誤日志
    }

    return response;
  }

}

標準的請求-應答模型,我們都會定義請求基類和應答基類,本文示例給到的是BaseApiRequest和BaseApiResponse,搜集日志時,可以對錯誤日志加以區(qū)分特殊處理。

注意上述代碼中的@Around環(huán)繞通知,參數(shù)類型是ProceedingJoinPoint,而前面第一個示例的@Before前置通知,參數(shù)類型是JoinPoint。

下面是AspectJ通知和增強的5種模式:

@Before前置通知,在目標方法執(zhí)行前實施增強,請求參數(shù)JoinPoint,用來連接當前連接點的連接細節(jié),一般包括方法名和參數(shù)值。在方法執(zhí)行前進行執(zhí)行方法體,不能改變方法參數(shù),也不能改變方法執(zhí)行結(jié)果。

@After 后置通知,請求參數(shù)JoinPoint,在目標方法執(zhí)行之后,無論是否發(fā)生異常,都進行執(zhí)行的通知。在后置通知中,不能訪問目標方法的執(zhí)行結(jié)果(因為有可能發(fā)生異常),不能改變方法執(zhí)行結(jié)果。

@AfterReturning 返回通知,在目標方法執(zhí)行后實施增強,請求參數(shù)JoinPoint,其能訪問方法執(zhí)行結(jié)果(因為正常執(zhí)行)和方法的連接細節(jié),但是不能改變方法執(zhí)行結(jié)果。(注意和后置通知的區(qū)別) 

@AfterThrowing 異常通知,在方法拋出異常后實施增強,請求參數(shù)JoinPoint,throwing屬性代表方法體執(zhí)行時候拋出的異常,其值一定與方法中Exception的值需要一致。

@Around 環(huán)繞通知,請求參數(shù)ProceedingJoinPoint,環(huán)繞通知類似于動態(tài)代理的全過程,ProceedingJoinPoint類型的參數(shù)可以決定是否執(zhí)行目標方法,而且環(huán)繞通知必須有返回值,返回值即為目標方法的返回值。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringSecurity實現(xiàn)訪問控制url匹配

    SpringSecurity實現(xiàn)訪問控制url匹配

    本文主要介紹了SpringSecurity實現(xiàn)訪問控制url匹配,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • Spring?IOC?xml方式進行工廠Bean操作詳解

    Spring?IOC?xml方式進行工廠Bean操作詳解

    這篇文章主要介紹了Spring?IOC?xml方式進行工廠Bean操作,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2023-01-01
  • 詳解JAVA如何實現(xiàn)樂觀鎖以及CAS機制

    詳解JAVA如何實現(xiàn)樂觀鎖以及CAS機制

    悲觀鎖和樂觀鎖其實本質(zhì)都是一種思想,在JAVA中對于悲觀鎖的實現(xiàn)大家可能都很了解,可以通過synchronized、ReentrantLock加鎖實現(xiàn),本文不展開講解了。那么樂觀鎖在JAVA中是如何實現(xiàn)的呢?底層的實現(xiàn)機制又是什么呢?本文就來和大家詳細講講
    2022-12-12
  • 解決SpringBoot整合MybatisPlus分模塊管理遇到的bug

    解決SpringBoot整合MybatisPlus分模塊管理遇到的bug

    這篇文章主要介紹了解決SpringBoot整合MybatisPlus分模塊管理遇到的bug,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring?Initializr只能創(chuàng)建為Java?17版本以上的問題解決

    Spring?Initializr只能創(chuàng)建為Java?17版本以上的問題解決

    這篇文章主要給大家介紹了關(guān)于Spring?Initializr只能創(chuàng)建為Java?17版本以上問題的解決辦法,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2024-01-01
  • SpringBoot多種環(huán)境自由切換的實現(xiàn)

    SpringBoot多種環(huán)境自由切換的實現(xiàn)

    本文主要介紹了SpringBoot多種環(huán)境自由切換的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • SpringBoot中REST API 接口傳參的實現(xiàn)

    SpringBoot中REST API 接口傳參的實現(xiàn)

    我們在開發(fā)?REST API?的過程中,經(jīng)常需要傳遞參數(shù),本文主要介紹了SpringBoot中REST API 接口傳參的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • Java?Float?保留小數(shù)位精度的實現(xiàn)

    Java?Float?保留小數(shù)位精度的實現(xiàn)

    這篇文章主要介紹了Java?Float?保留小數(shù)位精度的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 如何利用Ganymed SSH-2模擬SSH操作

    如何利用Ganymed SSH-2模擬SSH操作

    這幾天看SFTP資料時,無意中看到了Ganymed SSH-2,寫了個簡單demo,通過,感覺挺好用的,下面就和大家分享下。需要的朋友可以過來參考參考
    2013-08-08
  • idea關(guān)聯(lián)maven的使用詳解

    idea關(guān)聯(lián)maven的使用詳解

    這篇文章主要介紹了idea關(guān)聯(lián)maven的使用詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03

最新評論