ApplicationContextInitializer的理解和使用
一、ApplicationContextInitializer 介绍
1.1 作用
ApplicationContextInitializer 接口用于在 Spring 容器刷新之前执行的一个回调函数,通常用于向 SpringBoot 容器中注入属性。
1.2 springboot中ApplicationContextInitializer(系统初始化器)的三种加载方式
- spring.factories中配置(springboot会扫描所有jar包下的META-INF/spring.factorties)
resources目录下新建 : META-INF/spring.factories(key为org.springframework.context.ApplicationContextInitializer)
org.springframework.context.ApplicationContextInitializer=com.example.demo.initializer.Firstinitializer
- 启动类中配置
1 package com.example.demo; 2 import com.example.demo.initializer.SecondInitializer; 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 6 @SpringBootApplication 7 public class DemoApplication { 8 9 public static void main(String[] args) { 10 SpringApplication springApplication = new SpringApplication(DemoApplication.class); 11 springApplication.addInitializers(new SecondInitializer()); 12 springApplication.run(args); 13 } 14 15 }
- application.properties中配置
key为context.initializer.classes
context.initializer.classes=com.example.demo.initializer.ThirdInitializer
1.3 springboot下的spring.factories
spring-boot-2.1.6.RELEASE.jar中的spring.factories文件定义的ApplicationContextInitializer实现如下:
org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
1.4 内置实现类
DelegatingApplicationContextInitializer
使用环境属性 context.initializer.classes 指定的初始化器(initializers)进行初始化工作,如果没有指定则什么都不做。 通过它使得我们可以把自定义实现类配置在 application.properties 里成为了可能。
ContextIdApplicationContextInitializer
设置Spring应用上下文的ID,会参照环境属性。至于Id设置为什么值,将会参考环境属性: * spring.application.name * vcap.application.name * spring.config.name * spring.application.index * vcap.application.instance_index 如果这些属性都没有,ID 使用 application。
ConfigurationWarningsApplicationContextInitializer
对于一般配置错误在日志中作出警告
ServerPortInfoApplicationContextInitializer
将内置 servlet容器实际使用的监听端口写入到 Environment 环境属性中。这样属性 local.server.port 就可以直接通过 @Value 注入到测试中,或者通过环境属性 Environment 获取。
SharedMetadataReaderFactoryContextInitializer
创建一个 SpringBoot和ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory对象。实现类为:ConcurrentReferenceCachingMetadataReaderFactory
ConditionEvaluationReportLoggingListener
将 ConditionEvaluationReport写入日志。
二、实现方式
首先新建三个自定义类,实现 ApplicationContextInitializer 接口
1 public class FirstInitializer implements ApplicationContextInitializer { 2 3 @Override 4 public void initialize(ConfigurableApplicationContext applicationContext) { 5 ConfigurableEnvironment environment = applicationContext.getEnvironment(); 6 7 Map<String, Object> map = new HashMap<>(); 8 map.put("key1", "First"); 9 10 MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map); 11 environment.getPropertySources().addLast(mapPropertySource); 12 13 System.out.println("run firstInitializer"); 14 } 15 16 } 17 18 public class SecondInitializer implements ApplicationContextInitializer { 19 20 @Override 21 public void initialize(ConfigurableApplicationContext applicationContext) { 22 ConfigurableEnvironment environment = applicationContext.getEnvironment(); 23 24 Map<String, Object> map = new HashMap<>(); 25 map.put("key1", "Second"); 26 27 MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map); 28 environment.getPropertySources().addLast(mapPropertySource); 29 30 System.out.println("run secondInitializer"); 31 } 32 33 } 34 35 public class ThirdInitializer implements ApplicationContextInitializer { 36 37 @Override 38 public void initialize(ConfigurableApplicationContext applicationContext) { 39 ConfigurableEnvironment environment = applicationContext.getEnvironment(); 40 41 Map<String, Object> map = new HashMap<>(); 42 map.put("key1", "Third"); 43 44 MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map); 45 environment.getPropertySources().addLast(mapPropertySource); 46 47 System.out.println("run thirdInitializer"); 48 } 49 50 }
2.1 在 resources/META-INF/spring.factories 中配置
org.springframework.context.ApplicationContextInitializer=com.learn.springboot.initializer.FirstInitializer
2.2 在 mian 函数中添加
1 @SpringBootApplication 2 public class SpringbootApplication { 3 4 public static void main(String[] args) { 5 // SpringApplication.run(SpringbootApplication.class, args); 6 SpringApplication springApplication = new SpringApplication(SpringbootApplication.class); 7 springApplication.addInitializers(new SecondInitializer()); 8 springApplication.run(); 9 } 10 11 }
2.3 在配置文件中配置
context.initializer.classes=com.learn.springboot.initializer.ThirdInitializer
2.4 运行项目,查看控制台:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.5.RELEASE) run thirdInitializer run firstInitializer run secondInitializer
可以看到配置生效了,并且三种配置优先级不一样,配置文件优先级(application.properties)最高,spring.factories 其次,代码add最后。
三、获取属性值
1 @RestController 2 public class HelloController { 3 4 private ApplicationContext applicationContext; 5 6 public HelloController(ApplicationContext applicationContext) { 7 this.applicationContext = applicationContext; 8 } 9 10 @RequestMapping("/getAttributes") 11 public String getAttributes() { 12 String value = applicationContext.getEnvironment().getProperty("key1"); 13 System.out.println(value); 14 return value; 15 } 16 17 }
启动项目,访问http://localhost:8080/getAttributes
查看控制台输出:
Third
发现同名的 key,只会存在一个,并且只存第一次设置的值。
四、通过 @Order 注解修改执行顺序
注:@order 值越小,执行优先级越高
4.1 不同配置方式下,执行顺序
1 @Order(1) 2 public class SecondInitializer implements ApplicationContextInitializer { 3 ...... 4 } 5 6 @Order(2) 7 public class FirstInitializer implements ApplicationContextInitializer { 8 ...... 9 } 10 11 @Order(3) 12 public class ThirdInitializer implements ApplicationContextInitializer { 13 ...... 14 }
运行项目,查看控制台:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.5.RELEASE) run thirdInitializer run secondInitializer run firstInitializer
可以看到通过 @Order * 注解是可以改变spring.factories* 和代码形式的执行顺序的,但是application.properties
配置文件的优先级还是最高的。
4.2 同一配置下,执行顺序
新建实现类
1 @Order(1) 2 public class FourthInitializer implements ApplicationContextInitializer { 3 4 @Override 5 public void initialize(ConfigurableApplicationContext applicationContext) { 6 ConfigurableEnvironment environment = applicationContext.getEnvironment(); 7 8 Map<String, Object> map = new HashMap<>(); 9 map.put("key1", "Fourth"); 10 11 MapPropertySource mapPropertySource = new MapPropertySource("FourthInitializer", map); 12 environment.getPropertySources().addLast(mapPropertySource); 13 14 System.out.println("run fourthInitializer"); 15 } 16 17 }
在application.properties 文件中配置
context.initializer.classes=com.learn.springboot.initializer.ThirdInitializer,com.learn.springboot.initializer.FourthInitializer
运行项目,查看控制台:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.5.RELEASE) run fourthInitializer run thirdInitializer run secondInitializer run firstInitializer
五、系统初始化器原理解析
5.1 手动在main函数中添加原理分析
在之前我们知道 SpringApplication 初始化之后,就已经把 META-INF/spring.factories 中配置的初始化实现类添加到 initializers 列表中了,然后通过 addInitializers 方法,添加自定义的实现类:
1 public static void main(String[] args) { 2 // SpringApplication.run(SpringbootApplication.class, args); 3 SpringApplication springApplication = new SpringApplication(SpringbootApplication.class); 4 springApplication.addInitializers(new ThirdInitializer()); 5 springApplication.run(); 6 } 7 public void addInitializers(ApplicationContextInitializer<?>... initializers) { 8 this.initializers.addAll(Arrays.asList(initializers)); 9 }
在SpringApplication类中的run方法,有如下相关代码:
1 public ConfigurableApplicationContext run(String... args) { 2 StopWatch stopWatch = new StopWatch(); 3 stopWatch.start(); 4 ConfigurableApplicationContext context = null; 5 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); 6 this.configureHeadlessProperty(); 7 SpringApplicationRunListeners listeners = this.getRunListeners(args); 8 listeners.starting(); 9 10 Collection exceptionReporters; 11 try { 12 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 13 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); 14 this.configureIgnoreBeanInfo(environment); 15 Banner printedBanner = this.printBanner(environment); 16 context = this.createApplicationContext(); 17 exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); 18 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); #刷新容器前,初始化 19 this.refreshContext(context); 20 this.afterRefresh(context, applicationArguments); 21 stopWatch.stop(); 22 if (this.logStartupInfo) { 23 (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); 24 } 25 26 listeners.started(context); 27 this.callRunners(context, applicationArguments); 28 } catch (Throwable var10) { 29 this.handleRunFailure(context, var10, exceptionReporters, listeners); 30 throw new IllegalStateException(var10); 31 } 32 33 try { 34 listeners.running(context); 35 return context; 36 } catch (Throwable var9) { 37 this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); 38 throw new IllegalStateException(var9); 39 } 40 }
prepareContext方法的核心代码如下:
1 private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { 2 context.setEnvironment(environment); 3 this.postProcessApplicationContext(context); 4 this.applyInitializers(context); #遍历initializers集合中的SpringApplicationInitializer实例 5 listeners.contextPrepared(context); 6 if (this.logStartupInfo) { 7 this.logStartupInfo(context.getParent() == null); 8 this.logStartupProfileInfo(context); 9 } 10 11 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); 12 beanFactory.registerSingleton("springApplicationArguments", applicationArguments); 13 if (printedBanner != null) { 14 beanFactory.registerSingleton("springBootBanner", printedBanner); 15 } 16 17 if (beanFactory instanceof DefaultListableBeanFactory) { 18 ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); 19 } 20 21 Set<Object> sources = this.getAllSources(); 22 Assert.notEmpty(sources, "Sources must not be empty"); 23 this.load(context, sources.toArray(new Object[0])); 24 listeners.contextLoaded(context); 25 }
SpringApplication中的applyInitializers方法就是遍历initializers集合中的SpringApplicationInitializer实例,调用其initialize方法。
1 protected void applyInitializers(ConfigurableApplicationContext context) { 2 Iterator var2 = this.getInitializers().iterator(); 3 4 while(var2.hasNext()) { 5 ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next(); 6 Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); 7 Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); 8 initializer.initialize(context); 9 } 10 11 }
综上,ApplicationContextInitializer的调用时机,是在容器刷新之前的prepareContext方法,通过获取ApplicationContextInitializer的集合,遍历调用initialize方法。
5.2 在 resources/META-INF/spring.factories 中配置实现原理
实例化SpringApplication对象使,其构造方法如下
1 public SpringApplication(Class... primarySources) { 2 this((ResourceLoader)null, primarySources); 3 } 4 5 public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { 6 this.sources = new LinkedHashSet(); 7 this.bannerMode = Mode.CONSOLE; 8 this.logStartupInfo = true; 9 this.addCommandLineProperties = true; 10 this.addConversionService = true; 11 this.headless = true; 12 this.registerShutdownHook = true; 13 this.additionalProfiles = new HashSet(); 14 this.isCustomEnvironment = false; 15 this.resourceLoader = resourceLoader; 16 Assert.notNull(primarySources, "PrimarySources must not be null"); 17 this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); 18 this.webApplicationType = WebApplicationType.deduceFromClasspath(); 19 this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); 20 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); 21 this.mainApplicationClass = this.deduceMainApplicationClass(); 22 }
其中会调用setInitializers方法设置SpringApplicationInitializer集合;
getSpringFactoriesInstances(ApplicationContextInitializer.class)逻辑如下:
- 该方法会扫描所有的META-INF/spring.factorties文件
- 获取key为org.springframework.context.ApplicationContextInitializer的所有属性值(本质上是ApplicationContextInitializer的实现类的类全名)
- 然后使用其对应类的无参构造器进行反射实例化并进行排序,然后设置到SpringApplication实例中的initializers集合中;
所以我们创建spring.factories文件中的自定义ApplicationContextInitializer会加入到initializers集合中。
在 SpringApplication 初始化时通过 SpringFactoriesLoader 获取到配置在 META-INF/spring.factories 文件中的 ApplicationContextInitializer 的所有实现类.
1 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { 2 ...... 3 // 设置系统初始化器 4 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 5 ...... 6 } 7 // 获取工厂实例对象 8 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { 9 return getSpringFactoriesInstances(type, new Class<?>[] {}); 10 } 11 // 获取工厂实例对象 12 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { 13 // 获取类加载器 14 ClassLoader classLoader = getClassLoader(); 15 // 使用名称并确保唯一以防止重复 16 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 17 // 创建工厂实例对象 18 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); 19 // 对工厂实例对象列表进行排序 20 AnnotationAwareOrderComparator.sort(instances); 21 return instances; 22 } 23 24 // 创建工厂实例对象 25 private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, 26 ClassLoader classLoader, Object[] args, Set<String> names) { 27 List<T> instances = new ArrayList<>(names.size()); 28 for (String name : names) { 29 try { 30 Class<?> instanceClass = ClassUtils.forName(name, classLoader); 31 Assert.isAssignable(type, instanceClass); 32 Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); 33 T instance = (T) BeanUtils.instantiateClass(constructor, args); 34 instances.add(instance); 35 } 36 catch (Throwable ex) { 37 throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); 38 } 39 } 40 return instances; 41 }
在 apllication启动的run 方法中回调 ApplicationContextInitializer 接口函数
1 public ConfigurableApplicationContext run(String... args) { 2 ...... 3 // 准备上下文环境注入系统初始化信息 4 prepareContext(context, environment, listeners, applicationArguments, printedBanner); 5 ...... 6 } 7 private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, 8 SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { 9 ...... 10 // 应用初始化器 11 applyInitializers(context); 12 ...... 13 } 14 protected void applyInitializers(ConfigurableApplicationContext context) { 15 for (ApplicationContextInitializer initializer : getInitializers()) { 16 // 判断子类是否是 ConfigurableApplicationContext 类型 17 Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), 18 ApplicationContextInitializer.class); 19 Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); 20 // 回调 ApplicationContextInitializer接口的 initialize 方法 21 initializer.initialize(context); 22 } 23 }
获取初始化器列表
1 // 获取在 SpringApplication 构造函数中设置的初始化器列表 2 public Set<ApplicationContextInitializer<?>> getInitializers() { 3 return asUnmodifiableOrderedSet(this.initializers); 4 } 5 // 对初始化器列表进行排序 6 private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) { 7 List<E> list = new ArrayList<>(elements); 8 list.sort(AnnotationAwareOrderComparator.INSTANCE); 9 return new LinkedHashSet<>(list); 10 }
5.3 在配置文件中配置实现原理
在配置文件中配置方式,主要通过内置的 DelegatingApplicationContextInitializer 实现的,它实现了 Order
方法,所以优先级最高。
1 private int order = 0; 2 3 @Override 4 public int getOrder() { 5 return this.order; 6 }
然后我们看下它的 initialize
方法实现:
1 @Override 2 public void initialize(ConfigurableApplicationContext context) { 3 // 获取上下文环境变量 4 ConfigurableEnvironment environment = context.getEnvironment(); 5 // 从上下文环境变量中获取指定初始化类列表 6 List<Class<?>> initializerClasses = getInitializerClasses(environment); 7 if (!initializerClasses.isEmpty()) { 8 // 应用初始化器 9 applyInitializerClasses(context, initializerClasses); 10 } 11 }
从上下文环境变量获取指定的属性名,并实例化对象
1 private static final String PROPERTY_NAME = "context.initializer.classes"; 2 3 private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) { 4 // 从上下文环境变量获取指定的属性名 5 String classNames = env.getProperty(PROPERTY_NAME); 6 List<Class<?>> classes = new ArrayList<>(); 7 if (StringUtils.hasLength(classNames)) { 8 // 将逗号分割的属性值逐个取出 9 for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) { 10 // 实例化对象并添加到列表中 11 classes.add(getInitializerClass(className)); 12 } 13 } 14 return classes; 15 } 16 private Class<?> getInitializerClass(String className) throws LinkageError { 17 try { 18 Class<?> initializerClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader()); 19 Assert.isAssignable(ApplicationContextInitializer.class, initializerClass); 20 return initializerClass; 21 } 22 catch (ClassNotFoundException ex) { 23 throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex); 24 } 25 }
从容器上下文中的Environment中获取配置名为context.initializer.classes的属性值(多个用,隔开,实际配置的是类全名),如果存在就通过反射实例化这些ApplicationContextInitialzier对象并排序,遍历并调用其initialize方法。
该类实际上是一个委托类,将实际的初始化工作交给了context.initializer.classes环境变量指定的ApplicationContextInitialize对象
springboot中自带的DelegatingApplicationContextInitializer类的排序值为0,是springboot自带的ApplicationContextInitializer中排序最小,最先执行的类。(如果ApplicationContextInitializer没有实现Orderd接口,那么其排序值默认是最大,最后执行)
6.总结
所以可以得到其执行顺序如下:
1.application.properties中定义的ApplicationContextInitializer优先于其他定义的方式
因为配置中的ApplicationContextInitializer是通过DelegatingApplicationContextInitializer实现,而DelegatingApplicationContextInitializer的排序最小(order=0),在默认的ApplicationContextInitializer第一个执行。(同样通过配置文件添加的ApplicationContextInitializer通过@Order实现排序执行)
2.自己创建的spring.factories文件中自定义的ApplicationContextInitializer和通过addInitializers方法加入自定义的ApplicationContextInitializer(若未改order,按照添加顺序执行),order越小执行越早。若都未改order,则自己创建的spring.factories中定义的要更早执行(因为是在构造方法中添加的)
7.Spring Factories实现原理
spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:
- loadFactories :根据接口类获取其实现类的实例,这个方法返回的是对象列表。
- loadFactoryNames :根据接口获取其接口类的名称,这个方法返回的是类名的列表。
上面的两个方法的关键都是从指定的ClassLoader中获取spring.factories文件,并解析得到类名列表
,具体代码如下:
1 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { 2 MultiValueMap<String, String> result = cache.get(classLoader); 3 if (result != null) { 4 return result; 5 } 6 try { 7 Enumeration<URL> urls = (classLoader != null ? 8 //遍历整个ClassLoader中所有jar包下的spring.factories文件 9 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : 10 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); 11 result = new LinkedMultiValueMap<>(); 12 while (urls.hasMoreElements()) { 13 URL url = urls.nextElement(); 14 UrlResource resource = new UrlResource(url); 15 Properties properties = PropertiesLoaderUtils.loadProperties(resource); 16 for (Map.Entry<?, ?> entry : properties.entrySet()) { 17 String factoryClassName = ((String) entry.getKey()).trim(); 18 for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { 19 result.add(factoryClassName, factoryName.trim()); 20 } 21 } 22 } 23 cache.put(classLoader, result); 24 return result; 25 } 26 catch (IOException ex) { 27 throw new IllegalArgumentException("Unable to load factories from location [" + 28 FACTORIES_RESOURCE_LOCATION + "]", ex); 29 } 30 }
从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。也就是说我们可以在自己的jar中配置spring.factories文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。
spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:
com.xxx.interface=com.xxx.classname
如果一个接口希望配置多个实现类,可以使用’,’进行分割。
参考:
https://blog.csdn.net/weixin_45994575/article/details/124596081
https://blog.csdn.net/u014520047/article/details/94553296
https://blog.csdn.net/cristianoxm/article/details/119751841
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现