接下来研究一下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}.
*
* Types that carry this annotation are treated as controllers where
* {@link RequestMapping @RequestMapping} methods assume
* {@link ResponseBody @ResponseBody} semantics by default.
*
*
NOTE: {@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 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 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的语法。