SpringBoot如何实现自动配置


写在前面

本文参考自 Spring-Boot 官方文档,介绍了 Auto-configuration 常用特性,关于具体的说明可参考官方文档


Spring Boot 自动配置尝试根据添加的jar依赖项自动配置 Spring 应用程序。例如,如果 HSQLDB 在您的类路径中,并且您没有手动配置任何数据库连接bean,那么Spring Boot将自动配置内存中的数据库。

自动配置需要通过 @EnableAutoConfiguration 注解来启用,仅仅只需要将该注解添加到一个 @Configuration 所注解的类上。使用 @SpringBootApplication 注解来启用也是一样的(因为@SpringBootApplication 注解本身添加了 @EnableAutoConfiguration)。

应该只添加一个 @SpringBootApplication@EnableAutoConfiguration 注解。我们通常建议只向主@Configuration 类添加一个或另一个。


实现自定义自动配置

用例:实现一个自定义的 Derby 内嵌数据库(derby 的使用请参考这篇博文)。

  1. 导入依赖:

    		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <!-- 驱动 -->
            <dependency>
                <groupId>org.apache.derby</groupId>
                <artifactId>derbyclient</artifactId>
                <version>10.11.1.1</version>
            </dependency>
    

    spring-boot-starter 包是需要的,我曾在未导入该包的情况下运行程序,会出现无法加载配置文件(application.yml)。spring-boot-configuration-processor 是在我们使用 @ConfigurationProperties 注解时,就会提醒我们导入的。

  2. 配置属性类

    derby 的一些配置属性,以便在配置文件中使用:

    /**
     * derby 数据库基本配置
     * @author duofei
     * @date 2019/11/25
     */
    @ConfigurationProperties(prefix = "spring.derby")
    public class DerbyProperties {
    
        private String drivers = "org.apache.derby.jdbc.ClientDriver";
    
        private String url = "jdbc:derby://localhost:1527/COREJAVA;create=true";
    
        private String userName = "";
    
        private String password = "";
    
        public String getDrivers() {
            return drivers;
        }
    
        public void setDrivers(String drivers) {
            this.drivers = drivers;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
  3. 自动配置类

    该类启用配置属性,一般在 Spring 的使用中,会结合 @Conditional 的一系列注解来使用,以便达到在不同条件下实现不同配置的目的。

    @Configuration
    @EnableConfigurationProperties(DerbyProperties.class)
    public class DerbyAutoConfiguration {
    
        @Bean
        public Connection derbyConnection(DerbyProperties derbyProperties) throws Exception{
            System.setProperty("jdbc.drivers", derbyProperties.getDrivers());
            return DriverManager.getConnection(derbyProperties.getUrl(),
                    derbyProperties.getUserName(), derbyProperties.getPassword());
        }
    }
    
  4. 启动类

    该类作为启动类,但同时又负担了测试的功能,有关 derby 的使用疑惑,可以参考上面推荐的博文。

    @SpringBootApplication
    public class Application implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
            final Connection derbyConnection = applicationContext.getBean("derbyConnection", Connection.class);
            try {
                run(derbyConnection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        public static void run(Connection conn) throws SQLException {
            try {
                Statement stat = conn.createStatement();
                stat.executeUpdate("CREATE TABLE Greetings (Message CHAR(40))");
    
                // Using ' not "
                stat.executeUpdate("INSERT INTO Greetings VALUES('hello')");
                stat.executeUpdate("INSERT INTO Greetings VALUES('你好,世界')");
    
                try(ResultSet result = stat.executeQuery("SELECT * FROM Greetings")){
                    //将光标移动到下一行,初始在第一行之前
                    while(result.next()){
                        System.out.println(result.getString("Message"));
                    }
                }
                stat.executeUpdate("DROP TABLE Greetings");
            }finally {
                conn.close();
            }
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            Application.applicationContext = applicationContext;
        }
    }
    
  5. 配置文件

    spring:
      derby:
        user-name: duofei
        password: duofei001
    

启动测试类,我们可以在控制台取得

hello 你好,世界

输出。那如果通过 jar 包引入该类,仍然可用嘛?新建项目,引入该 jar 包。在新项目中,仍然使用以上的启动类和配置文件。结果与直接在 jar 包中执行并无区别。但是当你在新项目中,修改启动类的包路径(与 jar 包的包路径不同),你会发现,控制台抛出了异常,我们并不能找到这个 bean 了。


发生以上现象的原因是 @SpringBootApplication 注解的扫包路径只能在其所注解的类所在的路径及其子路径上,我们可以验证这个猜想,手动指定扫包路径如下:

@SpringBootApplication(scanBasePackages = "com.duofei") ,运行结果又正常了。难道我们一定需要这样做嘛?spring 自身实现的自动配置可没有要求我们声明包路径(那样一定糟糕透了)。


参考 spring 的自动配置,我们可以对以上用例进行修改。

修改原来的 jar 包。在 resource 目录下,新建目录 META-INF,在该目录下创建 spring.factories 文件,文件内容如下:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.duofei.derby.DerbyAutoConfiguration

现在,即使新建项目并未指定扫包路径,程序依然可以正确执行了。


取代自动配置

自动配置非侵入性。在任何时候,您都可以开始定义自己的配置来替换自动配置的特定部分。例如,如果您添加自己的 DataSource bean,则默认的嵌入式数据库支持将后退。

继续以上用例: 重新定义 Connection bean,不使用配置文件的用户名和密码,仅仅使用固定的 duofei007

启动类新增如下 bean 的配置:

    @Bean
    public Connection derbyConnection(DerbyProperties derbyProperties) throws Exception{
        System.setProperty("jdbc.drivers", derbyProperties.getDrivers());
        return DriverManager.getConnection(derbyProperties.getUrl(),
                "duofei", "007");
    }

重新启动程序,报错,无法定义相同名称的 bean ,提示可以修改 spring.main.allow-bean-definition-overriding = true 。在配置文件中按照提示修改,重新启动程序。程序正常运行,但通过断点,你会发现,后配置的 bean 根本就没有执行。所以,这不会是我们想要的。那么问题既然出在重名,那么我们可以重新定义之前的 jar 包中的 bean,为其添加以下注解:

@ConditionalOnMissingBean(name = {"derbyConnection"})

该注解表示只有不存在名为 "derbyConnection" 的 bean 时,才会注册该 bean,即实现了一个默认的作用。重新运行程序,启动类中新配置的 Connection 生效。


禁用自动配置

  1. 如果你发现你不想要的特定的自动配置类被应用,你可以使用 @EnableAutoConfigurationexclude 属性来禁用它们。

  2. 如果类不在类路径中,则可以使用注释的 exclude 属性并指定完全限定名。最后,您还可以使用spring.autoconfigure.exclude 属性来控制要排除的自动配置类列表。

  3. @SpringBootApplication 注解的 exclude 属性,也能达到同样的功能。

也就是说,我们能够在注解或者使用属性两个层面禁用自动配置功能。

继续以上用例: 禁用自动配置的 Connection,使用重新实现的 Connection

需要先取消@ConditionalOnMissingBean(name = {"derbyConnection"})注解。修改启动类上的 @SpringBootApplication 注解:@SpringBootApplication(exclude = DerbyAutoConfiguration.class)。重新启动程序,我们会得到 DerbyProperties 的 bean 无法找到,因为该 bean 是在配置类上启用的,现在禁用了该配置类,相应的,这个 bean 也找不到了。禁用自动配置的目的达到了,不过发生了这种现象,也是我们在使用时,需要注意的一方面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值