SpringBoot實(shí)現(xiàn)字段自動(dòng)填充的兩種方式
creatby,updateby等字段自動(dòng)填充
每個(gè)字段在插入數(shù)據(jù)庫(kù),或者更新時(shí)都要在serviceimpl層對(duì)creatby,updateby等字段進(jìn)行填充,這個(gè)太繁瑣了,以下兩種方法可以實(shí)現(xiàn)字段的自動(dòng)填充。本項(xiàng)目使用第一種。
方法一:
首先創(chuàng)建一個(gè)AutoFillInterceptor類(lèi)。下面會(huì)對(duì)代碼逐行分析。
以下代碼也可以直接復(fù)制粘貼,但前提是你的實(shí)體類(lèi)中的自動(dòng)填充的字段和下面四個(gè)靜態(tài)常量名字一樣。
@Component @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class AutoFillInterceptor implements Interceptor { private static final String CREATE_BY = "createdBy"; private static final String UPDATE_BY = "updatedBy"; private static final String CREATE_TIME = "createdAt"; private static final String UPDATE_TIME = "updatedAt"; @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; SqlCommandType sqlCommandType = ms.getSqlCommandType(); Object parameter = args[1]; if(parameter != null && sqlCommandType!=null){ if(SqlCommandType.INSERT.equals(sqlCommandType)){ if(parameter instanceof MapperMethod.ParamMap){ MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter; ArrayList list= (ArrayList) paramMap.get("list"); list.forEach(v->{ setFieldValByName(CREATE_TIME, LocalDateTime.now(), v); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v); }); paramMap.put("list", list); }else{ // 單條插入的情況 // 設(shè)置創(chuàng)建人和創(chuàng)建時(shí)間字段值 setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); } } else if(SqlCommandType.UPDATE.equals(sqlCommandType)){ // 更新操作 // 設(shè)置更新人和更新時(shí)間字段值 setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); } } // 繼續(xù)執(zhí)行原始方法 return invocation.proceed(); } private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) { MetaObject metaObject = SystemMetaObject.forObject(parameter); if (metaObject.hasSetter(fieldName)) { metaObject.setValue(fieldName, fieldVal); } } @Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } @Override public Object plugin(Object target) { return Interceptor.super.plugin(target); } }
代碼結(jié)構(gòu)與作用
這是一個(gè)實(shí)現(xiàn)了MyBatis攔截器(Interceptor
接口)的類(lèi)AutoFillInterceptor
,用于在執(zhí)行SQL操作(INSERT或UPDATE)時(shí),自動(dòng)填充一些通用字段,比如創(chuàng)建時(shí)間(createdAt
)、更新時(shí)間(updatedAt
)等。
在企業(yè)級(jí)項(xiàng)目中,通常需要記錄數(shù)據(jù)的創(chuàng)建時(shí)間和修改時(shí)間,這個(gè)攔截器就是為了解決這種需求,在新增和修改數(shù)據(jù)時(shí)自動(dòng)填充這些字段。下面我們來(lái)逐行分析代碼。
代碼逐行解析
@Component @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) })
@Component
:Spring的注解,將這個(gè)類(lèi)注冊(cè)為一個(gè)Spring Bean,便于管理。@Intercepts
:MyBatis的注解,聲明這是一個(gè)攔截器,并指定要攔截的目標(biāo)。@Signature
:定義攔截器的具體攔截方法。type = Executor.class
:表示攔截MyBatis的Executor
類(lèi)。method = "update"
:表示攔截update
方法,這個(gè)方法用于執(zhí)行更新操作(包括INSERT、UPDATE、DELETE)。args = {MappedStatement.class, Object.class}
:指定update
方法的參數(shù)類(lèi)型,即SQL映射信息MappedStatement
和參數(shù)對(duì)象Object
。
public class AutoFillInterceptor implements Interceptor {
- 這幾行定義了一些常量,分別表示字段名稱(chēng),如創(chuàng)建者、修改者、創(chuàng)建時(shí)間和修改時(shí)間。這些常量將在攔截邏輯中用來(lái)自動(dòng)填充字段。
@Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; SqlCommandType sqlCommandType = ms.getSqlCommandType(); Object parameter = args[1];
intercept
方法是攔截器的核心邏輯。Object[] args = invocation.getArgs()
:獲取攔截方法的參數(shù)。MappedStatement ms = (MappedStatement) args[0]
:獲取MappedStatement
,包含了有關(guān)SQL語(yǔ)句的信息。SqlCommandType sqlCommandType = ms.getSqlCommandType()
:獲取SQL的操作類(lèi)型(INSERT、UPDATE、DELETE)。Object parameter = args[1]
:獲取參數(shù)對(duì)象,通常是用戶(hù)要插入或更新的數(shù)據(jù)。
if(parameter != null && sqlCommandType != null){
- 檢查參數(shù)是否為空,并確認(rèn)操作類(lèi)型是否非空,確保有必要繼續(xù)執(zhí)行后續(xù)操作。
if(SqlCommandType.INSERT.equals(sqlCommandType)){ if(parameter instanceof MapperMethod.ParamMap){ MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter; ArrayList list= (ArrayList) paramMap.get("list"); list.forEach(v -> { setFieldValByName(CREATE_TIME, LocalDateTime.now(), v); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v); }); paramMap.put("list", list); } else { // 單條插入的情況 // 設(shè)置創(chuàng)建人和創(chuàng)建時(shí)間字段值 setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); } }
if (SqlCommandType.INSERT.equals(sqlCommandType))
:如果當(dāng)前SQL是INSERT操作:if (parameter instanceof MapperMethod.ParamMap)
:判斷參數(shù)是否是MapperMethod.ParamMap
類(lèi)型,這通常用于批量插入。ArrayList list = (ArrayList) paramMap.get("list")
:從參數(shù)Map中獲取名為list
的參數(shù),這是批量插入的數(shù)據(jù)集合。list.forEach(v -> {...})
:對(duì)每個(gè)元素進(jìn)行操作,調(diào)用setFieldValByName
方法設(shè)置createdAt
和updatedAt
為當(dāng)前時(shí)間。
else
部分:處理單條插入的情況,直接給parameter
對(duì)象設(shè)置創(chuàng)建時(shí)間和更新時(shí)間。
else if(SqlCommandType.UPDATE.equals(sqlCommandType)){ // 更新操作 // 設(shè)置更新人和更新時(shí)間字段值 setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); }
else if (SqlCommandType.UPDATE.equals(sqlCommandType))
:如果當(dāng)前SQL是UPDATE操作:- 使用
setFieldValByName
方法將updatedAt
字段設(shè)置為當(dāng)前時(shí)間。
- 使用
} // 繼續(xù)執(zhí)行原始方法 return invocation.proceed(); }
- 最終通過(guò)
invocation.proceed()
調(diào)用被攔截的方法,繼續(xù)執(zhí)行原始的數(shù)據(jù)庫(kù)操作。
private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) { MetaObject metaObject = SystemMetaObject.forObject(parameter); if (metaObject.hasSetter(fieldName)) { metaObject.setValue(fieldName, fieldVal); } }
setFieldValByName
方法用于設(shè)置對(duì)象中指定字段的值:MetaObject metaObject = SystemMetaObject.forObject(parameter)
:創(chuàng)建MetaObject
,用于操作傳入對(duì)象的元數(shù)據(jù)。if (metaObject.hasSetter(fieldName))
:檢查對(duì)象是否有對(duì)應(yīng)字段的setter方法。metaObject.setValue(fieldName, fieldVal)
:如果有setter方法,則設(shè)置字段的值。
@Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } @Override public Object plugin(Object target) { return Interceptor.super.plugin(target); } }
setProperties
和plugin
方法是Interceptor
接口的默認(rèn)實(shí)現(xiàn),plugin
方法用于生成代理對(duì)象。
總結(jié)
- 這個(gè)攔截器的作用是自動(dòng)填充
createdAt
和updatedAt
字段,以便在執(zhí)行INSERT和UPDATE操作時(shí)自動(dòng)記錄創(chuàng)建和更新時(shí)間。 - 主要攔截
Executor
的update
方法,通過(guò)判斷SQL類(lèi)型來(lái)確定是INSERT還是UPDATE操作,從而設(shè)置相應(yīng)字段。 - 使用了MyBatis的
MetaObject
工具類(lèi)來(lái)動(dòng)態(tài)操作參數(shù)對(duì)象的字段值。
通過(guò)這個(gè)攔截器,開(kāi)發(fā)者不需要在業(yè)務(wù)代碼中手動(dòng)設(shè)置createdAt
和updatedAt
,大大減少了重復(fù)代碼,也保證了這些公共字段的一致性和正確性。
方法二:
這個(gè)方法是使用自定義注解來(lái)寫(xiě)的,所以要在需要填充的sql上加上這個(gè)注解。這個(gè)可能更加靈活更加簡(jiǎn)單把。
公共字段自動(dòng)填充
技術(shù)點(diǎn):枚舉、注解、AOP、反射
創(chuàng)建時(shí)間、修改時(shí)間、創(chuàng)建人、修改人這4個(gè)公共字段。
為mapper方法加注解AutoFill,標(biāo)識(shí)需要進(jìn)行公共字段自動(dòng)填充
自定義切面類(lèi)AutoFillAspect,統(tǒng)一攔截加入了AutoFill注解的方法,通過(guò)反射為公共字段賦值。
在Mapper的方法上接入AutoFill注解。
public enum OperationType { 更新操作 UPDATE, 插入操作 INSERT } @Target(ElementType.METHOD)當(dāng)前注解加在什么位置 @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { //數(shù)據(jù)庫(kù)操作類(lèi)型:UPDATE INSERT OperationType value(); }
補(bǔ)充注解基本知識(shí)
public @interface MyAnnotation { // 定義注解的成員 String value(); // 這是一個(gè)名為"value"的成員 int count() default 1; // 這是一個(gè)名為"count"的成員,帶有默認(rèn)值 } @MyAnnotation(value = "Hello", count = 3) public class MyClass { // 類(lèi)的代碼 }
對(duì)于AutoFillAspect類(lèi)切點(diǎn)、execution表達(dá)式
/** * 自定義切面,實(shí)現(xiàn)公共字段自動(dòng)填充處理邏輯 */ @Aspect @Component @Slf4j public class AutoFillAspect { /** * 切入點(diǎn) */ 所有的類(lèi),所有的方法,所有的參數(shù)類(lèi)型 @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut(){} /** * 前置通知,在通知中進(jìn)行公共字段的賦值 */ @Before("autoFillPointCut()")指定切入點(diǎn) public void autoFill(JoinPoint joinPoint){連接點(diǎn) log.info("開(kāi)始進(jìn)行公共字段自動(dòng)填充..."); //獲取到當(dāng)前被攔截的方法上的數(shù)據(jù)庫(kù)操作類(lèi)型 MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法簽名對(duì)象 AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//獲得方法上的注解對(duì)象 OperationType operationType = autoFill.value();//獲得數(shù)據(jù)庫(kù)操作類(lèi)型 //獲取到當(dāng)前被攔截的方法的參數(shù)--實(shí)體對(duì)象 做一個(gè)約定,實(shí)體對(duì)象放第一個(gè) Object[] args = joinPoint.getArgs(); if(args == null || args.length == 0){ return; } Object entity = args[0];實(shí)體 //準(zhǔn)備賦值的數(shù)據(jù) LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId(); //根據(jù)當(dāng)前不同的操作類(lèi)型,為對(duì)應(yīng)的屬性通過(guò)反射來(lái)賦值 if(operationType == OperationType.INSERT){ //為4個(gè)公共字段賦值 try { Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class); Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); //通過(guò)反射為對(duì)象屬性賦值 setCreateTime.invoke(entity,now); setCreateUser.invoke(entity,currentId); setUpdateTime.invoke(entity,now); setUpdateUser.invoke(entity,currentId); } catch (Exception e) { e.printStackTrace(); } }else if(operationType == OperationType.UPDATE){ //為2個(gè)公共字段賦值 try { Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); //通過(guò)反射為對(duì)象屬性賦值 setUpdateTime.invoke(entity,now); setUpdateUser.invoke(entity,currentId); } catch (Exception e) { e.printStackTrace(); } } } }
使用
@AutoFill(value = OperationType.UPDATE) void update(Employee employee);
自定義切面:實(shí)現(xiàn)公共字段的自動(dòng)填充
這段代碼使用了 Spring AOP(面向切面編程)來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)操作時(shí),自動(dòng)填充一些公共字段,例如創(chuàng)建時(shí)間、更新時(shí)間、創(chuàng)建人、更新人等。接下來(lái),我們逐行解析這段代碼,以幫助你理解各個(gè)部分的功能和實(shí)現(xiàn)邏輯。
代碼結(jié)構(gòu)概覽
@Aspect @Component @Slf4j public class AutoFillAspect { // 切入點(diǎn) @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut(){} // 前置通知 @Before("autoFillPointCut()") public void autoFill(JoinPoint joinPoint){ log.info("開(kāi)始進(jìn)行公共字段自動(dòng)填充..."); ... } }
這段代碼定義了一個(gè)切面 AutoFillAspect
,它會(huì)在符合條件的數(shù)據(jù)庫(kù)操作方法執(zhí)行之前,通過(guò)前置通知 (@Before
) 自動(dòng)對(duì)某些公共字段進(jìn)行填充。
注解解釋
@Aspect
:表示當(dāng)前類(lèi)是一個(gè)切面類(lèi),用于定義通知和切入點(diǎn)。@Component
:把這個(gè)切面類(lèi)注冊(cè)為 Spring 容器中的一個(gè)組件。@Slf4j
:用來(lái)啟用日志功能,以方便調(diào)試和記錄信息。
切入點(diǎn)定義
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut(){}
解釋?zhuān)?/strong>
@Pointcut
:用于定義一個(gè)切入點(diǎn),描述哪些方法需要被切面邏輯攔截。execution(* com.sky.mapper.*.*(..))
:匹配com.sky.mapper
包下的所有類(lèi)和所有方法,(..)
表示任意參數(shù)類(lèi)型和數(shù)量。&& @annotation(com.sky.annotation.AutoFill)
:表示只攔截被@AutoFill
注解標(biāo)記的方法。
通過(guò)這種定義,只有符合指定包下的類(lèi)且有 @AutoFill
注解的方法,才會(huì)被切面邏輯攔截。
前置通知(Before Advice)
@Before("autoFillPointCut()") public void autoFill(JoinPoint joinPoint) { log.info("開(kāi)始進(jìn)行公共字段自動(dòng)填充..."); ... }
- @Before("autoFillPointCut()"):這是前置通知,表示在切入點(diǎn)所匹配的方法執(zhí)行之前,執(zhí)行 autoFill() 方法。
- JoinPoint joinPoint:JoinPoint 是一個(gè)連接點(diǎn),表示被攔截的方法,允許獲取到目標(biāo)方法的一些信息,比如方法名和參數(shù)等。
獲取注解和方法信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); OperationType operationType = autoFill.value();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
:獲取當(dāng)前攔截的方法的簽名信息,轉(zhuǎn)換為MethodSignature
類(lèi)型。AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
:獲取方法上的@AutoFill
注解對(duì)象。OperationType operationType = autoFill.value();
:獲取注解中指定的數(shù)據(jù)庫(kù)操作類(lèi)型(例如 INSERT 或 UPDATE)。
獲取方法參數(shù)
Object[] args = joinPoint.getArgs(); if (args == null || args.length == 0) { return; } Object entity = args[0];
Object[] args = joinPoint.getArgs();
:獲取當(dāng)前被攔截的方法的參數(shù)。if (args == null || args.length == 0)
:如果沒(méi)有參數(shù),直接返回。Object entity = args[0];
:假設(shè)第一個(gè)參數(shù)是實(shí)體對(duì)象,用于操作數(shù)據(jù)庫(kù)。這里有一個(gè)約定,即實(shí)體對(duì)象總是第一個(gè)參數(shù)。
準(zhǔn)備賦值的數(shù)據(jù)
LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId();
LocalDateTime now = LocalDateTime.now();
:獲取當(dāng)前時(shí)間,用于填充創(chuàng)建時(shí)間和更新時(shí)間。Long currentId = BaseContext.getCurrentId();
:獲取當(dāng)前操作用戶(hù)的 ID,用于填充創(chuàng)建人和更新人信息。
根據(jù)操作類(lèi)型進(jìn)行賦值
插入操作(INSERT)
if (operationType == OperationType.INSERT) { try { Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class); Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setCreateTime.invoke(entity, now); setCreateUser.invoke(entity, currentId); setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); } catch (Exception e) { e.printStackTrace(); } }
if (operationType == OperationType.INSERT)
:如果數(shù)據(jù)庫(kù)操作類(lèi)型是插入(INSERT)。- 通過(guò)反射的方式獲取實(shí)體類(lèi)中的
setCreateTime
、setCreateUser
、setUpdateTime
和setUpdateUser
方法。 invoke()
方法用于調(diào)用這些 setter 方法并傳入相應(yīng)的值,完成公共字段的賦值。
更新操作(UPDATE)
else if (operationType == OperationType.UPDATE) { try { Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); } catch (Exception e) { e.printStackTrace(); } }
else if (operationType == OperationType.UPDATE)
:如果操作類(lèi)型是更新(UPDATE)。- 這里只需填充更新相關(guān)的字段,即更新時(shí)間和更新人。
小結(jié)
這段代碼實(shí)現(xiàn)了對(duì)數(shù)據(jù)庫(kù)操作的公共字段自動(dòng)填充,具體如下:
- 定義一個(gè)切面
AutoFillAspect
,用于攔截特定包中的方法,并且方法需要用@AutoFill
注解進(jìn)行標(biāo)記。 - 使用 AOP 的前置通知在方法執(zhí)行前進(jìn)行字段自動(dòng)填充。
- 通過(guò)反射機(jī)制獲取實(shí)體對(duì)象的方法并進(jìn)行賦值,根據(jù)操作類(lèi)型填充不同的字段。
這使得代碼變得更加簡(jiǎn)潔和可維護(hù),減少了重復(fù)的公共字段賦值邏輯,也方便對(duì)創(chuàng)建時(shí)間、更新時(shí)間等公共屬性的一致性管理。
以上就是SpringBoot實(shí)現(xiàn)字段自動(dòng)填充的兩種方式的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot字段自動(dòng)填充的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解
這篇文章主要介紹了Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解,JCBC技術(shù)是java開(kāi)發(fā)必備的只是,jdbc連接mysql數(shù)據(jù)庫(kù),這是一個(gè)比較簡(jiǎn)單的方法,有興趣的可以了解一下2020-07-07關(guān)于MVC與SpringMVC的介紹、區(qū)別、執(zhí)行流程
這篇文章主要介紹了關(guān)于MVC與SpringMVC的介紹、區(qū)別、執(zhí)行流程,MVC框架的主要目標(biāo)是將應(yīng)用程序的業(yè)務(wù)邏輯(Model)與用戶(hù)界面(View)分離開(kāi)來(lái),從而提高應(yīng)用程序的可維護(hù)性和可擴(kuò)展性,需要的朋友可以參考下2023-05-05Mybatis下動(dòng)態(tài)sql中##和$$的區(qū)別講解
今天小編就為大家分享一篇關(guān)于Mybatis下動(dòng)態(tài)sql中##和$$的區(qū)別講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03純Java代碼實(shí)現(xiàn)流星劃過(guò)天空
本文給大家介紹純java代碼實(shí)現(xiàn)流星劃過(guò)天空,包括流星個(gè)數(shù),流星飛行的速度,色階,流星大小相關(guān)變量設(shè)置。對(duì)java流星劃過(guò)天空特效代碼感興趣的朋友可以參考下本文2015-10-10SpringBoot實(shí)現(xiàn)kafka多源配置的示例代碼
實(shí)際開(kāi)發(fā)中,不同的topic可能來(lái)自不同的集群,所以就需要配置不同的kafka數(shù)據(jù)源,基于springboot自動(dòng)配置的思想,最終通過(guò)配置文件的配置,自動(dòng)生成生產(chǎn)者及消費(fèi)者的配置,本文介紹了SpringBoot實(shí)現(xiàn)kafka多源配置,需要的朋友可以參考下2024-06-06Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問(wèn)題解決思路及源碼分享
這篇文章主要介紹了Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問(wèn)題解決思路及源碼分享,具有一定參考價(jià)值,這里分享給大家,供需要的朋友了解。2017-10-10使用Spring底層組件實(shí)現(xiàn)Aware接口
這篇文章主要介紹了使用Spring底層組件實(shí)現(xiàn)Aware接口,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07Java實(shí)現(xiàn)輕松處理日期和時(shí)間的API小結(jié)
這篇文章主要為大家詳細(xì)介紹了Java中的日期和時(shí)間API,可以輕松處理日期和時(shí)間,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03Java利用Request請(qǐng)求如何獲取IP地址對(duì)應(yīng)的省份、城市詳解
之前已經(jīng)給大家介紹了關(guān)于Java用Request請(qǐng)求獲取IP地址的相關(guān)內(nèi)容,那么下面這篇文章將給大家進(jìn)入深入的介紹,關(guān)于Java利用Request請(qǐng)求如何獲取IP地址對(duì)應(yīng)省份、城市的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-10-10