常规的Java项目,很多都是基于SSH/SSM三大框架搭建,开发起来显得格外的笨重:繁多的配置、低下的开发效率、复杂的部署流程以及第三方技术集成难度大。在上述环境下,Sping boot应运而生,它使用“习惯优于配置”(项目中存在大量的配置,此外还内置一个习惯性的配置,自动配置机制可以读取默认的配置,让你无需手动配置)的理念让你的项目快速运行起来,Spring boot可以不用或者只用很少的spring配置。但是由于Spring boot将配置封装了起来,就丧失了SSH配置的灵活性,造成维护的困难。可是如果我们熟悉Spring boot的自动配置运作原理,修改Spring boot参数配置将和SSH一样。
在讲解Spring boot自动配置运作原理之前,有必要先温习一下Spring的知识
Spring boot常用注解
- @configuation
声明当前类是一个配置类 - @ComponentScan
自动扫描包名下所使用的@Controller、@Service、@Repository、@Component的类,并注册为Bean - @PropertySource
注入配置文件 - @Conditional
根据特定条件控制Bean的创建行为,这样我们可以利用这个特性进行一些自动的配置(敲黑板,Spring的自动配置就是利用了这个注解)
Java配置
Java配置是Java 4.x推荐的配置方式,可以完全替代xml配置,Java配置也是Spring boot推荐的配置方式。
Java是通过@Configuation和@Bean来实现
- @Configuation声明当前类是一个配置文件,相当于一个spring配置的xml文件
- @Bean注解在方法上,声明当前方法的返回值为一个Bean
关于何时使用Java配置或者注解配置呢?一般全局配置使用Java配置(如数据库相关配置),业务Bean配置使用注解(如@Controller、@Service、@Component)
@Enable*注解的工作原理
如@EnableScheduling、@EnableWebMvc注解,来开启一项功能的支持,从而避免自己配置大量的代码,大大降低使用难度。那么这个神奇功能的原理是什么呢?通过观察@Enable*注解的源码,这些注解里面都有一个@Import注解,@Import注解是用来导入配置类的,这也意味着这些自动开启的实现其实是导入了一些自动配置的Bean。
组合注解
随着注解的大量使用,尤其相同的注解越来越多,会显得很啰嗦,这就所谓的模板代码,在Spring设计原则中是要消除的代码。把注解注解到别的注解上形成的新的注解,就叫做组合注解。
用一个例子结束上面的内容,例子尽可以覆盖以上的内容
#annotation.properties
program.type=python
#JavaConditional.java
@PropertySource("classpath:annotation.properties")
public class JavaConditional implements Condition{
@Value("${program.type}")
private String programType;
@Override
public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
//return "java".equals(programType);
//不知道为什么获取不到programType,为了能够继续调试,先写死
return true;
}
}
#PythonConditional .java
@PropertySource("classpath:demo.properties")
public class PythonConditional implements Condition{
@Value("${program.type}")
private String programType;
@Override
public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
return "python".equals(programType);
}
}
#JavaConfiguation.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Bean
@Conditional(JavaConditional.class)
public @interface JavaConfiguation {
}
#PythonConfiguation.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Bean
@Conditional(PythonConditional.class)
public @interface PythonConfiguation {
}
#ConditionConfig.java
@Configuration
public class ConditionConfig {
@JavaConfiguation
public ProgramLearnService javaLearnService() {
return new JavaLearnServiceImpl();
}
@PythonConfiguation
public ProgramLearnService pythonLearnService() {
return new PythonLearnServiceImpl();
}
}
#ProgramLearnService.java
public interface ProgramLearnService {
public String learn();
}
#JavaLearnServiceImpl.java
public class JavaLearnServiceImpl implements ProgramLearnService {
@Override
public String learn() {
return "I learn java";
}
}
#PythonLearnServiceImpl.java
public class PythonLearnServiceImpl implements ProgramLearnService {
@Override
public String learn() {
return "I learn python";
}
}
#App.java
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
ConditionConfig.class);
ProgramLearnService pl = context.getBean(ProgramLearnService.class);
System.out.println(pl.learn());
context.close();
}
}
分界线 -----------,以下开始Spring boot部分
类型安全的配置(基于properties)
在常规spring环境下,注入properties文件里值的方式,通过@propertiesSource指明值的位置,然后通过@Value注入值,在Spring boot中只需要把值写到application.properties, 直接用@Value注入值即可。
尽管方便了一点,可是在实际项目中,配置有很多,就要配置很多的@Value,显得格外麻烦,所以Spring boot提供了基于类型安全的配置方式,通过@ConfiguationProperties将propertis属性和一个bean及其属性关联。
#application.properties
author.name=jack
author.age=18
#AuthorSettings.java
@Component
@ConfigurationProperties(prefix="author")
public class AuthorSettings {
private String name;
private Integer age;
//setter、getter
}
#通过@ConfigurationProperties加载Propertis文件内的配置,通过prefix属性指定properties内配置的前缀,通过locations属性指定properties文件的位置,将AuthorSettings这个Bean注入到其他内即可使用,方便吧
Spring boot运作原理
关于Spring boot的运作原理,得从@SpringBootApplication讲起,这个注解是一个组合注解,它的核心功能是由@EnableAutoConfiguration注解提供的。来看看@EnableAutoConfiguration源码
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
这里的关键功能是@Import注解导入的配置功能,EnableAutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有MEAT-INF/spring.factories文件的jar包(1.5版本以前使用EnableAutoConfigurationImportSelector类,1.5以后这个类废弃了使用的是AutoConfigurationImportSelector类),而我们的spring-boot-autoconfigure-1.5.3.RELEASE.jar下面就有spring.factories文件,此文件中声明了有哪些自动配置。
spring.factories文件:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# 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.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
核心注解
打开任意*AutoConfiguration文件,一般都有下面的条件注解,在spring-boot-autoconfigure-1.5.3.RELEASE.jar的org.springframework.boot.autoconfigure.condition包下条件注解如下:
@ConditionalOnBean:当前容器有指定Bean的条件下。
@ConditionalOnClass:当前类路径下有指定的类的条件下。
@ConditionalOnExpression:基于SpEL表达式作为判断条件。
@ConditionalOnJava:基于JVM版本作为判断条件。
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean:当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication:当前项目不是WEB项目的条件下。
@ConditionalOnProperty:指定属性是否有指定的值。
@ConditionalOnResource:类路径是否有指定的值。
@ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但 是指定首选的Bean。
@ConditionalOnWebApplication:当前项目是WEB项目的条件下。
这些注解都组合了@Conditional元注解,只是使用了不同的条件(Conditional),Spring 条件注解(@Conditional)我们介绍过根据不同条件创建不同Bean
实战
最后,写一个例子,实现自动装载,而且写一个starter pom
spring-boot-starter-hello工程
# pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springwork.boot</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.3.0.M1</version>
</dependency>
</dependencies>
</project>
# HelloServiceProperties.java
package hello;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix="hello")
public class HelloServiceProperties {
private String msg = "world";
//setter、getter
}
# HelloService.java
package hello;
public class HelloService {
private String msg;
public String sayHello() {
return "hello " + msg;
}
//setter、getter
}
# HelloServiceAutoConfiguation.java
package hello;
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.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 HelloServiceAutoConfiguation {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService() {
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
# spring.factories(放在src.main.resource/META-INF路径下)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
hello.HelloServiceAutoConfiguation
其他工程,依赖spring-boot-starter-hello工程
# pom.xml
<dependency>
<groupId>org.springwork.boot</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
#SpringbootController.java
package base.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import hello.HelloService;
@RestController
@RequestMapping("springboot")
public class SpringbootController {
@Autowired
HelloService helloService;
@RequestMapping("autoConfigTest")
public String autoConfigTest() {
return helloService.sayHello();
}
}
测试结果
参考文献《JavaEE开发的颠覆者 Spring Boot实战》汪云飞编著
网友评论