詳解Java中自定義注解的使用
什么是注解
在早期的工作的時(shí)候 ,自定義注解寫的比較多,可大多都只是因?yàn)?這樣看起來(lái) 不會(huì)存在一堆代碼耦合在一起的情況,所以使用了自定義注解,這樣看起來(lái)清晰些,
Annontation是Java5開始引入的新特征,中文名稱叫注解。
它提供了一種安全的類似注釋的機(jī)制,用來(lái)將任何的信息或元數(shù)據(jù)(metadata)與程序元素(類、方法、成員變量等)進(jìn)行關(guān)聯(lián)。為程序的元素(類、方法、成員變量)加上更直觀、更明了的說(shuō)明,這些說(shuō)明信息是與程序的業(yè)務(wù)邏輯無(wú)關(guān),并且供指定的工具或框架使用。Annontation像一種修飾符一樣,應(yīng)用于包、類型、構(gòu)造方法、方法、成員變量、參數(shù)及本地變量的聲明語(yǔ)句中。
Java注解是附加在代碼中的一些元信息,用于一些工具在編譯、運(yùn)行時(shí)進(jìn)行解析和使用,起到說(shuō)明、配置的功能。注解不會(huì)也不能影響代碼的實(shí)際邏輯,僅僅起到輔助性的作用。
一般我們自定義一個(gè)注解的操作是這樣的:
public @interface MyAnnotation {
}
如果說(shuō)我們需要給他加上參數(shù),那么大概是這樣的
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface MyAnnotation {
public int age() default 18;
String name() ;
String [] books();
}我們可以關(guān)注到上面有些我們不曾見過(guò)的注解,而這類注解,統(tǒng)稱為元注解 ,我們可以大概來(lái)看一下
@Document
是被用來(lái)指定自定義注解是否能隨著被定義的java文件生成到JavaDoc文檔當(dāng)中。
@Target
是專門用來(lái)限定某個(gè)自定義注解能夠被應(yīng)用在哪些Java元素上面的,不定義說(shuō)明可以放在任何元素上。
上面這個(gè) Target這玩意有個(gè)枚舉,可以清晰的看出來(lái),他的 屬性
使用枚舉類ElementType來(lái)定義
public enum ElementType {
/** 類,接口(包括注解類型)或枚舉的聲明 */
TYPE,
/** 屬性的聲明 */
FIELD,
/** 方法的聲明 */
METHOD,
/** 方法形式參數(shù)聲明 */
PARAMETER,
/** 構(gòu)造方法的聲明 */
CONSTRUCTOR,
/** 局部變量聲明 */
LOCAL_VARIABLE,
/** 注解類型聲明 */
ANNOTATION_TYPE,
/** 包的聲明 */
PACKAGE
}@Retention
即用來(lái)修飾自定義注解的生命周期。
使用了RetentionPolicy枚舉類型定義了三個(gè)階段
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* (注解將被編譯器丟棄)
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* (注解將被編譯器記錄在class文件中,但在運(yùn)行時(shí)不會(huì)被虛擬機(jī)保留,這是一個(gè)默認(rèn)的行為)
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* (注解將被編譯器記錄在class文件中,而且在運(yùn)行時(shí)會(huì)被虛擬機(jī)保留,因此它們能通過(guò)反射被讀取到)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Inherited
允許子類繼承父類中的注解
注解的注意事項(xiàng)
1.訪問(wèn)修飾符必須為public,不寫默認(rèn)為public;
2.該元素的類型只能是基本數(shù)據(jù)類型、String、Class、枚舉類型、注解類型(體現(xiàn)了注解的嵌套效果)以及上述類型的一位數(shù)組;
3.該元素的名稱一般定義為名詞,如果注解中只有一個(gè)元素,請(qǐng)把名字起為value(后面使用會(huì)帶來(lái)便利操作);
4.()不是定義方法參數(shù)的地方,也不能在括號(hào)中定義任何參數(shù),僅僅只是一個(gè)特殊的語(yǔ)法;
5.default代表默認(rèn)值,值必須和第2點(diǎn)定義的類型一致;
6.如果沒有默認(rèn)值,代表后續(xù)使用注解時(shí)必須給該類型元素賦值。
注解的本質(zhì)
所有的Java注解都基于Annotation接口。但是,手動(dòng)定義一個(gè)繼承自Annotation接口的接口無(wú)效。要定義一個(gè)有效的Java注解,需要使用@interface關(guān)鍵字來(lái)聲明注解。Annotation接口本身只是一個(gè)普通的接口,并不定義任何注解類型。
public interface Annotation {
boolean equals(Object obj);
/**
* 獲取hashCode
*/
int hashCode();
String toString();
/**
*獲取注解類型
*/
Class<? extends Annotation> annotationType();
}在Java中,所有的注解都是基于Annotation接口的,但是手動(dòng)定義一個(gè)繼承自Annotation接口的接口并不會(huì)創(chuàng)建一個(gè)有效的注解。要定義有效的注解,需要使用特殊的關(guān)鍵字@interface來(lái)聲明注解類型。Annotation接口本身只是一個(gè)普通的接口,而不是一個(gè)定義注解的接口。因此,使用@interface聲明注解是定義Java注解的標(biāo)準(zhǔn)方法。
public @interface MyAnnotation1 {
}
public interface MyAnnotation2 extends Annotation {
}// javap -c TestAnnotation1.class
Compiled from "MyAnnotation1.java"
public interface com.spirimark.corejava.annotation.MyAnnotation1 extends java.lang.annotation.Annotation {}
?
// javap -c TestAnnotation2.class
Compiled from "MyAnnotation2.java"
public interface com.spirimark.corejava.annotation.MyAnnotation2 extends java.lang.annotation.Annotation {}雖然Java中的所有注解都是基于Annotation接口,但即使接口本身支持多繼承,注解的定義仍無(wú)法使用繼承關(guān)鍵字來(lái)實(shí)現(xiàn)。定義注解的正確方式是使用特殊的關(guān)鍵字@interface聲明注解類型。
同時(shí)需要注意的是,通過(guò)@interface聲明的注解類型不支持繼承其他注解或接口。任何嘗試?yán)^承注解類型的操作都會(huì)導(dǎo)致編譯錯(cuò)誤。
public @interface MyAnnotation1 {
}
/** 錯(cuò)誤的定義,注解不能繼承注解 */
@interface MyAnnotation2 extends MyAnnotation1 {
}
/** 錯(cuò)誤的定義,注解不能繼承接口 */
@interface MyAnnotation3 extends Annotation {
}自定義注解使用
使用方式 1
自定義注解的玩法有很多,最常見的莫過(guò)于
- 聲明注解
- 通過(guò)反射讀取
但是上面這種一般現(xiàn)在在開發(fā)中不怎么常用,最常用的就是,我們通過(guò) 切面去在注解的前后進(jìn)行加載
創(chuàng)建注解
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BussinessLog {
/**
* 功能
*/
BusinessTypeEnum value();
/**
* 是否保存請(qǐng)求的參數(shù)
*/
boolean isSaveRequestData() default true;
/**
* 是否保存響應(yīng)的參數(shù)
*/
boolean isSaveResponseData() default true;
}設(shè)置枚舉
public enum BusinessTypeEnum {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 刪除
*/
DELETE,
/**
* 授權(quán)
*/
GRANT,
/**
* 導(dǎo)出
*/
EXPORT,
/**
* 導(dǎo)入
*/
IMPORT,
}創(chuàng)建切面操作
@Slf4j
@Aspect
@Component
public class LogConfig {
@Autowired
private IUxmLogService uxmLogService;
/**
* 后置通過(guò),?標(biāo)?法正常執(zhí)?完畢時(shí)執(zhí)?
*
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 異常通知,?標(biāo)?法發(fā)?異常的時(shí)候執(zhí)?
*
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
try {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String title = methodSignature.getMethod().getAnnotation(ApiOperation.class).value();
// 獲取當(dāng)前的用戶
String userName = CurrentUser.getCurrentUserName();
// *========數(shù)據(jù)庫(kù)日志=========*//
UxmLog uxmLog = new UxmLog();
uxmLog.setStatus(BaseConstant.YES);
// 請(qǐng)求的地址
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = requestAttributes.getRequest();
String ip = getIpAddr(request);
// 設(shè)置標(biāo)題
uxmLog.setTitle(title);
uxmLog.setOperIp(ip);
uxmLog.setOperUrl(request.getRequestURI());
uxmLog.setOperName(userName);
if (e != null) {
uxmLog.setStatus(BaseConstant.NO);
uxmLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 設(shè)置方法名稱
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
uxmLog.setMethod(className + "." + methodName + "()");
// 設(shè)置請(qǐng)求方式
uxmLog.setRequestMethod(request.getMethod());
// 處理設(shè)置注解上的參數(shù)
getControllerMethodDescription(joinPoint, controllerLog, uxmLog, jsonResult, request);
// 保存數(shù)據(jù)庫(kù)
uxmLog.setOperTime(new Date());
uxmLogService.save(uxmLog);
} catch (Exception exp) {
// 記錄本地異常日志
log.error("==前置通知異常==");
log.error("異常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, UxmLog uxmLog, Object jsonResult, HttpServletRequest request) throws Exception {
// 設(shè)置action動(dòng)作
uxmLog.setBusinessType(log.value().ordinal());
// 是否需要保存request,參數(shù)和值
if (log.isSaveRequestData()) {
// 獲取參數(shù)的信息,傳入到數(shù)據(jù)庫(kù)中。
setRequestValue(joinPoint, uxmLog, request);
}
// 是否需要保存response,參數(shù)和值
if (log.isSaveResponseData()) {
uxmLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
private void setRequestValue(JoinPoint joinPoint, UxmLog uxmLog, HttpServletRequest request) throws Exception {
String requestMethod = uxmLog.getRequestMethod();
if (RequestMethod.PUT.name().equals(requestMethod) || RequestMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
uxmLog.setOperParam(StringUtils.substring(params, 0, 2000));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
uxmLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
private String argsArrayToString(Object[] paramsArray) {
StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
try {
Object jsonObj = JSON.toJSON(o);
params.append(jsonObj.toString()).append(" ");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
return params.toString().trim();
}
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}這樣的話,我們就可以 在 項(xiàng)目當(dāng)中 去在標(biāo)注注解的前后去進(jìn)行輸出 日志

使用方式 2
我們可能還會(huì)在每次請(qǐng)求的時(shí)候去輸出日志,所以 我們也可以去定義一個(gè) 請(qǐng)求的 注解
@HttpLog 自動(dòng)記錄Http日志
在很多時(shí)候我們要把一些接口的Http請(qǐng)求信息記錄到日志里面。通常原始的做法是利用日志框架如log4j,slf4j等,在方法里面打日志log.info(“xxxx”)。但是這樣的工作無(wú)疑是單調(diào)而又重復(fù)的,我們可以采用自定義注解+切面的來(lái)簡(jiǎn)化這一工作。通常的日志記錄都在Controller里面進(jìn)行的比較多,我們可以實(shí)現(xiàn)這樣的效果:
我們自定義@HttpLog注解,作用域在類上,凡是打上了這個(gè)注解的Controller類里面的所有方法都會(huì)自動(dòng)記錄Http日志。實(shí)現(xiàn)方式也很簡(jiǎn)單,主要寫好切面表達(dá)式:
日志切面
下面代碼的意思,就是當(dāng)標(biāo)注了注解,我們通過(guò) @Pointcut 定義了切入點(diǎn), 當(dāng)標(biāo)注了注解,我們會(huì)在標(biāo)注注解的 前后進(jìn)行輸出 ,當(dāng)然也包含了 Spring 官方 自帶的注解 例如 RestController
// 切面表達(dá)式,描述所有所有需要記錄log的類,所有有@HttpLog 并且有 @Controller 或 @RestController 類都會(huì)被代理
@Pointcut("@within(com.example.spiritmark.annotation.HttpLog) && (@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Controller))")
public void httpLog() {
}
@Before("httpLog()")
public void preHandler(JoinPoint joinPoint) {
startTime.set(System.currentTimeMillis());
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
log.info("Current Url: {}", httpServletRequest.getRequestURI());
log.info("Current Http Method: {}", httpServletRequest.getMethod());
log.info("Current IP: {}", httpServletRequest.getRemoteAddr());
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
log.info("=======http headers=======");
while (headerNames.hasMoreElements()) {
String nextName = headerNames.nextElement();
log.info(nextName.toUpperCase() + ": {}", httpServletRequest.getHeader(nextName));
}
log.info("======= header end =======");
log.info("Current Class Method: {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("Parms: {}", null != httpServletRequest.getQueryString() ? JSON.toJSONString(httpServletRequest.getQueryString().split("&")) : "EMPTY");
}
@AfterReturning(returning = "response", pointcut = "httpLog()")
public void afterReturn(Object response) {
log.info("Response: {}", JSON.toJSONString(response));
log.info("Spend Time: [ {}", System.currentTimeMillis() - startTime.get() + " ms ]");
}@TimeStamp 自動(dòng)注入時(shí)間戳
如果我們想通過(guò)自定義注解,在我們每次保存數(shù)據(jù)的時(shí)候,自動(dòng)的幫我們將標(biāo)注注解的方法內(nèi)的時(shí)間戳字段轉(zhuǎn)換成 正常日期,我們就需要
我們的很多數(shù)據(jù)需要記錄時(shí)間戳,最常見的就是記錄created_at和updated_at,通常我們可以通常實(shí)體類中的setCreatedAt()方法來(lái)寫入當(dāng)前時(shí)間,然后通過(guò)ORM來(lái)插入到數(shù)據(jù)庫(kù)里,但是這樣的方法比較重復(fù)枯燥,給每個(gè)需要加上時(shí)間戳的類都要寫入時(shí)間戳很麻煩而且不小心會(huì)漏掉。
另一個(gè)思路是在數(shù)據(jù)庫(kù)里面設(shè)置默認(rèn)值,插入的時(shí)候由數(shù)據(jù)庫(kù)自動(dòng)生成當(dāng)前時(shí)間戳,但是理想很豐滿,現(xiàn)實(shí)很骨感,在MySQL如果時(shí)間戳類型是datetime里即使你設(shè)置了默認(rèn)值為當(dāng)前時(shí)間也不會(huì)在時(shí)間戳為空時(shí)插入數(shù)據(jù)時(shí)自動(dòng)生成,而是會(huì)在已有時(shí)間戳記錄的情況下更新時(shí)間戳為當(dāng)前時(shí)間,這并不是我們所需要的,比如我們不希望created_at每次更改記錄時(shí)都被刷新,另外的方法是將時(shí)間戳類型改為timestamp,這樣第一個(gè)類型為timestamp的字段會(huì)在值為空時(shí)自動(dòng)生成,但是多個(gè)的話,后面的均不會(huì)自動(dòng)生成。再有一種思路是,直接在sql里面用now()函數(shù)生成,比如created_at = now()。
但是這樣必須要寫sql,如果使用的不是主打sql流的orm不會(huì)太方便,比如hibernate之類的,并且也會(huì)加大sql語(yǔ)句的復(fù)雜度,同時(shí)sql的可移植性也會(huì)降低,比如sqlServer中就不支持now()函數(shù)。為了簡(jiǎn)化這個(gè)問(wèn)題,我們可以自定義@TimeStamp注解,打上該注解的方法的入?yún)⒗锩娴乃袑?duì)象或者指定對(duì)象里面要是有setCreatedAt、setUpdatedAt這樣的方法,便會(huì)自動(dòng)注入時(shí)間戳,而無(wú)需手動(dòng)注入,同時(shí)還可以指定只注入created_at或updated_at。實(shí)現(xiàn)主要代碼如下:
@Aspect
@Component
public class TimeStampAspect {
@Pointcut("@annotation(com.example.spiritmark.annotation.TimeStamp)")
public void timeStampPointcut() {}
@Before("timeStampPointcut() && @annotation(timeStamp)")
public void setTimestamp(JoinPoint joinPoint, TimeStamp timeStamp) {
Long currentTime = System.currentTimeMillis();
Class<?> type = timeStamp.type();
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (type.isInstance(arg)) {
setTimestampForArg(arg, timeStamp);
}
}
}
private void setTimestampForArg(Object arg, TimeStamp timeStamp) {
Date currentDate = new Date(System.currentTimeMillis());
TimeStampRank rank = timeStamp.rank();
Method[] methods = arg.getClass().getMethods();
for (Method method : methods) {
String methodName = method.getName();
if (isSetter(methodName) && isRelevantSetter(methodName, rank)) {
try {
method.invoke(arg, currentDate);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
private boolean isSetter(String methodName) {
return methodName.startsWith("set") && methodName.length() > 3;
}
private boolean isRelevantSetter(String methodName, TimeStampRank rank) {
if (rank.equals(TimeStampRank.FULL)) {
return methodName.endsWith("At");
}
if (rank.equals(TimeStampRank.UPDATE)) {
return methodName.startsWith("setUpdated");
}
if (rank.equals(TimeStampRank.CREATE)) {
return methodName.startsWith("setCreated");
}
return false;
}
}
1.使用@Aspect和@Component注解分別標(biāo)注切面和切面類,更符合AOP的實(shí)現(xiàn)方式。
2.將pointCut()和before()方法分別改名為timeStampPointcut()和setTimestamp(),更能表達(dá)它們的作用。
3.通過(guò)Class.isInstance(Object obj)方法,將原先的流操作改為了一個(gè)簡(jiǎn)單的for循環(huán),使代碼更加簡(jiǎn)潔。
4.將原先的setCurrentTime()方法改名為setTimestampForArg(),更能表達(dá)它的作用。
5.新增了兩個(gè)私有方法isSetter()和isRelevantSetter(),將原先在setTimestampForArg()中的邏輯分離出來(lái),提高了代碼的可讀性和可維護(hù)性
到此這篇關(guān)于詳解Java中自定義注解的使用的文章就介紹到這了,更多相關(guān)Java自定義注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何根據(jù)帶賬號(hào)密碼的WSDL地址生成JAVA代碼
這篇文章主要介紹了如何根據(jù)帶賬號(hào)密碼的WSDL地址生成JAVA代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Jmeter參數(shù)化實(shí)現(xiàn)方法及應(yīng)用實(shí)例
這篇文章主要介紹了Jmeter參數(shù)化實(shí)現(xiàn)方法及應(yīng)用實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
spring源碼學(xué)習(xí)之bean的初始化以及循環(huán)引用
這篇文章主要給大家介紹了關(guān)于spring源碼學(xué)習(xí)之bean的初始化以及循環(huán)引用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
java與JSON數(shù)據(jù)的轉(zhuǎn)換實(shí)例詳解
這篇文章主要介紹了java與JSON數(shù)據(jù)的轉(zhuǎn)換實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03
Spring?Bean注冊(cè)與注入實(shí)現(xiàn)方法詳解
首先,要學(xué)習(xí)Spring中的Bean的注入方式,就要先了解什么是依賴注入。依賴注入是指:讓調(diào)用類對(duì)某一接口的實(shí)現(xiàn)類的實(shí)現(xiàn)類的依賴關(guān)系由第三方注入,以此來(lái)消除調(diào)用類對(duì)某一接口實(shí)現(xiàn)類的依賴。Spring容器中支持的依賴注入方式主要有屬性注入、構(gòu)造函數(shù)注入、工廠方法注入2022-10-10
Spring Boot 2.0.0 終于正式發(fā)布-重大修訂版本
北京時(shí)間 2018 年 3 月 1 日早上,如約發(fā)布的 Spring Boot 2.0 在同步至 Maven 倉(cāng)庫(kù)時(shí)出現(xiàn)問(wèn)題,導(dǎo)致在 GitHub 上發(fā)布的 v2.0.0.RELEASE 被撤回2018-03-03
Java8中LocalDateTime與時(shí)間戳timestamp的互相轉(zhuǎn)換
這篇文章主要給大家介紹了關(guān)于Java8中LocalDateTime與時(shí)間戳timestamp的互相轉(zhuǎn)換,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

