SpringBoot自定義注解


1.注解的概念

注解是一種能被添加到java代碼中的元數據,類、方法、變量、參數和包都可以用注解來修飾。用來定義一個類、屬性或一些方法,以便程序能被捕譯處理。 相當於一個說明文件,告訴應用程序某個被注解的類或屬性是什么,要怎么處理。注解對於它所修飾的代碼並沒有直接的影響。

2.注解的使用范圍

1)為編譯器提供信息:注解能被編譯器檢測到錯誤或抑制警告。

2)編譯時和部署時的處理: 軟件工具能處理注解信息從而生成代碼,XML文件等等。

3)運行時的處理:有些注解在運行時能被檢測到。

3.自定義注解的步驟

第一步:定義注解

第二步:配置注解

第三步:解析注解

4.注解的基本語法

4.1最基本的注解定義

package com.example.demo.config;

public @interface MyAnnotation {
    public String name();
    int age();
    String sex() default "";
}

在自定義注解中,其實現部分只能定義注解類型元素!

說明:

a.訪問修飾符必須為public,不寫默認為public;

b.該元素的類型只能是基本數據類型、String、Class、枚舉類型、注解類型以及一維數組;

c.該元素的名稱一般定義為名詞,如果注解中只有一個元素,名字起為value最好;

d.()不是定義方法參數的地方,也不能在括號中定義任何參數,僅僅只是一個特殊的語法;

e.default代表默認值,值必須定義的類型一致;

f.如果沒有默認值,代表后續使用注解時必須給該類型元素賦值。

4.2常用的元注解

元注解:專門修飾注解的注解。

4.2.1@Target

@Target是專門用來限定某個自定義注解能夠被應用在哪些Java元素上面的。其注解的源碼如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

從源碼可以看出它使用一個枚舉類型元素,接下來看這個枚舉類型的源碼:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

因此,我們可以在使用@Target時指定注解的使用范圍,示例如下:

//@MyAnnotation被限定只能使用在類、接口或方法上面
@Target(value = {ElementType.METHOD,ElementType.TYPE})
public @interface MyAnnotation {
    public String name();
    int age();
    String sex() default "";
}

4.2.2@Retention

@Retention注解,用來修飾自定義注解的生命力。

  a.如果一個注解被定義為RetentionPolicy.SOURCE,則它將被限定在Java源文件中,那么這個注解即不會參與編譯也不會在運行期起任何作用,這個注解就和一個注釋是一樣的效果,只能被閱讀Java文件的人看到;
  b.如果一個注解被定義為RetentionPolicy.CLASS,則它將被編譯到Class文件中,那么編譯器可以在編譯時根據注解做一些處理動作,但是運行時JVM(Java虛擬機)會忽略它,我們在運行期也不能讀取到,是默認的;
  c.如果一個注解被定義為RetentionPolicy.RUNTIME,那么這個注解可以在運行期的加載階段被加載到Class對象中。那么在程序運行階段,我們可以通過反射得到這個注解,並通過判斷是否有這個注解或這個注解中屬性的值,從而執行不同的程序代碼段。我們實際開發中的自定義注解幾乎都是使用的RetentionPolicy.RUNTIME。
@Retention注解源碼如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

里面也是一個枚舉類型元素,其源碼如下:

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,

    /**
     * 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.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

使用此注解修飾自定義注解生命力的示例如下:

//設置注解的生命力在運行期
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    public String name();
    int age();
    String sex() default "";
}

4.2.3@Documented

@Documented注解,是被用來指定自定義注解是否能隨着被定義的java文件生成到JavaDoc文檔當中。源碼如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

4.2.4@Inherited

@Inherited注解,是指定某個自定義注解如果寫在了父類的聲明部分,那么子類(繼承關系)的聲明部分也能自動擁有該注解。該注解只對@Target被定義為ElementType.TYPE的自定義注解起作用。

5.自定義注解舉例

第一步:自定義的注解如下

package com.example.demo.config;

import java.lang.annotation.*;

@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    public String name();
    int age();
    String sex() default "";
    String[] hobby();
}

第二步:創建一個類,新建方法使用該注解

package com.example.demo.controller;

import com.example.demo.config.MyAnnotation;

public class UserController {

    @MyAnnotation(name = "張三",age = 18,hobby = {"跑步,打游戲"})
    public String get(){
        return "Hello Annotation";
    }
}

第三步:利用反射獲取注解。創建一個類,代碼如下:

package com.example.demo.test;

import com.example.demo.config.MyAnnotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) {
        try {
            //獲取Class對象
            Class mylass = Class.forName("com.example.demo.controller.UserController");
            //獲得該對象身上配置的所有的注解
            Annotation[] annotations = mylass.getAnnotations();
            System.out.println(annotations.toString());
            //獲取里面的一個方法
            Method method = mylass.getMethod("get");
            //判斷該元素上是否配置有某個指定的注解
            if(method.isAnnotationPresent(MyAnnotation.class)){
                System.out.println("UserController類的get方法上配置了MyAnnotation注解!");
                //獲取該元素上指定類型的注解
                MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
                System.out.println("name: " + myAnnotation.name() + ", age: " + myAnnotation.age()
                        + ",sex:"+myAnnotation.sex()+", hobby: " + myAnnotation.hobby()[0]);
            }else{
                System.out.println("UserController類的get方法上沒有配置MyAnnotation注解!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

打印結果如下:

如果要獲得的注解是配置在方法上的,從Method對象上獲取;如果是配置在屬性上,就需要從該屬性對應的Field對象上去獲取,如果是配置在類型上,需要從Class對象上去獲取。

6.注解的特殊語法

特殊的語法是基於5的,這里就直接講述特殊的定義和使用。

1)如果注解沒有注解類型元素,那么在使用注解時可省略(),直接寫為:@注解名。

定義如下:

@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
}

使用如下:

public class UserController {

    @MyAnnotation public String get(){
        return "Hello Annotation";
    }
}

2)如果注解只有一個注解類型元素,且命名為value,那么在使用注解時可直接寫為:@注解名(注解值)。

定義如下:

@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    String value();
}

使用如下:

public class UserController {

    @MyAnnotation("hello")
    public String get(){
        return "Hello Annotation";
    }
}

3)如果注解中的某個注解類型元素是一個數組類型,在使用時又出現只需要填入一個值的情況,那么在使用注解時可直接寫為:@注解名(類型名 = 類型值),和標准的@注解名(類型名 = {類型值})等效!

定義如下:

@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    String[] arr();
}

使用如下:

public class UserController {

    @MyAnnotation(arr = "hello")
    public String get(){
        return "Hello Annotation";
    }
}

4)如果注解的@Target定義為Element.PACKAGE,那么這個注解是配置在package-info.java中的,而不能直接在某個類的package代碼上面配置。

7.在項目中使用自定義的注解

源代碼:https://github.com/zhongyushi-git/annotation-demo.git

7.1環境搭建

1)新建一個SpringBoot的項目,導入jar座標

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

2)配置application.yml

#數據源配置
spring:
  datasource:
    #使用阿里巴巴的druid
    type: com.alibaba.druid.pool.DruidDataSource
    #配置數據庫的路徑和用戶名密碼
    url: jdbc:mysql://127.0.0.1:3306/annotation?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
    username: root
    password: 123456


mybatis:
  mapperLocations: classpath*:mapper/*Mapper.xml

#開啟日志打印
logging:
  level:
    com.zys.training: debug

3)執行sql腳本

create database annotation;
use annotation;
CREATE TABLE `systemlog`  (
  `id` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志主鍵',
  `title` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '標題',
  `describe` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '模塊描述',
  `create_time` datetime NULL DEFAULT NULL COMMENT '記錄時間',
  `method` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '調用方法',
  `error` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '錯誤信息',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

7.2創建日志的MVC

1)創建日志類

package com.zys.springboot.annotationdemo.entity;


import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.Date;

@Getter
@Setter
@ToString
public class SystemLog {
    private String id;
    private String title;
    private String describe;
    private Date create_time;
    private String method;
    private String error;
}

2)創建service

package com.zys.springboot.annotationdemo.service;

import com.zys.springboot.annotationdemo.entity.SystemLog;

public interface SystemLogService {
    int createLog(SystemLog log);
}

3)創建impl

package com.zys.springboot.annotationdemo.service.impl;

import com.zys.springboot.annotationdemo.dao.SystemLogDao;
import com.zys.springboot.annotationdemo.entity.SystemLog;
import com.zys.springboot.annotationdemo.service.SystemLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SystemLogServiceImpl implements SystemLogService {
    @Autowired
    private SystemLogDao systemLogDao;
    @Override
    public int createLog(SystemLog log) {
        return systemLogDao.createLog(log);
    }
}

4)創建dao

package com.zys.springboot.annotationdemo.dao;

import com.zys.springboot.annotationdemo.entity.SystemLog;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SystemLogDao {
    int createLog(SystemLog log);
}

5)創建mapper

在resources目錄下新建mapper目錄,然后創建文件SystemLogMapper.xml  

<?xml version="1.0" encoding="uTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zys.springboot.annotationdemo.dao.SystemLogDao">
    <!--插入系統日志-->
    <insert id="createLog" parameterType="com.zys.springboot.annotationdemo.entity.SystemLog">
        insert into systemLog values(#{id},#{title},#{describe},sysdate(),#{method},#{error})
    </insert>

</mapper>

7.3自定義注解

1)創建注解

package com.zys.springboot.annotationdemo.config;

import java.lang.annotation.*;

/**
 * 自定義日志注解
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    String title() default "";//模塊名稱
    String describe() default "";//描述
}

2)創建aop切面

package com.zys.springboot.annotationdemo.config;

import com.zys.springboot.annotationdemo.entity.SystemLog;
import com.zys.springboot.annotationdemo.service.SystemLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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 java.lang.reflect.Method;
import java.util.UUID;

@Aspect
@Component("logAspect")
public class LogAspect {
    @Autowired
    private SystemLogService logService;

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    // 配置織入點
    @Pointcut("@annotation(com.zys.springboot.annotationdemo.config.Log)")
    public void logPointCut() {
    }

    /**
     * 前置通知 用於攔截操作,在方法返回后執行
     *
     * @param joinPoint 切點
     */
    @AfterReturning(pointcut = "logPointCut()")
    public void doBefore(JoinPoint joinPoint) {
        handleLog(joinPoint, null);
    }

    /**
     * 攔截異常操作,有異常時執行
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfter(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e);
    }

    private void handleLog(JoinPoint joinPoint, Exception e) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        SystemLog systemLog = new SystemLog();
        //獲取方法名
        String functionName = signature.getDeclaringTypeName() + "." + signature.getName() + "()";
        //獲取注解對象
        Log annotation = signature.getMethod().getAnnotation(Log.class);
        if (annotation != null) {
            systemLog.setId(UUID.randomUUID().toString().replace("-", ""));
            systemLog.setMethod(functionName);
            //獲取注解中對方法的描述信息
            systemLog.setTitle(annotation.title());
            systemLog.setDescribe(annotation.describe());
            if (e != null) {
                String err = e.getMessage();
                if (err != null && err.length() > 4000) {
                    err = err.substring(0, 4000);
                }
                systemLog.setError(err);
            }
        }
        //記錄到數據庫
        logService.createLog(systemLog);
    }

    /**
     * 是否存在注解,如果存在就獲取
     */
    private static Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

}

7.4創建測試接口

在controller包下創建UserController類,用於測試注解。

package com.zys.springboot.annotationdemo.controller;

import com.zys.springboot.annotationdemo.config.Log;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    //使用日志注解
    @Log(title = "用戶模塊",describe = "獲取用戶列表")
    @GetMapping("/get")
    public String get(){
        return "Hello word!";
    }

    @Log(title = "用戶模塊",describe = "測試接口")
    @GetMapping("/test")
    public String test(){
        return "Hello Test!";
    }

}

7.5測試

啟動項目,訪問http://localhost:8080/get,然后查詢數據庫,發現日志已經記錄了,如下圖,同理訪問http://localhost:8080/test。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM