Spring Boot不仅是简化Spring应用开发的工具,它还融合了许多先进的机制。本文深入探讨了Spring Boot中与Java的标准SPI相似的机制,揭示了它的工作原理、应用场景及与标准SPI的异同。文章通过实际代码示例为你展示了如何在Spring Boot中使用这一机制,并以形象的比喻帮助你理解其背后的思想。
package com.example.demo.service; public interface MessageService { String getMessage(); }步骤2:为服务接口提供实现,这里会提供两个简单的实现类。
package com.example.demo.service; // 堆代码 duidaima.com public class HelloMessageService implements MessageService { @Override public String getMessage() { return "Hello from HelloMessageService!"; } }HiMessageService.java
package com.example.demo.service; public class HiMessageService implements MessageService { @Override public String getMessage() { return "Hi from HiMessageService!"; } }这些实现就像不同品牌或型号的U盘或其他USB设备。每个设备都有自己的功能和特性,但都遵循相同的USB标准。
com.example.demo.service.HelloMessageService com.example.demo.service.HiMessageServiceMETA-INF/services/ 是 Java SPI (Service Provider Interface) 机制中约定俗成的特定目录。它不是随意选择的,而是 SPI 规范中明确定义的。因此,当使用 JDK 的 ServiceLoader 类来加载服务提供者时,它会特意去查找这个路径下的文件。
package com.example.demo; import com.example.demo.service.MessageService; import java.util.ServiceLoader; public class DemoApplication { public static void main(String[] args) { ServiceLoader<MessageService> loaders = ServiceLoader.load(MessageService.class); for (MessageService service : loaders) { System.out.println(service.getMessage()); } } }运行结果如下:
package com.example.demo.service; public class HelloMessageService implements MessageService { @Override public String getMessage() { return "Hello from HelloMessageService!"; } }HiMessageService.java
package com.example.demo.service; public class HiMessageService implements MessageService { @Override public String getMessage() { return "Hi from HiMessageService!"; } }定义BeanPostProcessor
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MessageServicePostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof MessageService) { return new MessageService() { @Override public String getMessage() { return ((MessageService) bean).getMessage() + " [Processed by Spring SPI]"; } }; } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }修改Spring配置
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MessageServiceConfig { @Bean public MessageService helloMessageService() { return new HelloMessageService(); } @Bean public MessageService hiMessageService() { return new HiMessageService(); } @Bean public MessageServicePostProcessor messageServicePostProcessor() { return new MessageServicePostProcessor(); } }执行程序
package com.example.demo; import com.example.demo.configuration.MessageServiceConfig; import com.example.demo.service.MessageService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class DemoApplication { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MessageServiceConfig.class); MessageService helloMessageService = context.getBean("helloMessageService", MessageService.class); MessageService hiMessageService = context.getBean("hiMessageService", MessageService.class); System.out.println(helloMessageService.getMessage()); System.out.println(hiMessageService.getMessage()); } }运行结果:
电视机与USB插口: 在这个新的示例中,电视机仍然是核心的Spring应用程序,具体来说是DemoApplication类。这个核心应用程序需要从某个服务(即MessageService)获取并打印一条消息。
BeanPostProcessor: 这是一个特殊的“魔法盒子”,可以将其视为一个能够拦截并修改电视机显示内容的智能设备。当插入USB设备(即MessageService的实现)并尝试从中获取消息时,这个“魔法盒子”会介入,并为每条消息添加“[Processed by Spring SPI]”。
Spring上下文配置: 这依然是电视机的使用说明书,但现在是使用了基于Java的配置方式,即MessageServiceConfig类。这个“使用说明书”指导Spring容器如何创建并管理MessageService的实例,并且还指导它如何使用“魔法盒子”(即MessageServicePostProcessor)来处理消息。
package com.example.demo.service; public interface MessageService { String getMessage(); }这里会提供两个简单的实现类。
package com.example.demo.service; public class HelloMessageService implements MessageService { @Override public String getMessage() { return "Hello from HelloMessageService!"; } }HiMessageService.java
package com.example.demo.service; public class HiMessageService implements MessageService { @Override public String getMessage() { return "Hi from HiMessageService!"; } }注册服务
com.example.demo.service.MessageService=com.example.demo.service.HelloMessageService,com.example.demo.service.HiMessageService注意这里com.example.demo.service.MessageService是接口的全路径,而com.example.demo.service.HelloMessageService,com.example.demo.service.HiMessageService是实现类的全路径。如果有多个实现类,它们应当用逗号分隔。
com.example.demo.service.MessageService=com.example.demo.service.HelloMessageService,\ com.example.demo.service.HiMessageService直接在逗号后面回车IDEA会自动补全反斜杠,保证键和值之间不能有换行即可。
package com.example.demo; import com.example.demo.service.MessageService; import org.springframework.core.io.support.SpringFactoriesLoader; import java.util.List; public class DemoApplication { public static void main(String[] args) { List<MessageService> services = SpringFactoriesLoader.loadFactories(MessageService.class, null); for (MessageService service : services) { System.out.println(service.getMessage()); } } }SpringFactoriesLoader.loadFactories的第二个参数是类加载器,此处我们使用默认的类加载器,所以传递null。
public class com.mysql.cj.jdbc.Driver implements java.sql.Driver { // 实现接口方法... }直接上图:
import java.sql.Connection; import java.sql.DriverManager; public class JdbcExample { public static void main(String[] args) { String jdbcUrl = "jdbc:mysql://localhost:3306/mydatabase"; String username = "root"; String password = "password"; try { Connection connection = DriverManager.getConnection(jdbcUrl, username, password); System.out.println("Connected to the database!"); connection.close(); } catch (Exception e) { e.printStackTrace(); } } }在上述代码中,我们没有明确指定使用哪个JDBC驱动程序,因为DriverManager会自动为我们选择合适的驱动程序。这种模块化和插件化的机制使得我们可以轻松地为不同的数据库切换驱动程序,只需要更改JDBC URL并确保相应的驱动程序JAR在类路径上即可。
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase spring.datasource.username=root spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver在上述步骤中,Spring Boot的自动配置机制会根据提供的依赖和配置信息来初始化和配置DataSource对象,这个对象管理数据库连接。实际上,添加JDBC驱动依赖时,Spring Boot会使用JDK的SPI机制(在JDBC规范中应用)来找到并加载相应的数据库驱动。开发者虽然不直接与JDK的SPI交互,但在背后Spring Boot确实利用了JDK SPI机制来获取数据库连接。
package com.example.demo.service; public interface MessageService { void send(String message); }SMS服务实现:
package com.example.demo.service.impl; import com.example.demo.service.MessageService; public class SmsService implements MessageService { @Override public void send(String message) { System.out.println("Sending SMS: " + message); } }Email服务实现:
package com.example.demo.service.impl; import com.example.demo.service.MessageService; public class EmailService implements MessageService { @Override public void send(String message) { System.out.println("Sending Email: " + message); } }自动配置类:
package com.example.demo.configuration; import com.example.demo.service.EmailService; import com.example.demo.service.MessageService; import com.example.demo.service.SmsService; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MessageAutoConfiguration { @Bean @ConditionalOnProperty(name = "message.type", havingValue = "sms") public MessageService smsService() { return new SmsService(); } @Bean @ConditionalOnProperty(name = "message.type", havingValue = "email") public MessageService emailService() { return new EmailService(); } }这个类提供两个条件性的beans(组件),分别是SmsService和EmailService。这些beans的创建取决于application.properties文件中特定的属性值。
@ConditionalOnProperty(name = “message.type”, havingValue = “sms”)当application.properties或application.yml中定义的属性message.type的值为sms时,此条件为true。此时,smsService()方法将被调用,从而创建一个SmsService的bean。
@ConditionalOnProperty(name = “message.type”, havingValue = “email”)当application.properties或application.yml中定义的属性message.type的值为email时,此条件为true。此时,emailService()方法将被调用,从而创建一个EmailService的bean。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.demo.configuration.MessageAutoConfiguration application.properties文件: message.type=smsMessageTester组件:
package com.example.demo; import com.example.demo.service.MessageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class MessageTester { @Autowired private MessageService messageService; @PostConstruct public void init() { messageService.send("Hello World"); } }DemoApplication主程序:
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }运行结果:
独立发展:框架与其SPI实现可以独立地进化和发展,互不影响。