开发颠覆者SpringBoot实战---------SpringBoot的基础学习
一、介绍
随着动态语言的流行,常规的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"));
}
}
}
可以看出它的判断条件是:
- GenericWebApplicationContext是否在路径中;
- 容器中是否有名为session的scope;
- 当前容器的Environment是否是ConfigurableWebEnvironment;
- 当前容器的getResourceLoader是否是WebApplicationContext(ResourceLoader是ApplicationContext的*接口之一)
- 构造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>
自动配置要满足两个条件:
- 能配置CharacterEncodingFilter这个Bean
- 能配置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;
}
}
}