接下来研究一下annotation
在开始看spring的注解之前,我想了想PHP注解及其处理方法,PHP不支持注解语法,只能用注释来模拟,而PHP的反射类是可以拿到类和方法以及属性的注释信息,再通过解析注释信息拿到相应的注解。解析出注解之后,调用对应的注解处理程序。这种方式的弊端我想应该是无法在PHP编译阶段发现错误,一般要到运行时,解析注解的阶段才能发现错误;还有就是没有统一的注解格式,完全要看解析注解的逻辑是什么样的,造成碎片化,增加学习成本。
我想spring也应该是一样的道理,通过反射拿到注解信息,再调用对应的注解处理程序。但JAVA的优势是原生支持注解且格式统一,所以如果有语法错误,编译时就能发现。
在学习一个新概念之前,总是要先想想,为什么需要它,它解决了什么问题?注解也一样,为什么需要注解,注解解决了什么问题。
我尝试用自己的理解去说一下,不一定准确,仅供参考。注解提供了一种非侵入式的,赋予类、方法或属性能力的能力。你只需要添加几种注解,就可以为程序提供强大的能力。而当你不需要它时,只需要删除相应的注解即可,一切就如同春雨,润物细无声。
想明白了这些,我去看看上个章节 DemoApplication.java
,看看它用到了哪些注解。
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @GetMapping("/hello") public String hello(@RequestParam(value = "name", defaultValue = "World") String name) { return String.format("Hello %s!", name); } }
用到了4个注解,到目前为止,我对JAVA如何定义注解一窍不通,但我完全可以合理地猜测它们的作用是什么。
@SpringBootApplication
,让这个类成为 SpringBootApplication
的子类。
@RestController
,让这个类成为Restful控制器。
@GetMapping
,设置一条路由,当系统匹配到这条路由时,转发给DemoApplication的hello方法进行处理。
@RequestParam
,规定好接收参数的KEY和默认值。
当然,这只是我的猜测,接下来,我要更加深入到源代码,看看注解的定义是什么样的,ctrl+click看看 @RestController
注解。
/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.bind.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; import org.springframework.stereotype.Controller; /** * A convenience annotation that is itself annotated with * {@link Controller @Controller} and {@link ResponseBody @ResponseBody}. * <p> * Types that carry this annotation are treated as controllers where * {@link RequestMapping @RequestMapping} methods assume * {@link ResponseBody @ResponseBody} semantics by default. * * <p><b>NOTE:</b> {@code @RestController} is processed if an appropriate * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the * {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter} * pair which are the default in the MVC Java config and the MVC namespace. * * @author Rossen Stoyanchev * @author Sam Brannen * @since 4.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) * @since 4.0.1 */ @AliasFor(annotation = Controller.class) String value() default ""; }
很清晰,定义注解的方法为:
public @interface 注解名 { /* */ }
Target
注解,用来定义修饰范围
@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(); }
再深入看看 ElementType
,注释把注解的范围已经描述得非常清楚了。
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, /** * Module declaration. * * @since 9 */ MODULE }
一个一个来看:
Element.Type
,用来修饰类、接口(包括注解)、枚举类型。
Element.FIELD
,用来修饰类属性,包括枚举常量。
Element.METHOD
,用来修饰类方法
Element.PARAMETER
,用来修饰方法的形参
Element.CONSTRUCTOR
,用来修饰构造方法
Element.LOCAL_VARIABLE
,用来修饰本地变量
Element.ANNOTATION_TYPE
,用来修饰注解
Element.PACKAGE
,用来修饰包
Element.TYPE_PARAMETER
,用来修饰类型参数,就是 Class<@MyAnnotation T>
Element.TYPE_USE
,用来修饰类型,这可以修饰任意类型,包括上面 Element.TYPE_PARAMETER
能修饰的,它都可以修饰
Element.MODULE
,用来修饰模块
除了 Element.TYPE_PARAMETER
和 Element.TYPE_USE
不能一眼看出来是修饰啥,其它的都很好理解。
看完了这些,我们再返回头看 Target
注解,这个注解的 @Target
,也就是修饰范围,就是注解,这是个修饰注解的注解。
再看另一个注解 Retention
,这也是个修饰注解的注解,再着重看一下 RetentionPolicy
.
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 }
一个一个看
RetentionPolicy.SOURCE
,这类注解会被编译器丢掉,是 @Override
这类注解的保留策略
RetentionPolicy.CLASS
,这类注解会被编译器记录下来,但是在虚拟机运行阶段是不会保留的,这也是 Retention
注解的默认行为
RetentionPolicy.RUNTIME
,这类注解会一直保留,所以可以使用反射类读取信息
看懂了这两个注解,再去一一分析其它注解,问题就不大。接下来就是分析一下注解处理器是怎么工作的,以 SpringCacheAnnotationParser
为例。
@Nullable private Collection<CacheOperation> parseCacheAnnotations( DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) { Collection<? extends Annotation> anns = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) : AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS)); if (anns.isEmpty()) { return null; } final Collection<CacheOperation> ops = new ArrayList<>(1); anns.stream().filter(ann -> ann instanceof Cacheable).forEach( ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann))); anns.stream().filter(ann -> ann instanceof CacheEvict).forEach( ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann))); anns.stream().filter(ann -> ann instanceof CachePut).forEach( ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann))); anns.stream().filter(ann -> ann instanceof Caching).forEach( ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops)); return ops; }
通过工具类获取 Cacheable
注解的修饰对象,做一些基本的判断,再迭代进行分类处理。
上面的语法我也不太熟悉,但大致还是能看出来什么意思,这里要感叹一声,命名真的是至关重要,写得好的代码读起来就跟读英文似的。笔者第一次看见 Collection<? extends Annotation>
就能秒懂什么意思,好的代码自带教程,看源码就能收获良多。
下一章,我将会去大致地浏览一下Spring官方文档,把一些示例跑起来,再仔细地分析代码,顺便熟悉JAVA的语法。