@SpringBootApplication核心原理(下)
在上个章节中,我们提到了@SpringBootApplication核心原理中十分重要的注解@ConditionalOnClass,解释了SpringBoot实现自动装配的关键步骤,其次对核心源码也做了简要介绍,本章将对SpringBoot外部化配置做详细说明。
首先回到上个章节,笔者提出的问题,很多时候我们引入的组件都是需要我们用户自定义的,比如数据库连接、ip、端口,这些是没有办法确定的,但是这些类却需要自动配置到SpringBoot工厂里面去,因此SpringBoot针对这类东西提出了一个解决方案,叫@EnableConfigurationProperties,从名字就可以看出这又是一种自动配置注解,可能大家都不太熟悉这个注解,我们下面还是用案例的方式说明。
首先新建一个属性类,这个类是用来存放配置信息的
package com.example.boot02.autoconfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;
@ConfigurationProperties(prefix= Properties.HELLO_FORMAT_PREFIX)
public class Properties {
public static final String HELLO_FORMAT_PREFIX="com.format";
private Map<String,Object> info;
public Map<String, Object> getInfo() {
return info;
}
public void setInfo(Map<String, Object> info) {
this.info = info;
}
}
在新建一个类,这个类是上个章节就创建好的
package com.example.boot02.autoconfiguration;
import com.example.boot02.format.FormatProcessor;
import com.example.boot02.format.JsonFormatProcessor;
import com.example.boot02.format.StringFormatProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
public class FormatAutoConfiguration {
@ConditionalOnMissingClass("com.alibaba.fastjson.JSON")
@Bean
public FormatProcessor stringFormat(){
System.out.println("stringFormat");
return new StringFormatProcessor();
}
@ConditionalOnClass(name = "com.alibaba.fastjson.JSON")
@Bean
public FormatProcessor jsonFormat(){
System.out.println("jsonFormat");
return new JsonFormatProcessor();
}
}
最后在创建我们的自动配置类
package com.example.boot02.autoconfiguration;
import com.example.boot02.FormatTemplate;
import com.example.boot02.format.FormatProcessor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Import(FormatAutoConfiguration.class)
@EnableConfigurationProperties({Properties.class})
@Configuration
public class AutoConfiguration {
@Bean
public FormatTemplate FormatTemplate(Properties properties, FormatProcessor formatProcessor){
return new FormatTemplate(properties,formatProcessor);
}
}
同样的我们的format接口和实现类
package com.example.boot02.format;
public interface FormatProcessor {
<T> String format();
}
package com.example.boot02.format;
public class JsonFormatProcessor implements FormatProcessor{
@Override
public <T> String format() {
return "JsonFormatProcessor";
}
}
package com.example.boot02.format;
import java.util.Objects;
public class StringFormatProcessor implements FormatProcessor{
@Override
public <T> String format() {
return "StringFormatProcessor";
}
}
这里在新建一个获取配置信息的类
package com.example.boot02;
import com.example.boot02.autoconfiguration.Properties;
import com.example.boot02.format.FormatProcessor;
public class FormatTemplate {
private FormatProcessor formatProcessor;
private Properties properties;
public FormatTemplate(Properties properties, FormatProcessor formatProcessor) {
this.properties=properties;
this.formatProcessor = formatProcessor;
}
public <T> String doFormat(){
StringBuilder stringBuilder=new StringBuilder();
stringBuilder.append("begin:Execute format").append("<br/>");
System.out.println(properties.getInfo().toString());
return stringBuilder.toString();
}
}
这个类是用来打印我们配置信息的,最后我们还是老规矩,在resouces下面的META-INF下面的spring.factories文件里添加下面的内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.boot02.autoconfiguration.AutoConfiguration
然后打包,放在我们的Boot1项目下
然后我们修改下Boot1的这个Boot01Application类
package com.example.boot01;
import com.example.boot02.FormatTemplate;
import com.example.boot02.autoconfiguration.FormatAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication()
public class Boot01Application {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(Boot01Application.class, args);
applicationContext.getBean(FormatTemplate.class).doFormat();
}
}
然后再配置下我们的属性,在application.properties添加
com.format.info.name=Li
com.format.info.age=18
然后我们再测试一下
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.5\lib\idea_rt.jar=49255:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.5\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;C:\Users\HP\IdeaProjects\Spring\boot01\target\classes;C:\Users\HP\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.1\spring-boot-starter-2.4.1.jar;C:\Users\HP\.m2\repository\org\springframework\boot\spring-boot\2.4.1\spring-boot-2.4.1.jar;C:\Users\HP\.m2\repository\org\springframework\spring-context\5.3.2\spring-context-5.3.2.jar;C:\Users\HP\.m2\repository\org\springframework\spring-aop\5.3.2\spring-aop-5.3.2.jar;C:\Users\HP\.m2\repository\org\springframework\spring-beans\5.3.2\spring-beans-5.3.2.jar;C:\Users\HP\.m2\repository\org\springframework\spring-expression\5.3.2\spring-expression-5.3.2.jar;C:\Users\HP\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.1\spring-boot-autoconfigure-2.4.1.jar;C:\Users\HP\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.1\spring-boot-starter-logging-2.4.1.jar;C:\Users\HP\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\HP\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\HP\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\HP\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\HP\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\HP\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\HP\.m2\repository\org\springframework\spring-core\5.3.2\spring-core-5.3.2.jar;C:\Users\HP\.m2\repository\org\springframework\spring-jcl\5.3.2\spring-jcl-5.3.2.jar;C:\Users\HP\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\HP\IdeaProjects\Spring\boot02\target\classes;C:\Users\HP\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar" com.example.boot01.Boot01Application
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.1)
2020-12-24 10:46:40.943 INFO 4616 --- [ main] com.example.boot01.Boot01Application : Starting Boot01Application using Java 1.8.0_201 on HP-PC with PID 4616 (C:\Users\HP\IdeaProjects\Spring\boot01\target\classes started by HP in C:\Users\HP\IdeaProjects\Spring)
2020-12-24 10:46:40.946 INFO 4616 --- [ main] com.example.boot01.Boot01Application : No active profile set, falling back to default profiles: default
stringFormat
2020-12-24 10:46:41.274 INFO 4616 --- [ main] com.example.boot01.Boot01Application : Started Boot01Application in 0.575 seconds (JVM running for 0.983)
{name=Li, age=18}
Process finished with exit code 0
可以看到,我们已经获取到了我们的配置信息,这种操作就好比将数据库连接需要用户配置,然后我们再获取用户的配置信息,已达到实现自定义的功能。然后至于底层逻辑,其实和我们的自动化配置是大差不差的,有兴趣的读者可以自行阅读,这里我就不再深入探究了。小结一下,这三个小的章节对SpringBoot自动配置和外部化配置的核心原理做了详细的介绍,并其核心源码做了简要分析。