phper學習spring第二章

接下來研究一下annotation

在開始看spring的註解以前,我想了想PHP註解及其處理方法,PHP不支持註解語法,只能用註釋來模擬,而PHP的反射類是能夠拿到類和方法以及屬性的註釋信息,再經過解析註釋信息拿到相應的註解。解析出註解以後,調用對應的註解處理程序。這種方式的弊端我想應該是沒法在PHP編譯階段發現錯誤,通常要到運行時,解析註解的階段才能發現錯誤;還有就是沒有統一的註解格式,徹底要看解析註解的邏輯是什麼樣的,形成碎片化,增長學習成本。java

我想spring也應該是同樣的道理,經過反射拿到註解信息,再調用對應的註解處理程序。但JAVA的優點是原生支持註解且格式統一,因此若是有語法錯誤,編譯時就能發現。web

在學習一個新概念以前,老是要先想一想,爲何須要它,它解決了什麼問題?註解也同樣,爲何須要註解,註解解決了什麼問題。spring

我嘗試用本身的理解去說一下,不必定準確,僅供參考。註解提供了一種非侵入式的,賦予類、方法或屬性能力的能力。你只須要添加幾種註解,就能夠爲程序提供強大的能力。而當你不須要它時,只須要刪除相應的註解便可,一切就如同春雨,潤物細無聲。express

想明白了這些,我去看看上個章節DemoApplication.java,看看它用到了哪些註解。apache

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和默認值。app

固然,這只是個人猜想,接下來,我要更加深刻到源代碼,看看註解的定義是什麼樣的,ctrl+click看看@RestController註解。less

/*
 * 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 "";

}

很清晰,定義註解的方法爲:ide

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的語法。

相關文章
相關標籤/搜索