Spring高级(四)—— SpringBoot

目录

1、构建boot项目

2、Boot 启动过程

1、SpringApplication 构造

​编辑

 2、执行 run 方法

3、Tomcat 内嵌容器

4、Boot 自动配置

1、AopAutoConfiguration

2、DataSourceAutoConfiguration

3、MybatisAutoConfiguration

4、TransactionAutoConfiguration

5、ServletWebServerFactoryAutoConfiguration

6、DispatcherServletAutoConfiguration

7、WebMvcAutoConfiguration

8、ErrorMvcAutoConfiguration

9、MultipartAutoConfiguration

10、HttpEncodingAutoConfiguration

5、条件装配底层


1、构建boot项目

如果是 linux 环境,用以下命令即可获取 spring boot 的骨架 pom.xml

//将需要的依赖写入到 pom.xml当中
curl -G https://start.spring.io/pom.xml -d dependencies=web,mysql,mybatis -o pom.xml

也可以使用 Postman 等工具实现
若想获取更多用法,请参考
curl https://start.spring.io
  • 必须添加如下依赖,因为此时用的还是内嵌 tomcat,而内嵌 tomcat 默认不带 jasper(用来解析 jsp)
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

也可以使用 Idea 配置 tomcat 来测试,此时用的是外置 tomcat

  • 骨架生成的代码中,多了一个 ServletInitializer,它的作用就是配置外置 Tomcat 使用的,在外置 Tomcat 启动后,去调用它创建和运行 SpringApplication

2、Boot 启动过程

1、SpringApplication 构造

public class Day39_1 {
    public static void main(String[] args) throws Exception {
        //1.获取 Bean Definition 源"
        SpringApplication application = new SpringApplication(Day39_1.class);
        application.setSources(Set.of("classpath:b01.xml"));

        //2. 推断应用类型(web的servlet、web的reactive、非web)
        Method method = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        method.setAccessible(true);
        System.err.println("应用类型:"+method.invoke(null));

        //3.ApplicationContext 初始化器
        application.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
                if (configurableApplicationContext instanceof GenericApplicationContext) {
                    GenericApplicationContext gac = (GenericApplicationContext) configurableApplicationContext;
                    gac.registerBean("bean3",Bean3.class);
                }
            }
        });

        //4.监听器与事件
        application.addListeners(applicationEvent -> {
            System.err.println("事件:"+applicationEvent.getClass());
        });

        //5.主类推断(main所在的类)
        Method clazz = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
        clazz.setAccessible(true);
        System.err.println("主类是:"+clazz.invoke(application));

        ConfigurableApplicationContext context = application.run(args);
        System.err.println();
        for (String name : context.getBeanDefinitionNames()) {
            String s = context.getBeanFactory().getBeanDefinition(name).getResourceDescription();
            System.err.println(name+" 来源:【"+s+"】");
        }
        context.close();
    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

 2、执行 run 方法

第 1 步:

public class Day39_2 {
    public static void main(String[] args) throws Exception {
        SpringApplication application = new SpringApplication(Day39_2.class);

        // 添加 app 监听器
        application.addListeners(applicationEvent -> {
            System.err.println("监听器:"+applicationEvent.getClass());
        });

        // 获取事件发送器实现类名
        List<String> names = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, Day39_2.class.getClassLoader());
        for (String name : names) {
            System.err.println("发送器:"+name);
            Class<?> clazz = Class.forName(name);
            Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class);
            SpringApplicationRunListener listener = (SpringApplicationRunListener) constructor.newInstance(application, args);

            // 发布事件
            DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
            listener.starting(bootstrapContext);
            listener.environmentPrepared(bootstrapContext,new StandardEnvironment());

            GenericApplicationContext context = new GenericApplicationContext();
            listener.contextPrepared(context);
            listener.contextLoaded(context);
            context.refresh();
            listener.started(context);
            listener.running(context);

            listener.failed(context,new Exception("出错了!"));
        }

    }
}

 第 2、8、9、11、12 步

public class Day39_3 {
    public static void main(String[] args) throws Exception {
        SpringApplication application = new SpringApplication();
        application.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
                System.err.println("执行初始化器增强...");
            }
        });

        System.err.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
        DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

        System.err.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
        GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

        System.err.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器");
        for (ApplicationContextInitializer initializer : application.getInitializers()) {
            initializer.initialize(context);
        }

        System.err.println(">>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
        DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();
        AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(factory);
        reader.register(Config.class);

        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(factory);
        xmlReader.loadBeanDefinitions(new ClassPathResource("b01.xml"));

        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(factory);
        scanner.scan("com.springboot.springbootmybatis.day39.sub");


        System.err.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.err.println(name+" 来源:【"+factory.getBeanDefinition(name).getResourceDescription()+"】");
        }

        System.err.println(">>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner");
        for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
            //main方法的 args
            runner.run(args);
        }

        for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
            //第二步封装的参数
            runner.run(arguments);
        }
    }

    private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
        GenericApplicationContext context = null;
        switch (type) {
            case SERVLET :
                context = new AnnotationConfigServletWebServerApplicationContext();
                break;
            case REACTIVE :
                context = new AnnotationConfigReactiveWebServerApplicationContext();
                break;
            case NONE :
                context = new AnnotationConfigApplicationContext();
                break;
        }
        return context;
    }

    static class Bean4 {

    }

    static class Bean5 {

    }

    static class Bean6 {

    }

    @Configuration
    static class Config {
        @Bean
        public Bean5 bean5() {
            return new Bean5();
        }

        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public CommandLineRunner commandLineRunner() {
            return new CommandLineRunner() {
                @Override
                public void run(String... args) throws Exception {
                    System.err.println("commandLineRunner()..." + Arrays.toString(args));
                }
            };
        }

        @Bean
        public ApplicationRunner applicationRunner() {
            return new ApplicationRunner() {
                @Override
                public void run(ApplicationArguments args) throws Exception {
                    System.err.println("applicationRunner()..." + Arrays.toString(args.getSourceArgs()));
                    System.err.println(args.getOptionNames());
                    System.err.println(args.getOptionValues("server.port"));
                    System.err.println(args.getNonOptionArgs());
                }
            };
        }
    }
}

第 3 步

public class Step3 {
    public static void main(String[] args) throws IOException {
        ApplicationEnvironment env = new ApplicationEnvironment(); // 系统环境变量, properties, yaml
        env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step3.properties")));
        env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
//        System.out.println(env.getProperty("JAVA_HOME"));

        System.out.println(env.getProperty("server.port"));
    }
}

 第 4 步

public class Step4 {
    public static void main(String[] args) throws IOException {
        ApplicationEnvironment environment = new ApplicationEnvironment();
        environment.getPropertySources().addLast(
                new ResourcePropertySource("step4", new ClassPathResource("step4.properties"))
        );

        ConfigurationPropertySources.attach(environment);
        for (PropertySource<?> source : environment.getPropertySources()) {
            System.err.println(">>>"+source);
        }

        System.err.println();
        System.err.println(environment.getProperty("user.first-name"));
        System.err.println(environment.getProperty("user.middle-name"));
        System.err.println(environment.getProperty("user.last-name"));
    }
}
user.first-name=George
user.middle_name=Walker
user.lastName=Bush

 第 5 步

public class Step5 {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication();
        ApplicationEnvironment environment = new ApplicationEnvironment();

        System.err.println("---------------- 增强前");
        for (PropertySource<?> source : environment.getPropertySources()) {
            System.err.println(">>>"+source);
        }

        System.err.println("\n---------------- 增强后");
        ConfigDataEnvironmentPostProcessor processor1
                = new ConfigDataEnvironmentPostProcessor(new DeferredLogs(),new DefaultBootstrapContext());
        processor1.postProcessEnvironment(environment,application);
        for (PropertySource<?> source : environment.getPropertySources()) {
            System.err.println(">>>"+source);
        }

        System.err.println("\n---------------- 增强后");
        RandomValuePropertySourceEnvironmentPostProcessor processor2
                = new RandomValuePropertySourceEnvironmentPostProcessor(new DeferredLog());
        processor2.postProcessEnvironment(environment,application);
        for (PropertySource<?> source : environment.getPropertySources()) {
            System.err.println(">>>"+source);
        }

        System.err.println(environment.getProperty("server.port"));
        //固定写法,获取随机值
        System.err.println(environment.getProperty("random.int"));
        System.err.println(environment.getProperty("random.uuid"));
    }
}

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication();
        application.addListeners(new EnvironmentPostProcessorApplicationListener());

//        List<String> names = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, Step5.class.getClassLoader());
//        for (String name : names) {
//            System.out.println(name);
//        }

        EventPublishingRunListener listener = new EventPublishingRunListener(application,args);
        ApplicationEnvironment e = new ApplicationEnvironment();
        System.err.println("-------------- 增强前");
        for (PropertySource<?> source : e.getPropertySources()) {
            System.err.println(">>>"+source);
        }

        listener.environmentPrepared(new DefaultBootstrapContext(),e);
        System.err.println("-------------- 增强后");
        for (PropertySource<?> source : e.getPropertySources()) {
            System.err.println(">>>"+source);
        }

    }

 第 6 步

public class Step6 {
    // 绑定 spring.main 前缀的 key value 至 SpringApplication, 请通过 debug 查看
    public static void main(String[] args) throws IOException {
        SpringApplication application = new SpringApplication();
        ApplicationEnvironment e = new ApplicationEnvironment();

        e.getPropertySources().addLast(new ResourcePropertySource("step4", new ClassPathResource("step4.properties")));
//        e.getPropertySources().addLast(new ResourcePropertySource("step6", new ClassPathResource("step6.properties")));

        User user = Binder.get(e).bind("user", User.class).get();
        System.err.println(user);

        User u = new User();
        Binder.get(e).bind("user", Bindable.ofInstance(u));
        System.err.println(u);

        System.out.println(application);
        Binder.get(e).bind("spring.main",Bindable.ofInstance(application));
        System.out.println(application);
    }

    static class User {
        private String firstName;
        private String middleName;
        private String lastName;
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getMiddleName() {
            return middleName;
        }
        public void setMiddleName(String middleName) {
            this.middleName = middleName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
        @Override
        public String toString() {
            return "User{" +
                    "firstName='" + firstName + '\'' +
                    ", middleName='" + middleName + '\'' +
                    ", lastName='" + lastName + '\'' +
                    '}';
        }
    }
}

 

 第 7 步

public class Step7 {
    public static void main(String[] args) {
        ApplicationEnvironment env = new ApplicationEnvironment();
        SpringApplicationBannerPrinter printer = new SpringApplicationBannerPrinter(
                new DefaultResourceLoader(),
                new SpringBootBanner()
        );
        // 测试文字 banner
        env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.location","banner1.txt")));
        // 测试图片 banner
        env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.image.location","banner2.png")));
        // 版本号的获取
        System.out.println(SpringBootVersion.getVersion());
        printer.print(env, Step7.class, System.out);

    }
}
  1. 得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器

    • 发布 application starting 事件1️⃣

  2. 封装启动 args

  3. 准备 Environment 添加命令行参数(*)

  4. ConfigurationPropertySources 处理(*)

    • 发布 application environment 已准备事件2️⃣

  5. 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)

    • application.properties,由 StandardConfigDataLocationResolver 解析

    • spring.application.json

  6. 绑定 spring.main 到 SpringApplication 对象(*)

  7. 打印 banner(*)

  8. 创建容器

  9. 准备容器

    • 发布 application context 已初始化事件3️⃣

  10. 加载 bean 定义

    • 发布 application prepared 事件4️⃣

  11. refresh 容器

    • 发布 application started 事件5️⃣

  12. 执行 runner

    • 发布 application ready 事件6️⃣

    • 这其中有异常,发布 application failed 事件7️⃣

3、Tomcat 内嵌容器

public class MyTomcat {
    public static void main(String[] args) throws IOException, LifecycleException {
        // 1.创建 Tomcat 对象
        Tomcat tomcat = new Tomcat();
        tomcat.setBaseDir("mytomcat");

        // 2.创建项目文件夹, 即 docBase 文件夹
        File docBase = Files.createTempDirectory("boot.").toFile();
        docBase.deleteOnExit();

        // 3.创建 Tomcat 项目, 在 Tomcat 中称为 Context
        Context context = tomcat.addContext("", docBase.getAbsolutePath());
        //集成spring容器
        WebApplicationContext springContext = getWebApplicationContext();

        // 4.编程添加 Servlet
        context.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
                HelloServlet helloServlet = new HelloServlet();
                servletContext.addServlet("aaa",helloServlet).addMapping("/hello");

//                DispatcherServlet dispatcherServlet = springContext.getBean(DispatcherServlet.class);
//                servletContext.addServlet("dispatcherServlet",dispatcherServlet).addMapping("/");

                for (ServletRegistrationBean bean : springContext.getBeansOfType(ServletRegistrationBean.class).values()) {
                    System.err.println(bean);
                    bean.onStartup(servletContext);
                }
            }
        }, Collections.emptySet());

        // 5.启动 Tomcat
        tomcat.start();

        // 6.创建连接器, 设置监听端口
        Connector connector = new Connector(new Http11Nio2Protocol());
        connector.setPort(8090);
        tomcat.setConnector(connector);

    }

    public static WebApplicationContext getWebApplicationContext(){
        //内嵌了tomcat
//        AnnotationConfigServletWebServerApplicationContext

        //没有内嵌tomcat
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(Config.class);
        context.refresh();
        return context;
    }

    @Configuration
    static class Config {
        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }

        @Bean
        // 这个例子中必须为 DispatcherServlet 提供 AnnotationConfigWebApplicationContext, 否则会选择 XmlWebApplicationContext 实现
        public DispatcherServlet dispatcherServlet(WebApplicationContext applicationContext) {
            return new DispatcherServlet(applicationContext);
        }

        @Bean
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
            RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
            handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
            return handlerAdapter;
        }

        @RestController
        static class MyController {
            @GetMapping("hello2")
            public Map<String,Object> hello() {
                return Map.of("hello2", "hello2, spring!");
            }
        }
    }

4、Boot 自动配置

1、AopAutoConfiguration

public class Test1 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
//        StandardEnvironment env = new StandardEnvironment();
//        env.getPropertySources().addLast(new SimpleCommandLinePropertySource("--spring.aop.auto=false"));
//        context.setEnvironment(env);

        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(Config.class);

        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.err.println(name);
        }
        AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        System.err.println(">>>"+creator.isProxyTargetClass());
        context.close();
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config {

    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{AopAutoConfiguration.class.getName()};
        }
    }
}

Spring Boot 是利用了自动配置类来简化了 aop 相关配置

  • AOP 自动配置类为 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

  • 可以通过 spring.aop.auto=false 禁用 aop 自动配置

  • AOP 自动配置的本质是通过 @EnableAspectJAutoProxy 来开启了自动代理,如果在引导类上自己添加了 @EnableAspectJAutoProxy 那么以自己添加的为准

  • @EnableAspectJAutoProxy 的本质是向容器中添加了 AnnotationAwareAspectJAutoProxyCreator 这个 bean 后处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的

2、DataSourceAutoConfiguration

  • 对应的自动配置类为:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

  • 它内部采用了条件装配,通过检查容器的 bean,以及类路径下的 class,来决定该 @Bean 是否生效

简单说明一下,Spring Boot 支持两大类数据源:

  • EmbeddedDatabase - 内嵌数据库连接池

  • PooledDataSource - 非内嵌数据库连接池

PooledDataSource 又支持如下数据源

  • hikari 提供的 HikariDataSource

  • tomcat-jdbc 提供的 DataSource

  • dbcp2 提供的 BasicDataSource

  • oracle 提供的 PoolDataSourceImpl

如果知道数据源的实现类类型,即指定了 spring.datasource.type,理论上可以支持所有数据源,但这样做的一个最大问题是无法订制每种数据源的详细配置(如最大、最小连接数等)

3、MybatisAutoConfiguration

  • MyBatis 自动配置类为 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

  • 它主要配置了两个 bean

    • SqlSessionFactory - MyBatis 核心对象,用来创建 SqlSession

    • SqlSessionTemplate - SqlSession 的实现,此实现会与当前线程绑定

    • 用 ImportBeanDefinitionRegistrar 的方式扫描所有标注了 @Mapper 注解的接口

    • 用 AutoConfigurationPackages 来确定扫描的包

  • 还有一个相关的 bean:MybatisProperties,它会读取配置文件中带 mybatis. 前缀的配置项进行定制配置

@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别

  • @MapperScan 扫描具体包(当然也可以配置关注哪个注解)

  • @MapperScan 如果不指定扫描具体包,则会把引导类范围内,所有接口当做 Mapper 接口

  • MybatisAutoConfiguration 关注的是所有标注 @Mapper 注解的接口,会忽略掉非 @Mapper 标注的接口

这里有同学有疑问,之前介绍的都是将具体类交给 Spring 管理,怎么到了 MyBatis 这儿,接口就可以被管理呢?

  • 其实并非将接口交给 Spring 管理,而是每个接口会对应一个 MapperFactoryBean,是后者被 Spring 所管理,接口只是作为 MapperFactoryBean 的一个属性来配置

4、TransactionAutoConfiguration

  • 事务自动配置类有两个:

    • org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

    • org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

  • 前者配置了 DataSourceTransactionManager 用来执行事务的提交、回滚操作

  • 后者功能上对标 @EnableTransactionM anagement,包含以下三个 bean

    • BeanFactoryTransactionAttributeSourceAdvisor 事务切面类,包含通知和切点

    • TransactionInterceptor 事务通知类,由它在目标方法调用前后加入事务操作

    • AnnotationTransactionAttributeSource 会解析 @Transactional 及事务属性,也包含了切点功能

  • 如果自己配置了 DataSourceTransactionManager 或是在引导类加了 @EnableTransactionManagement,则以自己配置的为准

5、ServletWebServerFactoryAutoConfiguration

  • 提供 ServletWebServerFactory

6、DispatcherServletAutoConfiguration

  • 提供 DispatcherServlet

  • 提供 DispatcherServletRegistrationBean

7、WebMvcAutoConfiguration

  • 配置 DispatcherServlet 的各项组件,提供的 bean 见过的有

    • 多项 HandlerMapping

    • 多项 HandlerAdapter

    • HandlerExceptionResolver

8、ErrorMvcAutoConfiguration

  • 提供的 bean 有 BasicErrorController

9、MultipartAutoConfiguration

  • 它提供了 org.springframework.web.multipart.support.StandardServletMultipartResolver

  • 该 bean 用来解析 multipart/form-data 格式的数据

10、HttpEncodingAutoConfiguration

  • POST 请求参数如果有中文,无需特殊设置,这是因为 Spring Boot 已经配置了 org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter

  • 对应配置 server.servlet.encoding.charset=UTF-8,默认就是 UTF-8

  • 当然,它只影响非 json 格式的数据

public class Day41_1 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);//不允许同名bean覆盖(报错提示),默认true
        context.registerBean("config",Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.err.println(name);
        }

        System.err.println(context.getBean("bean1"));

        context.close();
    }

    @Configuration
    //第一种方式
//    @Import({AutoConfiguration1.class,AutoConfiguration2.class})
    @Import(MyImportSelector.class)
    static class Config{

        @Bean
        public Bean1 bean1() {
            return new Bean1("本项目");
        }

    }

//    static class MyImportSelector implements ImportSelector{
    //推迟导入
    static class MyImportSelector implements DeferredImportSelector{
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            //第二种方式
//            return new String[]{AutoConfiguration1.class.getName(),AutoConfiguration2.class.getName()};
            //第三种方式
            List<String> list = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return list.toArray(new String[0]);
        }
    }

    @Configuration // 第三方的配置类
    static class AutoConfiguration1 {
        @Bean
        @ConditionalOnMissingBean //缺失某个bean时,才会使用该bean
        public Bean1 bean1() {
            return new Bean1("第三方");
        }
    }

    static class Bean1 {
        private String name;

        public Bean1() {
        }

        public Bean1(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Bean1{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    @Configuration // 第三方的配置类
    static class AutoConfiguration2 {
        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean2 {

    }
}
#内部类用 $
com.springboot.springbootmybatis.day41.Day41_1$MyImportSelector=\
  com.springboot.springbootmybatis.day41.Day41_1.AutoConfiguration1,\
  com.springboot.springbootmybatis.day41.Day41_1.AutoConfiguration2
  1. 自动配置类本质上就是一个配置类而已,只是用 META-INF/spring.factories 管理,与应用配置类解耦

  2. @Enable 打头的注解本质是利用了 @Import

  3. @Import 配合 DeferredImportSelector 即可实现导入,selectImports 方法的返回值即为要导入的配置类名

  4. DeferredImportSelector 的导入会在最后执行,为的是让其它配置优先解析

5、条件装配底层

条件装配的底层是本质上是 @Conditional 与 Condition,这两个注解。引入自动配置类时,期望满足一定条件才能被 Spring 管理,不满足则不管理。

  1. 首先编写条件判断类,它实现 Condition 接口,编写条件判断逻辑 
  2. 其次,在要导入的自动配置类上添加 @Conditional(MyCondition1.class),将来此类被导入时就会做条件检查
public class Day41_2 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config",Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.err.println(name);
        }

        context.close();
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{

    }

    static class MyImportSelector implements DeferredImportSelector{
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {

            List<String> list = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return list.toArray(new String[0]);
        }
    }

    static class MyCondition1 implements Condition{

        @Override
        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
            //存在 druid
            Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
            String name = map.get("className").toString();
            boolean exists = (boolean) map.get("exists");
            boolean flag = ClassUtils.isPresent(name,null);
            return exists ?  flag: !flag;
        }
    }
//    static class MyCondition2 implements Condition{
//
//        @Override
//        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//            return !ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource",null);
//        }
//    }

    //自定义注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Conditional(MyCondition1.class)
    @interface ConditionOnClass{
        boolean exists(); //true 存在,false 不存在
        String className();
    }

    @Configuration
//    @Conditional(MyCondition1.class)
    @ConditionOnClass(className = "com.alibaba.druid.pool.DruidDataSource",exists = false)
    static class AutoConfiguration1 {
        @Bean
        public Bean1 bean1() {
            return new Bean1("第三方");
        }
    }

    static class Bean1 {
        private String name;

        public Bean1() {
        }

        public Bean1(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Bean1{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    @Configuration
//    @Conditional(MyCondition2.class)
    @ConditionOnClass(className = "com.alibaba.druid.pool.DruidDataSource",exists = true)
    static class AutoConfiguration2 {
        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean2 {

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值