美文网首页
Spring Boot学习笔记(二):Spring Boot 运

Spring Boot学习笔记(二):Spring Boot 运

作者: 简单一点点 | 来源:发表于2019-03-22 23:40 被阅读0次

    看一下 Spring 实现自动配置的原理~~

    全部章节传送门:
    Spring Boot学习笔记(一):Spring Boot 入门基础
    Spring Boot学习笔记(二):Spring Boot 运行原理
    Spring Boot学习笔记(三):Spring Boot Web开发
    Spring Boot学习笔记(四):Spring Boot 数据访问
    Spring Boot学习笔记(五):Spring Boot 企业级开发
    Spring Boot学习笔记(六):Spring Boot 应用监控

    Spring Boot 启动原理

    任何一个 Spring Boot 项目都会有一个启动类,其中有一个 @SpringBootApplication 注解。

    @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 {
        ...
    }
    

    其中,比较关键的注解:

    • @SpringBootConfiguration:标记当前类为配置类
    • @EnableAutoConfiguration:开启自动配置
    • @ComponentScan:扫描主类所在的同级包以及下级包里的Bean

    在进入核心注解 @EnableAutoConfiguration 的源码中。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    

    其中的关键部分是 @Import 注解导入的配置功能,AutoConfigurationImportSelector 使用 getCandidateConfigurations 方法得到待配置的class的类名集合。

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    

    SpringFactoriesLoader.loadFactoryNames 方法会扫描 META-INF/spring.factories 文件。

    查看 spring-boot-autoconfigure-2.1.3.RELEASE.jar 中的 spring.factories 。


    spring-factories.png

    可以看到其中的自动配置。

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    ...
    

    核心注解

    打开任意的 AutoConfiguration 文件(spring-boot-autoconfigure-2.1.3.RELEASE.jar 中),可以看到很多的条件注解,这些注解包含在 org.springframework.boot.autoconfigure.condition 包中,如下所示:

    • @ConditionalOnBean:当容器里有指定Bean的条件下
    • @ConditionalOnClass:当类路径下有指定的类的条件下
    • @ConditionalOnExpression:基于SpEL表达式作为判断条件
    • @ConditionalOnJava:基于JVM版本作为判断条件
    • @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
    • @ConditionalOnMissingBean:当容器里没有指定Bean的情况下
    • @ConditionalOnMissingClass:当容器里没有指定类的情况下
    • @ConditionalOnWebApplication:当前项目时Web项目的条件下
    • @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
    • @ConditionalOnProperty:指定的属性是否有指定的值
    • @ConditionalOnResource:类路径是否有指定的值
    • @ConditionalOnOnSingleCandidate:当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean
    • @ConditionalOnWebApplication: 当前项目是在 Web 项目的条件下。

    这些注解都组合了@Conditional注解,只是使用了不同的条件。

    实例分析

    下面通过一个简单的 Spring Boot 内置的自动配置: http的编码配置来讲解一下配置流程。

    在常规项目中配置 Http 编码的时候是在 web.xml 中配置一个 filter 。

    <filter> 
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param> 
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value> 
        </init-param> 
        <init-param> 
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value> 
        </init-param> 
    </filter> 
    

    自动配置需要满足两个条件:

    1. 能配置 CharacterEncodingFilter 这个 Bean;
    2. 能配置 encoding 和 forceEncoding 两个参数。

    配置参数的时候使用了在SpringBoot基础章节中讲述的类型安全配置,Spring Boot 也是基于这一点实现的。双击shift全局搜索 HttpProperties(这里需要注意,不是老版本中的 HttpEncodingProperties)。

    @ConfigurationProperties(
        prefix = "spring.http"
    ) // 配置前缀
    public class HttpProperties {
        private boolean logRequestDetails;
        private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();
    
        public HttpProperties() {
        }
    
        public boolean isLogRequestDetails() {
            return this.logRequestDetails;
        }
    
        public void setLogRequestDetails(boolean logRequestDetails) {
            this.logRequestDetails = logRequestDetails;
        }
    
        public HttpProperties.Encoding getEncoding() {
            return this.encoding;
        }
    
        public static class Encoding {
            public static final Charset DEFAULT_CHARSET;
            private Charset charset;
            private Boolean force;
            private Boolean forceRequest;
            private Boolean forceResponse;
            private Map<Locale, Charset> mapping;
    
            public Encoding() {
                this.charset = DEFAULT_CHARSET;
            }
    
            public Charset getCharset() {
                return this.charset;
            }
    
            public void setCharset(Charset charset) {
                this.charset = charset;
            }
    
            public boolean isForce() {
                return Boolean.TRUE.equals(this.force);
            }
    
            public void setForce(boolean force) {
                this.force = force;
            }
    
            public boolean isForceRequest() {
                return Boolean.TRUE.equals(this.forceRequest);
            }
    
            public void setForceRequest(boolean forceRequest) {
                this.forceRequest = forceRequest;
            }
    
            public boolean isForceResponse() {
                return Boolean.TRUE.equals(this.forceResponse);
            }
    
            public void setForceResponse(boolean forceResponse) {
                this.forceResponse = forceResponse;
            }
    
            public Map<Locale, Charset> getMapping() {
                return this.mapping;
            }
    
            public void setMapping(Map<Locale, Charset> mapping) {
                this.mapping = mapping;
            }
    
            public boolean shouldForce(HttpProperties.Encoding.Type type) {
                Boolean force = type != HttpProperties.Encoding.Type.REQUEST ? this.forceResponse : this.forceRequest;
                if (force == null) {
                    force = this.force;
                }
    
                if (force == null) {
                    force = type == HttpProperties.Encoding.Type.REQUEST;
                }
    
                return force;
            }
    
            static {
                DEFAULT_CHARSET = StandardCharsets.UTF_8;//默认编码是UTF8
            }
    
            public static enum Type {
                REQUEST,
                RESPONSE;
    
                private Type() {
                }
            }
        }
    }
    

    通过调用上述配置,然后根据条件配置 CharacterEncodingFilter 的 Bean 。

    @Configuration 
    @EnableConfigurationProperties({HttpProperties.class}) 
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    @ConditionalOnClass({CharacterEncodingFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.http.encoding",
        value = {"enabled"},
        matchIfMissing = true
    )
    public class HttpEncodingAutoConfiguration {
        private final Encoding properties;
    
        public HttpEncodingAutoConfiguration(HttpProperties properties) {
            this.properties = properties.getEncoding();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public CharacterEncodingFilter characterEncodingFilter() {
            CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
            filter.setEncoding(this.properties.getCharset().name());
            filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
            filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
            return filter;
        }
        ...
    }
    

    看上面的注解:

    • @Configuration:标明为配置类
    • @EnableConfigurationProperties(HttpEncodingProperties.class)声明开启属性注入
    • @ConditionalOnClass(CharacterEncodingFilter.class)当CharacterEncodingFilter在类路径的条件下
    • @ConditionalOnProperty(prefix = “spring.http.encoding”, value = “enabled”, matchIfMissing = true)当spring.http.encoding=enabled的情况下,如果没有设置则默认为true,即条件符合
    • @ConditionalOnMissingBean当容器中没有这个Bean时新建Bean 。

    实现 starter pom

    使用 idea 创建 Maven 的 quickstart 项目,项目信息如下。

    <groupId>com.wyk</groupId>
    <artifactId>spring-boot-starter-hello</artifactId>
    <version>1.0-SNAPSHOT</version>
    

    在 pom.xml 中添加 spring-boot-autoconfigure 依赖。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <version>2.1.2.RELEASE</version>
    </dependency>
    

    创建属性配置类,用来获取类型安全的属性。

    package com.wyk.springbootstarterhello;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    /**
     * hello属性配置
     */
    @ConfigurationProperties(prefix="hello")
    public class HelloServiceProperties {
        private static final String MSG = "world";
        //添加默认值
        private String msg = MSG;
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
    

    创建判断依据类。

    package com.wyk.springbootstarterhello;
    
    /**
     * 判断依据类
     */
    public class HelloService {
        private String msg;
    
        public String sayHello() {
            return "Hello " + msg;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
    

    创建自动配置类。

    package com.wyk.springbootstarterhello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @EnableConfigurationProperties(HelloServiceProperties.class)
    @ConditionalOnClass(HelloService.class)
    @ConditionalOnProperty(prefix="hello", value="enabled", matchIfMissing=true)
    public class HelloServiceAutoConfiguration {
        @Autowired
        private HelloServiceProperties helloServiceProperties;
    
        @Bean
        @ConditionalOnMissingBean
        public HelloService helloService() {
            HelloService helloService = new HelloService();
            helloService.setMsg(helloServiceProperties.getMsg());
            return helloService;
        }
    }
    

    若要配置类生效,还需要注册自动配置类,在src/main/resources 下新建 META-INF/spring.factories,并在其中填写如下内容:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.wyk.springbootstarterhello.HelloServiceAutoConfiguration
    

    多个配置的话需要使用逗号隔开。

    另外,在idea中默认没有resources目录,需要手工创建,然后点击 File->Project Structure...,在弹出的窗口中选择 Modules,然后右击resources目录将其设置为资源。

    [图片上传失败...(image-5b5c5b-1553269214786)]

    然后需要将项目添加到 Maven 中方便引用。

    点击 View->Tool Window->Maven Projects ,会在右侧弹出Maven的工具栏。

    [图片上传失败...(image-a6ee64-1553269214786)]

    点击Lifecycle中的install即可构建Maven工程并安装到本地仓库,需要清除的话需点击clean 。

    接下来创建一个 Spring Boot 的Web项目用来使用前面的 starter 。创建好之后需要添加starter依赖。

    <dependency>
        <groupId>com.wyk</groupId>
        <artifactId>spring-boot-starter-hello</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    

    然后简单的修改一下运行类。

    @RestController
    @SpringBootApplication
    public class StatertestdemoApplication {
    
        @Autowired
        HelloService helloService;
    
        public static void main(String[] args) {
            SpringApplication.run(StatertestdemoApplication.class, args);
        }
    
        @RequestMapping("/")
        public String index() {
            return helloService.sayHello();
        }
    }
    

    运行程序,访问 http://localhost:8080 ,效果如下图。

    starter-hello-world.png

    在application.properties中添加配置。

    hello.msg=wyk
    

    重新运行, 访问 http://localhost:8080 ,效果如下图。

    starter-hello-wyk.png

    相关文章

      网友评论

          本文标题:Spring Boot学习笔记(二):Spring Boot 运

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