一、注解的分类
java.lang.annotation 提供了四种元注解:
1、java本身自带的注解 ,如:
@Override(重写父类方法)
@Deprecated(过时注解)
@SuppressWarnings(警告),使用这些注解后编译器就会进行检查。
2、元注解,元注解是用于定义注解的注解:
@Target:注解用于什么地方
@Retention:注解的生命周期
@Documented:注解是否应当被包含在 JavaDoc 文档中
@Inherited: 是否允许子类继承该注解
@Documented:注解是否将包含在JavaDoc中
3、自定义注解,根据项目开发需要,自己定义的注解
二、自定义注解
1、自定义注解类编写时,需要遵循一些基本的规范:
(1)、注解类的类型定义为@interface, 所有的注解类会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
(2)、参数成员只能用public 或默认(default) 这两个访问权修饰
(3)、参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
(4)、要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象
这是自己定义的一个注解
import com.dianyi.common.enums.BusinessType;
import com.dianyi.common.enums.OperatorType;
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;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String title() default "";
BusinessType businessType() default BusinessType.OTHER;
OperatorType operatorType() default OperatorType.MANAGE;
boolean isSaveRequestData() default true;
}
2、正式的开发一个注解
(1)、先开发一个注解类 Log
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.OperatorType;
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;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String title() default "";
BusinessType businessType() default BusinessType.OTHER;
OperatorType operatorType() default OperatorType.MANAGE;
boolean isSaveRequestData() default true;
}
(2)、使用注解很简单,直接在需要的方法上面加上即可
@PreAuthorize("@ss.hasPermi('backup:queue:edit')")
@Log(title = "XXXX", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody BackupQueueManage backupQueueManage)
{
backupQueueManage.setUpdateBy(SecurityContextHolder.getContext().getAuthentication().getName());
return backupQueueManageService.updateBackupQueueManage(backupQueueManage);
}
(3)、使用反射的方式,找到该注解,本案例主要结合了面向切面Aop技术,
@Aspect
@Component
public class LogAspect
{
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.annotation.Log)")
public void logPointCut()
{
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()")
public void doAfterReturning(JoinPoint joinPoint)
{
handleLog(joinPoint, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e)
{
try
{
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null)
{
return;
}
// 获取当前的用户
SysUser currentUser = ShiroUtils.getSysUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ShiroUtils.getIp();
operLog.setOperIp(ip);
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (currentUser != null)
{
operLog.setOperName(currentUser.getLoginName());
if (StringUtils.isNotNull(currentUser.getDept())
&& StringUtils.isNotEmpty(currentUser.getDept().getDeptName()))
{
operLog.setDeptName(currentUser.getDept().getDeptName());
}
}
if (e != null)
{
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
catch (Exception exp)
{
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
三、注解的原理及本质
注解其实就是一个继承了Annotation类的接口,它具体实现类是Java 运行时生成的动态代理类。而当我们通过java反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。
通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke方法。
该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池,部分代码如下:
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}