按照我的理解,Spring的注解是一個標簽,為的是標注一下某個方法(目前我所經歷的注解應用的最多的就是在方法名上),然后在某個地方找到有這個標記的方法,對這些方法做一些自定義的處理。
現在知道在我們項目中自定義的注解的處理方法的實現是通過寫切面來實現的,比如有個@Log注解,它可以把一些關鍵點的日志保存到數據庫里。于是就定義了一個切面,切面會找到被這個標簽標注的類,然后為它們保存日志信息。
import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
import java.lang.reflect.Method;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import com.kyee.common.service.LogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.kyee.common.annotation.Log;
import com.kyee.common.domain.LogDO;
import com.kyee.common.utils.HttpContextUtils;
import com.kyee.common.utils.IPUtils;
import com.kyee.common.utils.JSONUtils;
import com.kyee.common.utils.ShiroUtils;
import com.kyee.system.domain.UserDO;
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Autowired
LogService logService;
@Pointcut("@annotation(com.kyee.common.annotation.Log)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 執行方法
Object result = point.proceed();
// 執行時長(毫秒)
long time = System.currentTimeMillis() - beginTime;
//異步保存日志
saveLog(point, time);
return result;
}
void saveLog(ProceedingJoinPoint joinPoint, long time) throws InterruptedException {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogDO sysLog = new LogDO();
Log syslog = method.getAnnotation(Log.class);
if (syslog != null) {
// 注解上的描述
sysLog.setOperation(syslog.value());
}
// 請求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
// 請求的參數
Object[] args = joinPoint.getArgs();
try {
String params = JSONUtils.beanToJson(args[0]).substring(0, 4999);
sysLog.setParams(params);
} catch (Exception e) {
}
// 獲取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// 設置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
// 用戶名
UserDO currUser = ShiroUtils.getUser();
if (null == currUser) {
if (null != sysLog.getParams()) {
sysLog.setUserId(-1L);
sysLog.setUsername(sysLog.getParams());
} else {
sysLog.setUserId(-1L);
sysLog.setUsername("獲取用戶信息為空");
}
} else {
sysLog.setUserId(ShiroUtils.getUserId());
sysLog.setUsername(ShiroUtils.getUser().getUsername());
}
sysLog.setTime((int) time);
// 系統當前時間
Date date = new Date();
sysLog.setGmtCreate(date);
// 保存系統日志
logService.save(sysLog);
}
}
那么Spring的這些注解理應在Spring框架中進行實現了,比如說RequestMapping這個注解,但是點擊進去只能看到注解的定義,并不知道它的實現代碼在哪里.
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
看到一篇文章:https://dzone.com/articles/spring-annotation-processing-how-it-works
If you see an annotation, there must be some code somewhere to process it.
如果你看到了一個注解,那么一定在某個地方有段代碼去處理它。
在InitDestroyBeanPostProcessor類中有下面一段代碼:
private InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata buildLifecycleMetadata(Class<?> clazz) {
boolean debug = this.logger.isDebugEnabled();
LinkedList initMethods = new LinkedList();
LinkedList destroyMethods = new LinkedList();
Class targetClass = clazz;
do {
LinkedList currInitMethods = new LinkedList();
LinkedList currDestroyMethods = new LinkedList();
ReflectionUtils.doWithLocalMethods(targetClass, (method) -> {
if(this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
InitDestroyAnnotationBeanPostProcessor.LifecycleElement element = new InitDestroyAnnotationBeanPostProcessor.LifecycleElement(method);
currInitMethods.add(element);
if(debug) {
this.logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
if(this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
currDestroyMethods.add(new InitDestroyAnnotationBeanPostProcessor.LifecycleElement(method));
if(debug) {
this.logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
});
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
} while(targetClass != null && targetClass != Object.class);
return new InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata(clazz, initMethods, destroyMethods);
}
根據反射方法尋找注解標記,如果找到了注解標記,這個被標記的方法被當做一個初始化方法存了下來。然后就會調用所有找到的初始化方法,給對象執行這些操作。
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata metadata = this.findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
return bean;
} catch (InvocationTargetException var5) {
throw new BeanCreationException(beanName, "Invocation of init method failed", var5.getTargetException());
} catch (Throwable var6) {
throw new BeanCreationException(beanName, "Failed to invoke init method", var6);
}
}
隱隱的感覺到是這么處理的,感覺還沒有理解很透徹,待我繼續調查回來再更。。。