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。