下面这15+个bean,可以很方便的帮我们获取当前环境信息,运行信息,参数信息等等
1. 应用程序参数Environment和ApplicationArguments
SpringBoot程序在启动时,可以通过如下方式设置启动参数:
java -jar app.jar --pack.title=xxx --pack.version=1.0.0
一般访问上面的参数可以通过如下方式:
@Resource
private Environment env ;
public void getArgs() {
String title = env.getProperty("pack.title") ;
String version = env.getProperty("pack.version") ;
}
其实SpringBoot为我们注册了一个Bean对象ApplicationArguments,在代码中可以直接注入该对象
@Resource
private ApplicationArguments applicationArguments ;
public void getArgs() {
List<String> titles = applicationArguments.getOptionValues("pack.title") ;
List<String> version = applicationArguments.getOptionValues("pack.version") ;
}
我们还可以拿到原始配置的参数信息
String[] args = applicationArguments.getSourceArgs() ;
输出结果
[--pack.title=xxx, --pack.version=1.0.0]
可以根据自己的需要进行解析处理。
2. Banner
如果你想在项目中获取Banner信息,那么你可以直接在代码中注入Banner对象。
@Resource
private Banner banner ;
@Resource
private Environment env ;
public void printBanner() {
banner.printBanner(env, PackApplication.class, System.out) ;
}
注意:确保你没有关闭Banner,也就是说你没有进行如下的配置。
spring:
main:
banner-mode: off #关闭Banner
编程方式
SpringApplication app = new SpringApplication(PackApplication.class);
app.setBannerMode(Mode.OFF) ;
如果你关闭了Banner,那么上面的注入将会报错。
3. 类型转换器ConversionService
ConversionService是个非常重要及强大的类,该类在SpringBoot启动过程中将配置文件中的配置数据转换为对应的数据类型。Controller请求参数数据类型进行转换。在代码中我们可以直接注入该类,如下示例:
@Resource
private ConversionService conversionService ;
Integer ret = conversionService.convert("6666",
TypeDescriptor.valueOf(String.class),
TypeDescriptor.valueOf(Integer.class)) ;
你也可以注册自定义的类型转换
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new ConverterFactory<String, User>() {
@Override
public <T extends User> Converter<String, T> getConverter(Class<T> targetType) {
return new Converter<String, T>() {
@Override
public T convert(String source) {
String[] s = source.split(",") ;
return (T) new User(Integer.valueOf(s[0]), s[1]) ;
}
} ;
}
}) ;
}
}
添加自定义类型转换后,我们可以在代码中自己通过ConversionService进行类型转换。
4. Servlet相关对象:ServletRequest、ServletResponse、HttpSession、WebRequest
SpringBoot启动过程中会注册以下4个与Web相关的Bean对象。
-
ServletRequest
-
ServletResponse
-
HttpSession
-
WebRequest
这4个对象,我们可以在代码中任意的注入,而不必担心线程安全问题。因为这些对象实际对应的是ObjectFactory。而对应的内部实现是从ThreadLocal中获取。如下示例:
@Resource
private HttpServletRequest request ;
@Resource
private HttpServletResponse response ;
public void getParam() {
String value = request.getParameter("name") ;
}
每个请求到来时都会将当前对应的Request存入到ThreadLocal中。
5. 国际化MessageSource
我们可以在代码中直接注入MessageSource对象,进行国际化资源的访问,如下示例:
@Resource
private MessageSource messageSource ;
public void getMsg() {
String message = this.messageSource.getMessage("pack.info.message",
new Object[] {"张三"}, "我是默认消息", Locale.CHINA) ;
}
上面代码如果你没有设置默认消息,那么会报错,在默认情况下Spring实例化的MessageSource对象是DelegatingMessageSource这是一个空的实现。要能使的我们的国际化资源生效,你还需要在配置文件中进行如下配置:
spring:
messages:
basename: i18n/message #这里根据自己实际情况自定义basename
这样配置后,SpringBoot会自动注册ResourceBundleMessageSource对象。
其实我们也可以不使用MessageSource,而直接注入ApplicationContext对象,如下示例:
@Resource
private ConfigurableApplicationContext context ;
String message = context.getMessage("pack.info.message",
new Object[] {"张三"}, "我是默认消息", Locale.CHINA) ;
通过ApplicationContext对象获取资源消息,起内部还是使用的上面的MessageSource。
6. 事件发布对象ApplicationEventMulticaster
在项目中你要发布事件你可以通过ApplicationContext对象来发布,如下示例:
@Resource
private ConfigurableApplicationContext context ;
// 发布事件
context.publishEvent(new TxApplicationEvent(context)) ;
一般在项目中通过上面的方式发布一个事件。而实际Spring为我们还注册了一个ApplicationEventMulticaster对象,该对象专门用来广播事件。
@Resource
private ApplicationEventMulticaster eventMulticast ;
eventMulticast.multicastEvent(new TxApplicationEvent(context)) ;
我们也可以自定义beanName为applicationEventMulticaster的Bean对象实现自定义。
7. 优雅关闭服务WebServerGracefulShutdownLifecycle
如果你是内嵌Tomcat启动(以Jar包形式)SpringBoot项目,那么会向容器中注册一个WebServerGracefulShutdownLifecycle对象,通过该对象你可以优雅的关闭服务。
备注:当然,你也可以通过actuator来进行优雅的关闭webserver。
@Resource
private WebServerGracefulShutdownLifecycle webServerGracefullShutdown ;
public void shutdownWebServer() {
webServerGracefullShutdown.stop(() -> {
System.out.println("优雅关闭Web Server");
context.close() ;
}) ;
}
注意:你还需要开启如下配置
server:
shutdown: graceful
8. ApplicationPid
获取进程ID
如果你想在程序中获取当前SpringBoot运行的进程号,那么你可以使用ApplicationPid,该类非常方便的获取当前进程ID。
ApplicationPid pid = new ApplicationPid() ;
System.out.printf("进程ID: %s%n", pid.toString()) ;
输出结果
进程ID: 24416
当然你还可以通过如下方式,获取当前的进程号
#在META-INF/spring.factories中注册监听器
org.springframework.context.ApplicationListener=\
org.springframework.boot.context.ApplicationPidFileWriter
该监听器会将当前的进程ID写入文件中,通过如下配置文件路径
spring:
pid:
file: d:/app.pid
文件内容:当前进程id
如果你觉得无聊,那么你还可以通过如下方式
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]
这样也可以获取进程ID。
9. 应用运行主目录ApplicationHome
ApplicationHome提供访问应用程序主目录的途径。尝试为Jar文件、解压缩文件和直接运行的应用程序选择一个合理的主目录。
ApplicationHome home = new ApplicationHome() ;
System.out.printf("dir: %s, source: %s%n", home.getDir(), home.getSource()) ;
在IDE中运行输出结果
dir: D:\java\workspace\test-app, source: null
打成Jar后运行输出结果
dir: D:\java\workspace\test-app\target,
source: D:\java\workspace\test-app\target\test-app-1.0.0.jar
通过jar运行后,source输出的是当前运行的jar包路径。
10. 获取Java版本JavaVersion
要想知道当前SpringBoot运行时的java版本可以通过JavaVersion获取
System.out.printf("Java Version: %s%n", JavaVersion.getJavaVersion()) ;
输出结果
Java Version: 17
JavaVersion是个枚举类,定义了17~22枚举值,你还可以调用isEqualOrNewerThan和isOlderThan进行java版本的比较。
11. 应用临时目录ApplicationTemp
ApplicationTemp类提供了访问应用程序特定的临时目录的功能。一般来说,不同的Spring Boot应用程序将得到不同的位置,但是,只需重新启动应用程序即可获得相同的位置。
ApplicationTemp temp = new ApplicationTemp() ;
System.out.printf("临时目录: %s%n", temp.getDir()) ;
输出结果
临时目录: C:\Users\MSI-NB\AppData\Local\Temp\561929B2C764E67BCDA2DF9DAE26EF121F7E5365
不论你在IDE下还是Jar方式运行,windows平台下临时目录都在这里的Temp下
12. SystemProperties系统属性/环境变量访问
当你需要访问系统属性时可以通过SystemProperties类非常方便的获取。如果你访问的属性不存在时(null),那么它会再从环境变量中获取(System#getenv)
System.out.printf("java.home=%s%n", SystemProperties.get("java.home")) ;
输出结果
java.home=D:\software\jre
注:这里的get方法参数是可变长参数,你可以传递多个key,获取时遍历遇到不为null的直接返回。
13. Instantiator
实例化对象
Instantiator通过注入可用参数来实例化对象的简单工厂。
public interface DAO {}
public class A implements DAO {}
public class B implements DAO {}
注备上面几个类,接下通过Instantiator一次性实例化多个对象。
Instantiator<DAO> instant = new Instantiator<>(DAO.class, p -> {}) ;
List<DAO> ret = instant.instantiate(List.of("com.pack.A", "com.pack.B")) ;
System.out.printf("%s%n", ret) ;
输出结果
[com.pack.A@3127cb44, com.pack.B@3234474]
非常方便的一次性帮助你实例化多个同类型的类
14. 资源加载PropertiesPropertySourceLoader与YamlPropertySourceLoader
如果你想将后缀为.properties,.xml,.yaml资源文件加载,那么你可以使用PropertiesPropertySourceLoader与YamlPropertySourceLoader。
// 加载properties文件
PropertiesPropertySourceLoader propertyLoader = new PropertiesPropertySourceLoader() ;
List<PropertySource<?>> list = propertyLoader.load("pack", new ClassPathResource("pack.properties")) ;
System.out.printf("pack.*: %s%n", list.get(0).getSource()) ;
// 加载yaml文件
YamlPropertySourceLoader yamlLoader = new YamlPropertySourceLoader() ;
List<PropertySource<?>> yamls = yamlLoader.load("pack", new ClassPathResource("pack.yml")) ;
System.out.printf("pack.*: %s%n", yamls.get(0).getSource()) ;
通过上面2个Loader非常方便的将资源文件加载,加载后的List<PropertySource>还可以注册到Environment中,在系统中直接访问。
在SpringBoot兴起之前,Spring项目通常需要手动配置PropertySourcesPlaceholderConfigurer来解析配置文件中的占位符,以及PropertyOverrideConfigurer来允许属性覆盖(该类可能用的也是表少的)。这2个类使用也相对比较简单,它能帮助我们加载配置文件及处理${xxx}占位符。然而,SpringBoot出现后,似乎在项目中基本不会去定义这2个类,SpringBoot已经帮我们自动的配置(PropertyOverrideConfigurer并没有需要我们自己配置)。接下来我们就再来介绍这2个类在SpringBoot中如何覆盖系统默认的配置及使用方式。
实战案例
1 PropertySourcesPlaceholderConfigurer
属性不存在问题
在默认下,当使用${xxx}配置的属性环境中不存在时,将会报错。如下示例:
@Value("pack.title")
private String title ;
当你的环境中没有配置pack.title属性时,容器启动将会报错。
不过我们可以通过下面的方式来规避该错误
@Value("${pack.title:xx}")
private String title ;
通过上面的方式设置默认值,这里你也直接使用冒号":"后面不设置值${pack.title:}。
通过自定义PropertySourcesPlaceholderConfigurer修改属性,使其支持不存在属性情况,如下:
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer() ;
// 设置为true,忽略不能解析的占位符
placeholderConfigurer.setIgnoreUnresolvablePlaceholders(true) ;
return placeholderConfigurer ;
}
上面设置后服务能正确启动。
修改占位符
我们还可以修改占位符的定义方式(不推荐)
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer() ;
// 设置占位符前缀&后缀,@[xxx]
placeholderConfigurer.setPlaceholderPrefix("@[") ;
placeholderConfigurer.setPlaceholderSuffix("]") ;
return placeholderConfigurer ;
}
如上配置后,使用时就只能通过@[xxx]。
自定义配置文件
在自定义时,你还可以指定自己的配置文件,初始化该bean时会加载对应的配置文件
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer() ;
// 指定配置文件
placeholderConfigurer.setLocations(new ClassPathResource("pack.properties")) ;
return placeholderConfigurer ;
}
你也可以指定多个配置文件。
2 PropertyOverrideConfigurer
该配置文件相比较用的比较少了。该类同样是个BeanFactoryPostProcessor处理器。通过类名也能猜到是用来覆盖属性值的处理器类。直接上代码
@Component
public class PropertyOverrideBean {
@Value("${pack.title}")
private String title ;
@Value("${pack.os}")
private String os ;
// getters, setters
}
@Resource
private PropertyOverrideBean pob ;
System.out.println(pob) ;
输出结果
PropertyOverrideBean [title=xxxooo, os=window]
接下来配置PropertyOverrideConfigurer
@Bean
PropertyOverrideConfigurer propertyOverrideConfigurer() {
PropertyOverrideConfigurer propertyOverrideConfigurer = new PropertyOverrideConfigurer() ;
propertyOverrideConfigurer.setLocation(new ClassPathResource("pack.properties")) ;
return propertyOverrideConfigurer ;
}
同样这里我们指定一个自定义配置文件;但是这里的pack.properties文件中的内容就要主要属性key的格式了,[beanName].[key]=xxxx这里的属性key的前缀必须是你要覆盖bean的名称。如下示例:
propertyOverrideBean.title=my title
propertyOverrideBean.os=linux
上面的propertyOverrideBean就是PropertyOverrideBean的beanName。
输出结果:
PropertyOverrideBean [title=my title, os=linux]
覆盖了系统默认的属性值。
嵌套属性
@Component
public class PropertyOverrideBean {
// other property
private User user = new User() ;
}
public class User {
@Value("pack.age")
private Integer age ;
// getters, setters
}
在配置文件中添加配置
propertyOverrideBean.user.age=88
输出结果
PropertyOverrideBean [title=my title, os=linux, user=User [age=88]]
注意:这里的嵌套属性User必须事先创建好否则将会抛出NPE异常。
15. 获取basePackages
如果你需要在代码中获取当前应用启动类所在的基包basePackages,那么你可以通过如下方式
private ConfigurableApplicationContext context ;
System.out.printf("basepPckages: %s%n", AutoConfigurationPackages.get(context)) ;
输出结果
basepPckages: [com.pack]
内部注册的是一个BasePackages Bean,该类是静态私有的所以你没法直接访问,只能通过上面的方式。