相信每一個程式設計師,對於spring註解這個知識點一定不陌生,我這邊也整理了一些常用的,以備不時之需
這不是打算年後準備跳槽了啊,所以最近摸魚比較多一些,老大默許了,我覺得我老大還是很好的。也在網上看了一些資料,但是,我發現很多講解註解的時候,對於一些可以直接點選原始碼檢視的內容講解的佔多數,但是授人以魚不如授人以漁,尤其是最近再某平臺看到一篇文章,大致內容是:為什麼現在的程式設計師都不去學習原始碼呢?
其中有一句話說的很好:原始碼很複雜,閱讀很浪費時間並且短時間內看不到什麼效果,無論基於什麼樣的原因,放棄閱讀原始碼始終不是一個明智的選擇,因為你失去了一個跟大師學習的機會
這一點在我最近看原始碼的過程中真的是體現的淋漓盡致,經常晚上太累了,然後就算了,不看了,但是,臨睡前,平板開啟看一下,又覺得真香,所以,針對註解,我今天以幾個比較經典的註解,帶大家看一下註解的實現過程再原始碼中是怎麼展現的,我想會對大家再原始碼的閱讀過程中起一點作用的
好了,話不多說,看正文
@AliasFor@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Documentedpublic @interface AliasFor { @AliasFor("attribute") String value() default ""; @AliasFor("value") String attribute() default ""; Class<? extends Annotation> annotation() default Annotation.class;}
AliasFor這個註解很奇怪,value的別名是attribute,attribute的別名是value
那麼它的行為在哪裡被定義的呢?在AnnotationTypeMapping中我們可以找到答案
// 這裡使用了AnnotationsScanner的getDeclaredAnnotation方法來獲取所有的AliasFor註解的方法// AnnotationsScanner 是spring中的非公開抽象類,在我們的程式碼中不能直接進行使用// Spring中沒有提供子類private Map<Method, List<Method>> resolveAliasedForTargets() { Map<Method, List<Method>> aliasedBy = new HashMap<>(); for (int i = 0; i < this.attributes.size(); i++) { Method attribute = this.attributes.get(i); AliasFor aliasFor = AnnotationsScanner.getDeclaredAnnotation(attribute, AliasFor.class); if (aliasFor != null) { Method target = resolveAliasTarget(attribute, aliasFor); aliasedBy.computeIfAbsent(target, key -> new ArrayList<>()).add(attribute); } } return Collections.unmodifiableMap(aliasedBy);}// 為了簡潔,我將原始碼中其餘部分省略掉了,可以看到,這裡使用用反射得到的Method的getAnnotation方法得到例項private Method resolveAliasTarget(Method attribute, AliasFor aliasFor, boolean checkAliasPair) { // ... ... Method target = AttributeMethods.forAnnotationType(targetAnnotation).get(targetAttributeName); // ... ... if (isAliasPair(target) && checkAliasPair) { AliasFor targetAliasFor = target.getAnnotation(AliasFor.class); if (targetAliasFor != null) { Method mirror = resolveAliasTarget(target, targetAliasFor, false); if (!mirror.equals(attribute)) { throw new AnnotationConfigurationException(String.format( "%s must be declared as an @AliasFor %s, not %s.", StringUtils.capitalize(AttributeMethods.describe(target)), AttributeMethods.describe(attribute), AttributeMethods.describe(mirror))); } } } return target;}
透過學習@AliasFor,我們知道了可以透過先活動Method,再獲得其修飾的註解的方法。
根據這樣的方法,我們可以使用下面的程式碼,找到類DockingHandlers中所有被註解@DockIngMessage修飾的方法
// DockIngMessage 是自定義的註解Method[] methods = DockingHandlers.class.getMethods();for (Method method : methods) { DockIngMessage dockIngMessage = method.getAnnotation(DockIngMessage.class); if (dockIngMessage != null) { System.out.println(dockIngMessage.name()); }}
@Bean
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Bean { @AliasFor("name") String[] value() default {}; @AliasFor("value") String[] name() default {}; @Deprecated Autowire autowire() default Autowire.NO; boolean autowireCandidate() default true; String initMethod() default ""; String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;}
@Bean註解是Spring中用得比較廣泛的註解之一,來看看Spring原始碼中是怎麼查詢@Bean註解的
public static boolean isBeanAnnotated(Method method) { return AnnotatedElementUtils.hasAnnotation(method, Bean.class);}
使用了AnnotatedElementUtils工具類,那麼我們就可以把上面的程式碼改造一下
Method[] methods = DockingHandlers.class.getMethods();for (Method method : methods) { if (AnnotatedElementUtils.hasAnnotation(method, DockIngMessage.class)) { DockIngMessage dockIngMessage = AnnotatedElementUtils.getMergedAnnotation(method,DockIngMessage.class); System.out.println(dockIngMessage.name()); }}// 相比於判斷 != null , 這樣的寫法相對優雅了許多
至於Bean到底是怎麼生效的,我們需要留到以後研究Spring容器的時候再討論
@Controller@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface Controller { @AliasFor(annotation = Component.class) String value() default "";}
在Controller的test裡面有這麼一段程式碼
@Testpublic void testWithComponentAnnotationOnly() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class)); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); assertThat(candidates.size()).isEqualTo(3); assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isFalse(); assertThat(containsBeanClass(candidates, StubFooDao.class)).isFalse(); assertThat(containsBeanClass(candidates, NamedStubDao.class)).isFalse();}
也就是說,可以利用掃包的方式來獲取某個包下被某個註解修飾的類。
總結查詢某註解修飾的所有類就使用 ClassPathScanningCandidateComponentProvider 進行掃描。
查詢某註解修飾的方法,就先找到那個類,然後得到所有的方法,使用AnnotatedElementUtils.hasAnnotation判斷方法是否被某註解修飾即可
下面是一個簡單的例子
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);provider.addIncludeFilter(new AnnotationTypeFilter(DockingAnnotation.class));Set<BeanDefinition> candidates = provider.findCandidateComponents("package_name");for (BeanDefinition definition : candidates){ try { Class clz = Class.forName(definition.getBeanClassName()); Method[] methods = clz.getMethods(); for (Method method : methods){ if (AnnotatedElementUtils.hasAnnotation(method,DockIngMessage.class)){ DockIngMessage dockIngMessage = AnnotatedElementUtils.getMergedAnnotation(method,DockIngMessage.class); System.out.println(dockIngMessage.name()); } } } catch (ClassNotFoundException e) { e.printStackTrace(); }}