1. 程式人生 > >Spring原始碼(九)-SpringBoot中的註解

Spring原始碼(九)-SpringBoot中的註解

前言

上一篇寫了Spring相關的註解,由於我的原始碼分析主要採用的是Springboot的方式,所以這裡也順便把springboot相關的註解也進行一些簡單的分析。

1、配置類

Springboot比Spring之前的xml那種配置方式比較優秀的方式,我覺得最大就在於,減少了大量的配置檔案,提供了很多spring-boot-starter-*的包,提供這些開箱即用的方式。當然配置也改用這種註解式的。

1.1、@SpringBootApplication

Springboot最核心的包,配置在啟動類上,main方法作為server的啟動入口,先看下他的內部實現。

@Target
(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class
) }) public @interface SpringBootApplication { ················ }

從原始碼中可以看到該註解就是@Configuration,@EnableAutoConfiguration和@ComponentScan這三個註解的集合,優化使用,那麼接下來延伸解析下這三個註解。

1.2、@Configuration

先看原始碼

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface
Configuration {
String value() default ""; }

Spring是給予IOC的,在4.0之前的版本,通常都是程式依賴上下文xml檔案來管理bean,儘管有了掃描配置後相對簡單,然而java配置的方式不同於xml,通過註解能夠更簡單。下面我們通過這兩種方式比較下。

  • xml中bean的定義
<beans>
    <bean id="course" class="demo.Course">
        <property name="module" ref="module"/>
    </bean>
    <bean id="module" class="demo.Module">
        <property name="assignment" ref="assignment"/>
    </bean>
    <bean id="assignment" class="demo.Assignment" />
</beans>
  • 註解配置類
@Configuration
public class AppContext {
    @Bean
    public Course course() {
        Course course = new Course();
        course.setModule(module());
        return course;
    }
    @Bean
    public Module module() {
        Module module = new Module();
        module.setAssignment(assignment());
        return module;
    }
    @Bean
    public Assignment assignment() {
        return new Assignment();
    }
}

@Configuration,該註解配置在類上,告知Spring這個類是一個擁有bean定義和依賴項的配置類。@Bean註釋用於定義Bean,該註解位於例項化bean並設定依賴項的方法上。方法名預設通beanId活預設名稱相同,該方法返回型別是Spring註冊的bean。總體來說就是告訴Spring容器載入這個配置,相對於xml,這個註解就是將*.xml配置進web.xml

1.3、@EnableAutoConfiguration

  • spring-boot 定義的註解,屬於包 org.springframework.boot.autoconfigure,先看原始碼:
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    String[] excludeName() default {};
}

自己動手寫註解,請參考動手寫註解
這個註解告訴Spring Boot 根據新增的jar依賴猜測你想如何配置Spring。比如spring-boot-starter-web添加了Tomcat和Spring MVC,所以auto-configuration將假定你正在開發一個web應用並相應地對Spring進行設定。

1.4、@ComponentScan

先看原始碼

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};
    @AliasFor("value")
    String[] basePackages() default {};
    //還有一些屬性並不常用,所以不列舉了。。。。。。
}
  • @ComponentScan 註解的作用就是開啟spring的註解掃描,與xml配置方式下的 作用一樣。
    可以設定一個值指定basePackages,就是開始掃描的包。如果沒有設定 預設從定義這個註解的類所屬包開始一直到所有子包。

1.5、@Import

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}
  • @Import 與xml配置方式下的 作用一樣。支援匯入的型別有:
    一個或多個擁有 @Configuration 註解的配置類
  • ImportSelector 介面的實現類
  • ImportBeanDefinitionRegistrar 的實現類

1)、如果Import註解中Class為ImportSelector子類,通過invokeAwareMethods(selector)設定aware值,如果型別為DeferredImportSelector則新增到deferredImportSelectors集合中,待前面的parser.parse(configCandidates)
方法中processDeferredImportSelectors()處理;如果不是,則執行selectImports方法,將獲取到的結果遞迴呼叫processImports,解析selectImports得到的結果

2)、如果Import註解中Class為ImportBeanDefinitionRegistrar子類,則新增到importBeanDefinitionRegistrars中,注意該部分的資料在執行完parser.parse(configCandidates)後呼叫this.reader.loadBeanDefinitions(configClasses)解析,否則執行配置資訊的解析操作。

1.6、@Conditional

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
    Class<? extends Condition>[] value();
}

可以看出value的值必須是實現了Condition介面的類,Condition介面定義如下:

public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

matchs()返回true 表明該bean(註解了@Conditional的類)需要被建立,否則不建立。
延伸幾個 spring-boot提供的 @Conditional的子集,具體了Conditional的條件:
- @ConditionalOnClass: 等同於 @Conditional(OnClassCondition.class),表示存在對應的Class檔案時才會去建立該bean
- @ConditionalOnMissingBean: 等同於 @Conditional(OnBeanCondition.class),表示spring上下文裡缺失某個bean時才會去建立該bean
- @ConditionalOnWebApplication: 等同於 - @Conditional(OnWebApplicationCondition.class),表示只有在WEB應用時才會建立該bean
更多請參考 org.springframework.boot.autoconfigure.condition 包下面的類

1.7、@EnableConfigurationProperties , @ConfigurationProperties

使用@Value(“${property}”)註解注入配置屬性有時可能比較笨重,特別是需要使用多個properties或你的資料本身有層次結構。為了控制和校驗你的應用配置,Spring Boot提供一個允許強型別beans的替代方法來使用properties。

@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {
    private String username;
    private InetAddress remoteAddress;
}

當@EnableConfigurationProperties註解應用到你的@Configuration時,任何被@ConfigurationProperties註解的beans將自動被Environment屬性配置。這種風格的配置特別適合與SpringApplication的外部YAML配置進行配合使用。

# application.yml
connection:
    username: admin
    remoteAddress: 192.168.1.1
# additional configuration as required

為了使用@ConfigurationProperties beans,你可以使用與其他任何bean相同的方式注入它們。

@Service
public class MyService {
    @Autowired
    private ConnectionSettings connection;
    @PostConstruct
    public void openConnection() {
        Server server = new Server();
        this.connection.configure(server);
    }
}
//@Component 這樣ConnectionSettings類上面就不用標示 @Component註解了
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {
    private String username;
    private InetAddress remoteAddress;
    // ... getters and setters
}

2、自己動手寫註解

先動手寫註解

2.1、註解類編寫

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface UserDoc {
    String author() default "Sunliangliang";
    int revision() default 1;
    String comments() default "written";
}
  • 註解方法不能有引數。
  • 註解方法的返回型別侷限於原始型別,字串,列舉,註解,或以上型別構成的陣列。
  • 註解方法可以包含預設值。
  • 註解可以包含與其繫結的元註解,元註解為註解提供資訊,有四種元註解型別:
  • 該註解在執行時使用,只能應用於方法上

2.2、註解的使用

public class AnnotationExample {
    public static void main(String[] args) {
    }

    @UserDoc()
    public static void oldMethod() {
        System.out.println("old method, don't use it.");
    }

}

2.3、Java註解解析

我們將使用Java反射機制從一個類中解析註解,請記住,註解保持性策略應該是RUNTIME,否則它的資訊在執行期無效,我們也不能從中獲取任何資料。

public class AnnotationParsing {

    public static void main(String[] args) {
        try {
            for (Method method : AnnotationParsing.class
                    .getClassLoader()
                    .loadClass(("com.liangliang.demo.AnnotationExample"))
                    .getMethods()) {
                if (method
                        .isAnnotationPresent(UserDoc.class)) {
                    try {
                        // iterates all the annotations available in the method
                        for (Annotation anno : method.getDeclaredAnnotations()) {
                            System.out.println("Annotation in Method :'"
                                    + method + "' : " + anno);
                        }
                        UserDoc methodAnno = method
                                .getAnnotation(UserDoc.class);
                        if (methodAnno.revision() == 1) {
                            System.out.println("Method with revision no 1 = "
                                    + method);
                        }

                    } catch (Throwable ex) {
                        ex.printStackTrace();
                    }
                }
            }
        } catch (SecurityException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

以上程式的輸出結果是:

Annotation in Method 'public static void com.liangliang.demo.AnnotationExample.oldMethod()' : @com.liangliang.annotations.UserDoc(author=Sunliangliang, revision=1, comments=written)
Method with revision no 1 = public static void com.liangliang.demo.AnnotationExample.oldMethod()

註解API非常強大,被廣泛應用於各種Java框架,如Spring,Hibernate,JUnit。可以檢視《Java中的反射》獲得更多資訊。