权衡轻重 發表於 2025-11-14 20:03:00

JAVA自定义注解

<h2><strong>什么是注解?</strong></h2>
<p><span style="font-family: 宋体">注解是一种特殊的接口,用于为</span>Java<span style="font-family: 宋体">代码提供元数据。它们不会直接影响代码的执行,但可以被编译器、开发工具或运行时环境读取和使用。</span></p>
<p>Java<span style="font-family: 宋体">内置了一些常用的注解,如:</span></p>
<p>@Override&nbsp;- <span style="font-family: 宋体">表示方法重写父类方法</span></p>
<p>@Deprecated&nbsp;- <span style="font-family: 宋体">表示代码已过时</span></p>
<p>@SuppressWarnings&nbsp;- <span style="font-family: 宋体">抑制编译器警告</span></p>
<h2><strong>注解的基本语法</strong></h2>
<h3><strong>定义注解</strong></h3>
<p><span style="font-family: 宋体">使用</span>@interface<span style="font-family: 宋体">关键字来定义注解:</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> @<span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> AutoFill {
}</span></pre>
</div>
<p>&nbsp;</p>
<h3><strong>元注解</strong></h3>
<p><span style="font-family: 宋体">元注解是用来注解其他注解的注解,</span>Java<span style="font-family: 宋体">提供了以下几种元注解:</span></p>
<p>@Target&nbsp;- <span style="font-family: 宋体">指定注解可以应用的目标元素类型</span></p>
<p>@Retention&nbsp;- <span style="font-family: 宋体">指定注解的保留策略</span></p>
<p>@Documented&nbsp;- <span style="font-family: 宋体">表示注解应该被包含在</span><span style="font-family: Calibri">Javadoc</span><span style="font-family: 宋体">中</span></p>
<p>@Inherited&nbsp;- <span style="font-family: 宋体">表示注解可以被继承</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> @<span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> AutoFill {
    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 数据库操作类型:INSERT、UPDATE
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    OperationType value();
}</span></pre>
</div>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">示例代码展示了一个用于公共字段自动填充的自定义注解,</span><span style="font-family: Calibri">@Target</span><span style="font-family: 宋体">明确注解可在方法上使用,</span><span style="font-family: Calibri">@Retention</span><span style="font-family: 宋体">明确在程序运行时可见。</span></p>
<p>&nbsp;</p>
<h3><strong>注解元素</strong></h3>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">注解中可以定义元素,这些元素可以有默认值:</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">enum</span><span style="color: rgba(0, 0, 0, 1)"> OperationType {

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 更新操作
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    UPDATE,

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 插入操作
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    INSERT

}</span></pre>
</div>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">示例自定义注解中的</span><span style="font-family: Calibri">value</span><span style="font-family: 宋体">方法则用来返回上示枚举类型数据,明确 使用该注解的方法 的作用,使用方式如下:</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
      * 更新员工信息
      * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> employee
      </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
   @AutoFill(OperationType.UPDATE)
   </span><span style="color: rgba(0, 0, 255, 1)">void</span> updateById(Employee employee);</pre>
</div>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">当注解只有一个方法且方法名为</span>&nbsp;value&nbsp;<span style="font-family: 宋体">时,使用时可以省略方法名</span>,<span style="font-family: 宋体">如果方法不叫</span>&nbsp;value<span style="font-family: 宋体">,就必须明确指定方法名</span>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> @<span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> AutoFill {
    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 数据库操作类型:INSERT、UPDATE
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    OperationType type();
}<br><br></span></pre>
<pre>/**
      * 更新员工信息
      * @param employee
      */<span>
   @AutoFill(type = OperationType.UPDATE)
   void updateById(Employee employee);</span></pre>
</div>
<h2><strong>自定义注解</strong><strong>的使用</strong></h2>
<h3><strong>通过反射处理注解</strong></h3>
<p><span style="font-family: 宋体">我们可以使用反射机制在运行时读取和处理注解:</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Aspect
@Component
@Slf4j
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> AutoFillAspect {

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 公共字段自动填充切入点
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    @Pointcut(</span>"execution(* com.sky.mapper.*.*(..)) &amp;&amp; @annotation(com.sky.annotation.AutoFill)"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> autoFillPointCut() {}

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 公共字段自动填充
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    @Before(</span>"autoFillPointCut()"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> autoFill(JoinPoint joinPoint) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable {
      log.info(</span>"公共字段自动填充通知开始"<span style="color: rgba(0, 0, 0, 1)">);
      MethodSignature signature </span>=<span style="color: rgba(0, 0, 0, 1)"> (MethodSignature)joinPoint.getSignature();
      AutoFill autoFill</span>= signature.getMethod().getAnnotation(AutoFill.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取数据库操作类型</span>
      Enum operationType =<span style="color: rgba(0, 0, 0, 1)"> autoFill.value();
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从ThreadLocal中获取当前登录用户的id</span>
      Long currentId =<span style="color: rgba(0, 0, 0, 1)"> BaseContext.getCurrentId();
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取当前时间</span>
      LocalDateTime now =<span style="color: rgba(0, 0, 0, 1)"> LocalDateTime.now();
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从joinPoint中获取参数</span>
      Object[] args =<span style="color: rgba(0, 0, 0, 1)"> joinPoint.getArgs();
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(args==<span style="color: rgba(0, 0, 255, 1)">null</span> || args.length==0<span style="color: rgba(0, 0, 0, 1)">){
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从参数中获取实体对象</span>
      Object entity = args;
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 调用实体对象的方法,设置创建时间、创建人、更新时间、更新人</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span>(operationType==<span style="color: rgba(0, 0, 0, 1)">OperationType.INSERT){
            </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">{
                Method setCreateTime </span>= entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                Method setUpdateTime </span>= entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                Method setCreateUser </span>= entity.getClass().getDeclaredMethod("setCreateUser", Long.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                Method setUpdateUser </span>= entity.getClass().getDeclaredMethod("setUpdateUser", Long.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                setCreateTime.invoke(entity, now);
                setUpdateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateUser.invoke(entity, currentId);
            }</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e){
                log.error(</span>"公共字段自动填充通知异常"<span style="color: rgba(0, 0, 0, 1)">, e);
            }
      }</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span>(operationType==<span style="color: rgba(0, 0, 0, 1)">OperationType.UPDATE){
            </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">{
                Method setUpdateTime </span>= entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                Method setUpdateUser </span>= entity.getClass().getDeclaredMethod("setUpdateUser", Long.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            }</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e){
                log.error(</span>"公共字段自动填充通知异常"<span style="color: rgba(0, 0, 0, 1)">, e);
            }
      }

    }
}</span></pre>
</div>
<p>&nbsp;</p>
<p>上述示例展示的是@AutoFill注解进行公共字段自动填充必要的切面类。</p>
<div class="cnblogs_code">
<pre>@Aspect</pre>
</div>
<p>该注解用于声明切面类。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 公共字段自动填充切入点
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    @Pointcut(</span>"execution(* com.sky.mapper.*.*(..)) &amp;&amp; @annotation(com.sky.annotation.AutoFill)"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> autoFillPointCut() {}</pre>
</div>
<p>这一部分用于声明哪些部分需要拦截。(在com.sky.mapper包下的所有类及其所有方法 <strong>且</strong> 被自定义注解(@AutoFill)所标注)</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 公共字段自动填充
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    @Before(</span>"autoFillPointCut()"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> autoFill(JoinPoint joinPoint) <span style="color: rgba(0, 0, 255, 1)">throws</span> Throwable </pre>
</div>
<p>@Before: <span style="font-family: 宋体">表示这是一个</span><span style="font-family: 宋体">前置通知,在目标方法执行之前运行。</span></p>
<p>autoFillPointCut(): <span style="font-family: 宋体">引用一个切点表达式,定义了哪些方法需要被拦截。</span></p>
<p>&nbsp;</p>
<p>JoinPoint: AOP <span style="font-family: 宋体">连接点对象,可以获取被拦截方法的详细信息:</span><span style="font-family: 宋体">方法名、参数、目标对象等</span>。<span style="font-family: 宋体">通过</span>&nbsp;joinPoint.getArgs()&nbsp;<span style="font-family: 宋体">获取方法参数。</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre>MethodSignature signature =<span style="color: rgba(0, 0, 0, 1)"> (MethodSignature)joinPoint.getSignature();
AutoFill autoFill</span>= signature.getMethod().getAnnotation(AutoFill.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取数据库操作类型</span>
Enum operationType = autoFill.value();</pre>
</div>
<p>&nbsp;</p>
<p>joinPoint.getSignature(): <span style="font-family: 宋体">获取连接点的签名信息。</span></p>
<p>(MethodSignature): <span style="font-family: 宋体">强制转换为</span><span style="font-family: Calibri">&nbsp;MethodSignature&nbsp;</span><span style="font-family: 宋体">类型,因为 </span><span style="font-family: Calibri">Spring AOP </span><span style="font-family: 宋体">只拦截方法。</span></p>
<p>signature.getMethod(): <span style="font-family: 宋体">获取被拦截的</span>&nbsp;Method&nbsp;<span style="font-family: 宋体">对象。</span></p>
<p>.getAnnotation(AutoFill.class): <span style="font-family: 宋体">从方法上获取</span><span style="font-family: Calibri">&nbsp;@AutoFill&nbsp;</span><span style="font-family: 宋体">注解。</span></p>
<p>autoFill.value(): <span style="font-family: 宋体">调用注解的</span>&nbsp;value()&nbsp;<span style="font-family: 宋体">方法,获取注解中定义的枚举值。</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从joinPoint中获取参数</span>
      Object[] args =<span style="color: rgba(0, 0, 0, 1)"> joinPoint.getArgs();
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(args==<span style="color: rgba(0, 0, 255, 1)">null</span> || args.length==0<span style="color: rgba(0, 0, 0, 1)">){
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从参数中获取实体对象</span>
      Object entity = args;</pre>
</div>
<p>&nbsp;</p>
<p>利用从joinPoint中获取的参数获得原本方法中传递的实体对象。</p>
<div class="cnblogs_code">
<pre>Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);</span><span style="color: rgba(0, 0, 0, 1)">
Method setCreateUser </span>= entity.getClass().getDeclaredMethod("setCreateUser", Long.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);</span><span style="color: rgba(0, 0, 0, 1)">
setCreateTime.invoke(entity, now);
setCreateUser.invoke(entity, currentId);</span></pre>
</div>
<p>利用反射为对象设置公共字段。</p>
<p><strong><span style="font-size: 14pt">最终效果</span></strong></p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/3291169/202511/3291169-20251114200942358-2086750487.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/3291169/202511/3291169-20251114200946401-2000674547.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/3291169/202511/3291169-20251114200953896-1186014324.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/3291169/202511/3291169-20251114200959439-1357514389.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/3291169/202511/3291169-20251114201154520-130228613.png"></p>
<p>@AutoFill注解能有效替代原本冗长且重复的公共字段设置代码。</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/syf0824/p/19223247
頁: [1]
查看完整版本: JAVA自定义注解