转载

phper学习spring第二章

接下来研究一下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_PARAMETERElement.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的语法。

原文  https://segmentfault.com/a/1190000021981019
正文到此结束
Loading...