java自定注解完整示例代碼
1 理解Java注解
實際上Java注解與普通修飾符(public、static、void等)的使用方式并沒有多大區(qū)別,下面的例子是常見的注解:
public class AnnotationDemo {
//@Test注解修飾方法A
@Test
public static void A(){
System.out.println("Test.....");
}
//一個方法上可以擁有多個不同的注解
@Deprecated
@SuppressWarnings("uncheck")
public static void B(){
}
}
@Test實際上是一種標記注解,起標記作用,運行時告訴測試框架該方法為測試方法。
而對于@Deprecated和@SuppressWarnings(“uncheck”),則是Java本身內(nèi)置的注解,在代碼中,可以經(jīng)常看見它們。
2 Java內(nèi)置標準注解
JavaSE中內(nèi)置三個標準注解,定義在java.lang中:
@Override:用于修飾此方法覆蓋了父類的方法;@Deprecated:用于修飾已經(jīng)過時的方法;@SuppressWarnnings:用于通知java編譯器禁止特定的編譯警告。
2.1@Override
用于標明此方法覆蓋了父類的方法。
2.2@Deprecated
用于標明已經(jīng)過時的方法或類。用 @Deprecated 注釋的程序元素,不鼓勵程序員使用這樣的元素,通常是因為它很危險或存在更好的選擇。在使用不被贊成的程序元素或在不被贊成的代碼中執(zhí)行重寫時,編譯器會發(fā)出警告。
2.3@SuppressWarnnings
用于有選擇的關閉編譯器對類、方法、成員變量、變量初始化的警告。SuppressWarnings annotation類型只定義了一個單一的成員,所以只有一個簡單的value={…}作為name=value對。又由于成員值是一個數(shù)組,故使用大括號來聲明數(shù)組值。
其數(shù)組的值可以為下來枚舉:
- deprecation:使用了不贊成使用的類或方法時的警告;
- unchecked:執(zhí)行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型;
- fallthrough:當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告;
- path:在類路徑、源文件路徑等中有不存在的路徑時的警告;
- serial:當在可序列化的類上缺少 serialVersionUID 定義時的警告;
- finally:任何 finally 子句不能正常完成時的警告;
- all:關于以上所有情況的警告。
注意:我們可以在下面的情況中縮寫annotation:當annotation只有單一成員,并成員命名為"value=“。這時可以省去"value=”。如:
@SuppressWarnings({"unchecked","deprecation"})
public void test1() {
}
3 注解基礎
3.1 注解分類
根據(jù)注解參數(shù)的個數(shù),注解分為標記注解、單值注解、完整注解三類。
(1)標記注解:一個沒有成員定義的Annotation類型被稱為標記注解。如:@Test,@Inherited,@Documented 等
(2)單值注解:只有一個值
(3)完整注解:擁有多個值。
根據(jù)注解使用方法和用途:
(1)JDK內(nèi)置系統(tǒng)注解
(2)元注解
(3)自定義注解
3.2 元注解
所謂元注解就是標記其他注解的注解。
Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型作說明。
3.2.1 @Target
表示該注解用于什么地方,可能的值在枚舉類ElemenetType。
| 類型枚舉 | 說明 |
|---|---|
| ElemenetType.CONSTRUCTOR | 標明注解可以用于構造函數(shù)聲明 |
| ElemenetType.FIELD | 標明該注解可以用于字段(域)聲明,包括enum實例 |
| ElemenetType.LOCAL_VARIABLE | 標明注解可以用于局部變量聲明 |
| ElemenetType.METHOD | 標明該注解可以用于方法聲明 |
| ElemenetType.PACKAGE | 標明注解可以用于包聲明 |
| ElemenetType.PARAMETER | 標明該注解可以用于參數(shù)聲明 |
| ElemenetType.TYPE | 標明該注解可以用于類、接口(包括注解類型)或enum聲明 |
| ElemenetType.ANNOTATION_TYPE | 標明注解可以用于注解聲明(應用于另一個注解上) |
| ElemenetType.TYPE_PARAMETER | 標明注解可以用于類型參數(shù)聲明(1.8新加入) |
| ElemenetType.TYPE_USE | 類型使用聲明(1.8新加入) |
示例1:
@Target(ElementType.TYPE)
public @interface Table {
/**
* 數(shù)據(jù)表名稱注解,默認值為類名稱
* @return
*/
public String tableName() default "className";
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {
}
@Table 可以用于注解類、接口(包括注解類型) 或enum聲明,而@NoDBColumn僅可用于注解類的成員變量。
3.2.2 @Retention
用來約束注解的生命周期??蛇x的參數(shù)值在枚舉類型RetentionPolicy中,分別有三個值,源碼級別(source),類文件級別(class)或者運行時級別(runtime)。
| 類型枚舉 | 說明 |
|---|---|
| RetentionPolicy.SOURCE | 注解將被編譯器丟棄(該類型的注解信息只會保留在源碼里,源碼經(jīng)過編譯后,注解信息會被丟棄,不會保留在編譯好的class文件里,如@Override) |
| RetentionPolicy.CLASS | 注解在class文件中可用,但會被VM丟棄(該類型的注解信息會保留在源碼里和class文件里,在執(zhí)行的時候,不會加載到虛擬機中),請注意,當注解未定義Retention值時,默認值是CLASS |
| RetentionPolicy.RUNTIME | 注解信息將在運行期(JVM)也保留,因此可以通過反射機制讀取注解的信息(源碼、class文件和執(zhí)行的時候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。這也是我們常用的。 |
示例1:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
Column注解的的RetentionPolicy的屬性值是RUTIME,這樣注解處理器可以通過反射,獲取到該注解的屬性值,從而去做一些運行時的邏輯處理。
3.2.3 @Documented
用于描述其它類型的annotation應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記注解,沒有成員。
3.2.4 @Inherited
@Inherited元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation將被用于該class的子類。
注意:@Inherited annotation類型是被標注過的class的子類所繼承。類并不從它所實現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
當@Inherited annotation類型標注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達類繼承結構的頂層。
3.3 注解支持的數(shù)據(jù)類型
注解支持的元素數(shù)據(jù)有:
- 所有基本類型(int,float,boolean,byte,double,char,long,short)
- String
- Class
- enum
- Annotation
- 上述類型的數(shù)組
注意:倘若使用了其他數(shù)據(jù)類型,編譯器將會丟出一個編譯錯誤,注意,聲明注解元素時可以使用基本類型但不允許使用任何包裝類型,同時還應該注意到注解也可以作為元素的類型,也就是嵌套注解(SpringBoot中多為嵌套注解)。
示例1:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference{
boolean next() default false;
}
public @interface AnnotationElementDemo {
//枚舉類型
enum Status {FIXED,NORMAL};
//聲明枚舉
Status status() default Status.FIXED;
//布爾類型
boolean showSupport() default false;
//String類型
String name()default "";
//class類型
Class<?> testCase() default Void.class;
//注解嵌套
Reference reference() default @Reference(next=true);
//數(shù)組類型
long[] value();
}
3.4 編譯器對默認值的限制
編譯器對元素的默認值有些過分挑剔。首先,元素不能有不確定的值。也就是說,元素必須要么具有默認值,要么在使用注解時提供元素的值。其次,對于非基本類型的元素,無論是在源代碼中聲明,還是在注解接口中定義默認值,都不能以null作為值,這就是限制,沒有什么利用可言,但造成一個元素的存在或缺失狀態(tài),因為每個注解的聲明中,所有的元素都存在,并且都具有相應的值,為了繞開這個限制,只能定義一些特殊的值,例如空字符串或負數(shù),表示某個元素不存在。
如:@Test
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
/**
* Default empty exception
*/
static class None extends Throwable {
private static final long serialVersionUID = 1L;
private None() {
}
}
Class<? extends Throwable> expected() default None.class;
long timeout() default 0L;
}
3.5 注解不支持繼承
注解是不支持繼承的,因此不能使用關鍵字extends來繼承某個@interface,但注解在編譯后,編譯器會自動繼承java.lang.annotation.Annotation接口。
3.6 快捷方式
所謂的快捷方式就是注解中定義了名為value的元素,并且在使用該注解時,如果該元素是唯一需要賦值的一個元素,那么此時無需使用key=value的語法,而只需在括號內(nèi)給出value元素所需的值即可。這可以應用于任何合法類型的元素,記住,這限制了元素名必須為value。
如:2.3節(jié)的示例中。
4 自定義注解
使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節(jié)。在定義注解時,不能繼承其他的注解或接口。
4.1 注解示例
先看一個Java的注解類@Deprecated的源碼:
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
(1)首先,使用@interface聲明了Deprecated注解
(2)其次,使用@Target注解傳入{CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}參數(shù),來標明@Deprecated可以用在構造器,字段,局部變量,方法,包,參數(shù),類或接口上。
(3)再者,使用@Retention(RetentionPolicy.RUNTIME)則用來表示該注解生存期是運行時。
(4)最后,使用@Documented則用來表明,當前注解在生成javadoc時需要展示,否則不予顯示。
從代碼上看注解的定義很像接口的定義,確實如此,畢竟在編譯后也會生成Deprecated.class文件。對于@Target和@Retention,@Documented是由Java提供的元注解。
4.2 定義注解格式
@interface用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數(shù)。方法的名稱就是參數(shù)的名稱,返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class、String、enum)??梢酝ㄟ^default來聲明參數(shù)的默認值。public @interface 注解名{定義體s}
4.3 注解參數(shù)(即方法)
注解里面的每一個方法實際上就是聲明了一個配置參數(shù),其規(guī)則如下:
①修飾符
只能用public或默認(default)這兩個訪問權修飾 ,默認為default
②類型
注解參數(shù)只支持以下數(shù)據(jù)類型:
- 基本數(shù)據(jù)類型(int,float,boolean,byte,double,char,long,short);
- String類型;
- Class類型;
- enum類型;
- Annotation類型;
- 以上所有類型的數(shù)組
③命名
對取名沒有要求,如果只有一個參數(shù)成員,最好把參數(shù)名稱設為"value",后加小括號。
④參數(shù)
注解中的方法不能存在參數(shù)
⑤默認值
可以包含默認值,使用default來聲明默認值。
4.4 示例
以下舉兩個例子:
LogTreadAnnotation 使用Log4j2時,控制多線程中線程日志數(shù)據(jù)的注解。
/**
* @author pangzi
* @version V1.0
* @Description log4j多線程日志輸出注解
* 使用該注解的方法,其運行日志除了在正常的log文件中輸出外。
* 還會在 thread目錄下 job:uuid-YYYYMMDD.log 文件中輸出。
* @date 2019-11-02 16:34
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogTreadAnnotation {
}
DataSource 多數(shù)據(jù)源切換注解
/**
* @author pangzi
* @version V1.0
* @Description 用于aop類中當作切入點來選擇數(shù)據(jù)源
* @date 2019-06-03 14:43
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceEnum value() default DataSourceEnum.MYSQL;
}
5 注解處理器
如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創(chuàng)建于使用注解處理器。
5.1 反射機制
Retention.RUNTIME時,Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現(xiàn)類:
- Class:類定義
- Constructor:構造器定義
- Field:類的成員變量定義
- Method:類的方法定義
- Package:類的包定義

java.lang.reflect 包下主要包含一些實現(xiàn)反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義為運行時的Annotation后,該注解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation才會被虛擬機讀取。
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調(diào)用該對象的如下四個個方法來訪問Annotation信息(以上5個類都實現(xiàn)以下的方法):
| 返回值 | 方法名稱 | 說明 |
|---|---|---|
| getAnnotation(Class annotationClass) | 該元素如果存在指定類型的注解,則返回這些注解,否則返回 null。 | |
| Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括從父類繼承的 |
| boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定類型的注解存在于此元素上,則返回 true,否則返回 false。 |
| Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父類的注解,調(diào)用者可以隨意修改返回的數(shù)組;這不會對其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響,沒有則返回長度為0的數(shù)組 |
| Annotation[] | getAnnotationsByType(Class annotationClass) | JDK1.8新增 |
| Annotation[] | getDeclaredAnnotationsByType(Class annotationClass) | JDK1.8新增 |
示例:
/***********注解聲明***************/
/**
* 水果名稱注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果顏色注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 顏色枚舉
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};
/**
* 顏色屬性
* @return
*/
Color fruitColor() default Color.GREEN;
}
/**
* 水果供應者注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供應商編號
* @return
*/
public int id() default -1;
/**
* 供應商名稱
* @return
*/
public String name() default "";
/**
* 供應商地址
* @return
*/
public String address() default "";
}
/***********注解使用***************/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
@FruitProvider(id=1,name="陜西紅富士集團",address="陜西省西安市延安路89號紅富士大廈")
private String appleProvider;
}
/***********注解處理器***************/
public class FruitInfoUtil {
public static void getFruitInfo(Class</> clazz){
String strFruitName=" 水果名稱:";
String strFruitColor=" 水果顏色:";
String strFruitProvicer="供應商信息:";
Field[] fields = clazz.getDeclaredFields();
for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供應商編號:"+fruitProvider.id()+" 供應商名稱:"+fruitProvider.name()+" 供應商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
/***********輸出結果***************/
public class FruitRun {
/**
* @param args
*/
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
5.2 SpringAOP
除了通過反射工具自定義注解解釋器外,在日常開發(fā)中用的最多的就是注解與Spring AOP結合完成特定的工作。
示例
下面以4.4節(jié)中的日志注解為例介紹。
LogTreadAnnotation主要的通途就是,當標注@LogTreadAnnotation方法執(zhí)行時,會根據(jù)方法參數(shù)判斷,如果參數(shù)不一致,則整個方法及后續(xù)方法的日志都輸出在一個log文件中,即每次標注@LogTreadAnnotation方法的日志都會在不同的日志文件中。
/**
* @author pangzi
* @version V1.0
* @Description log4j多線程日志輸出注解
* 使用該注解的方法,其運行日志除了在正常的log文件中輸出外。
* 還會在 thread目錄下 job:uuid-YYYYMMDD.log 文件中輸出。
* @date 2019-11-02 16:34
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogTreadAnnotation {
}
/**
* @author pangzi
* @version V1.0
* @Description log4j多線程日志控制切面
* @date 2019-11-02 16:30
*/
@Component //聲明組件
@Aspect //聲明切面
@ComponentScan //組件自動掃描
@EnableAspectJAutoProxy //spring自動切換JDK動態(tài)代理和CGLIB
public class LogTreadRecordAspect {
@Pointcut("@annotation(com.zznode.gum.task.core.aop.LogTreadAnnotation)")
public void addLogTread(){}
@Before("addLogTread()")
public void beforeAdvide(){
//TODO 操作日志@Before 方法執(zhí)行前
}
@After("addLogTread()")
public void afterAdvide() {
//TODO 操作日志@After 方法執(zhí)行后
}
@Around("addLogTread()")
public void aroundAvide(ProceedingJoinPoint pjp) throws Throwable {
//操作日志@Around 方法執(zhí)行前
Object[] args = pjp.getArgs();
if(args != null && args.length >1) {
String jobId = args[0].toString();
String uuid = args[1].toString();
LogUtils.logThreadBegin(jobId+":"+uuid);
}
//方法執(zhí)行
pjp.proceed();
//操作日志@Around 方法執(zhí)行后
LogUtils.logTreadEnd();
}
}
/**
* @author pangzi
* @version V1.0
* @Description Log4j2多線程輸出日志工具類
* @date 2019-11-02 15:07
*/
public class LogUtils {
/**
* 開始日志輸出到指定線程
* @param key
*/
public static void logThreadBegin(String key) {
ThreadContext.put("JobUUID",key);
}
/**
* 結束日志輸出
*/
public static void logTreadEnd() {
ThreadContext.remove("JobUUID");
}
}
使用
/**
* @author pangzi
* @version V1.0
* @Description 數(shù)據(jù)匯聚計算Controller
* @date 2019-09-16 14:20
*/
@Slf4j
@RestController
@RequestMapping("/dataConverge")
@Api(value = "數(shù)據(jù)計算任務RESTFUL")
public class DataConvergeController {
@Autowired
private DataConvergeService dataConvergeService;
@ApiOperation(value = "報表指標天匯聚計算任務", notes = "報表指標天匯聚計算任務")
@GetMapping("/rptIndexDayConverge")
@ApiImplicitParams({
@ApiImplicitParam(name = "jobId", value = "任務編號", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "uuid", value = "任務實例編號", required = true, dataType = "String", paramType = "query")
})
@ResponseBody
@LogTreadAnnotation
protected Result<String> rptIndexDayConverge(@RequestParam(name="jobId") String jobId,
@RequestParam(name="uuid") String uuid) {
dataConvergeServiceAh.rptIndexDayConverge(jobId,uuid);
//返回處理中
return new Result<>(EnumResult.EXECUTING.getIndex(), EnumResult.EXECUTING.getName());
}
另附 log4j2.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN" monitorInterval="60">
<Properties>
<Property name="PATTERN">%d{DEFAULT} [%t] %-5p %c{1.}.%M %L - %msg%xEx%n</Property>
</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${PATTERN}" />
</Console>
<RollingFile name="TRACE" fileName="${sys:app.log.home}/task-all.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-all-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${PATTERN}" />
<SizeBasedTriggeringPolicy size="20MB" />
</RollingFile>
<RollingFile name="DEBUG" fileName="${sys:app.log.home}/task-debug.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-debug-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${PATTERN}" />
<SizeBasedTriggeringPolicy size="20MB" />
</RollingFile>
<RollingFile name="INFO" fileName="${sys:app.log.home}/task-info.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-info-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${PATTERN}" />
<SizeBasedTriggeringPolicy size="20MB" />
</RollingFile>
<RollingFile name="ERROR" fileName="${sys:app.log.home}/task-error.log" filePattern="${sys:app.log.home}/$${date:yyyy-MM}/task-error-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${PATTERN}" />
<SizeBasedTriggeringPolicy size="20MB" />
</RollingFile>
<Routing name="thread">
<Routes pattern="$${ctx:JobUUID}">
<Route>
<File name="File-${ctx:JobUUID}" fileName="${sys:app.log.home}/threads/${ctx:JobUUID}.log">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${PATTERN}" />
</File>
</Route>
</Routes>
</Routing>
</appenders>
<!--然后定義logger,只有定義了logger并引入的appender,appender才會生效 -->
<loggers>
<!--過濾掉spring和mybatis的一些無用的DEBUG信息 -->
<logger name="org.springframework" level="INFO" />
<logger name="org.mybatis" level="debug" />
<logger name="springfox.documentation" level="ERROR" />
<logger name="io.netty" level="INFO" />
<logger name="org.apache" level="info" />
<logger name="reactor.util" level="info" />
<logger name="org.flowable" level="info" />
<logger name="com.test" level="debug" />
<logger name="io.lettuce" level="info" />
<logger name="org.quartz" level="info" />
<logger name="org.hibernate.validator" level="info" />
<root level="trace">
<appender-ref ref="Console" />
<appender-ref ref="TRACE" />
<appender-ref ref="DEBUG" />
<appender-ref ref="INFO" />
<appender-ref ref="ERROR" />
<appender-ref ref="thread" />
</root>
</loggers>
</configuration>
5.3 Spring IOC
除了配置AOP之外,其次就是結合IOC,直接從IOC容器中拿取標注了指定注解的Bean。
5.3.1 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ColumnCommandOrder {
/**
* 默認值處理順序
* @return 序號
*/
int valueOrder() default 99;
/**
* 檢查處理順序
* @return 序號
*/
int checkOrder() default 99;
/**
* 指定報文類型。
* 只有指定的報文類型,才使用該Command
* @return 報文類型數(shù)組
*/
String[] telexType() default {AnalyseConstants.TELEX_TYPE_NOTAMNCR};
}
5.3.2 注解解析
通過IOC獲取標注了該注解的類實例。
(1)ApplicationContext的工具類
@Component
public class AnalyseServiceContext implements ApplicationContextAware {
private static ApplicationContext applicationContext=null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AnalyseServiceContext.applicationContext = applicationContext;
}
public static <T> T getBean(String beanName) {
return (T)applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static Map<String,Object> getBeanMap(Class<? extends Annotation> tClass) {
return applicationContext.getBeansWithAnnotation(tClass);
}
public static <T> Map<String,T> getBeansOfType(Class<T> clazz) {
return applicationContext.getBeansOfType(clazz);
}
}
注解解析
public class TelexBusinessCheckParserHandler implements ITelexParserHandler{
@Override
public void parserTelex(ITelexBusinessHandler businessHandler) {
if (businessHandler.isErrorOut()) {
return;
}
TreeMap<Integer, ITelexColumnParserCommand> commandTreeMap= Maps.newTreeMap();
Map<String,Object> maps = AnalyseServiceContext.getBeanMap(ColumnCommandOrder.class);
for (Map.Entry<String, Object> bean: maps.entrySet()) {
Object obj = bean.getValue();
ColumnCommandOrder annotation = obj.getClass().getAnnotation(ColumnCommandOrder.class);
if (null!=annotation) {
int order = annotation.checkOrder();
commandTreeMap.put(order, (ITelexColumnParserCommand) obj);
}
}
commandTreeMap.forEach((k,v)->{
v.setBusinessHandler(businessHandler);
if (log.isDebugEnabled()) {
log.debug("業(yè)務規(guī)則判斷{},處理類{}",k,v.getClass().getSimpleName());
}
try {
ColumnCommandOrder annotation = v.getClass().getAnnotation(ColumnCommandOrder.class);
if (ArrayUtils.contains(annotation.telexType(),
businessHandler.getOutputType().getApplicationCode())) {
v.columnCheckHandler();
}
} catch (Exception e) {
log.error("業(yè)務規(guī)則判斷{},處理類{},異常{}",k,v.getClass().getSimpleName(),e);
}
});
}
}
5.3.3 注解標注
@Slf4j
@Component
@ColumnCommandOrder(valueOrder = 12,checkOrder = 12)
public class ItemBParserCommand extends AbstractColunmParserCommand {
...
}
6 Java 8中注解增強
對于元注解,Java 8 主要有兩點改進:類型注解和重復注解。
6.1 重復注解
元注解@Repeatable是JDK1.8新加入的,它表示在同一個位置重復相同的注解。在沒有該注解前,一般是無法在同一個類型上使用相同的注解的。
//Java8前無法這樣使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}
Java8前如果是想實現(xiàn)類似的功能,我們需要在定義@FilterPath注解時定義一個數(shù)組元素接收多個值如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
String [] value();
}
//使用
@FilterPath({"/update","/add"})
public class A { }
但在Java8新增了@Repeatable注解后就可以采用如下的方式定義并使用了
//使用Java8新增@Repeatable原注解
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class)//參數(shù)指明接收的注解class
public @interface FilterPath {
String value();
}
// 自定義一個包裝類FilterPaths注解用來放置一組具體的FilterPath注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
FilterPath[] value();
}
// 使用案例新方法
@FilterPath("/web/update")
@FilterPath("/web/add")
class AA{ }
// 使用案例舊方法
@FilterPaths ({@FilterPath("/web/update"), @FilterPath("/web/add")})
class AA{ }
見5.1節(jié),為了處理上述的新增注解,Java8還在AnnotatedElement接口新增了getDeclaredAnnotationsByType() 和 getAnnotationsByType()兩個方法并在接口給出了默認實現(xiàn),在指定@Repeatable的注解時,可以通過這兩個方法獲取到注解相關信息。
注意:
- 舊版API中的
getDeclaredAnnotation()和getAnnotation()是不對@Repeatable注解的處理的(除非該注解沒有在同一個聲明上重復出現(xiàn))。 getDeclaredAnnotationsByType方法獲取到的注解不包括父類,其實當getAnnotationsByType()方法調(diào)用時,其內(nèi)部先執(zhí)行了getDeclaredAnnotationsByType方法,只有當前類不存在指定注解時,getAnnotationsByType()才會繼續(xù)從其父類尋找,但請注意如果@FilterPath和@FilterPaths沒有使用了@Inherited的話,仍然無法獲取。
6.2 類型注解
6.2.1 注解使用的范圍。
在java 8之前,注解只能是在聲明的地方所使用,java8開始,注解可以應用在任何地方。
- TYPE_USE則可以用于標注任意類型(不包括class)
// 用于構造函數(shù),創(chuàng)建類實例
new@Interned MyObject();
// 用于強制類型轉換和instanceof檢查,注意這些注解中用于外部工具,它們不會對類型轉換或者instanceof的檢查行為帶來任何影響。
myString = (@NonNull String) str;
if(input instanceof @NonNull String)
// 用于父類或者接口
class Image implements @Rectangular Shape { }
// 用于指定異常
void monitorTemperature() throws @Critical TemperatureException { ... }
- TYPE_PARAMETER 標注在類型參數(shù)上
// 標注在類型參數(shù)上
class D<@Parameter T> { }
注意:
- 在Java 8里面,當類型轉化甚至分配新對象的時候,都可以在聲明變量或者參數(shù)的時候使用注解。
- Java注解可以支持任意類型。
- 類型注解只是語法而不是語義,并不會影響java的編譯時間,加載時間,以及運行時間,也就是說,編譯成class文件的時候并不包含類型注解。
由上面的注解使用范圍的變更,引出ElementType新增的兩個類型。
6.2.2 新增的兩種ElementType
新增的兩個注釋的程序元素類型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER用來描述注解的新場合 。
- ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中。
- ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中(eg:聲明語句、泛型和強制轉換語句中的類型)。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
6.2.3 類型注解的作用
類型注解被用來支持在Java的程序中做強類型檢查。配合第三方插件工具Checker Framework,可以在編譯的時候檢測出runtime error(eg:UnsupportedOperationException; NumberFormatException;NullPointerException異常等都是runtime error),以提高代碼質量。這就是類型注解的作用。
注意:
使用Checker Framework可以找到類型注解出現(xiàn)的地方并檢查。
總結
到此這篇關于java自定注解的文章就介紹到這了,更多相關java自定注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用Java將Excel中的數(shù)字轉換為文本或將文本型數(shù)字轉換為數(shù)字
在 Excel 中,數(shù)字有時會以文本的形式存儲,這可能會影響數(shù)據(jù)的計算、排序和處理,無論是將數(shù)字轉換為文本,還是將文本型數(shù)字轉換為數(shù)字,這兩種操作在處理 Excel 數(shù)據(jù)時都非常常見且重要,本文將介紹如何使用 Java 完成這兩個任務,需要的朋友可以參考下2025-12-12
java中申請不定長度數(shù)組ArrayList的方法
今天小編就為大家分享一篇java中申請不定長度數(shù)組ArrayList的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07
Caffeine中本地緩存的最佳實踐與性能優(yōu)化指南
作為 Java 生態(tài)中性能優(yōu)異的本地緩存實現(xiàn),Caffeine 提供了更高的吞吐量和更低的延遲,本文將深入剖析 Caffeine 的核心原理、源碼實現(xiàn)和實際應用,希望對大家有所幫助2025-09-09

