1 背景
由于项目需要,得在通用框架中引入sdk,切面实现数据上报相关的功能,为最小化修改范围和影响,避免其他未用到改sdk的服务的启动、加载时间,实现了xml文件的动态化导入,满足条件才会加载。
服务是springboot项目,rpc接口。
2 实现
基于configuration、conditional,import,importResource注解,和thrift的interceptor实现。
- 实现Condition接口的matches(ConditionContext context, AnnotatedTypeMetadata metadata)方法,写一个条件类(conditional注解),满足该类的条件时才会执行xml的加载操作。
- 实现一个子配置类,importResource导入xml文件和相关bean
- 在主配置类Import子配置类,启动类import主配置类实现xml的动态带入和interceptor的动态注入
示例
java
// 条件类
public class XXEnabledCondition implements Condition {
private static final Logger LOGGER = LoggerFactory.getLogger(XXEnabledCondition.class);
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
boolean isEnabled = XXDebugConfig.isEnabled();
LOGGER.info("XX Debug Condition Check: XX.debug.enable={}, condition={}", isEnabled, isEnabled);
return isEnabled;
} catch (Exception e) {
LOGGER.warn("Failed to check XX Debug condition, XX Debug disabled", e);
return false;
}
}
}
//子配置类
@Configuration
@Conditional(XXEnabledCondition.class)
@ImportResource(locations = {"classpath:XX-client.xml"})
public class XXClientConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(XXClientConfiguration.class);
public XXClientConfiguration() {
LOGGER.info("XXClientConfiguration initialized - XX Debug is enabled");
}
}
//主配置类和子配置类一样的做法,就不贴了
3 扩展知识
conditional注解&condition接口
conditional注解是spring4.0引入的条件化bean注册注解,底层依赖Condition接口的match方法。
应用场景:
- 根据文件 或 配置 差异定制化注册bean
- 根据某个类是否存在判断是否注册bean
springboot基于conditional注解加了派生类:
-
@ConditionalOnProperty -
@ConditionalOnClass -
@ConditionalOnMissingBean -
等20+条件注解
注意事项
-
条件判断时机:发生在Bean定义阶段,早于Bean实例化
-
优先级问题 :
@Conditional的优先级高于@Profile -
避免循环依赖:条件判断中不要直接获取Bean实例
-
适用范围: 可以加在类 或 方法上
-
调试技巧 :通过
-Ddebug查看条件评估报告(Spring Boot)
configuration注解
核心作用:将 java类 转为spring配置类,保存在spring容器中,被统一管理。
高级用法:
- 模块化配置:@configuration + @import注解,不同模块分开配置。
- 条件化加载:利用@condition注解或springboot的@ConditionalOnProperty等注解
- 环境特定配置:结合@profile注解
注解参数:
-
proxyBeanMethods:bean代理方式,默认true,通过CGLIB代理,耗时更长!但确保单例(如通过serviceA()方法注入,多次调用该方法返回同个bean);false 耗时更短,但不保证单例。
-
value:配置bean名字
spring容器加载流程
java
┌─────────────────┐
│ Spring 容器启动 │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ 扫描 @Configuration 类 │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ 创建配置类实例 │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ 处理 @Bean 方法 │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ proxyBeanMethods? │
└─────┬───────┬───┘
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│创建CGLIB│ │直接调用 │
│ 代理 │ │ 方法 │
└─────┬───┘ └─────┬───┘
│ │
└─────┬─────┘
│
▼
┌─────────────────┐
│ 注册 Bean 到容器 │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ 处理依赖注入 │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ 完成配置 │
└─────────────────┘
import注解
**作用:**把其他类拉进当前spring容器,解决bean扫不到的问题(如不在扫描路径下的第三方jar包的类,没加component等注解的普通类,想动态决定要不要引入的类)。
使用场景:
- 把普通类引入:这时类会被注入,spring会自动加载里面的各bean
- 导入配置类:把配置类里的bean导入
导入 "ImportSelector 接口实现类"------ 动态导入(高级用法)。
- 实现
ImportSelector接口,在selectImports方法里返回要导入的类名数组 - 在配置类上
@Import这个实现类
其实在被import的子配置上加condition注解也可实现3
importResource注解
**作用:**将xml配置加载到基于 Java 的到配置中,进而进入spring上下文中
@ImportResource支持各种 XML 配置文件的位置,包括:
- 类路径 :
classpath:beans.xml - 文件系统 :
file:/path/to/beans.xml - URL :
http://example.com/beans.xml