spring自定義注解及使用方法詳細例子
簡介
在spring項目中使用注解,簡化了代碼量,減輕對業(yè)務(wù)代碼的侵入性;對框架統(tǒng)一處理鑒權(quán)、日志等起到極大的作用,可以結(jié)合著攔截器、aop在請求調(diào)用前后添加額外處理。spring有內(nèi)置的@Controller、@Service等注解,出于業(yè)務(wù)考慮,我們可以自定義想要的注解。
一、定義注解
自定義注解類似于定義接口,但是需要指明注解的作用范圍、生命周期等屬性。
1.注解示例
下面是一個簡單的自定義注解示例,使用@interface修飾,定義了三個屬性值,使用注解的時候可以給這些屬性賦值。
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface LogAnnotation { String moduleName() default ""; String operaName() default ""; String operaType() default ""; }
2.元注解含義
從jdk1.5開始,在包java.lang.annotation下提供了四種元注解:@Target、@Retention、@Documented、@Inherited,java1.8后,annotation包下新提供了兩種元注解:@Native、@Repeatable。自定義注解的時候需要使用元注解修飾,來看下各個元注解的使用說明。
(1)@Target
標識注解可以使用的范圍,例如使用在方法、字段、構(gòu)造方法上??聪略创a:
//Target源碼 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); } //Target可配置的類型 public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE }
從源碼中可以看出@Target只有一個屬性value,屬性類型為ElementType類型的數(shù)組,ElementType各個枚舉值的作用范圍如下:
①ElementType.TYPE:允許被修飾的注解作用在:類、接口、枚舉上;
②ElementType.FIELD:允許被修飾的注解作用在:屬性字段上;
③ElementType.METHOD:允許被修飾的注解作用在:方法上;
④ElementType.PARAMETER:允許被修飾的注解作用在:方法參數(shù)上;
⑤ElementType.CONSTRUCTOR:允許被修飾的注解作用在:構(gòu)造器上;
⑥ElementType.LOCAL_VARIABLE:允許被修飾的注解作用在:本地局部變量上;
⑦ElementType.ANNOTATION_TYPE:允許被修飾的注解作用在:注解上;
⑧ElementType.PACKAGE:允許被修飾的注解作用在:包名上;
⑨ElementType.TYPE_PARAMETER:允許被修飾的注解作用在:類型參數(shù)上,jdk1.8提供;
//ElementType.TYPE_PARAMETER示例 @Target(ElementType.TYPE_PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TypeParameterAnnotation { } //泛型聲明 public class TypeParameterClass<@TypeParameterAnnotation T> { public <@TypeParameterAnnotation P> T too(T t){ return t; } }
⑩ElementType.TYPE_USE:允許被修飾的注解作用在:任何語句中(聲明語句、泛型、強制轉(zhuǎn)化),jdk1.8提供。
(2)@Retention
標識注解的生命周期,來看下源碼:
//Retention源碼 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); } //RetentionPolicy源碼 public enum RetentionPolicy { SOURCE, CLASS, RUNTIME }
從源碼可以看出@Retention只有一個屬性value,屬性類型為RetentionPolicy,看下RetentionPolicy枚舉值的生命周期:
①RetentionPolicy.SOURCE:編譯階段丟棄,編譯之后注解沒有任何作用,不會寫入字節(jié)碼文件中。例如@Override、@SuppressWarnings、@Deprecated都屬于這類注解;
②RetentionPolicy.CLASS:類加載階段丟棄,類加載進jvm后沒有任何作用,在字節(jié)碼文件處理中有用。注解默認使用這種方式;
③RetentionPolicy.RUNTIME:始終不會丟棄,程序運行期也保留此注解,自定義注解通常使用這種方式,因此可以通過反射獲取到注解配置的屬性值。
(3)@Documented
標識注解是否在javadoc文檔中顯示,看下源碼:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
當定義的注解中加入了@Documented元注解,則生成的javadoc文檔中包含注解,來看一個例子:
@Documented public @interface DocumentAnnotation { String name() default "張三"; int age() default 18; } public class DocumentTest { @DocumentAnnotation(name="lisi",age = 30) public void test(){ } }
此時生成javadoc文件,生成的方式為:
? 文檔中包含注解信息:
自定義注解DocumentAnnotation去掉@Documented,javadoc文檔中不包含注解:
(4)@Inherited
標識注解是否能繼承到子類,看下源碼:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
使用@Inherited修飾的注解,在class使用它時,class的子類能夠繼承此注解,類似于InheritableThreadLocal,父子類能夠共享資源。
(5)@Native
標識字段是否可以被本地代碼引用,看下源碼:
@Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Native { }
此注解作用在字段上,生命周期為編譯階段丟棄。
(6)@Repeatable
標識可以重復使用注解,看下源碼:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Repeatable { Class<? extends Annotation> value(); }
作用在注解上,只有一個屬性value,屬性的類型繼承了Annotation,之所以繼承Annotation是因為Annotation是所有注解的父接口,看下關(guān)系圖:
來看一個demo:
//定義注解 @Target(value={ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatableAnnotations { RepeatableAnnotation[] value(); } //定義注解,Repeatable聲明RepeatableAnnotations @Target(value={ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(RepeatableAnnotations.class) public @interface RepeatableAnnotation { String name(); int age(); } //測試類 public class RepeatableDemo { @RepeatableAnnotation(name="張三",age=18) @RepeatableAnnotation(name="李四",age=30) private String userMessage; public static void main(String[] args) throws NoSuchFieldException { Field declaredField = RepeatableDemo.class.getDeclaredField("userMessage"); Annotation[] annotations = declaredField.getDeclaredAnnotations(); System.out.println("注解的數(shù)量:"+annotations.length); System.out.println("注解內(nèi)容:"+Arrays.toString(annotations)); } }
測試類輸出結(jié)果:
注解的數(shù)量:1
注解內(nèi)容:[@com.RepeatableAnnotations(value=[@com.RepeatableAnnotation(name=張三, age=18), @com.RepeatableAnnotation(name=李四, age=30)])]
定義一個可重復的注解,需要使用@Repeatable來聲明,@Repeatable的值為此原注解數(shù)組形式的新注解。從測試類可以看出最終注解的數(shù)量還是1個,是使用@Repeatable值的數(shù)組形式接收,每個值為原注解類型。
在spring中ComponentScan定義bean的掃描范圍,就是這樣使用的,看下它的源碼:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface ComponentScans { ComponentScan[] value(); } //使用 @ComponentScan(basePackages = {"com.xxx1","com.xxx2"})
使用@Repeatable注意事項:
①原注解的@Target作用范圍要比@Repeatable值的范圍大或者相同,否則編譯錯誤,例如:
//比RepeatableAnnotation多了ElementType.METHOD @Target(value={ElementType.FIELD,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatableAnnotations { RepeatableAnnotation[] value(); } @Target(value={ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(RepeatableAnnotations.class) public @interface RepeatableAnnotation { String name(); int age(); }
②原注解的@Retention生命周期要比@Repeatable值的小或者相同,否則編譯錯誤,生命周期大?。篠OURCE <
CLASS < RUNTIME。例如:
//定義的CLASS比RUNTIME要小 @Target(value={ElementType.FIELD}) @Retention(RetentionPolicy.CLASS) @Documented public @interface RepeatableAnnotations { RepeatableAnnotation[] value(); } @Target(value={ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(RepeatableAnnotations.class) public @interface RepeatableAnnotation { String name(); int age(); }
二、使用注解
定義注解就是為了方便系統(tǒng)開發(fā),現(xiàn)在來看一些使用場景。
1.aop切點使用注解
自定義注解結(jié)合著aop來使用的場景很多,例如日志的收集就可以使用。
①定義注解:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAnnotation { //模塊名稱(枚舉類) ModuleNameEnum moduleName() default ModuleNameEnum.UNKNOWN; //操作對象 String operaName() default ""; //操作類型(枚舉類) OperaTypeEnum operaType() default OperaTypeEnum.UNKNOWN; }
②定義aop切面類:
@Aspect @Component @Slf4j public class LogAspect { @Autowired XxxLogService xxxLogService; //切點:使用LogAnnotation注解標識的方法都進行切入,也可以使用通配符配置具體要切入的方法名 @Pointcut("@annotation(com.xxx.aop.LogAnnotation)") public void pointCut(){ } //環(huán)繞通知 @Around("pointCut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { Object jsonResult = joinPoint.proceed(); //執(zhí)行方法 try { //獲取請求簽名 MethodSignature signature = (MethodSignature)joinPoint.getSignature(); //獲取切入點所在的方法 Method method = signature.getMethod(); //獲取注解值 LogAnnotation annotation = method.getAnnotation(LogAnnotation.class); //獲取屬性 String moduleName = annotation.moduleName().getValue(); String operaName = annotation.operaName(); String operaType = annotation.operaType().getValue(); XxxLog xxxLog = new XxxLog(); xxxLog.setModuleName(moduleName); xxxLog.setOperaName(operaName); xxxLog.setOperaType(operaType); //添加日志 xxxLogService.insertOne(xxxLog); } catch (Exception e){ e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } return jsonResult; } }
③方法中添加注解
當注解屬性名為value時,賦值的時候可以省略屬性名,其他名稱的屬性名需要使用xx=yy的方式指定。
@LogAnnotation(moduleName= ModuleNameEnum.FeedBack,operaName="添加消息",operaType=OperaTypeEnum.Insert) public void insertOne(Integer id) { }
過程為:定義注解,定義屬性值;創(chuàng)建切面類,使用@annotation來指定切點為自定義注解,環(huán)繞方法獲取注解及屬性值,把屬性值保存到業(yè)務(wù)數(shù)據(jù)庫中;業(yè)務(wù)代碼中需要保存日志的方法加上注解,并設(shè)置屬性值。
2.攔截器獲取注解
可以在攔截器中獲取注解,在controller層響應(yīng)前后做一些額外的處理或判斷,例如判斷權(quán)限、判斷是否需要分頁等。來看一個分頁的demo:
①定義注解
@Target(ElementType.METHOD) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface EnablePaging { int value() default 50; }
②定義攔截器
public class PagingInterceptor implements HandlerInterceptor { //controller響應(yīng)之前執(zhí)行 @Override public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; //獲取方法中的注解 EnablePaging enablePaging = handlerMethod.getMethodAnnotation(EnablePaging.class); //不包含注解,直接通過 if (enablePaging == null) { return true; } //包含注解,則獲取注解中的值,值保存到TreadLocal線程變量中(此處使用RequestContextHolder.currentRequestAttributes().setAttribute保存),在執(zhí)行sql查詢時取出使用 PagingContextData data = PagingContextData.getInstance(RequestAttributes.SCOPE_REQUEST, true); data.setEnabled(true); //把注解中配置的值設(shè)置進去 if (enablePaging.value() > 0) { data.setDefaultPageSize(enablePaging.value()); } return true; } }
③注冊攔截器
@Configuration public class PagingHttpConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new PagingInterceptor()).order(Ordered.HIGHEST_PRECEDENCE); } }
④方法中添加注解
@PostMapping("/datasource/xxxPage") @EnablePaging(20) public Object xxxPage(@RequestBody String json) { return xxxService.xxxPage(json); }
過程為:定義注解,定義屬性值;創(chuàng)建攔截器,在攔截器的方法中獲取注解及屬性值,把屬性值保存到線程變量ThreadLocal中;把攔截器注冊到InterceptorRegistry中;業(yè)務(wù)代碼中需要分頁的接口方法加上注解,并設(shè)置屬性值。
3.class獲取注解
通過class可以獲取到注解,提供了從method方法、field字段等獲取注解。獲取class的方式有:
①對象.getClass()方法
Student stu = new Student(); Class clazz = stu.getClass();
②對象.class
Class clazz = Student.class;
③Class.forName(“xxx”),例如數(shù)據(jù)庫驅(qū)動的獲取
Class clazz = Class.forName("com.xxx.Student")
從method中獲取注解示例:
//獲取所有方法 Method[] methods = SampleClass.class.getMethods(); for(int i = 0;i < methods.length;i++) { //獲取方法中的注解 CustomAnnotaion annotation = methods[i].getAnnotation(CustomAnnotaion.class); if(null != annotation) { //輸出屬性值 System.out.println(annotation.name()); } } //獲取指定方法 Method oneMethod = SampleClass.class.getDeclaredMethod("getSampleField"); //獲取方法中的注解值 CustomAnnotaion annotation = oneMethod.getAnnotation(CustomAnnotaion.class); System.out.println("annotation="+annotation.name());
從字段中獲取注解示例:
//獲取指定字段 Field declaredField = RepeatableDemo.class.getDeclaredField("userMessage"); //獲取字段中的注解 Annotation[] annotations = declaredField.getDeclaredAnnotations();
4.spring容器獲取注解
在bean對象中加入注解,當spring容器加載完bean之后,可以從bean中獲取到哪些方法加了指定的注解,從而拿到方法,對這些方法進行特殊處理。在xxl-job開源項目中就有使用,看下使用方式:
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) { if (applicationContext == null) { return; } // init job handler from method //從程序上下文中獲取到所有的bean名稱集合 String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true); //遍歷bean集合 for (String beanDefinitionName : beanDefinitionNames) { //根據(jù)bean名稱從程序上下文獲取到此bean對象 Object bean = applicationContext.getBean(beanDefinitionName); Map<Method, XxlJob> annotatedMethods = null; try { //對Bean對象進行方法過濾,查詢到方法被XxlJob注解修飾,是則放到annotatedMethods集合中 annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(), new MethodIntrospector.MetadataLookup<XxlJob>() { @Override public XxlJob inspect(Method method) { //判斷方法被XxlJob注解修飾才返回 return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class); } }); } catch (Throwable ex) { logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex); } //當前遍歷的bean沒有被XxlJob注解修飾,則跳過處理 if (annotatedMethods==null || annotatedMethods.isEmpty()) { continue; } //循環(huán)處理當前Bean下被XxlJob修飾的方法 for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) { //執(zhí)行的方法 Method executeMethod = methodXxlJobEntry.getKey(); //XxlJob注解類 XxlJob xxlJob = methodXxlJobEntry.getValue(); //注冊此任務(wù)處理器 registJobHandler(xxlJob, bean, executeMethod); } } }
從spring上下文applicationContext中獲取到所有的bean名稱集合,遍歷bean名稱集合,根據(jù)bean名稱從程序上下文獲取到此bean對象,對Bean對象進行方法過濾,查詢到被XxlJob注解修飾的方法,放到map集合中,循環(huán)處理map中的記錄,key為Method方法,value為XxlJob注解,這也是使用注解的場景。
總結(jié)
到此這篇關(guān)于spring自定義注解及使用方法的文章就介紹到這了,更多相關(guān)spring自定義注解使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中實現(xiàn)高清圖片壓縮的兩種方案(最新推薦)
文章首先介紹了Java中進行高清圖片壓縮的基本方法,包括使用Java標準庫ImageIO和第三方庫ApacheCommonsCompress,通過示例代碼展示了如何調(diào)整圖像質(zhì)量和使用第三方工具來壓縮圖片文件,感興趣的朋友跟隨小編一起看看吧2025-01-01SpringBoot整合Mybatis Plus多數(shù)據(jù)源的實現(xiàn)示例
本文主要介紹了SpringBoot整合Mybatis Plus多數(shù)據(jù)源的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文(必看)
這篇文章主要介紹了IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文了,本文通過截圖的形式給大家展示,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04SpringBoot個性化啟動Banner設(shè)置方法解析
這篇文章主要介紹了SpringBoot個性化啟動Banner設(shè)置方法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-03-03Spring在多線程下@Resource注入為null的問題
這篇文章主要介紹了Spring在多線程下@Resource注入為null的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02