愿你如阳光,明媚不忧伤。
目録
- 1. 配置文件简介
- 2. 少量配置信息的情形
- 3. 多个配置信息的情形
- 3.1 指定项目配置文件
- 4. Spring 初始化 Bean
- 4.1 @PostConstruct
- 4.2 实现InitializingBean接口afterPropertiesSet方法
- 4.3 使用 InitializingBean 自定义初始化
- 【每日一面】
- jar 包和 war 包的区别
1. 配置文件简介
配置文件(Configuration File)在我们编写Java程序时,有很多参数是会经常改变的。比如环境的配置,我们开发的时候是一套环境,测试使用的是另外一套环境,最后交付给用户的时候,用户用的又是另外一套环境,不能每次都重新编译,那样成本太高,所以对于这些参数往往不能直接写死在程序里,不然会非常麻烦,每次修改都需要重新运行一次程序,这时就需要借助配置文件来存储这些常变的参数,在程序中是读取配置文件中的变量的值,如果我们需要修改这些值,只需要修改配置文件即可。
本篇文章代码涉及日志记录,如有不明白请参考 → Java 日志记录 SLF4J
2. 少量配置信息的情形
举个例子,在微服务架构中,最常见的就是某个服务需要调用其他服务来获取其提供的相关信息,那么在该服务的配置文件中需要配置被调用的服务地址,比如在当前服务里,我们需要调用订单微服务获取订单相关的信息,假设订单服务的端口号是 8002,那我们可以做如下配置:
server:
# 配置微服务的地址
port: 8001
url:
# 订单微服务的地址
orderUrl: http://localhost:8002
然后在业务代码中如何获取到这个配置的订单服务地址呢?我们可以使用 @Value 注解来解决。在对应的类中加上一个属性,在属性上使用 @Value 注解即可获取到配置文件中的配置信息,如下:
- ConfigController.java
@RestController
@RequestMapping("/config")
public class ConfigController extends BaseController {
@Value("${url.orderUrl}")
private String orderUrl;
@RequestMapping("/less")
public JsonResult<String> lessConfig() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
log.info("获取的订单服务地址为:{}", orderUrl);
log.info(endLog("lessConfig"));
return new JsonResult<>(orderUrl, "获取订单服务地址!");
}
}
浏览器访问 http://localhost:8001/config/less
控制台打印出订单服务的地址,我们成功获取到了配置文件中的数据,在实际项目中也是这么用的,后面如果因为服务器部署的原因,需要修改某个服务的地址,那么只要在配置文件中修改即可。
3. 多个配置信息的情形
随着业务复杂度的增加,一个项目中可能会有越来越多的微服务,某个模块可能
需要调用多个微服务获取不同的信息,那么就需要在配置文件中配置多个微服务的地址。可是,在需要调用这些微服务的代码中,如果这样一个个去使用 @Value 注解引入相应的微服务地址的话,太过于繁琐,也不科学。所以,在实际项目中,业务繁琐,逻辑复杂的情况下,需要考虑封装一个或多个配置类。
server:
# 配置微服务的地址
port: 8001
url:
# 订单微服务的地址
orderUrl: http://localhost:8002
# 用户微服务的地址
userUrl: http://localhost:8003
# 购物车微服务的地址
shoppingUrl: http://localhost:8004
- MicroServiceUrl.java
使用 @ConfigurationProperties 注解并且使用 prefix 来指定一个前缀,然后该类中的属性名就是配置中去掉前缀后的名字,一一对应即可。即:前缀名 + 属性名就是配置文件中定义的 key。同时,该类上面需要加上 @Component 注解,把该类作为组件放到Spring容器中,让 Spring 去管理,我们使用的时候直接注入即可。
@Component
@ConfigurationProperties(prefix = "url")
public class MicroServiceUrl {
private String orderUrl;
private String userUrl;
private String shoppingUrl;
// 省略get和set
}
- pom.xml
使用 @ConfigurationProperties 注解需要导入它的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
- ConfigController.java
使用@Resource 注解将刚刚写好配置类注入进来
@RestController
@RequestMapping("/config")
public class ConfigController extends BaseController {
@Resource
private MicroServiceUrl microServiceUrl;
@Value("${url.orderUrl}")
private String orderUrl;
@RequestMapping("/less")
public JsonResult<String> lessConfig() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
log.info("获取的订单服务地址为:{}", orderUrl);
log.info(endLog("lessConfig"));
return new JsonResult<>(orderUrl);
}
@RequestMapping("/lot")
public JsonResult<List<String>> lotConfig() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
log.info("获取的订单服务地址为:{}", microServiceUrl.getOrderUrl());
log.info("获取的用户服务地址为:{}", microServiceUrl.getUserUrl());
log.info("获取的购物车服务地址为:{}", microServiceUrl.getShoppingUrl());
List<String> list = new ArrayList<>();
list.add(microServiceUrl.getOrderUrl());
list.add(microServiceUrl.getUserUrl());
list.add(microServiceUrl.getShoppingUrl());
log.info(endLog("lotConfig"));
return new JsonResult<>(list, orderUrl);
}
}
浏览器访问 http://localhost:8001/config/lot
控制台打印出如下信息,说明配置文件生效,同时正确获取配置文件内容
3.1 指定项目配置文件
实际项目中,一般有两个环境:开发环境和生产环境。开发环境中的配置和生产环境中的配置往往不同,比如:环境、端口、数据库、相关地址等等。我们不可能在开发环境调试好之后,部署到生产环境后,又要将配置信息全部修改成生产环境上的配置,这样太麻烦,也不科学。
最好的解决方法就是开发环境和生产环境都有一套对应的配置信息,然后当我们在开发时,指定读取开发环境的配置,当我们将项目部署到服务器上之后,再指定去读取生产环境的配置。
- application-dev.yml
# 开发环境配置文件
server:
port: 80
- application-pro.yml
# 生产环境配置文件
server:
port: 443
- application.yml
# 指定读取的配置文件
spring:
profiles:
active:
- dev
4. Spring 初始化 Bean
spring初始化bean有三种方式:
- 方式三:反射原理,通过xml配置
<bean init-method="myInit" /bean>
直接注入bean。
执行顺序:Constructor > @Autowired > @PostConstruct > InitializingBean > init-method
4.1 @PostConstruct
- AbstractAutowireCapableBeanFactory.java#initializeBean
源码位置:package org.springframework.beans.factory.support;
这里主要包含了初始化前置处理、调用初始化方法、初始化后置处理。
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
this.invokeAwareMethods(beanName, bean);
return null;
}, this.getAccessControlContext());
} else {
this.invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 初始化前置处理
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
try {
// 调用初始化方法
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
}
if (mbd == null || !mbd.isSynthetic()) {
// 初始化后置处理
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
- AbstractAutowireCapableBeanFactory.java#applyBeanPostProcessorsBeforeInitialization
跟进 applyBeanPostProcessorsBeforeInitialization 方法
@PostConstruct注解在applyBeanPostProcessorsBeforeInitialization这个前置处理中起作用
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException {
Object result = existingBean;
Object current;
for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) {
BeanPostProcessor processor = (BeanPostProcessor)var4.next();
current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
}
return result;
}
- InitDestroyAnnotationBeanPostProcessor.java#postProcessBeforeInitialization
源码位置:package org.springframework.beans.factory.annotation;
findLifecycleMetadata方法会解析元数据,所以@PostConstruct注解的初始化方法在这里找到了。
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// findLifecycleMetadata方法会解析元数据
InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata metadata = this.findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
return bean;
} catch (InvocationTargetException var5) {
throw new BeanCreationException(beanName, "Invocation of init method failed", var5.getTargetException());
} catch (Throwable var6) {
throw new BeanCreationException(beanName, "Failed to invoke init method", var6);
}
}
- InitDestroyAnnotationBeanPostProcessor.java#findLifecycleMetadata
跟进 findLifecycleMetadata 方法
private InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
if (this.lifecycleMetadataCache == null) {
return this.buildLifecycleMetadata(clazz);
} else {
InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata metadata = (InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata)this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
synchronized(this.lifecycleMetadataCache) {
metadata = (InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata)this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
metadata = this.buildLifecycleMetadata(clazz);
this.lifecycleMetadataCache.put(clazz, metadata);
}
return metadata;
}
} else {
return metadata;
}
}
}
- InitDestroyAnnotationBeanPostProcessor.java#buildLifecycleMetadata
跟进 buildLifecycleMetadata 方法
可以看到有个判断是否被 initAnnotationType 注解,然后添加到集合中。
private InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata buildLifecycleMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {
return this.emptyLifecycleMetadata;
} else {
List<InitDestroyAnnotationBeanPostProcessor.LifecycleElement> initMethods = new ArrayList();
List<InitDestroyAnnotationBeanPostProcessor.LifecycleElement> destroyMethods = new ArrayList();
Class targetClass = clazz;
do {
List<InitDestroyAnnotationBeanPostProcessor.LifecycleElement> currInitMethods = new ArrayList();
List<InitDestroyAnnotationBeanPostProcessor.LifecycleElement> currDestroyMethods = new ArrayList();
ReflectionUtils.doWithLocalMethods(targetClass, (method) -> {
// 判断是否被 initAnnotationType 注解
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
InitDestroyAnnotationBeanPostProcessor.LifecycleElement element = new InitDestroyAnnotationBeanPostProcessor.LifecycleElement(method);
currInitMethods.add(element);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
currDestroyMethods.add(new InitDestroyAnnotationBeanPostProcessor.LifecycleElement(method));
if (this.logger.isTraceEnabled()) {
this.logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
});
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
} while(targetClass != null && targetClass != Object.class);
return initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata : new InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata(clazz, initMethods, destroyMethods);
}
}
- CommonAnnotationBeanPostProcessor.java
initAnnotationType位于CommonAnnotationBeanPostProcessor类中
public CommonAnnotationBeanPostProcessor() {
this.setOrder(2147483644);
this.setInitAnnotationType(PostConstruct.class);
this.setDestroyAnnotationType(PreDestroy.class);
this.ignoreResourceType("javax.xml.ws.WebServiceContext");
}
了解更多 → Spring 框架中 @PostConstruct 注解详解
4.2 实现InitializingBean接口afterPropertiesSet方法
- AbstractAutowireCapableBeanFactory.java#initializeBean
源码位置:package org.springframework.beans.factory.support;
这里主要包含了初始化前置处理、调用初始化方法、初始化后置处理。
继续initializeBean方法,这次来看invokeInitMethods方法
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
this.invokeAwareMethods(beanName, bean);
return null;
}, this.getAccessControlContext());
} else {
this.invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 初始化前置处理
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
try {
// 调用初始化方法
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
}
if (mbd == null || !mbd.isSynthetic()) {
// 初始化后置处理
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
- AbstractAutowireCapableBeanFactory.java#initializeBean
可以看出首先会检查是否是 InitializingBean(判断bean是否实现了InitializingBean接口),如果是调用afterPropertiesSet()方法,如果不是则执行 init-method 配置的内容
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) throws Throwable {
boolean isInitializingBean = bean instanceof InitializingBean;
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(() -> {
((InitializingBean)bean).afterPropertiesSet();
return null;
}, this.getAccessControlContext());
} catch (PrivilegedActionException var6) {
throw var6.getException();
}
} else {
((InitializingBean)bean).afterPropertiesSet();
}
}
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) && (!isInitializingBean || !"afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) {
this.invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
4.3 使用 InitializingBean 自定义初始化
- InitializingBean.java
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是实现该接口的类,在初始化bean的时候都会执行该方法。
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
- SpringInitializingBean.java
自定义初始化类 SpringInitializingBean ,它实现了 InitializingBean 接口,并实现了接口的 afterPropertiesSet 方法,该方法调用系统配置类 SysConfig 中的 init 方法。
@Component
public class SpringInitializingBean extends LogBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
SysConfig.init(ItGodRoadApplication.server, ItGodRoadApplication.CALLBACK_DOMAIN);
}
}
- SysConfig.java
自定义的系统配置类,封装了系统需要使用的相关配置,通过 getSettingsByServerType 方法,根据传递的参数动态实例化 BaseSettings。
public class SysConfig {
protected static String CALLBACK_DOMAIN = null;
protected static String SERVER_URL = null;
protected static String GATEWAY_SERVER_URL = null;
public static String[] GATEWAY_SERVER_URL_IP = null;
public static String MCH_CODE = null;
public static String API_KEY = null;
public static boolean certUse = false;
public static String certPW = null;
public static String certFilePath = null;
public static void init(String serverType, String callbackUrl) {
/**
* callback用url
*/
SysConfig.CALLBACK_DOMAIN = callbackUrl;
BaseSettings settings = getSettingsByServerType(serverType);
/**
* 访问
*/
SysConfig.SERVER_URL = settings.SERVER_URL;
SysConfig.GATEWAY_SERVER_URL = settings.GATEWAY_SERVER_URL;
SysConfig.GATEWAY_SERVER_URL_IP = settings.GATEWAY_SERVER_URL_IP;
/**
* 业务
*/
SysConfig.MCH_CODE = settings.MCH_CODE;
SysConfig.API_KEY = settings.API_KEY;
/**
* 证书
*/
SysConfig.certUse = settings.certUse;
SysConfig.certPW = settings.certPW;
SysConfig.certFilePath = settings.certFilePath;
}
private static BaseSettings getSettingsByServerType(String server) {
BaseSettings settings = null;
/**
* 测试环境
*/
if (Server.test_localhost.equals(server)) {
settings = new Test_LocalSettings();
} else if (Server.test_old.equals(server)) {
settings = new Test_OldSettings();
} else if (Server.test_new.equals(server)) {
settings = new Test_NewSettings();
} else if (Server.test_release.equals(server)) {
settings = new Test_ReleaseSettings();
}
/**
* 生产环境
*/
else if (Server.product_release.equals(server)) {
settings = new Product_ReleaseSettings();
} else if (Server.product_gateway.equals(server)) {
settings = new Product_GatewaySettings();
} else if (Server.product_freeGateway.equals(server)) {
settings = new Product_FreeGatewaySettings();
}
return settings;
}
}
- ItGodRoadApplication.java
Springboot启动类,该类在项目根目录下,所有类都可以直接访问。在这里指定启动的服务环境和回调地址,项目启动后就会自动装配好我们设定的属性配置了。
@SpringBootApplication
public class ItGodRoadApplication extends SpringBootServletInitializer {
/**
* 环境
*/
public static String server = Server.test_localhost;
/**
* callback用url domain
*/
public static String CALLBACK_DOMAIN = "https://ITGodRoad.CallBack.com";
public static void main(String[] args) {
SpringApplication.run(ItGodRoadApplication.class, args);
}
- BaseSettings.java
封装所有的配置属性
public class BaseSettings {
/**
* 访问
*/
public String SERVER_URL = null;
public String GATEWAY_SERVER_URL = null;
public String[] GATEWAY_SERVER_URL_IP = null;
/**
* 业务
*/
public String MCH_CODE = null;
public String API_KEY = null;
/**
* 证书
*/
public boolean certUse = false;
public String certPW = null;
public String certFilePath = null;
}
- Test_LocalSettings.java
不同环境下的配置类,继承BaseSettings,保证了配置类的通用性。
public class Test_LocalSettings extends BaseSettings {
public Test_LocalSettings() {
/**
* 访问
*/
this.SERVER_URL = "http://localhost:8080";
this.GATEWAY_SERVER_URL = "http://localhost:8080";
this.GATEWAY_SERVER_URL_IP = new String[]{};
/**
* 业务
*/
this.MCH_CODE = "A1000010006";
this.API_KEY = "random_123";
/**
* 证书
*/
this.certUse = false;
this.certPW = "ITGodRoad";
this.certFilePath = "D:\\myCA.cer";
}
}
- 需要多少个配置环境就创建多少个对应的配置类
【每日一面】
jar 包和 war 包的区别
JAR(Java ARchive)Java 归档文件。是与平台无关的文件格式,它允许将许多文件组合成一个压缩文件。JAR 文件格式以流行的 ZIP 文件格式为基础。与 ZIP 文件不同的是,JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用来指示工具如何处理特定的 JAR。
启动方式:执行SpringBootApplication的run方法,启动IOC容器,然后创建嵌入式Servlet容器
WAR(Web ARchive)是一个可以直接运行的web模块,通常用于网站,打成包部署到容器中。以Tomcat来说,将war包放置在其\webapps\目录下,然后启动Tomcat,这个包就会自动解压,就相当于发布了。
先是启动Servlet服务器,服务器启动Springboot应用(springBootServletInitizer),然后启动IOC容器。
启动方式:先是启动Servlet服务器,服务器启动Springboot应用(springBootServletInitizer),然后启动IOC容器
JAR是类的归档文件,WAR包是JavaWeb程序打的包,是一个可以直接发布的Web应用程序
- WAR包目录结构
- IDEA 打war包
Ctrl+Shift+Alt+S 打开Project Structure,选择Artifacts
点击+
选择Web Application Archive
使用 Maven 命令mvn pakage
打包更快捷
修改打包的war包名字和存放路径,默认是项目路径 \out\artifacts
,添加部署依赖。
应用,保存
回到首页中,点击 Build
选择 Build Artifacts
进行打包项目成 war
Buid 完成后,war 包显示在项目结构中
Ctrl+Alt+F12