一、注解的分类
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; } } } }