美文网首页Spring源码分析spring源码
Spring源码(九)-SpringBoot中的注解

Spring源码(九)-SpringBoot中的注解

作者: 阿亮私语 | 来源:发表于2017-08-24 19:11 被阅读194次

    前言

    上一篇写了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中的反射》获得更多信息。

    相关文章

      网友评论

        本文标题:Spring源码(九)-SpringBoot中的注解

        本文链接:https://www.haomeiwen.com/subject/fuoedxtx.html