欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

开发颠覆者SpringBoot实战---------SpringBoot的基础学习

程序员文章站 2022-05-24 11:18:42
...

一、介绍

随着动态语言的流行,常规的java开发S显得格外笨重:繁多的配置、低下的开发效率、复杂的部署流程以及第三方技术集成难度大。为了解决这些问题,SpringBoot应运而生。它使用“习惯优化配置的理念”(项目中存在大量的配置,还内置一个习惯性配置,让你无需手动进行配置)来让项目快速运行起来,很容易创建一个独立运行、准生产级别的项目,可以不用或者需要很少的Spring配置。

1、核心功能:

  • 独立运行Spring项目
  • 内嵌Servlet容器
  • 提供start简化Maven配置
  • 自动配置Spring
  • 准生产的应用监控

2、SpringBoot快速搭建:

下载http://start.spring.io,导入项目,或者自行创建。

简单演示:

直接在入口类编写控制器:

@SpringBootApplication
@RestController
public class Springboot2Application {
    @RequestMapping("/")
    public String index(){
        return "hello spring boot";
    }
    public static void main(String[] args) {
        SpringApplication.run(Springboot2Application.class, args);
    }
}

运行主方法即可。

@SpringBootApplication注解组合了三个有用的注解:

  • Spring的 @Configuration:Spring基于java的配置类
  • Spring的 @ComponentScan:开启组建扫描
  • SpringBoot的 @EnableAutoConfiguration:开启Springboot的自动配置

3、使用起步依赖

Spring Boot通过提供众多起步依赖降低项目依赖的复杂度。起步依赖本质上是一个Maven项目对象模型(Project Object Model, POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。很多起步依赖的命名都暗示了它们提供的某种或某类功能。如:

  • org.springframework.boot:spring-boot-starter-web:添加web项目依赖
  • org.springframework.boot:spring-boot-starter-thymeleaf:添加Thymeleaf模块试图依赖
  • org.springframework.boot:spring-boot-starter-data-jpa:添加Spring Data JPA依赖
  • org.springframework.boot:spring-boot-starter-test:添加测试依赖

我们并不需要指定版本号,起步依赖本身的版本是由正在使用的Spring Boot的版本来决定的,而起步依赖则会决定它们引入的传递依赖的版本。不知道自己所用依赖的版本,你多少会有些不安。你要有信心,相信Spring Boot经过了足够的测试,确保引入的全部依赖都能相互兼容。

通过传递依赖,添加这些依赖就等价于加了一大把独立的库。这些传递依赖涵盖了Spring MVC、Spring Data JPA、 Thymeleaf等内容,它们声明的依赖也会被传递依赖进来。最值得注意的是,这四个起步依赖的具体程度恰到好处。我们并没有说想要Spring MVC,只是说想要构建一个Web应用程序。我们并没有指定JUnit或其他测试工具,只是说我们想要测试自己的代码。 Thymeleaf和Spring Data JPA的起步依赖稍微具体一点,但这也只是由于没有更模糊的方法声明这种需要。

同时我们也可以自己声明其他的依赖,如果与传递依赖重复,会自动覆盖传递依赖,但要注意版本的兼容性,但要知道,起步依赖中各个依赖版本之间的兼容性都经过了精心的测试。应该只在特殊的情况下覆盖这些传递依赖(比如新版本修复了一个bug)。

二、核心

1、基本配置

SpringBoot的全局配置文件application.properties,可以对一些默认配置进行修改,如修改端口号和访问路径:

server.port=9090
server.servlet.context-path=/springboot2

如有一些特殊要求必须使用xml配置,可以使用@ImportResource来加载xml文件:

@ImportResource({"classpath:other.xml"})

2.、外部配置

常规属性配置:全局配置文件中增加

springboot.author=think
springboot.time=2018-5-18

修改入口类:

    @Value("${springboot.author}")
    private String author;
    @Value("${springboot.time}")
    private String time;
    @RequestMapping("/")
    public String index(){
        return "hello spring boot,Springboot.author=" + author + ",Springboot.time=" + time;
    }

类型安全配置: SpringBoot还允许使用properties文件作为参数引入:(prefix:引入的配置文件参数前缀,locations配置文件位置)

/**
 * 作为参数引入类
 * @author think
 */
@Component
@ConfigurationProperties(prefix = "springboot")
//prefix指定配置文件中参数的前缀,locations执行配置文件位置
public class MyAuthor {
    private String author;
    private String time;
    public String getTime() {
        return time;
    }
    public void setTime(String time) {
        this.time = time;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
}
/**
 * 修改入口类
 * @author think
 */
    @Autowired
    private MyAuthor author;
    @RequestMapping("/")
    public String index(){
        return "hello spring boot,Springboot.author=" + author.getAuthor() + ",Springboot.time=" + author.getTime();
    }

3、日志配置

SpringBoot支持多种日志框架,并已为当前使用的日志框架做好了配置,默认情况下使用Logback作为日志框架。

增加配置:

logging.file=F:/springboot/log.log
logging.level.org.springframework.web=debug

4、Profile配置

Profile是Spring用来针对不同环境来使用不同的配置。

增加两个配置文件:

application-dev.properties

server.port=8085

application-prod.properties

server.port=8088

在全局配置文件application.properties中增加:

spring.profiles.active=dev

启动后就可以看见端口号的变化。spring.profiles.active是固定api,根据增加的不同环境的配置文件application-XX.properties中的XX来识别不同环境。

三、SpringBoot原理

1、运作原理

关于SpringBoot运作原理,体现在@EnableAutoConfiguration这个注解中,源码都在org.springframework.boot.autoconfigure包中。下面是@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的源码:

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;
}

AutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包,这个jar包就在自动配置的核心包org.springframework.boot.autoconfigure中。

2、核心注解

spring.factories文件中声明了有哪些自动配置,随意打开一个autoconfigure的文件,都会有下面的条件注解:

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

这些注解都是组合了@Conditional元注解,只是使用了不用的条件(condition),如ConditionalOnWebApplication:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnWebApplicationCondition.class})
public @interface ConditionalOnWebApplication {
    ConditionalOnWebApplication.Type type() default ConditionalOnWebApplication.Type.ANY;
    public static enum Type {
        ANY,
        SERVLET,
        REACTIVE;
        private Type() {
        }
    }
}

ConditionalOnWebApplication注解的条件是OnWebApplicationCondition,下面是OnWebApplicationCondition中的一段代码:

private ConditionOutcome isServletWebApplication(ConditionContext context) {
    Builder message = ConditionMessage.forCondition("", new Object[0]);
    if (!ClassUtils.isPresent("org.springframework.web.context.support.GenericWebApplicationContext", context.getClassLoader())) {
        return ConditionOutcome.noMatch(message.didNotFind("web application classes").atAll());
    } else {
        if (context.getBeanFactory() != null) {
            String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
            if (ObjectUtils.containsElement(scopes, "session")) {
                return ConditionOutcome.match(message.foundExactly("'session' scope"));
            }
        }
        if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
            return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
        } else {
            return context.getResourceLoader() instanceof WebApplicationContext ? ConditionOutcome.match(message.foundExactly("WebApplicationContext")) : ConditionOutcome.noMatch(message.because("not a servlet web application"));
        }
    }
}

可以看出它的判断条件是:

  1. GenericWebApplicationContext是否在路径中;
  2. 容器中是否有名为session的scope;
  3. 当前容器的Environment是否是ConfigurableWebEnvironment;
  4. 当前容器的getResourceLoader是否是WebApplicationContext(ResourceLoader是ApplicationContext的*接口之一)
  5. 构造ConditionOutcome对象,通过isMatch再确定。

3、实例分析

在常规项目中配置http编码时候是在web.xml中配置一个filter:

<filter>
    <filter-name>encodingFilter</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也是基于这一点来实现的,在全局配置文件application.properties中直接配置:

//默认编码方式为UTF-8
spring.http.encoding.charset=utf-8
//设置forceEncoding
spring.http.encoding.force=true

上述的属性信息通过类型安全的配置,注入到下面这个类中:

@ConfigurationProperties(
    prefix = "spring.http.encoding"
)//在application.properties配置中,前缀是spring.http.encoding的
public class HttpEncodingProperties {
    ......
}

通过调用上述的配置,并根据条件配置CharacterEncodingFilter的Bean:

@Configuration
//开启属性注入,通过@EnableConfigurationProperties声明,使用@Autowired注入
@EnableConfigurationProperties({HttpEncodingProperties.class})
@ConditionalOnWebApplication(type = Type.SERVLET)
//当CharacterEncodingFilter在类路径的条件下
@ConditionalOnClass({CharacterEncodingFilter.class})
//当设置spring.http.encoding=enabled的情况下,如果没有设置默认为true,即条件符合
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
    private final HttpEncodingProperties properties;
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }
    @Bean//配置这个Bean
    @ConditionalOnMissingBean//当容器没有这Bean时候新建Bean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.RESPONSE));
        return filter;
    }
    @Bean
    public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
        return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
    }
    private static class LocaleCharsetMappingsCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
        private final HttpEncodingProperties properties;
        LocaleCharsetMappingsCustomizer(HttpEncodingProperties properties) {
            this.properties = properties;
        }
        public void customize(ConfigurableServletWebServerFactory factory) {
            if (this.properties.getMapping() != null) {
                factory.setLocaleCharsetMappings(this.properties.getMapping());
            }
        }
        public int getOrder() {
            return 0;
        }
    }
}
相关标签: springboot