Springboot @Enable*注解的工作原理

先看一个简单的demo,我们定义一个springboot项目,最简单的依赖:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
</parent>

<dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
     </dependency>
</dependencies>

定义一个实体类:ServerBean

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "instance")
public class ServerBean {

    private String ip;

    private Integer port;

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    @Override
    public String toString() {
        return "ServerBean{" +
                "ip='" + ip + '\'' +
                ", port=" + port +
                '}';
    }
}

之前的博客springboot配置详解,详细的讲解了@ConfigurationProperties的使用。

配置文件配置:

instance.ip=192.168.1.111
instance.port=8090

启动类启动,

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);
        System.out.println(context.getBean(ServerBean.class));
    }
}

打印:

ServerBean{ip='192.168.1.111', port=8090}

我们发现这个程序自动的会把配置文件注入到bean中,原因在哪里呢?

之前我们讲过启动类上的注解@SpringBootApplication是一个复合注解,其由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,@SpringBootConfiguration和@ComponentScan我们在
springboot快速入门及@SpringBootApplication注解分析
分析过了,那么故名思义,我们知道肯定是@EnableAutoConfiguration注解的作用了。

修改启动类,

@EnableAutoConfiguration
@ComponentScan
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);
        System.out.println(context.getBean(ServerBean.class));
    }
}

发现打印结果还是一样。然后再去修改一下启动类,使用@EnableConfigurationProperties也可以替换@EnableAutoConfiguration

@EnableConfigurationProperties
@ComponentScan
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);
        System.out.println(context.getBean(ServerBean.class));
    }
}

@EnableConfigurationProperties注解一般和ConfigurationProperties注解搭配使用,可以将配置文件属性注入到bean中。

第二个demo

定义一个类UserRunnable,纳入到spring容器中,

package com.zhihao.miao.enable.bean;


import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class UserRunnable implements Runnable{

    @Override
    public void run() {
        try{
            for (int i = 0; i <10 ; i++) {
                System.out.println("============"+i);
                TimeUnit.SECONDS.sleep(1);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

启动类:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);
        UserRunnable userRunnable = context.getBean(UserRunnable.class);
        userRunnable.run();
        System.out.println("end");
    }
}

打印结果:

============0
============1
============2
============3
============4
============5
============6
============7
============8
============9
end

我们发现执行过程是一个同步的过程,只有userRunnable.run方法执行完毕之后才执行下面的打印过程。

修改一下代码,将UserRunnable的run方法上加入@Async注解

@Component
public class UserRunnable implements Runnable{

    @Override
    @Async
    public void run() {
        try{
            for (int i = 0; i <10 ; i++) {
                System.out.println("============"+i);
                TimeUnit.SECONDS.sleep(1);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

修改启动类,

@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);
        context.getBean(Runnable.class).run();
        System.out.println("end");
    }
}

执行结果:

============0
end
============1
============2
============3
============4
============5
============6
============7
============8
============9

我们查看上面二个demo的Enable*注解的源码,

@EnableAsync注解

@EnableAsync
AsyncConfigurationSelector注解

最后实现ImportSelector接口

@EnableAutoConfiguration注解

都使用到了@Import注解,最后也是实现ImportSelector接口。

@Import注解

@Import注解

@Import其实就是引入一个或多个配置,可以导入普通类,也可以导入配置类。
@Import用来导入一个或多个类(会被spring容器管理),或者配置类(配置类里的@Bean标记的类也会被spring容器管理)

看一个demo,定义四个实体类,User,People,Cat,Dog

public class User {
}
public class People{  
}
public class Cat {
}
public class Dog {
}
public class MyConfig {

    @Bean
    public Dog dog(){
        return new Dog();
    }

    @Bean
    public Cat cat(){
        return new Cat();
    }
}

我们要将这四个类纳入到spring容器中,我们之前的做法是在User,People上加上了@Component注解(或者@Service,@Controller)或者在MyConfig类上加上@Configuration注解。很显然我们这边并没有这般做,使用@Import注解也可以加对象纳入到spring容器中。

启动类:

package com.zhihao.miao.imp;

import com.zhihao.miao.imp.bean.*;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

@Import({User.class,People.class, MyConfig.class})
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);
        System.out.println(context.getBean(User.class));
        System.out.println(context.getBean(Dog.class));
        System.out.println(context.getBean(Cat.class));
        System.out.println(context.getBean(People.class));
    }
}

ImportSelector接口

ImportSelector接口

Interface to be implemented by types that determine which @{@link Configuration}
class(es) should be imported based on a given selection criteria, usually one or more
annotation attributes.
接口被实现那些Configuration的类被导入到spring容器根据指定的一些条件,通常是一个到多个导入类的注解属性。

An ImportSelector may implement any of the following
org.springframework.beans.factory.Aware Aware interfaces, and their respective methods will be called prior to selectImports:

  • org.springframework.context.EnvironmentAware EnvironmentAware
  • org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware
  • org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware
  • org.springframework.context.ResourceLoaderAware ResourceLoaderAware

ImportSelectors are usually processed in the same way as regular @Import
annotations, however, it is also possible to defer selection of imports until all
@Configuration classes have been processed (see DeferredImportSelector
for details).
实现ImportSelectors接口的类通常与常规的@Import注解作用相同,然而,它也可能被延迟处理直到所有被@Configuration标记的类处理完之后采取处理。

定义一个MyImportSelector继承ImportSelector,ImportSelector返回的String[]数组是类的全类名会被纳入到spring容器内。

/**
 * selectImports方法的返回值,必须是一个class(全称),该class会被spring容器所托管起来
 */
public class MyImportSelector implements ImportSelector{

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //获取注解的属性信息
        System.out.println(importingClassMetadata.getAllAnnotationAttributes(EnableLog.class.getName()));
       //这里可以获取到注解的详细信息,然后根据信息去动态的返回需要被spring容器管理的bean
        return new String[]{"com.zhihao.miao.imp.bean.User",People.class.getName(),MyConfig.class.getName()};
    }

}

定义一个EnableLog注解,可以得到属性的值,@Import(MyImportSelector.class),可以在MyImportSelector中获取name属性值。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)
public @interface EnableLog {
    String name();
}

启动类,在启动类上加入@EnableLog(name="com.zhihao.miao")注解,
我们知道@EnableLog中@Import(MyImportSelector.class)会将MyImportSelector对象纳入到容器中。

package com.zhihao.miao.imp;
import com.zhihao.miao.imp.bean.*;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

@EnableLog(name="com.zhihao.miao")
public class Application2 {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application2.class,args);
        System.out.println(context.getBean(User.class));
        System.out.println(context.getBean(Dog.class));
        System.out.println(context.getBean(Cat.class));
        System.out.println(context.getBean(People.class));
    }

}

打印结果:

{name=[com.zhihao.miao]}
2017-07-20 11:27:08.446  INFO 11551 --- [           main] com.zhihao.miao.imp.Application2         : Started Application2 in 11.754 seconds (JVM running for 12.614)
com.zhihao.miao.imp.bean.User@1e4d3ce5
com.zhihao.miao.imp.bean.Dog@3ddc6915
com.zhihao.miao.imp.bean.Cat@704deff2
com.zhihao.miao.imp.bean.People@379614be

ImportBeanDefinitionRegistrar接口

ImportBeanDefinitionRegistrar接口

Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes. Useful when operating at the bean definition level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
接口实现可以额外的注册类的定义到spring容器中。

Along with {@code @Configuration} and {@link ImportSelector}, classes of this type may be provided to the @{@link Import} annotation (or may also be returned from an {@code ImportSelector}).

An {@link ImportBeanDefinitionRegistrar} may implement any of the following
{@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
methods will be called prior to {@link #registerBeanDefinitions}:

{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}

See implementations and associated unit tests for usage examples.

定义MyImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar接口,将User类,People类,Myconfig中的Dog和Cat类注入到spring容器中

package com.zhihao.miao.imp.bean;


import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
 * registerBeanDefinitions方法的参数有一个BeanDefinitionRegistry,
 * BeanDefinitionRegistry可以用来往spring容器中注入bean
 * 如此,我们就可以在registerBeanDefinitions方法里面动态的注入bean
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(People.class);
        registry.registerBeanDefinition(People.class.getName(),bdb.getBeanDefinition());

        BeanDefinitionBuilder bdb2 = BeanDefinitionBuilder.rootBeanDefinition(User.class);
        registry.registerBeanDefinition(User.class.getName(),bdb2.getBeanDefinition());

        BeanDefinitionBuilder bdb3 = BeanDefinitionBuilder.rootBeanDefinition(MyConfig.class);
        registry.registerBeanDefinition(MyConfig.class.getName(),bdb3.getBeanDefinition());
    }
}

主启动类:

package com.zhihao.miao.imp;

import com.zhihao.miao.imp.bean.*;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

@Import(MyImportBeanDefinitionRegistrar.class)
public class Application3 {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application3.class,args);
        System.out.println(context.getBean(User.class));
        System.out.println(context.getBean(Dog.class));
        System.out.println(context.getBean(Cat.class));
        System.out.println(context.getBean(People.class));
    }
}

打印结果

...
com.zhihao.miao.imp.bean.User@3e694b3f
com.zhihao.miao.imp.bean.Dog@1bb5a082
com.zhihao.miao.imp.bean.Cat@78691363
com.zhihao.miao.imp.bean.People@41d477ed
...

当然也可以写成一个注解,@EnableImportConfig

package com.zhihao.miao.imp.bean;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableImportConfig {
}

启动类:

package com.zhihao.miao.imp;

import com.zhihao.miao.imp.bean.*;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

@EnableImportConfig
public class Application4 {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application4.class,args);
        System.out.println(context.getBean(User.class));
        System.out.println(context.getBean(Dog.class));
        System.out.println(context.getBean(Cat.class));
        System.out.println(context.getBean(People.class));
    }
}

也是可以将这些对象注入到spring容器的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容